From 8d92c93639bd6a8d836b221e0ee69c7e433c47e0 Mon Sep 17 00:00:00 2001 From: VsevolodX Date: Tue, 6 Oct 2026 16:56:42 -0700 Subject: [PATCH 01/18] SOF-8065: graphene/SiO2 metastable registry, back passivation, simulation notebook Structure notebook: 120-degree cell, balanced registry shift, bottom H, strained free-standing graphene saved as the Dirac-point reference. New simulation notebook: band structure at K, E_D - E_F and the gap beside Kang et al. (2008), RELAX switch. Introduction row linked. Co-Authored-By: Claude Opus 5.5 --- .../specific_examples/Introduction.ipynb | 2 +- ...rface_2d_3d_graphene_silicon_dioxide.ipynb | 161 ++++- ..._graphene_silicon_dioxide_SIMULATION.ipynb | 663 ++++++++++++++++++ 3 files changed, 821 insertions(+), 5 deletions(-) create mode 100644 other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide_SIMULATION.ipynb diff --git a/other/materials_designer/specific_examples/Introduction.ipynb b/other/materials_designer/specific_examples/Introduction.ipynb index 71075fbc4..705d11224 100644 --- a/other/materials_designer/specific_examples/Introduction.ipynb +++ b/other/materials_designer/specific_examples/Introduction.ipynb @@ -25,7 +25,7 @@ "| `C-2D-HST` | Heterostack | [Si/SiO₂/HfO₂/TiN Heterostructure](heterostructure_silicon_silicon_dioxide_hafnium_dioxide_titanium_nitride.ipynb) | *To be added* | [[3]](#ref3) |\n", "| `C-2D-INT-S` | Interface Simple | *To be added* | — | — |\n", "| `C-2D-INT-Z` | Interface ZSL | [BN/Graphene 2D–2D Interface](interface_2d_2d_boron_nitride_graphene.ipynb) | *To be added* | [[4]](#ref4) |\n", - "| `C-2D-INT-Z` | Interface ZSL | [Graphene/SiO₂ 2D–3D Interface](interface_2d_3d_graphene_silicon_dioxide.ipynb) | *To be added* | [[5]](#ref5) |\n", + "| `C-2D-INT-Z` | Interface ZSL | [Graphene/SiO₂ 2D–3D Interface](interface_2d_3d_graphene_silicon_dioxide.ipynb) | [Doping and Gap at the Dirac Point](interface_2d_3d_graphene_silicon_dioxide_SIMULATION.ipynb) | [[5]](#ref5) |\n", "| `C-2D-INT-Z` | Interface ZSL | [Cu/Cristobalite 3D–3D Interface](interface_3d_3d_copper_cristobalite.ipynb) | *To be added* | [[6]](#ref6) |\n", "| `C-2D-INT-Z` | Interface ZSL | [Graphene/Ni Interface Film XY Position Optimization](optimization_interface_film_xy_position_graphene_nickel.ipynb) | [Gr/Ni(111) Registry and Work of Adhesion](optimization_interface_film_xy_position_graphene_nickel_SIMULATION.ipynb) | [[7]](#ref7) |\n", "| `C-2D-INT-T` | Interface Twisted | *To be added* | — | — |\n", diff --git a/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide.ipynb b/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide.ipynb index 99b913da3..0eb07af03 100644 --- a/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide.ipynb +++ b/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide.ipynb @@ -18,7 +18,7 @@ "\n", "\n", "\n", - "Replicating the materials from the manuscript, FIG. 1. (b):\n", + "Builds the **metastable geometry of Sec. III** -- O-terminated α-quartz(0001) with the registry shifted so one surface O sits under a carbon atom and the other under a hexagon centre, and the back surface H-passivated. (Fig. 1(b) below shows the manuscript's *stable*, bonded registry for reference -- this notebook does not build that one.)\n", "\n", "\n" ] @@ -358,7 +358,159 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## 5. Pass data to the outside runtime\n" + "## 5. Correct the geometry to the paper's metastable registry\n", + "\n", + "As built, the ZSL interface has a 60° cell (made's symbolic K-point assumes 120°) and a registry close to the manuscript's *stable* bonded geometry: both surface O atoms sit near a C atom. Sec. III's metastable geometry needs one O under a C and the other under a hexagon centre, and the manuscript H-passivates the back surface; neither follows from `create_interface_zsl_between_slabs` alone." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 5.1. Save the strained free-standing film as the Dirac-point reference\n", + "\n", + "Taken off the film part of the as-built interface -- same strain as in the interface, no substrate -- before the cell reset, registry shift and passivation below, which only act on the interface." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from mat3ra.made.tools.helpers import create_supercell\n", + "from mat3ra.made.tools.modify import interface_get_part, add_vacuum\n", + "from mat3ra.made.tools.convert.interface_parts_enum import InterfacePartsEnum\n", + "\n", + "film_reference = interface_get_part(interface, InterfacePartsEnum.FILM)\n", + "film_reference = create_supercell(film_reference, supercell_matrix=[[1, 0, 0], [-1, 1, 0], [0, 0, 1]])\n", + "film_reference.lattice.type = \"HEX\"\n", + "film_reference = add_vacuum(film_reference, vacuum=INTERFACE_VACUUM, on_top=True, to_bottom=True)\n", + "film_reference.name = \"Graphene 2x2 on SiO2, free-standing reference\"\n", + "print(f\"{film_reference.basis.number_of_atoms} atoms, gamma = {film_reference.lattice.gamma:.3f} deg\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 5.2. Reset the interface cell to a 120° hexagonal setting" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# The ZSL match above is a 60° cell; made's symbolic K point assumes 120°, so the\n", + "# hexagonal path point is meaningless until the cell is re-set (same physics, same atoms).\n", + "interface = create_supercell(interface, supercell_matrix=[[1, 0, 0], [-1, 1, 0], [0, 0, 1]])\n", + "interface.lattice.type = \"HEX\"\n", + "print(f\"gamma = {interface.lattice.gamma:.3f} deg, a = {interface.lattice.a:.4f} Å\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 5.3. Shift the film to the manuscript's registry\n", + "\n", + "`REGISTRY_SHIFT` is the balanced choice: the real lattice cannot put one surface O exactly under a C while the other sits exactly over a hexagon centre (a 0.71 Å misfit between the two O positions and the graphene lattice), so the shift splits that misfit evenly, leaving each O 0.35 Å from its ideal site rather than putting one exactly on site and the other off by the full 0.71 Å." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "from collections import Counter\n", + "from mat3ra.made.tools.modify import interface_displace_part\n", + "\n", + "REGISTRY_SHIFT = [-1.010994, -0.724791, 0.0] # Angstrom, cartesian, applied to the film\n", + "\n", + "interface = interface_displace_part(interface, REGISTRY_SHIFT, label=InterfacePartsEnum.FILM)\n", + "\n", + "\n", + "def cartesian_coordinates(material):\n", + " clone = material.clone()\n", + " clone.to_cartesian()\n", + " return np.array(clone.basis.coordinates.values)\n", + "\n", + "\n", + "def xy_distance(p, q, cell_xy):\n", + " \"\"\"Minimum-image in-plane distance; the two lattice vectors are periodic, z is not.\"\"\"\n", + " d_xy = (p - q)[:2]\n", + " return min(np.linalg.norm(d_xy - i * cell_xy[0] - j * cell_xy[1])\n", + " for i in (-1, 0, 1) for j in (-1, 0, 1))\n", + "\n", + "\n", + "elements = interface.basis.elements.values\n", + "coordinates = cartesian_coordinates(interface)\n", + "cell_xy = np.array(interface.lattice.vector_arrays)[:2, :2]\n", + "carbon_indices = [i for i, el in enumerate(elements) if el == \"C\"]\n", + "oxygen_indices = [i for i, el in enumerate(elements) if el == \"O\"]\n", + "surface_oxygens = sorted(oxygen_indices, key=lambda i: -coordinates[i, 2])[:2]\n", + "for i in surface_oxygens:\n", + " nearest_c = min(xy_distance(coordinates[i], coordinates[j], cell_xy) for j in carbon_indices)\n", + " print(f\"O at z={coordinates[i, 2]:.4f}: nearest C at {nearest_c:.4f} Å\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 5.4. Passivate the back surface\n", + "\n", + "`passivate_surface` places one H per exposed atom. The back Si is missing two bonds (coordination 2 of 4), so one dangling bond remains after this call -- declared, not chased further; see the tutorial's Troubleshooting section." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from mat3ra.made.tools.helpers import passivate_surface, SurfaceTypesEnum\n", + "\n", + "GBRV_VALENCE = {\"Si\": 4, \"O\": 6, \"C\": 4, \"H\": 1}\n", + "\n", + "natoms_before = interface.basis.number_of_atoms\n", + "interface = passivate_surface(interface, passivant=\"H\", surface=SurfaceTypesEnum.BOTTOM)\n", + "interface.basis.set_labels_from_list([])\n", + "composition = Counter(interface.basis.elements.values)\n", + "electron_count = sum(GBRV_VALENCE[element] * count for element, count in composition.items())\n", + "print(f\"H added = {interface.basis.number_of_atoms - natoms_before}; \"\n", + " f\"{interface.basis.number_of_atoms} atoms, composition {dict(composition)}\")\n", + "print(f\"{electron_count} valence electrons ({'odd' if electron_count % 2 else 'even'}, GBRV: \"\n", + " \"Si 4, O 6, C 4, H 1)\")\n", + "interface.name = \"Graphene 2x2 on SiO2, metastable registry, H-passivated\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 5.5. Preview the corrected interface" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "visualize(interface, repetitions=[1, 1, 1])\n", + "visualize(interface, repetitions=[1, 1, 1], rotation=\"-90x\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 6. Pass data to the outside runtime\n" ] }, { @@ -367,9 +519,10 @@ "metadata": {}, "outputs": [], "source": [ - "from mat3ra.notebooks_utils.material import set_materials, download_content_to_file\n", + "from mat3ra.notebooks_utils.io import download_content_to_file\n", + "from mat3ra.notebooks_utils.material import set_materials\n", "\n", - "set_materials(interface)\n", + "set_materials([interface, film_reference])\n", "download_content_to_file(interface.to_json(), f\"{interface.name}.json\")" ] }, diff --git a/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide_SIMULATION.ipynb b/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide_SIMULATION.ipynb new file mode 100644 index 000000000..2d914396e --- /dev/null +++ b/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide_SIMULATION.ipynb @@ -0,0 +1,663 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Doping and Gap at the Dirac Point of Graphene on SiO2\n", + "\n", + "> **Yong-Ju Kang, Joongoo Kang, and K. J. Chang**, \"Electronic structure of graphene and doping\n", + "> effect on SiO2\", Physical Review B 78, 115404 (2008).\n", + "> [DOI:10.1103/PhysRevB.78.115404](https://doi.org/10.1103/PhysRevB.78.115404)\n", + "\n", + "Computes the band structure of graphene on O-terminated α-quartz(0001), in the manuscript's\n", + "metastable registry of Sec. III built in the [structure notebook](interface_2d_3d_graphene_silicon_dioxide.ipynb),\n", + "and of a free-standing reference of the same strained film, then reads the Dirac point's position\n", + "relative to the Fermi level and the gap at K, and compares both with Kang et al. (2008) Sec. III\n", + "and Fig. 3(a).\n", + "\n", + "

Usage

\n", + "\n", + "1. Create the materials in the [structure notebook](interface_2d_3d_graphene_silicon_dioxide.ipynb), which saves them to the `uploads` folder under the names used in cell 1.2 below.\n", + "1. Set the material names and parameters in cells 1.2-1.4, or use the defaults; `RELAX = True` relaxes the interface once (fixed-cell, whole slab) and saves it as ` relaxed` for reuse by this and other notebooks.\n", + "1. Click \"Run\" > \"Run All\" to run all cells.\n", + "1. Wait for the jobs to complete.\n", + "1. Scroll down to view the results; the last section prints the comparison with the manuscript.\n", + "\n", + "## Summary\n", + "\n", + "1. Set up the environment and parameters: install packages (JupyterLite only) and configure the material names, model and compute parameters.\n", + "1. Authenticate and initialize API client: authenticate via browser, initialize the client, then select account and project.\n", + "1. Load the materials by name from the `uploads` folder, print their provenance and save them to the platform.\n", + "1. Configure the shared DFT model and k-grid.\n", + "1. Configure compute: get the list of clusters and create a compute configuration.\n", + "1. Relax the interface if `RELAX`, reusing a saved relaxed structure when one already exists.\n", + "1. Configure the Band Structure workflow and run one job per material, re-using a job that already ran under the same workflow name.\n", + "1. Retrieve the band structures, the Fermi level and the Dirac point's position and gap at K.\n", + "1. Compare with Kang et al. (2008).\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. Set up the environment and parameters\n", + "### 1.1. Install packages (JupyterLite)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from mat3ra.notebooks_utils.packages import install_packages\n", + "\n", + "await install_packages(\"made|specific_examples|api_examples\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 1.2. Material names" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Names saved by interface_2d_3d_graphene_silicon_dioxide.ipynb.\n", + "INTERFACE_NAME = \"Graphene 2x2 on SiO2, metastable registry, H-passivated\"\n", + "REFERENCE_NAME = \"Graphene 2x2 on SiO2, free-standing reference\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 1.3. Parameters" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from datetime import datetime\n", + "from mat3ra.ide.compute import QueueName\n", + "\n", + "ORGANIZATION_NAME = None # set to your organization name (full or partial); otherwise, your default one is used\n", + "FOLDER = \"./uploads\"\n", + "\n", + "RELAX_WORKFLOW_SEARCH_TERM = \"fixed_cell_relaxation.json\"\n", + "BAND_STRUCTURE_WORKFLOW_SEARCH_TERM = \"band_structure.json\"\n", + "MY_WORKFLOW_NAME = \"Band Structure\"\n", + "APPLICATION_NAME = \"espresso\"\n", + "\n", + "# NOTE: False reads the band structure as built; True relaxes the interface once (fixed cell, whole\n", + "# slab -- made has no per-atom constraint wired to the platform workflow, so the back cannot be\n", + "# frozen separately) before the band structure. Kang et al.'s 0.13 eV gap comes from the relaxed O\n", + "# positions, so RELAX = True is the regime that can reproduce it; it is also the slowest Group B job.\n", + "RELAX = False\n", + "\n", + "CLUSTER_NAME = \"001\" # specify full or partial name i.e. \"cluster-001\" to select\n", + "QUEUE_NAME = QueueName.OR\n", + "PPN = 16 # queue OR on cluster-001 allows at most 16 cores per node\n", + "TIME_LIMIT = \"24:00:00\" # covers the optional relaxation of this 72-atom cell\n", + "\n", + "timestamp = datetime.now().strftime(\"%Y-%m-%d %H:%M\")\n", + "POLL_INTERVAL = 60 # seconds" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 1.4. DFT model parameters" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "MODEL_SUBTYPE = \"lda\"\n", + "FUNCTIONAL = \"pz\" # Kang et al. 2008 use LDA\n", + "PSEUDOPOTENTIAL_TYPE = \"us\" # GBRV ultrasoft, the only LDA family the platform publishes for Si, O, C and H\n", + "ECUTWFC = 40 # Ry, GBRV's recommended wavefunction cutoff\n", + "ECUTRHO = 200 # Ry, GBRV's recommended charge-density cutoff\n", + "\n", + "KPOINT_DENSITY = 4 # gives 6x6x1 on the measured 21.05 Ų interface cell, Kang et al. Sec. II\n", + "MODEL_TAG = f\"{FUNCTIONAL}-{PSEUDOPOTENTIAL_TYPE} {ECUTWFC}-{ECUTRHO}Ry k{KPOINT_DENSITY}\"\n", + "\n", + "SCF_UNIT = \"pw_scf\"\n", + "BANDS_UNIT = \"pw_bands\"\n", + "RELAX_UNIT = \"pw_relax\"\n", + "# The interface carries 369 valence electrons (odd); Gaussian smearing handles the fractional\n", + "# occupation the same way every other doped or metallic job in this corpus does (D6).\n", + "SMEARING_SETTINGS = {\"degauss\": 0.01} # Ry\n", + "RELAXATION_SETTINGS = {\"forc_conv_thr\": 1.17e-3, \"nstep\": 100} # 0.03 eV/Å, Kang et al. 2008 Sec. II\n", + "# Names the relaxation job; the relaxed structure itself is found by content hash.\n", + "RELAX_TAG = f\"{MODEL_TAG} f{RELAXATION_SETTINGS['forc_conv_thr']}\"\n", + "\n", + "KPATH_STEPS = 20\n", + "KPATH = [\n", + " {\"point\": \"K\", \"steps\": KPATH_STEPS},\n", + " {\"point\": \"Γ\", \"steps\": KPATH_STEPS},\n", + " {\"point\": \"M\", \"steps\": KPATH_STEPS},\n", + " {\"point\": \"K\", \"steps\": 1},\n", + "]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. Authenticate and initialize API client\n", + "### 2.1. Authenticate" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from mat3ra.notebooks_utils.auth import authenticate\n", + "\n", + "await authenticate()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 2.2. Initialize API client" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from mat3ra.api_client import APIClient\n", + "\n", + "client = APIClient.authenticate()\n", + "client" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 2.3. Select account" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "client.list_accounts()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "selected_account = client.my_account\n", + "\n", + "if ORGANIZATION_NAME:\n", + " selected_account = client.get_account(name=ORGANIZATION_NAME)\n", + "\n", + "ACCOUNT_ID = selected_account.id\n", + "print(f\"✅ Selected account ID: {ACCOUNT_ID}, name: {selected_account.name}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 2.4. Select project" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "projects = client.projects.list({\"isDefault\": True, \"owner._id\": ACCOUNT_ID})\n", + "project_id = projects[0][\"_id\"]\n", + "print(f\"✅ Using project: {projects[0]['name']} ({project_id})\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. Load the materials\n", + "### 3.1. Load from the uploads folder and print provenance\n", + "\n", + "The structures, their registry and the back passivation all come from the structure notebook; this one only loads them by name." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from collections import Counter\n", + "from mat3ra.notebooks_utils.core.entity.material.api import load_material\n", + "\n", + "GBRV_VALENCE = {\"Si\": 4, \"O\": 6, \"C\": 4, \"H\": 1}\n", + "\n", + "materials_by_name = {name: load_material(client, FOLDER, name, ACCOUNT_ID) for name in (INTERFACE_NAME, REFERENCE_NAME)}\n", + "interface, reference = materials_by_name[INTERFACE_NAME], materials_by_name[REFERENCE_NAME]\n", + "for name, material in materials_by_name.items():\n", + " composition = Counter(material.basis.elements.values)\n", + " electron_count = sum(GBRV_VALENCE[element] * count for element, count in composition.items())\n", + " print(f\"{name}: {dict(composition)}, {material.basis.number_of_atoms} atoms, \"\n", + " f\"gamma = {material.lattice.gamma:.3f}°, {electron_count} valence electrons \"\n", + " f\"({'odd' if electron_count % 2 else 'even'})\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 3.2. Save the materials to the platform" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from mat3ra.notebooks_utils.core.entity.material.api import get_or_create_material\n", + "\n", + "saved_interface = get_or_create_material(client, interface, ACCOUNT_ID)\n", + "saved_reference = get_or_create_material(client, reference, ACCOUNT_ID)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 4. Configure the shared DFT model and k-grid\n", + "### 4.1. DFT model" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from mat3ra.standata.applications import ApplicationStandata\n", + "from mat3ra.standata.model_tree import ModelTreeStandata\n", + "from mat3ra.ade.application import Application\n", + "from mat3ra.mode import ModelFactory\n", + "\n", + "app_config = ApplicationStandata.get_by_name_first_match(APPLICATION_NAME)\n", + "app = Application(**app_config)\n", + "\n", + "model_config = ModelTreeStandata.get_model_by_parameters(type=\"dft\", subtype=MODEL_SUBTYPE, functional=FUNCTIONAL)\n", + "model_config[\"method\"] = {\"type\": \"pseudopotential\", \"subtype\": PSEUDOPOTENTIAL_TYPE}\n", + "model = ModelFactory.create(model_config)\n", + "print(f\"Using application: {app.name}, model: {MODEL_TAG}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 4.2. k-grid per material" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from mat3ra.notebooks_utils.workflow import kgrid_from_density\n", + "\n", + "kgrid = {\n", + " INTERFACE_NAME: kgrid_from_density(interface, KPOINT_DENSITY, periodic_dims=(0, 1)),\n", + " REFERENCE_NAME: kgrid_from_density(reference, KPOINT_DENSITY, periodic_dims=(0, 1)),\n", + "}\n", + "for name, grid in kgrid.items():\n", + " print(f\"{name}: k-grid {grid}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 5. Create the compute configuration\n", + "### 5.1. Select cluster" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "clusters = client.clusters.list()\n", + "print(f\"Available clusters: {[c['hostname'] for c in clusters]}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 5.2. Create the compute configuration for the jobs" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from mat3ra.ide.compute import Compute\n", + "\n", + "if CLUSTER_NAME:\n", + " cluster = next((c for c in clusters if CLUSTER_NAME in c[\"hostname\"]), None)\n", + " if cluster is None:\n", + " raise ValueError(f\"Cluster '{CLUSTER_NAME}' not found. Available: {[c['hostname'] for c in clusters]}\")\n", + "else:\n", + " cluster = clusters[0]\n", + "compute = Compute(cluster=cluster, queue=QUEUE_NAME, ppn=PPN, timeLimit=TIME_LIMIT)\n", + "print(f\"Using cluster: {compute.cluster.hostname}, queue: {QUEUE_NAME}, ppn: {PPN}, \"\n", + " f\"time limit: {TIME_LIMIT}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 6. Relax the interface (optional)\n", + "\n", + "Runs only if `RELAX`: finds any relaxed version of this structure already on the account, regardless of who relaxed it or with what, before spending any compute on a new one. The band structure is then read off that geometry. Kang et al.'s 0.13 eV gap comes from the second surface O relaxing toward a neighbouring C atom, so an unrelaxed run is the as-built geometry rather than a rougher version of the relaxed one." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from mat3ra.made.material import Material\n", + "from mat3ra.standata.workflows import WorkflowStandata\n", + "from mat3ra.wode.workflows import Workflow\n", + "from mat3ra.notebooks_utils.workflow import apply_planewave_cutoffs, apply_scf_kgrid, patch_workflow_qe_input\n", + "from mat3ra.notebooks_utils.core.entity.job.api import find_job_for_material\n", + "from mat3ra.notebooks_utils.core.entity.material.api import find_relaxed_material, get_final_structure_for_job\n", + "from mat3ra.notebooks_utils.core.entity.property.api import get_properties_for_job\n", + "from mat3ra.notebooks_utils.job import create_job\n", + "from mat3ra.notebooks_utils.api.job import submit_jobs, wait_for_jobs_to_finish_async\n", + "\n", + "relaxed_interface = None\n", + "if RELAX:\n", + " relax_workflow_name = f\"Fixed-cell Relaxation {INTERFACE_NAME} {RELAX_TAG}\"\n", + " relaxed_interface = find_relaxed_material(client, interface, ACCOUNT_ID)\n", + " if relaxed_interface is not None:\n", + " print(f\"♻️ Relaxed interface: {relaxed_interface.name} ({relaxed_interface.id})\")\n", + " else:\n", + " relax_workflow = Workflow.create(\n", + " WorkflowStandata.filter_by_application(app.name).get_by_name_first_match(RELAX_WORKFLOW_SEARCH_TERM)\n", + " )\n", + " relax_workflow.name = relax_workflow_name\n", + " relax_workflow.subworkflows[0].model = model\n", + " apply_planewave_cutoffs(relax_workflow, ECUTWFC, ECUTRHO, unit_name=RELAX_UNIT)\n", + " apply_scf_kgrid(relax_workflow, kgrid[INTERFACE_NAME], material=interface, unit_name=RELAX_UNIT)\n", + " patch_workflow_qe_input(relax_workflow, {\"system\": SMEARING_SETTINGS}, [RELAX_UNIT])\n", + " patch_workflow_qe_input(relax_workflow, {\"control\": RELAXATION_SETTINGS}, [RELAX_UNIT])\n", + "\n", + " relax_job = find_job_for_material(\n", + " client, saved_interface[\"_id\"], relax_workflow_name, ACCOUNT_ID,\n", + " statuses=(\"submitted\", \"queued\", \"active\", \"finished\"),\n", + " )\n", + " if relax_job is None:\n", + " relax_job = create_job(\n", + " api_client=client, materials=[saved_interface], workflow=relax_workflow,\n", + " project_id=project_id, owner_id=ACCOUNT_ID, compute=compute.to_dict(),\n", + " prefix=f\"{relax_workflow_name} {timestamp}\",\n", + " )\n", + " submit_jobs(client.jobs, [relax_job[\"_id\"]])\n", + " await wait_for_jobs_to_finish_async(client.jobs, [relax_job[\"_id\"]], poll_interval=POLL_INTERVAL)\n", + "\n", + " relaxed_material = get_final_structure_for_job(client, relax_job[\"_id\"])\n", + " client.materials.update(relaxed_material.id, {\"name\": f\"{INTERFACE_NAME} relaxed\"})\n", + " relaxed_interface = Material.create(client.materials.get(relaxed_material.id))\n", + " print(f\"✅ Relaxed interface: {relaxed_interface.name} ({relaxed_interface.id})\")\n", + "\n", + " total_force = get_properties_for_job(client, relax_job[\"_id\"], \"total_force\")[0]\n", + " print(f\"Residual force after relaxation (norm over all atoms): \"\n", + " f\"{total_force['value']:.4f} {total_force['units']}\")\n", + "\n", + "interface_for_jobs = relaxed_interface if RELAX else interface\n", + "interface_for_jobs.lattice.type = \"HEX\" # the symbolic K-Γ-M-K path needs a named lattice type\n", + "saved_interface_for_jobs = get_or_create_material(client, interface_for_jobs, ACCOUNT_ID)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 7. Band structure jobs\n", + "### 7.1. Configure one workflow per material" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from mat3ra.wode.context.providers import PointsPathDataProvider\n", + "from mat3ra.notebooks_utils.ipython.entity.workflow.visualize import visualize_workflow\n", + "from copy import deepcopy\n", + "\n", + "materials_for_jobs = {INTERFACE_NAME: (interface_for_jobs, saved_interface_for_jobs), REFERENCE_NAME: (reference, saved_reference)}\n", + "band_structure_workflow_config = WorkflowStandata.filter_by_application(app.name).get_by_name_first_match(\n", + " BAND_STRUCTURE_WORKFLOW_SEARCH_TERM\n", + ")\n", + "\n", + "workflows = {}\n", + "for name, (material, _) in materials_for_jobs.items():\n", + " workflow = Workflow.create(deepcopy(band_structure_workflow_config))\n", + " workflow.name = f\"{MY_WORKFLOW_NAME} {name} {MODEL_TAG}\"\n", + " workflow.subworkflows[0].model = model\n", + " apply_planewave_cutoffs(workflow, ECUTWFC, ECUTRHO, unit_name=SCF_UNIT)\n", + " apply_planewave_cutoffs(workflow, ECUTWFC, ECUTRHO, unit_name=BANDS_UNIT)\n", + " apply_scf_kgrid(workflow, kgrid[name], material=material)\n", + " patch_workflow_qe_input(workflow, {\"system\": SMEARING_SETTINGS}, [SCF_UNIT, BANDS_UNIT])\n", + "\n", + " bands_subworkflow = workflow.subworkflows[0]\n", + " bands_unit = bands_subworkflow.get_unit_by_name(name=BANDS_UNIT)\n", + " bands_unit.add_context(PointsPathDataProvider(path=KPATH, isEdited=True).get_context_item_data())\n", + " bands_subworkflow.set_unit(bands_unit)\n", + "\n", + " workflows[name] = workflow\n", + " print(workflow.name)\n", + "\n", + "visualize_workflow(workflows[INTERFACE_NAME])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 7.2. Run the jobs one at a time\n", + "\n", + "A job is found by its material and its workflow name, which carries the model tag, and re-used if it exists; otherwise it is created and submitted. The jobs run one after another." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "job_ids = {}\n", + "for name, workflow in workflows.items():\n", + " _, saved_material = materials_for_jobs[name]\n", + " job = find_job_for_material(\n", + " client, saved_material[\"_id\"], workflow.name, ACCOUNT_ID,\n", + " statuses=(\"submitted\", \"queued\", \"active\", \"finished\"),\n", + " )\n", + " if job is None:\n", + " job = create_job(\n", + " api_client=client, materials=[saved_material], workflow=workflow, project_id=project_id,\n", + " owner_id=ACCOUNT_ID, compute=compute.to_dict(), prefix=f\"{workflow.name} {timestamp}\",\n", + " )\n", + " submit_jobs(client.jobs, [job[\"_id\"]])\n", + " print(f\"✅ {name}: submitted job {job['_id']}\")\n", + " else:\n", + " print(f\"♻️ {name}: reusing job {job['_id']}\")\n", + " await wait_for_jobs_to_finish_async(client.jobs, [job[\"_id\"]], poll_interval=POLL_INTERVAL)\n", + " job_ids[name] = job[\"_id\"]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 8. Retrieve the results\n", + "### 8.1. Band structures" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from mat3ra.notebooks_utils.core.entity.property.api import get_properties_for_job\n", + "from mat3ra.notebooks_utils.ipython.entity.property.visualize import visualize_properties\n", + "\n", + "band_structures = {}\n", + "for name, job_id in job_ids.items():\n", + " material, _ = materials_for_jobs[name]\n", + " band_structures[name] = get_properties_for_job(client, job_id, property_name=\"band_structure\")\n", + " visualize_properties(band_structures[name], title=f\"Band Structure: {name}\",\n", + " extra_config={\"material\": material.to_dict()})" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 8.2. Dirac point and gap at K\n", + "\n", + "`KPATH` puts K first (see 1.4), so the first point of the band structure's path is K. At that point, the Dirac point is the midpoint between the band immediately below and the band immediately above the Fermi level, and the gap is their difference -- read directly, with no assumption about which band index that is." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "\n", + "K_INDEX = 0 # KPATH's first point is K\n", + "\n", + "dirac_energies, gaps = {}, {}\n", + "for name, band_structure in band_structures.items():\n", + " bands = np.array(band_structure[0][\"yDataSeries\"])\n", + " fermi_energy = band_structure[0][\"fermiEnergy\"]\n", + " energies_at_k = bands[:, K_INDEX]\n", + " e_above = energies_at_k[energies_at_k > fermi_energy].min()\n", + " e_below = energies_at_k[energies_at_k <= fermi_energy].max()\n", + " dirac_energies[name] = (e_above + e_below) / 2 - fermi_energy\n", + " gaps[name] = e_above - e_below\n", + " print(f\"{name}: E_F = {fermi_energy:.4f} eV, E_D - E_F = {dirac_energies[name]:+.4f} eV, \"\n", + " f\"gap at K = {1000 * gaps[name]:.1f} meV\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 9. Compare with Kang et al. (2008)\n", + "\n", + "Settings that differ from the manuscript: GBRV ultrasoft LDA pseudopotentials against VASP at 396 eV; the standata quartz cell is +2.3% / +2.0% (a/c) off the manuscript's own LDA-relaxed cell; the substrate is 21 Si planes where the manuscript's \"14 bilayers\" is not reproduced exactly (D2); the back carries one dangling bond after passivation, not zero (D3)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "KANG_DIRAC_MINUS_FERMI = 1.2 # eV, Fig. 3(a) read-off\n", + "KANG_GAP = 0.13 # eV, Sec. III text\n", + "\n", + "regime = \"relaxed interface\" if RELAX else \"unrelaxed SCF\"\n", + "print(f\"Regime: {regime}\")\n", + "print(f\"Doping sign: {'p-type (Dirac point above E_F)' if dirac_energies[INTERFACE_NAME] > 0 else 'n-type (Dirac point below E_F)'}\")\n", + "print(f\"{'E_D - E_F (Kang et al., 2008):':<32}{KANG_DIRAC_MINUS_FERMI:+.3f} eV\")\n", + "print(f\"{'E_D - E_F (this notebook):':<32}{dirac_energies[INTERFACE_NAME]:+.3f} eV \"\n", + " f\"({100 * (dirac_energies[INTERFACE_NAME] - KANG_DIRAC_MINUS_FERMI) / KANG_DIRAC_MINUS_FERMI:+.1f} % deviation)\")\n", + "print(f\"{'Gap at K (Kang et al., 2008):':<32}{KANG_GAP:.3f} eV\")\n", + "print(f\"{'Gap at K (this notebook):':<32}{gaps[INTERFACE_NAME]:.3f} eV \"\n", + " f\"({100 * (gaps[INTERFACE_NAME] - KANG_GAP) / KANG_GAP:+.1f} % deviation)\")\n", + "print(f\"{'Gap at K (free-standing reference):':<32}{gaps[REFERENCE_NAME]:.3f} eV, \"\n", + " f\"E_D - E_F = {dirac_energies[REFERENCE_NAME]:+.3f} eV\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## References" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[1] Y.-J. Kang, J. Kang and K. J. Chang, \"Electronic structure of graphene and doping effect on SiO2\", Phys. Rev. B 78, 115404 (2008). https://doi.org/10.1103/PhysRevB.78.115404\n", + "\n", + "[2] K. F. Garrity, J. W. Bennett, K. M. Rabe and D. Vanderbilt, \"Pseudopotentials for high-throughput DFT calculations\", Comput. Mater. Sci. 81, 446 (2014). https://doi.org/10.1016/j.commatsci.2013.08.053" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.12" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} From 594e4f492a8ab1519919b2146a6bc63c404ab613 Mon Sep 17 00:00:00 2001 From: VsevolodX Date: Tue, 6 Oct 2026 17:36:25 -0700 Subject: [PATCH 02/18] SOF-8065: structure notebook as on main plus the import fix; one-material simulation notebook Co-Authored-By: Claude Sonnet 5.5 --- ...rface_2d_3d_graphene_silicon_dioxide.ipynb | 158 +----------- ..._graphene_silicon_dioxide_SIMULATION.ipynb | 234 ++++++++---------- 2 files changed, 110 insertions(+), 282 deletions(-) diff --git a/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide.ipynb b/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide.ipynb index 0eb07af03..ba0c0ce34 100644 --- a/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide.ipynb +++ b/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide.ipynb @@ -18,7 +18,7 @@ "\n", "\n", "\n", - "Builds the **metastable geometry of Sec. III** -- O-terminated α-quartz(0001) with the registry shifted so one surface O sits under a carbon atom and the other under a hexagon centre, and the back surface H-passivated. (Fig. 1(b) below shows the manuscript's *stable*, bonded registry for reference -- this notebook does not build that one.)\n", + "Replicating the materials from the manuscript, FIG. 1. (b):\n", "\n", "\n" ] @@ -358,159 +358,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## 5. Correct the geometry to the paper's metastable registry\n", - "\n", - "As built, the ZSL interface has a 60° cell (made's symbolic K-point assumes 120°) and a registry close to the manuscript's *stable* bonded geometry: both surface O atoms sit near a C atom. Sec. III's metastable geometry needs one O under a C and the other under a hexagon centre, and the manuscript H-passivates the back surface; neither follows from `create_interface_zsl_between_slabs` alone." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 5.1. Save the strained free-standing film as the Dirac-point reference\n", - "\n", - "Taken off the film part of the as-built interface -- same strain as in the interface, no substrate -- before the cell reset, registry shift and passivation below, which only act on the interface." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from mat3ra.made.tools.helpers import create_supercell\n", - "from mat3ra.made.tools.modify import interface_get_part, add_vacuum\n", - "from mat3ra.made.tools.convert.interface_parts_enum import InterfacePartsEnum\n", - "\n", - "film_reference = interface_get_part(interface, InterfacePartsEnum.FILM)\n", - "film_reference = create_supercell(film_reference, supercell_matrix=[[1, 0, 0], [-1, 1, 0], [0, 0, 1]])\n", - "film_reference.lattice.type = \"HEX\"\n", - "film_reference = add_vacuum(film_reference, vacuum=INTERFACE_VACUUM, on_top=True, to_bottom=True)\n", - "film_reference.name = \"Graphene 2x2 on SiO2, free-standing reference\"\n", - "print(f\"{film_reference.basis.number_of_atoms} atoms, gamma = {film_reference.lattice.gamma:.3f} deg\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 5.2. Reset the interface cell to a 120° hexagonal setting" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# The ZSL match above is a 60° cell; made's symbolic K point assumes 120°, so the\n", - "# hexagonal path point is meaningless until the cell is re-set (same physics, same atoms).\n", - "interface = create_supercell(interface, supercell_matrix=[[1, 0, 0], [-1, 1, 0], [0, 0, 1]])\n", - "interface.lattice.type = \"HEX\"\n", - "print(f\"gamma = {interface.lattice.gamma:.3f} deg, a = {interface.lattice.a:.4f} Å\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 5.3. Shift the film to the manuscript's registry\n", - "\n", - "`REGISTRY_SHIFT` is the balanced choice: the real lattice cannot put one surface O exactly under a C while the other sits exactly over a hexagon centre (a 0.71 Å misfit between the two O positions and the graphene lattice), so the shift splits that misfit evenly, leaving each O 0.35 Å from its ideal site rather than putting one exactly on site and the other off by the full 0.71 Å." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import numpy as np\n", - "from collections import Counter\n", - "from mat3ra.made.tools.modify import interface_displace_part\n", - "\n", - "REGISTRY_SHIFT = [-1.010994, -0.724791, 0.0] # Angstrom, cartesian, applied to the film\n", - "\n", - "interface = interface_displace_part(interface, REGISTRY_SHIFT, label=InterfacePartsEnum.FILM)\n", - "\n", - "\n", - "def cartesian_coordinates(material):\n", - " clone = material.clone()\n", - " clone.to_cartesian()\n", - " return np.array(clone.basis.coordinates.values)\n", - "\n", - "\n", - "def xy_distance(p, q, cell_xy):\n", - " \"\"\"Minimum-image in-plane distance; the two lattice vectors are periodic, z is not.\"\"\"\n", - " d_xy = (p - q)[:2]\n", - " return min(np.linalg.norm(d_xy - i * cell_xy[0] - j * cell_xy[1])\n", - " for i in (-1, 0, 1) for j in (-1, 0, 1))\n", - "\n", - "\n", - "elements = interface.basis.elements.values\n", - "coordinates = cartesian_coordinates(interface)\n", - "cell_xy = np.array(interface.lattice.vector_arrays)[:2, :2]\n", - "carbon_indices = [i for i, el in enumerate(elements) if el == \"C\"]\n", - "oxygen_indices = [i for i, el in enumerate(elements) if el == \"O\"]\n", - "surface_oxygens = sorted(oxygen_indices, key=lambda i: -coordinates[i, 2])[:2]\n", - "for i in surface_oxygens:\n", - " nearest_c = min(xy_distance(coordinates[i], coordinates[j], cell_xy) for j in carbon_indices)\n", - " print(f\"O at z={coordinates[i, 2]:.4f}: nearest C at {nearest_c:.4f} Å\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 5.4. Passivate the back surface\n", - "\n", - "`passivate_surface` places one H per exposed atom. The back Si is missing two bonds (coordination 2 of 4), so one dangling bond remains after this call -- declared, not chased further; see the tutorial's Troubleshooting section." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from mat3ra.made.tools.helpers import passivate_surface, SurfaceTypesEnum\n", - "\n", - "GBRV_VALENCE = {\"Si\": 4, \"O\": 6, \"C\": 4, \"H\": 1}\n", - "\n", - "natoms_before = interface.basis.number_of_atoms\n", - "interface = passivate_surface(interface, passivant=\"H\", surface=SurfaceTypesEnum.BOTTOM)\n", - "interface.basis.set_labels_from_list([])\n", - "composition = Counter(interface.basis.elements.values)\n", - "electron_count = sum(GBRV_VALENCE[element] * count for element, count in composition.items())\n", - "print(f\"H added = {interface.basis.number_of_atoms - natoms_before}; \"\n", - " f\"{interface.basis.number_of_atoms} atoms, composition {dict(composition)}\")\n", - "print(f\"{electron_count} valence electrons ({'odd' if electron_count % 2 else 'even'}, GBRV: \"\n", - " \"Si 4, O 6, C 4, H 1)\")\n", - "interface.name = \"Graphene 2x2 on SiO2, metastable registry, H-passivated\"" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 5.5. Preview the corrected interface" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "visualize(interface, repetitions=[1, 1, 1])\n", - "visualize(interface, repetitions=[1, 1, 1], rotation=\"-90x\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 6. Pass data to the outside runtime\n" + "## 5. Pass data to the outside runtime\n" ] }, { @@ -522,7 +370,7 @@ "from mat3ra.notebooks_utils.io import download_content_to_file\n", "from mat3ra.notebooks_utils.material import set_materials\n", "\n", - "set_materials([interface, film_reference])\n", + "set_materials(interface)\n", "download_content_to_file(interface.to_json(), f\"{interface.name}.json\")" ] }, diff --git a/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide_SIMULATION.ipynb b/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide_SIMULATION.ipynb index 2d914396e..beb6d0b81 100644 --- a/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide_SIMULATION.ipynb +++ b/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide_SIMULATION.ipynb @@ -10,30 +10,29 @@ "> effect on SiO2\", Physical Review B 78, 115404 (2008).\n", "> [DOI:10.1103/PhysRevB.78.115404](https://doi.org/10.1103/PhysRevB.78.115404)\n", "\n", - "Computes the band structure of graphene on O-terminated α-quartz(0001), in the manuscript's\n", - "metastable registry of Sec. III built in the [structure notebook](interface_2d_3d_graphene_silicon_dioxide.ipynb),\n", - "and of a free-standing reference of the same strained film, then reads the Dirac point's position\n", - "relative to the Fermi level and the gap at K, and compares both with Kang et al. (2008) Sec. III\n", - "and Fig. 3(a).\n", + "Computes the band structure of the graphene on O-terminated α-quartz(0001) interface created in the\n", + "[structure notebook](interface_2d_3d_graphene_silicon_dioxide.ipynb), then reads the Dirac point's\n", + "position relative to the Fermi level and the gap at K, and compares both with Kang et al. (2008)\n", + "Sec. III and Fig. 3(a).\n", "\n", "

Usage

\n", "\n", - "1. Create the materials in the [structure notebook](interface_2d_3d_graphene_silicon_dioxide.ipynb), which saves them to the `uploads` folder under the names used in cell 1.2 below.\n", - "1. Set the material names and parameters in cells 1.2-1.4, or use the defaults; `RELAX = True` relaxes the interface once (fixed-cell, whole slab) and saves it as ` relaxed` for reuse by this and other notebooks.\n", + "1. Create the interface in the [structure notebook](interface_2d_3d_graphene_silicon_dioxide.ipynb), which saves it to the `uploads` folder under the name used in cell 1.2 below.\n", + "1. Set the material name and parameters in cells 1.2-1.4, or use the defaults; `RELAX = True` relaxes the interface once (fixed-cell, whole slab) and saves it as ` relaxed` for reuse.\n", "1. Click \"Run\" > \"Run All\" to run all cells.\n", - "1. Wait for the jobs to complete.\n", + "1. Wait for the job to complete.\n", "1. Scroll down to view the results; the last section prints the comparison with the manuscript.\n", "\n", "## Summary\n", "\n", - "1. Set up the environment and parameters: install packages (JupyterLite only) and configure the material names, model and compute parameters.\n", + "1. Set up the environment and parameters: install packages (JupyterLite only) and configure the material name, model and compute parameters.\n", "1. Authenticate and initialize API client: authenticate via browser, initialize the client, then select account and project.\n", - "1. Load the materials by name from the `uploads` folder, print their provenance and save them to the platform.\n", - "1. Configure the shared DFT model and k-grid.\n", + "1. Load the interface by name from the `uploads` folder, re-set its cell to 120°, print its provenance and save it to the platform.\n", + "1. Configure the DFT model and k-grid.\n", "1. Configure compute: get the list of clusters and create a compute configuration.\n", "1. Relax the interface if `RELAX`, reusing a saved relaxed structure when one already exists.\n", - "1. Configure the Band Structure workflow and run one job per material, re-using a job that already ran under the same workflow name.\n", - "1. Retrieve the band structures, the Fermi level and the Dirac point's position and gap at K.\n", + "1. Configure the Band Structure workflow and run the job, re-using a job that already ran under the same workflow name.\n", + "1. Retrieve the band structure, the Fermi level and the Dirac point's position and gap at K.\n", "1. Compare with Kang et al. (2008).\n" ] }, @@ -60,7 +59,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### 1.2. Material names" + "### 1.2. Material name" ] }, { @@ -69,9 +68,8 @@ "metadata": {}, "outputs": [], "source": [ - "# Names saved by interface_2d_3d_graphene_silicon_dioxide.ipynb.\n", - "INTERFACE_NAME = \"Graphene 2x2 on SiO2, metastable registry, H-passivated\"\n", - "REFERENCE_NAME = \"Graphene 2x2 on SiO2, free-standing reference\"" + "# Name saved by interface_2d_3d_graphene_silicon_dioxide.ipynb.\n", + "INTERFACE_NAME = \"C(001)-O2Si(001), Interface, Strain 1.875pct\"" ] }, { @@ -99,15 +97,14 @@ "APPLICATION_NAME = \"espresso\"\n", "\n", "# NOTE: False reads the band structure as built; True relaxes the interface once (fixed cell, whole\n", - "# slab -- made has no per-atom constraint wired to the platform workflow, so the back cannot be\n", - "# frozen separately) before the band structure. Kang et al.'s 0.13 eV gap comes from the relaxed O\n", - "# positions, so RELAX = True is the regime that can reproduce it; it is also the slowest Group B job.\n", + "# slab) before the band structure. Kang et al.'s 0.13 eV gap comes from the relaxed O positions, so\n", + "# RELAX = True is the regime that can reproduce it; it is also the slowest job.\n", "RELAX = False\n", "\n", "CLUSTER_NAME = \"001\" # specify full or partial name i.e. \"cluster-001\" to select\n", "QUEUE_NAME = QueueName.OR\n", "PPN = 16 # queue OR on cluster-001 allows at most 16 cores per node\n", - "TIME_LIMIT = \"24:00:00\" # covers the optional relaxation of this 72-atom cell\n", + "TIME_LIMIT = \"24:00:00\" # covers the optional relaxation of this 71-atom cell\n", "\n", "timestamp = datetime.now().strftime(\"%Y-%m-%d %H:%M\")\n", "POLL_INTERVAL = 60 # seconds" @@ -128,19 +125,17 @@ "source": [ "MODEL_SUBTYPE = \"lda\"\n", "FUNCTIONAL = \"pz\" # Kang et al. 2008 use LDA\n", - "PSEUDOPOTENTIAL_TYPE = \"us\" # GBRV ultrasoft, the only LDA family the platform publishes for Si, O, C and H\n", + "PSEUDOPOTENTIAL_TYPE = \"us\" # GBRV ultrasoft, the only LDA family the platform publishes for Si, O and C\n", "ECUTWFC = 40 # Ry, GBRV's recommended wavefunction cutoff\n", "ECUTRHO = 200 # Ry, GBRV's recommended charge-density cutoff\n", "\n", - "KPOINT_DENSITY = 4 # gives 6x6x1 on the measured 21.05 Ų interface cell, Kang et al. Sec. II\n", + "KPOINT_DENSITY = 4 # gives 6 x 6 x 1 on the 1x1 quartz cell, Kang et al. Sec. II\n", "MODEL_TAG = f\"{FUNCTIONAL}-{PSEUDOPOTENTIAL_TYPE} {ECUTWFC}-{ECUTRHO}Ry k{KPOINT_DENSITY}\"\n", "\n", "SCF_UNIT = \"pw_scf\"\n", "BANDS_UNIT = \"pw_bands\"\n", "RELAX_UNIT = \"pw_relax\"\n", - "# The interface carries 369 valence electrons (odd); Gaussian smearing handles the fractional\n", - "# occupation the same way every other doped or metallic job in this corpus does (D6).\n", - "SMEARING_SETTINGS = {\"degauss\": 0.01} # Ry\n", + "SMEARING_SETTINGS = {\"degauss\": 0.01} # Ry; the doped graphene has no gap at E_F\n", "RELAXATION_SETTINGS = {\"forc_conv_thr\": 1.17e-3, \"nstep\": 100} # 0.03 eV/Å, Kang et al. 2008 Sec. II\n", "# Names the relaxation job; the relaxed structure itself is found by content hash.\n", "RELAX_TAG = f\"{MODEL_TAG} f{RELAXATION_SETTINGS['forc_conv_thr']}\"\n", @@ -245,10 +240,12 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## 3. Load the materials\n", - "### 3.1. Load from the uploads folder and print provenance\n", + "## 3. Load the material\n", + "### 3.1. Load from the uploads folder, re-set the cell and print provenance\n", "\n", - "The structures, their registry and the back passivation all come from the structure notebook; this one only loads them by name." + "The structure comes from the structure notebook and is loaded by name. Its cell is a 60° setting,\n", + "while the symbolic K point of the band path assumes 120°, so the cell is re-set to the equivalent\n", + "120° one: same atoms, same registry." ] }, { @@ -258,25 +255,26 @@ "outputs": [], "source": [ "from collections import Counter\n", + "from mat3ra.made.tools.helpers import create_supercell\n", "from mat3ra.notebooks_utils.core.entity.material.api import load_material\n", "\n", - "GBRV_VALENCE = {\"Si\": 4, \"O\": 6, \"C\": 4, \"H\": 1}\n", + "GBRV_VALENCE = {\"Si\": 4, \"O\": 6, \"C\": 4}\n", "\n", - "materials_by_name = {name: load_material(client, FOLDER, name, ACCOUNT_ID) for name in (INTERFACE_NAME, REFERENCE_NAME)}\n", - "interface, reference = materials_by_name[INTERFACE_NAME], materials_by_name[REFERENCE_NAME]\n", - "for name, material in materials_by_name.items():\n", - " composition = Counter(material.basis.elements.values)\n", - " electron_count = sum(GBRV_VALENCE[element] * count for element, count in composition.items())\n", - " print(f\"{name}: {dict(composition)}, {material.basis.number_of_atoms} atoms, \"\n", - " f\"gamma = {material.lattice.gamma:.3f}°, {electron_count} valence electrons \"\n", - " f\"({'odd' if electron_count % 2 else 'even'})\")" + "interface = load_material(client, FOLDER, INTERFACE_NAME, ACCOUNT_ID)\n", + "interface = create_supercell(interface, supercell_matrix=[[1, 0, 0], [-1, 1, 0], [0, 0, 1]])\n", + "interface.lattice.type = \"HEX\"\n", + "\n", + "composition = Counter(interface.basis.elements.values)\n", + "electron_count = sum(GBRV_VALENCE[element] * count for element, count in composition.items())\n", + "print(f\"{INTERFACE_NAME}: {dict(composition)}, {interface.basis.number_of_atoms} atoms, \"\n", + " f\"gamma = {interface.lattice.gamma:.3f}°, {electron_count} valence electrons\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### 3.2. Save the materials to the platform" + "### 3.2. Save the material to the platform" ] }, { @@ -287,15 +285,14 @@ "source": [ "from mat3ra.notebooks_utils.core.entity.material.api import get_or_create_material\n", "\n", - "saved_interface = get_or_create_material(client, interface, ACCOUNT_ID)\n", - "saved_reference = get_or_create_material(client, reference, ACCOUNT_ID)" + "saved_interface = get_or_create_material(client, interface, ACCOUNT_ID)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## 4. Configure the shared DFT model and k-grid\n", + "## 4. Configure the DFT model and k-grid\n", "### 4.1. DFT model" ] }, @@ -323,7 +320,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### 4.2. k-grid per material" + "### 4.2. k-grid" ] }, { @@ -334,12 +331,8 @@ "source": [ "from mat3ra.notebooks_utils.workflow import kgrid_from_density\n", "\n", - "kgrid = {\n", - " INTERFACE_NAME: kgrid_from_density(interface, KPOINT_DENSITY, periodic_dims=(0, 1)),\n", - " REFERENCE_NAME: kgrid_from_density(reference, KPOINT_DENSITY, periodic_dims=(0, 1)),\n", - "}\n", - "for name, grid in kgrid.items():\n", - " print(f\"{name}: k-grid {grid}\")" + "kgrid = kgrid_from_density(interface, KPOINT_DENSITY, periodic_dims=(0, 1))\n", + "print(f\"k-grid {kgrid}\")" ] }, { @@ -392,7 +385,11 @@ "source": [ "## 6. Relax the interface (optional)\n", "\n", - "Runs only if `RELAX`: finds any relaxed version of this structure already on the account, regardless of who relaxed it or with what, before spending any compute on a new one. The band structure is then read off that geometry. Kang et al.'s 0.13 eV gap comes from the second surface O relaxing toward a neighbouring C atom, so an unrelaxed run is the as-built geometry rather than a rougher version of the relaxed one." + "Runs only if `RELAX`: finds any relaxed version of this structure already on the account,\n", + "regardless of who relaxed it or with what, before spending any compute on a new one. The band\n", + "structure is then read off that geometry. Kang et al.'s 0.13 eV gap comes from the second surface\n", + "O relaxing toward a neighbouring C atom, so an unrelaxed run is the as-built geometry rather than\n", + "a rougher version of the relaxed one." ] }, { @@ -424,7 +421,7 @@ " relax_workflow.name = relax_workflow_name\n", " relax_workflow.subworkflows[0].model = model\n", " apply_planewave_cutoffs(relax_workflow, ECUTWFC, ECUTRHO, unit_name=RELAX_UNIT)\n", - " apply_scf_kgrid(relax_workflow, kgrid[INTERFACE_NAME], material=interface, unit_name=RELAX_UNIT)\n", + " apply_scf_kgrid(relax_workflow, kgrid, material=interface, unit_name=RELAX_UNIT)\n", " patch_workflow_qe_input(relax_workflow, {\"system\": SMEARING_SETTINGS}, [RELAX_UNIT])\n", " patch_workflow_qe_input(relax_workflow, {\"control\": RELAXATION_SETTINGS}, [RELAX_UNIT])\n", "\n", @@ -450,17 +447,17 @@ " print(f\"Residual force after relaxation (norm over all atoms): \"\n", " f\"{total_force['value']:.4f} {total_force['units']}\")\n", "\n", - "interface_for_jobs = relaxed_interface if RELAX else interface\n", - "interface_for_jobs.lattice.type = \"HEX\" # the symbolic K-Γ-M-K path needs a named lattice type\n", - "saved_interface_for_jobs = get_or_create_material(client, interface_for_jobs, ACCOUNT_ID)" + "interface_for_job = relaxed_interface if RELAX else interface\n", + "interface_for_job.lattice.type = \"HEX\" # the symbolic K-Γ-M-K path needs a named lattice type\n", + "saved_interface_for_job = get_or_create_material(client, interface_for_job, ACCOUNT_ID)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## 7. Band structure jobs\n", - "### 7.1. Configure one workflow per material" + "## 7. Band structure job\n", + "### 7.1. Configure the workflow" ] }, { @@ -471,41 +468,32 @@ "source": [ "from mat3ra.wode.context.providers import PointsPathDataProvider\n", "from mat3ra.notebooks_utils.ipython.entity.workflow.visualize import visualize_workflow\n", - "from copy import deepcopy\n", "\n", - "materials_for_jobs = {INTERFACE_NAME: (interface_for_jobs, saved_interface_for_jobs), REFERENCE_NAME: (reference, saved_reference)}\n", - "band_structure_workflow_config = WorkflowStandata.filter_by_application(app.name).get_by_name_first_match(\n", - " BAND_STRUCTURE_WORKFLOW_SEARCH_TERM\n", + "workflow = Workflow.create(\n", + " WorkflowStandata.filter_by_application(app.name).get_by_name_first_match(BAND_STRUCTURE_WORKFLOW_SEARCH_TERM)\n", ")\n", + "workflow.name = f\"{MY_WORKFLOW_NAME} {INTERFACE_NAME} {MODEL_TAG}\"\n", + "workflow.subworkflows[0].model = model\n", + "apply_planewave_cutoffs(workflow, ECUTWFC, ECUTRHO, unit_name=SCF_UNIT)\n", + "apply_planewave_cutoffs(workflow, ECUTWFC, ECUTRHO, unit_name=BANDS_UNIT)\n", + "apply_scf_kgrid(workflow, kgrid, material=interface_for_job)\n", + "patch_workflow_qe_input(workflow, {\"system\": SMEARING_SETTINGS}, [SCF_UNIT, BANDS_UNIT])\n", "\n", - "workflows = {}\n", - "for name, (material, _) in materials_for_jobs.items():\n", - " workflow = Workflow.create(deepcopy(band_structure_workflow_config))\n", - " workflow.name = f\"{MY_WORKFLOW_NAME} {name} {MODEL_TAG}\"\n", - " workflow.subworkflows[0].model = model\n", - " apply_planewave_cutoffs(workflow, ECUTWFC, ECUTRHO, unit_name=SCF_UNIT)\n", - " apply_planewave_cutoffs(workflow, ECUTWFC, ECUTRHO, unit_name=BANDS_UNIT)\n", - " apply_scf_kgrid(workflow, kgrid[name], material=material)\n", - " patch_workflow_qe_input(workflow, {\"system\": SMEARING_SETTINGS}, [SCF_UNIT, BANDS_UNIT])\n", - "\n", - " bands_subworkflow = workflow.subworkflows[0]\n", - " bands_unit = bands_subworkflow.get_unit_by_name(name=BANDS_UNIT)\n", - " bands_unit.add_context(PointsPathDataProvider(path=KPATH, isEdited=True).get_context_item_data())\n", - " bands_subworkflow.set_unit(bands_unit)\n", + "bands_subworkflow = workflow.subworkflows[0]\n", + "bands_unit = bands_subworkflow.get_unit_by_name(name=BANDS_UNIT)\n", + "bands_unit.add_context(PointsPathDataProvider(path=KPATH, isEdited=True).get_context_item_data())\n", + "bands_subworkflow.set_unit(bands_unit)\n", "\n", - " workflows[name] = workflow\n", - " print(workflow.name)\n", - "\n", - "visualize_workflow(workflows[INTERFACE_NAME])" + "visualize_workflow(workflow)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### 7.2. Run the jobs one at a time\n", + "### 7.2. Run the job\n", "\n", - "A job is found by its material and its workflow name, which carries the model tag, and re-used if it exists; otherwise it is created and submitted. The jobs run one after another." + "A job is found by its material and its workflow name, which carries the model tag, and re-used if it exists; otherwise it is created and submitted." ] }, { @@ -514,24 +502,21 @@ "metadata": {}, "outputs": [], "source": [ - "job_ids = {}\n", - "for name, workflow in workflows.items():\n", - " _, saved_material = materials_for_jobs[name]\n", - " job = find_job_for_material(\n", - " client, saved_material[\"_id\"], workflow.name, ACCOUNT_ID,\n", - " statuses=(\"submitted\", \"queued\", \"active\", \"finished\"),\n", + "job = find_job_for_material(\n", + " client, saved_interface_for_job[\"_id\"], workflow.name, ACCOUNT_ID,\n", + " statuses=(\"submitted\", \"queued\", \"active\", \"finished\"),\n", + ")\n", + "if job is None:\n", + " job = create_job(\n", + " api_client=client, materials=[saved_interface_for_job], workflow=workflow, project_id=project_id,\n", + " owner_id=ACCOUNT_ID, compute=compute.to_dict(), prefix=f\"{workflow.name} {timestamp}\",\n", " )\n", - " if job is None:\n", - " job = create_job(\n", - " api_client=client, materials=[saved_material], workflow=workflow, project_id=project_id,\n", - " owner_id=ACCOUNT_ID, compute=compute.to_dict(), prefix=f\"{workflow.name} {timestamp}\",\n", - " )\n", - " submit_jobs(client.jobs, [job[\"_id\"]])\n", - " print(f\"✅ {name}: submitted job {job['_id']}\")\n", - " else:\n", - " print(f\"♻️ {name}: reusing job {job['_id']}\")\n", - " await wait_for_jobs_to_finish_async(client.jobs, [job[\"_id\"]], poll_interval=POLL_INTERVAL)\n", - " job_ids[name] = job[\"_id\"]" + " submit_jobs(client.jobs, [job[\"_id\"]])\n", + " print(f\"✅ Submitted job {job['_id']}\")\n", + "else:\n", + " print(f\"♻️ Reusing job {job['_id']}\")\n", + "await wait_for_jobs_to_finish_async(client.jobs, [job[\"_id\"]], poll_interval=POLL_INTERVAL)\n", + "job_id = job[\"_id\"]" ] }, { @@ -539,7 +524,7 @@ "metadata": {}, "source": [ "## 8. Retrieve the results\n", - "### 8.1. Band structures" + "### 8.1. Band structure" ] }, { @@ -548,15 +533,11 @@ "metadata": {}, "outputs": [], "source": [ - "from mat3ra.notebooks_utils.core.entity.property.api import get_properties_for_job\n", "from mat3ra.notebooks_utils.ipython.entity.property.visualize import visualize_properties\n", "\n", - "band_structures = {}\n", - "for name, job_id in job_ids.items():\n", - " material, _ = materials_for_jobs[name]\n", - " band_structures[name] = get_properties_for_job(client, job_id, property_name=\"band_structure\")\n", - " visualize_properties(band_structures[name], title=f\"Band Structure: {name}\",\n", - " extra_config={\"material\": material.to_dict()})" + "band_structure = get_properties_for_job(client, job_id, property_name=\"band_structure\")\n", + "visualize_properties(band_structure, title=\"Band Structure\",\n", + " extra_config={\"material\": interface_for_job.to_dict()})" ] }, { @@ -565,7 +546,9 @@ "source": [ "### 8.2. Dirac point and gap at K\n", "\n", - "`KPATH` puts K first (see 1.4), so the first point of the band structure's path is K. At that point, the Dirac point is the midpoint between the band immediately below and the band immediately above the Fermi level, and the gap is their difference -- read directly, with no assumption about which band index that is." + "`KPATH` starts at K, so the first point of the band structure's path is K. There, the Dirac point is\n", + "the midpoint between the band immediately below and the band immediately above the Fermi level, and\n", + "the gap is their difference." ] }, { @@ -578,17 +561,14 @@ "\n", "K_INDEX = 0 # KPATH's first point is K\n", "\n", - "dirac_energies, gaps = {}, {}\n", - "for name, band_structure in band_structures.items():\n", - " bands = np.array(band_structure[0][\"yDataSeries\"])\n", - " fermi_energy = band_structure[0][\"fermiEnergy\"]\n", - " energies_at_k = bands[:, K_INDEX]\n", - " e_above = energies_at_k[energies_at_k > fermi_energy].min()\n", - " e_below = energies_at_k[energies_at_k <= fermi_energy].max()\n", - " dirac_energies[name] = (e_above + e_below) / 2 - fermi_energy\n", - " gaps[name] = e_above - e_below\n", - " print(f\"{name}: E_F = {fermi_energy:.4f} eV, E_D - E_F = {dirac_energies[name]:+.4f} eV, \"\n", - " f\"gap at K = {1000 * gaps[name]:.1f} meV\")" + "bands = np.array(band_structure[0][\"yDataSeries\"])\n", + "fermi_energy = band_structure[0][\"fermiEnergy\"]\n", + "energies_at_k = bands[:, K_INDEX]\n", + "e_above = energies_at_k[energies_at_k > fermi_energy].min()\n", + "e_below = energies_at_k[energies_at_k <= fermi_energy].max()\n", + "dirac_minus_fermi = (e_above + e_below) / 2 - fermi_energy\n", + "gap = e_above - e_below\n", + "print(f\"E_F = {fermi_energy:.4f} eV, E_D - E_F = {dirac_minus_fermi:+.4f} eV, gap at K = {1000 * gap:.1f} meV\")" ] }, { @@ -597,7 +577,10 @@ "source": [ "## 9. Compare with Kang et al. (2008)\n", "\n", - "Settings that differ from the manuscript: GBRV ultrasoft LDA pseudopotentials against VASP at 396 eV; the standata quartz cell is +2.3% / +2.0% (a/c) off the manuscript's own LDA-relaxed cell; the substrate is 21 Si planes where the manuscript's \"14 bilayers\" is not reproduced exactly (D2); the back carries one dangling bond after passivation, not zero (D3)." + "Settings that differ from the manuscript: GBRV ultrasoft LDA pseudopotentials against VASP at 396 eV;\n", + "the standata quartz cell is +2.3 % / +2.0 % (a / c) against the manuscript's own cell; the substrate\n", + "is 21 Si planes where the manuscript has 14 bilayers; the back surface is the bare cut, not\n", + "H-passivated." ] }, { @@ -609,17 +592,14 @@ "KANG_DIRAC_MINUS_FERMI = 1.2 # eV, Fig. 3(a) read-off\n", "KANG_GAP = 0.13 # eV, Sec. III text\n", "\n", - "regime = \"relaxed interface\" if RELAX else \"unrelaxed SCF\"\n", - "print(f\"Regime: {regime}\")\n", - "print(f\"Doping sign: {'p-type (Dirac point above E_F)' if dirac_energies[INTERFACE_NAME] > 0 else 'n-type (Dirac point below E_F)'}\")\n", + "print(f\"Regime: {'relaxed interface' if RELAX else 'unrelaxed SCF'}\")\n", + "print(f\"Doping: {'p-type' if dirac_minus_fermi > 0 else 'n-type'} (Kang et al., 2008: p-type)\")\n", "print(f\"{'E_D - E_F (Kang et al., 2008):':<32}{KANG_DIRAC_MINUS_FERMI:+.3f} eV\")\n", - "print(f\"{'E_D - E_F (this notebook):':<32}{dirac_energies[INTERFACE_NAME]:+.3f} eV \"\n", - " f\"({100 * (dirac_energies[INTERFACE_NAME] - KANG_DIRAC_MINUS_FERMI) / KANG_DIRAC_MINUS_FERMI:+.1f} % deviation)\")\n", + "print(f\"{'E_D - E_F (this notebook):':<32}{dirac_minus_fermi:+.3f} eV \"\n", + " f\"({100 * (dirac_minus_fermi - KANG_DIRAC_MINUS_FERMI) / KANG_DIRAC_MINUS_FERMI:+.1f} % deviation)\")\n", "print(f\"{'Gap at K (Kang et al., 2008):':<32}{KANG_GAP:.3f} eV\")\n", - "print(f\"{'Gap at K (this notebook):':<32}{gaps[INTERFACE_NAME]:.3f} eV \"\n", - " f\"({100 * (gaps[INTERFACE_NAME] - KANG_GAP) / KANG_GAP:+.1f} % deviation)\")\n", - "print(f\"{'Gap at K (free-standing reference):':<32}{gaps[REFERENCE_NAME]:.3f} eV, \"\n", - " f\"E_D - E_F = {dirac_energies[REFERENCE_NAME]:+.3f} eV\")" + "print(f\"{'Gap at K (this notebook):':<32}{gap:.3f} eV \"\n", + " f\"({100 * (gap - KANG_GAP) / KANG_GAP:+.1f} % deviation)\")" ] }, { From 12b67d265e8c5108d495a515c09f25d34c376495 Mon Sep 17 00:00:00 2001 From: VsevolodX Date: Tue, 6 Oct 2026 18:00:13 -0700 Subject: [PATCH 03/18] SOF-8065: clear atom labels after load; read the Dirac pair by band index Co-Authored-By: Claude Sonnet 5.5 --- ..._graphene_silicon_dioxide_SIMULATION.ipynb | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide_SIMULATION.ipynb b/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide_SIMULATION.ipynb index beb6d0b81..7d245627a 100644 --- a/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide_SIMULATION.ipynb +++ b/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide_SIMULATION.ipynb @@ -261,13 +261,15 @@ "GBRV_VALENCE = {\"Si\": 4, \"O\": 6, \"C\": 4}\n", "\n", "interface = load_material(client, FOLDER, INTERFACE_NAME, ACCOUNT_ID)\n", + "interface.basis.set_labels_from_list([])\n", "interface = create_supercell(interface, supercell_matrix=[[1, 0, 0], [-1, 1, 0], [0, 0, 1]])\n", "interface.lattice.type = \"HEX\"\n", "\n", "composition = Counter(interface.basis.elements.values)\n", "electron_count = sum(GBRV_VALENCE[element] * count for element, count in composition.items())\n", + "N_OCCUPIED = electron_count // 2\n", "print(f\"{INTERFACE_NAME}: {dict(composition)}, {interface.basis.number_of_atoms} atoms, \"\n", - " f\"gamma = {interface.lattice.gamma:.3f}°, {electron_count} valence electrons\")" + " f\"gamma = {interface.lattice.gamma:.3f}°, {electron_count} valence electrons, {N_OCCUPIED} occupied bands\")" ] }, { @@ -546,9 +548,11 @@ "source": [ "### 8.2. Dirac point and gap at K\n", "\n", - "`KPATH` starts at K, so the first point of the band structure's path is K. There, the Dirac point is\n", - "the midpoint between the band immediately below and the band immediately above the Fermi level, and\n", - "the gap is their difference." + "`KPATH` starts at K, so the first point of the band structure's path is K. With `N_OCCUPIED` doubly\n", + "occupied bands, the two Dirac bands at K are bands `N_OCCUPIED - 1` and `N_OCCUPIED` (0-based) for\n", + "neutral graphene. If a surface O state sits below the Dirac point, as in p-doped graphene, they are\n", + "`N_OCCUPIED` and `N_OCCUPIED + 1`. The pair with the smaller splitting at K is the Dirac pair; the\n", + "Dirac point is its midpoint and the gap its splitting." ] }, { @@ -564,10 +568,15 @@ "bands = np.array(band_structure[0][\"yDataSeries\"])\n", "fermi_energy = band_structure[0][\"fermiEnergy\"]\n", "energies_at_k = bands[:, K_INDEX]\n", - "e_above = energies_at_k[energies_at_k > fermi_energy].min()\n", - "e_below = energies_at_k[energies_at_k <= fermi_energy].max()\n", - "dirac_minus_fermi = (e_above + e_below) / 2 - fermi_energy\n", - "gap = e_above - e_below\n", + "print(f\"Bands at K relative to E_F (eV), 0-based indices {N_OCCUPIED - 2}..{N_OCCUPIED + 2}:\")\n", + "for index in range(N_OCCUPIED - 2, N_OCCUPIED + 3):\n", + " print(f\" {index}: {energies_at_k[index] - fermi_energy:+.4f}\")\n", + "\n", + "candidates = [(N_OCCUPIED - 1, N_OCCUPIED), (N_OCCUPIED, N_OCCUPIED + 1)]\n", + "below, above = min(candidates, key=lambda pair: energies_at_k[pair[1]] - energies_at_k[pair[0]])\n", + "gap = energies_at_k[above] - energies_at_k[below]\n", + "dirac_minus_fermi = (energies_at_k[above] + energies_at_k[below]) / 2 - fermi_energy\n", + "print(f\"Dirac pair: bands {below} and {above}\")\n", "print(f\"E_F = {fermi_energy:.4f} eV, E_D - E_F = {dirac_minus_fermi:+.4f} eV, gap at K = {1000 * gap:.1f} meV\")" ] }, From 6c8a0aad65bc4594d03f5dead752d92711b687aa Mon Sep 17 00:00:00 2001 From: VsevolodX Date: Tue, 6 Oct 2026 18:02:11 -0700 Subject: [PATCH 04/18] SOF-8065: TIME_LIMIT 04:00:00 as the sibling notebooks ship Co-Authored-By: Claude Sonnet 5.5 --- .../interface_2d_3d_graphene_silicon_dioxide_SIMULATION.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide_SIMULATION.ipynb b/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide_SIMULATION.ipynb index 7d245627a..ebaad7cf2 100644 --- a/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide_SIMULATION.ipynb +++ b/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide_SIMULATION.ipynb @@ -104,7 +104,7 @@ "CLUSTER_NAME = \"001\" # specify full or partial name i.e. \"cluster-001\" to select\n", "QUEUE_NAME = QueueName.OR\n", "PPN = 16 # queue OR on cluster-001 allows at most 16 cores per node\n", - "TIME_LIMIT = \"24:00:00\" # covers the optional relaxation of this 71-atom cell\n", + "TIME_LIMIT = \"04:00:00\"\n", "\n", "timestamp = datetime.now().strftime(\"%Y-%m-%d %H:%M\")\n", "POLL_INTERVAL = 60 # seconds" From 801ccd2cb5d748b924c2ff17c2c346a1272ec07a Mon Sep 17 00:00:00 2001 From: VsevolodX Date: Tue, 6 Oct 2026 18:35:52 -0700 Subject: [PATCH 05/18] SOF-8065: substrate thickness 5 and vacuum about 20 A above graphene Co-Authored-By: Claude Sonnet 5.5 --- .../interface_2d_3d_graphene_silicon_dioxide.ipynb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide.ipynb b/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide.ipynb index ba0c0ce34..7db5b05c4 100644 --- a/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide.ipynb +++ b/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide.ipynb @@ -45,12 +45,12 @@ "FILM_VACUUM = 0.0 # in angstroms\n", "\n", "SUBSTRATE_MILLER_INDICES = (0, 0, 1)\n", - "SUBSTRATE_THICKNESS = 7 # in atomic layers (for 14 bilayers -- from manuscript)\n", + "SUBSTRATE_THICKNESS = 5 # conventional cells along c; 5 gives 15 Si planes, the manuscript's 14 SiO2 bilayers rounded to a whole cell\n", "SUBSTRATE_TERMINATION_FORMULA = None # if None, the first termination will be used\n", "SUBSTRATE_VACUUM = 0.0 # in angstroms\n", "\n", "INTERFACE_DISTANCE = 2.58 # Gap between substrate and film, in Angstrom -- from manuscript\n", - "INTERFACE_VACUUM = 20.0 # Vacuum over film, in Angstrom -- from manuscript\n", + "INTERFACE_VACUUM = 17.5 # Vacuum over film, in Angstrom; gives about 20 A above graphene, as in the manuscript\n", "\n", "# Whether to convert materials to conventional cells before creating slabs.\n", "USE_CONVENTIONAL_CELL = True\n", From 0b5e82be40c78ca9046a8423a1922900825987a7 Mon Sep 17 00:00:00 2001 From: VsevolodX Date: Tue, 6 Oct 2026 20:22:40 -0700 Subject: [PATCH 06/18] SOF-8065: review round 1 fixes in the simulation and structure notebooks Co-Authored-By: Claude Sonnet 5.5 --- ...rface_2d_3d_graphene_silicon_dioxide.ipynb | 4 +- ..._graphene_silicon_dioxide_SIMULATION.ipynb | 74 +++++++++++-------- 2 files changed, 47 insertions(+), 31 deletions(-) diff --git a/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide.ipynb b/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide.ipynb index 7db5b05c4..34f7676b0 100644 --- a/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide.ipynb +++ b/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide.ipynb @@ -45,12 +45,12 @@ "FILM_VACUUM = 0.0 # in angstroms\n", "\n", "SUBSTRATE_MILLER_INDICES = (0, 0, 1)\n", - "SUBSTRATE_THICKNESS = 5 # conventional cells along c; 5 gives 15 Si planes, the manuscript's 14 SiO2 bilayers rounded to a whole cell\n", + "SUBSTRATE_THICKNESS = 5 # in conventional cells along c: 15 Si planes, the manuscript's 14 bilayers\n", "SUBSTRATE_TERMINATION_FORMULA = None # if None, the first termination will be used\n", "SUBSTRATE_VACUUM = 0.0 # in angstroms\n", "\n", "INTERFACE_DISTANCE = 2.58 # Gap between substrate and film, in Angstrom -- from manuscript\n", - "INTERFACE_VACUUM = 17.5 # Vacuum over film, in Angstrom; gives about 20 A above graphene, as in the manuscript\n", + "INTERFACE_VACUUM = 17.5 # Angstrom; gives about 20 A above graphene, as the builder adds INTERFACE_DISTANCE above the film too\n", "\n", "# Whether to convert materials to conventional cells before creating slabs.\n", "USE_CONVENTIONAL_CELL = True\n", diff --git a/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide_SIMULATION.ipynb b/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide_SIMULATION.ipynb index ebaad7cf2..56eded460 100644 --- a/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide_SIMULATION.ipynb +++ b/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide_SIMULATION.ipynb @@ -97,8 +97,7 @@ "APPLICATION_NAME = \"espresso\"\n", "\n", "# NOTE: False reads the band structure as built; True relaxes the interface once (fixed cell, whole\n", - "# slab) before the band structure. Kang et al.'s 0.13 eV gap comes from the relaxed O positions, so\n", - "# RELAX = True is the regime that can reproduce it; it is also the slowest job.\n", + "# slab) and reads the band structure off the relaxed structure.\n", "RELAX = False\n", "\n", "CLUSTER_NAME = \"001\" # specify full or partial name i.e. \"cluster-001\" to select\n", @@ -126,6 +125,7 @@ "MODEL_SUBTYPE = \"lda\"\n", "FUNCTIONAL = \"pz\" # Kang et al. 2008 use LDA\n", "PSEUDOPOTENTIAL_TYPE = \"us\" # GBRV ultrasoft, the only LDA family the platform publishes for Si, O and C\n", + "GBRV_VALENCE = {\"Si\": 4, \"O\": 6, \"C\": 4} # valence electrons per atom of the GBRV pseudopotentials\n", "ECUTWFC = 40 # Ry, GBRV's recommended wavefunction cutoff\n", "ECUTRHO = 200 # Ry, GBRV's recommended charge-density cutoff\n", "\n", @@ -146,7 +146,8 @@ " {\"point\": \"Γ\", \"steps\": KPATH_STEPS},\n", " {\"point\": \"M\", \"steps\": KPATH_STEPS},\n", " {\"point\": \"K\", \"steps\": 1},\n", - "]" + "]\n", + "K_INDEX = 0 # KPATH starts at K, so the first point of the band structure's path is K" ] }, { @@ -254,12 +255,11 @@ "metadata": {}, "outputs": [], "source": [ + "import numpy as np\n", "from collections import Counter\n", "from mat3ra.made.tools.helpers import create_supercell\n", "from mat3ra.notebooks_utils.core.entity.material.api import load_material\n", "\n", - "GBRV_VALENCE = {\"Si\": 4, \"O\": 6, \"C\": 4}\n", - "\n", "interface = load_material(client, FOLDER, INTERFACE_NAME, ACCOUNT_ID)\n", "interface.basis.set_labels_from_list([])\n", "interface = create_supercell(interface, supercell_matrix=[[1, 0, 0], [-1, 1, 0], [0, 0, 1]])\n", @@ -267,16 +267,41 @@ "\n", "composition = Counter(interface.basis.elements.values)\n", "electron_count = sum(GBRV_VALENCE[element] * count for element, count in composition.items())\n", - "N_OCCUPIED = electron_count // 2\n", + "NUMBER_OF_OCCUPIED_BANDS = electron_count // 2\n", + "\n", + "cartesian = interface.clone()\n", + "cartesian.to_cartesian()\n", + "z = np.array(cartesian.basis.coordinates.values)[:, 2]\n", + "is_carbon = np.array(interface.basis.elements.values) == \"C\"\n", + "interlayer_distance = z[is_carbon].min() - z[~is_carbon].max()\n", "print(f\"{INTERFACE_NAME}: {dict(composition)}, {interface.basis.number_of_atoms} atoms, \"\n", - " f\"gamma = {interface.lattice.gamma:.3f}°, {electron_count} valence electrons, {N_OCCUPIED} occupied bands\")" + " f\"gamma = {interface.lattice.gamma:.3f}°, interlayer distance = {interlayer_distance:.3f} Å, \"\n", + " f\"{electron_count} valence electrons, {NUMBER_OF_OCCUPIED_BANDS} occupied bands\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 3.2. Preview the material" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from mat3ra.notebooks_utils.ipython.entity.material.visualize import visualize_materials\n", + "\n", + "visualize_materials([{\"material\": interface, \"title\": INTERFACE_NAME}], viewer=\"wave\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### 3.2. Save the material to the platform" + "### 3.3. Save the material to the platform" ] }, { @@ -548,10 +573,10 @@ "source": [ "### 8.2. Dirac point and gap at K\n", "\n", - "`KPATH` starts at K, so the first point of the band structure's path is K. With `N_OCCUPIED` doubly\n", - "occupied bands, the two Dirac bands at K are bands `N_OCCUPIED - 1` and `N_OCCUPIED` (0-based) for\n", + "`KPATH` starts at K, so the first point of the band structure's path is K. With `NUMBER_OF_OCCUPIED_BANDS` doubly\n", + "occupied bands, the two Dirac bands at K are bands `NUMBER_OF_OCCUPIED_BANDS - 1` and `NUMBER_OF_OCCUPIED_BANDS` (0-based) for\n", "neutral graphene. If a surface O state sits below the Dirac point, as in p-doped graphene, they are\n", - "`N_OCCUPIED` and `N_OCCUPIED + 1`. The pair with the smaller splitting at K is the Dirac pair; the\n", + "`NUMBER_OF_OCCUPIED_BANDS` and `NUMBER_OF_OCCUPIED_BANDS + 1`. The pair with the smaller splitting at K is the Dirac pair; the\n", "Dirac point is its midpoint and the gap its splitting." ] }, @@ -561,18 +586,14 @@ "metadata": {}, "outputs": [], "source": [ - "import numpy as np\n", - "\n", - "K_INDEX = 0 # KPATH's first point is K\n", - "\n", "bands = np.array(band_structure[0][\"yDataSeries\"])\n", "fermi_energy = band_structure[0][\"fermiEnergy\"]\n", "energies_at_k = bands[:, K_INDEX]\n", - "print(f\"Bands at K relative to E_F (eV), 0-based indices {N_OCCUPIED - 2}..{N_OCCUPIED + 2}:\")\n", - "for index in range(N_OCCUPIED - 2, N_OCCUPIED + 3):\n", + "print(f\"Bands at K relative to E_F (eV), 0-based indices {NUMBER_OF_OCCUPIED_BANDS - 2}..{NUMBER_OF_OCCUPIED_BANDS + 2}:\")\n", + "for index in range(NUMBER_OF_OCCUPIED_BANDS - 2, NUMBER_OF_OCCUPIED_BANDS + 3):\n", " print(f\" {index}: {energies_at_k[index] - fermi_energy:+.4f}\")\n", "\n", - "candidates = [(N_OCCUPIED - 1, N_OCCUPIED), (N_OCCUPIED, N_OCCUPIED + 1)]\n", + "candidates = [(NUMBER_OF_OCCUPIED_BANDS - 1, NUMBER_OF_OCCUPIED_BANDS), (NUMBER_OF_OCCUPIED_BANDS, NUMBER_OF_OCCUPIED_BANDS + 1)]\n", "below, above = min(candidates, key=lambda pair: energies_at_k[pair[1]] - energies_at_k[pair[0]])\n", "gap = energies_at_k[above] - energies_at_k[below]\n", "dirac_minus_fermi = (energies_at_k[above] + energies_at_k[below]) / 2 - fermi_energy\n", @@ -587,8 +608,8 @@ "## 9. Compare with Kang et al. (2008)\n", "\n", "Settings that differ from the manuscript: GBRV ultrasoft LDA pseudopotentials against VASP at 396 eV;\n", - "the standata quartz cell is +2.3 % / +2.0 % (a / c) against the manuscript's own cell; the substrate\n", - "is 21 Si planes where the manuscript has 14 bilayers; the back surface is the bare cut, not\n", + "the standata quartz cell is +2.3 % on a against the manuscript's 4.91 Å; the substrate is 5\n", + "conventional quartz cells (15 Si planes) where the manuscript has 14 bilayers; the back surface is the bare cut, not\n", "H-passivated." ] }, @@ -615,13 +636,8 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## References" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ + "## References\n", + "\n", "[1] Y.-J. Kang, J. Kang and K. J. Chang, \"Electronic structure of graphene and doping effect on SiO2\", Phys. Rev. B 78, 115404 (2008). https://doi.org/10.1103/PhysRevB.78.115404\n", "\n", "[2] K. F. Garrity, J. W. Bennett, K. M. Rabe and D. Vanderbilt, \"Pseudopotentials for high-throughput DFT calculations\", Comput. Mater. Sci. 81, 446 (2014). https://doi.org/10.1016/j.commatsci.2013.08.053" @@ -630,7 +646,7 @@ ], "metadata": { "kernelspec": { - "display_name": ".venv", + "display_name": "Python 3", "language": "python", "name": "python3" }, @@ -644,7 +660,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.12" + "version": "3.11.2" } }, "nbformat": 4, From c8853102dcffcf6e8a53e59737242685d10118a5 Mon Sep 17 00:00:00 2001 From: VsevolodX Date: Tue, 6 Oct 2026 20:26:55 -0700 Subject: [PATCH 07/18] SOF-8065: cell reset in the structure notebook, band path and smearing in MODEL_TAG Co-Authored-By: Claude Sonnet 5.5 --- ...rface_2d_3d_graphene_silicon_dioxide.ipynb | 22 +++++++++++++++++++ ..._graphene_silicon_dioxide_SIMULATION.ipynb | 19 +++++++--------- 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide.ipynb b/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide.ipynb index 34f7676b0..9e9b87e79 100644 --- a/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide.ipynb +++ b/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide.ipynb @@ -337,6 +337,28 @@ ")\n" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 3.5. Set the cell to the standard hexagonal setting\n", + "The ZSL interface cell comes out with γ = 60°. The cell is put in the 120° hexagonal setting (same atoms) and typed HEX, so the symbolic K point of the band path is graphene's K.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from mat3ra.made.tools.helpers import create_supercell\n", + "\n", + "interface = create_supercell(interface, supercell_matrix=[[1, 0, 0], [-1, 1, 0], [0, 0, 1]])\n", + "interface.lattice.type = \"HEX\"\n", + "print(f\"{interface.basis.number_of_atoms} atoms, a = {interface.lattice.a:.4f} Å, \"\n", + " f\"gamma = {interface.lattice.gamma:.1f}°\")" + ] + }, { "cell_type": "markdown", "metadata": {}, diff --git a/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide_SIMULATION.ipynb b/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide_SIMULATION.ipynb index 56eded460..abe5ffa11 100644 --- a/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide_SIMULATION.ipynb +++ b/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide_SIMULATION.ipynb @@ -27,7 +27,7 @@ "\n", "1. Set up the environment and parameters: install packages (JupyterLite only) and configure the material name, model and compute parameters.\n", "1. Authenticate and initialize API client: authenticate via browser, initialize the client, then select account and project.\n", - "1. Load the interface by name from the `uploads` folder, re-set its cell to 120°, print its provenance and save it to the platform.\n", + "1. Load the interface by name from the `uploads` folder, print its provenance and save it to the platform.\n", "1. Configure the DFT model and k-grid.\n", "1. Configure compute: get the list of clusters and create a compute configuration.\n", "1. Relax the interface if `RELAX`, reusing a saved relaxed structure when one already exists.\n", @@ -130,17 +130,18 @@ "ECUTRHO = 200 # Ry, GBRV's recommended charge-density cutoff\n", "\n", "KPOINT_DENSITY = 4 # gives 6 x 6 x 1 on the 1x1 quartz cell, Kang et al. Sec. II\n", - "MODEL_TAG = f\"{FUNCTIONAL}-{PSEUDOPOTENTIAL_TYPE} {ECUTWFC}-{ECUTRHO}Ry k{KPOINT_DENSITY}\"\n", + "SMEARING_SETTINGS = {\"degauss\": 0.01} # Ry; the doped graphene has no gap at E_F\n", + "KPATH_STEPS = 20\n", + "MODEL_TAG = (f\"{FUNCTIONAL}-{PSEUDOPOTENTIAL_TYPE} {ECUTWFC}-{ECUTRHO}Ry k{KPOINT_DENSITY} \"\n", + " f\"p{KPATH_STEPS} g{SMEARING_SETTINGS['degauss']}\")\n", "\n", "SCF_UNIT = \"pw_scf\"\n", "BANDS_UNIT = \"pw_bands\"\n", "RELAX_UNIT = \"pw_relax\"\n", - "SMEARING_SETTINGS = {\"degauss\": 0.01} # Ry; the doped graphene has no gap at E_F\n", "RELAXATION_SETTINGS = {\"forc_conv_thr\": 1.17e-3, \"nstep\": 100} # 0.03 eV/Å, Kang et al. 2008 Sec. II\n", "# Names the relaxation job; the relaxed structure itself is found by content hash.\n", "RELAX_TAG = f\"{MODEL_TAG} f{RELAXATION_SETTINGS['forc_conv_thr']}\"\n", "\n", - "KPATH_STEPS = 20\n", "KPATH = [\n", " {\"point\": \"K\", \"steps\": KPATH_STEPS},\n", " {\"point\": \"Γ\", \"steps\": KPATH_STEPS},\n", @@ -242,11 +243,10 @@ "metadata": {}, "source": [ "## 3. Load the material\n", - "### 3.1. Load from the uploads folder, re-set the cell and print provenance\n", + "### 3.1. Load from the uploads folder and print provenance\n", "\n", - "The structure comes from the structure notebook and is loaded by name. Its cell is a 60° setting,\n", - "while the symbolic K point of the band path assumes 120°, so the cell is re-set to the equivalent\n", - "120° one: same atoms, same registry." + "The structure, its registry and its 120° hexagonal cell come from the structure notebook; this one\n", + "only loads it by name." ] }, { @@ -257,13 +257,10 @@ "source": [ "import numpy as np\n", "from collections import Counter\n", - "from mat3ra.made.tools.helpers import create_supercell\n", "from mat3ra.notebooks_utils.core.entity.material.api import load_material\n", "\n", "interface = load_material(client, FOLDER, INTERFACE_NAME, ACCOUNT_ID)\n", "interface.basis.set_labels_from_list([])\n", - "interface = create_supercell(interface, supercell_matrix=[[1, 0, 0], [-1, 1, 0], [0, 0, 1]])\n", - "interface.lattice.type = \"HEX\"\n", "\n", "composition = Counter(interface.basis.elements.values)\n", "electron_count = sum(GBRV_VALENCE[element] * count for element, count in composition.items())\n", From e1e3816f937f752088730e7012d412c9cd0ddab0 Mon Sep 17 00:00:00 2001 From: VsevolodX Date: Tue, 6 Oct 2026 20:32:05 -0700 Subject: [PATCH 08/18] SOF-8065: re-review fixes (names, markdown, thickness comment) Co-Authored-By: Claude Sonnet 5.5 --- ...rface_2d_3d_graphene_silicon_dioxide.ipynb | 2 +- ..._graphene_silicon_dioxide_SIMULATION.ipynb | 28 +++++++++---------- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide.ipynb b/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide.ipynb index 9e9b87e79..011e2c363 100644 --- a/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide.ipynb +++ b/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide.ipynb @@ -45,7 +45,7 @@ "FILM_VACUUM = 0.0 # in angstroms\n", "\n", "SUBSTRATE_MILLER_INDICES = (0, 0, 1)\n", - "SUBSTRATE_THICKNESS = 5 # in conventional cells along c: 15 Si planes, the manuscript's 14 bilayers\n", + "SUBSTRATE_THICKNESS = 5 # conventional cells along c: 15 Si planes; the manuscript has 14 bilayers\n", "SUBSTRATE_TERMINATION_FORMULA = None # if None, the first termination will be used\n", "SUBSTRATE_VACUUM = 0.0 # in angstroms\n", "\n", diff --git a/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide_SIMULATION.ipynb b/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide_SIMULATION.ipynb index abe5ffa11..7a618864a 100644 --- a/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide_SIMULATION.ipynb +++ b/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide_SIMULATION.ipynb @@ -264,16 +264,16 @@ "\n", "composition = Counter(interface.basis.elements.values)\n", "electron_count = sum(GBRV_VALENCE[element] * count for element, count in composition.items())\n", - "NUMBER_OF_OCCUPIED_BANDS = electron_count // 2\n", + "number_of_occupied_bands = electron_count // 2\n", "\n", - "cartesian = interface.clone()\n", - "cartesian.to_cartesian()\n", - "z = np.array(cartesian.basis.coordinates.values)[:, 2]\n", + "interface_in_cartesian = interface.clone()\n", + "interface_in_cartesian.to_cartesian()\n", + "z_coordinates = np.array(interface_in_cartesian.basis.coordinates.values)[:, 2]\n", "is_carbon = np.array(interface.basis.elements.values) == \"C\"\n", - "interlayer_distance = z[is_carbon].min() - z[~is_carbon].max()\n", + "interlayer_distance = z_coordinates[is_carbon].min() - z_coordinates[~is_carbon].max()\n", "print(f\"{INTERFACE_NAME}: {dict(composition)}, {interface.basis.number_of_atoms} atoms, \"\n", " f\"gamma = {interface.lattice.gamma:.3f}°, interlayer distance = {interlayer_distance:.3f} Å, \"\n", - " f\"{electron_count} valence electrons, {NUMBER_OF_OCCUPIED_BANDS} occupied bands\")" + " f\"{electron_count} valence electrons, {number_of_occupied_bands} occupied bands\")" ] }, { @@ -411,9 +411,7 @@ "\n", "Runs only if `RELAX`: finds any relaxed version of this structure already on the account,\n", "regardless of who relaxed it or with what, before spending any compute on a new one. The band\n", - "structure is then read off that geometry. Kang et al.'s 0.13 eV gap comes from the second surface\n", - "O relaxing toward a neighbouring C atom, so an unrelaxed run is the as-built geometry rather than\n", - "a rougher version of the relaxed one." + "structure is then read off that geometry." ] }, { @@ -570,10 +568,10 @@ "source": [ "### 8.2. Dirac point and gap at K\n", "\n", - "`KPATH` starts at K, so the first point of the band structure's path is K. With `NUMBER_OF_OCCUPIED_BANDS` doubly\n", - "occupied bands, the two Dirac bands at K are bands `NUMBER_OF_OCCUPIED_BANDS - 1` and `NUMBER_OF_OCCUPIED_BANDS` (0-based) for\n", + "At K, with `number_of_occupied_bands` doubly\n", + "occupied bands, the two Dirac bands at K are bands `number_of_occupied_bands - 1` and `number_of_occupied_bands` (0-based) for\n", "neutral graphene. If a surface O state sits below the Dirac point, as in p-doped graphene, they are\n", - "`NUMBER_OF_OCCUPIED_BANDS` and `NUMBER_OF_OCCUPIED_BANDS + 1`. The pair with the smaller splitting at K is the Dirac pair; the\n", + "`number_of_occupied_bands` and `number_of_occupied_bands + 1`. The pair with the smaller splitting at K is the Dirac pair; the\n", "Dirac point is its midpoint and the gap its splitting." ] }, @@ -586,11 +584,11 @@ "bands = np.array(band_structure[0][\"yDataSeries\"])\n", "fermi_energy = band_structure[0][\"fermiEnergy\"]\n", "energies_at_k = bands[:, K_INDEX]\n", - "print(f\"Bands at K relative to E_F (eV), 0-based indices {NUMBER_OF_OCCUPIED_BANDS - 2}..{NUMBER_OF_OCCUPIED_BANDS + 2}:\")\n", - "for index in range(NUMBER_OF_OCCUPIED_BANDS - 2, NUMBER_OF_OCCUPIED_BANDS + 3):\n", + "print(f\"Bands at K relative to E_F (eV), 0-based indices {number_of_occupied_bands - 2}..{number_of_occupied_bands + 2}:\")\n", + "for index in range(number_of_occupied_bands - 2, number_of_occupied_bands + 3):\n", " print(f\" {index}: {energies_at_k[index] - fermi_energy:+.4f}\")\n", "\n", - "candidates = [(NUMBER_OF_OCCUPIED_BANDS - 1, NUMBER_OF_OCCUPIED_BANDS), (NUMBER_OF_OCCUPIED_BANDS, NUMBER_OF_OCCUPIED_BANDS + 1)]\n", + "candidates = [(number_of_occupied_bands - 1, number_of_occupied_bands), (number_of_occupied_bands, number_of_occupied_bands + 1)]\n", "below, above = min(candidates, key=lambda pair: energies_at_k[pair[1]] - energies_at_k[pair[0]])\n", "gap = energies_at_k[above] - energies_at_k[below]\n", "dirac_minus_fermi = (energies_at_k[above] + energies_at_k[below]) / 2 - fermi_energy\n", From c06e072e60e7f896efa475b9172e6e881e66f71e Mon Sep 17 00:00:00 2001 From: VsevolodX Date: Tue, 6 Oct 2026 20:32:12 -0700 Subject: [PATCH 09/18] SOF-8065: 8.2 markdown wording Co-Authored-By: Claude Sonnet 5.5 --- .../interface_2d_3d_graphene_silicon_dioxide_SIMULATION.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide_SIMULATION.ipynb b/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide_SIMULATION.ipynb index 7a618864a..742e1cb46 100644 --- a/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide_SIMULATION.ipynb +++ b/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide_SIMULATION.ipynb @@ -568,7 +568,7 @@ "source": [ "### 8.2. Dirac point and gap at K\n", "\n", - "At K, with `number_of_occupied_bands` doubly\n", + "With `number_of_occupied_bands` doubly\n", "occupied bands, the two Dirac bands at K are bands `number_of_occupied_bands - 1` and `number_of_occupied_bands` (0-based) for\n", "neutral graphene. If a surface O state sits below the Dirac point, as in p-doped graphene, they are\n", "`number_of_occupied_bands` and `number_of_occupied_bands + 1`. The pair with the smaller splitting at K is the Dirac pair; the\n", From 604f21a462ad06ad9e5d2783e5372ec52953a45f Mon Sep 17 00:00:00 2001 From: VsevolodX Date: Tue, 6 Oct 2026 20:41:44 -0700 Subject: [PATCH 10/18] SOF-8065: markdown nits in 8.2 and 3.5 Co-Authored-By: Claude Sonnet 5.5 --- .../interface_2d_3d_graphene_silicon_dioxide.ipynb | 2 +- ...erface_2d_3d_graphene_silicon_dioxide_SIMULATION.ipynb | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide.ipynb b/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide.ipynb index 011e2c363..d2a8751b0 100644 --- a/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide.ipynb +++ b/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide.ipynb @@ -342,7 +342,7 @@ "metadata": {}, "source": [ "### 3.5. Set the cell to the standard hexagonal setting\n", - "The ZSL interface cell comes out with γ = 60°. The cell is put in the 120° hexagonal setting (same atoms) and typed HEX, so the symbolic K point of the band path is graphene's K.\n" + "The ZSL interface cell comes out with γ = 60°. The cell is put in the 120° hexagonal setting (same atoms) and typed HEX, so the symbolic K point of the band path in [interface_2d_3d_graphene_silicon_dioxide_SIMULATION.ipynb](interface_2d_3d_graphene_silicon_dioxide_SIMULATION.ipynb) is graphene's K.\n" ] }, { diff --git a/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide_SIMULATION.ipynb b/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide_SIMULATION.ipynb index 742e1cb46..cd36d1b97 100644 --- a/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide_SIMULATION.ipynb +++ b/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide_SIMULATION.ipynb @@ -568,10 +568,10 @@ "source": [ "### 8.2. Dirac point and gap at K\n", "\n", - "With `number_of_occupied_bands` doubly\n", - "occupied bands, the two Dirac bands at K are bands `number_of_occupied_bands - 1` and `number_of_occupied_bands` (0-based) for\n", - "neutral graphene. If a surface O state sits below the Dirac point, as in p-doped graphene, they are\n", - "`number_of_occupied_bands` and `number_of_occupied_bands + 1`. The pair with the smaller splitting at K is the Dirac pair; the\n", + "With `number_of_occupied_bands` doubly occupied bands, the two Dirac bands at K are bands\n", + "`number_of_occupied_bands - 1` and `number_of_occupied_bands` (0-based) for neutral graphene. If a\n", + "surface O state sits below the Dirac point, as in p-doped graphene, they are `number_of_occupied_bands`\n", + "and `number_of_occupied_bands + 1`. The pair with the smaller splitting at K is the Dirac pair; the\n", "Dirac point is its midpoint and the gap its splitting." ] }, From 1c3af468cda4e033797b61955e2a28e64e6a6783 Mon Sep 17 00:00:00 2001 From: VsevolodX Date: Wed, 7 Oct 2026 12:15:54 -0700 Subject: [PATCH 11/18] SOF-8065: rev 4 - metastable registry shift, relaxation default, values-only comparison Co-Authored-By: Claude Sonnet 5.5 --- ...rface_2d_3d_graphene_silicon_dioxide.ipynb | 35 ++++++++++++++++++- ..._graphene_silicon_dioxide_SIMULATION.ipynb | 33 ++++++++--------- 2 files changed, 51 insertions(+), 17 deletions(-) diff --git a/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide.ipynb b/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide.ipynb index d2a8751b0..720b3bdd9 100644 --- a/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide.ipynb +++ b/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide.ipynb @@ -51,6 +51,7 @@ "\n", "INTERFACE_DISTANCE = 2.58 # Gap between substrate and film, in Angstrom -- from manuscript\n", "INTERFACE_VACUUM = 17.5 # Angstrom; gives about 20 A above graphene, as the builder adds INTERFACE_DISTANCE above the film too\n", + "REGISTRY_SHIFT = [-1.011, -0.725, 0.0] # Å, in-plane shift of graphene to the manuscript's metastable registry, Sec. III\n", "\n", "# Whether to convert materials to conventional cells before creating slabs.\n", "USE_CONVENTIONAL_CELL = True\n", @@ -341,7 +342,39 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### 3.5. Set the cell to the standard hexagonal setting\n", + "### 3.5. Shift graphene to the metastable registry\n", + "The ZSL match leaves the registry undefined; as built, both surface O atoms sit near a C atom. The film is shifted in-plane so that one surface O sits near a C atom and the other near a hexagon centre, the registry of the manuscript's metastable geometry (Sec. III).\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "from mat3ra.made.tools.modify import interface_displace_part\n", + "\n", + "interface = interface_displace_part(interface, displacement=REGISTRY_SHIFT, use_cartesian_coordinates=True)\n", + "\n", + "interface_in_cartesian = interface.clone()\n", + "interface_in_cartesian.to_cartesian()\n", + "coordinates = np.array(interface_in_cartesian.basis.coordinates.values)\n", + "elements = np.array(interface.basis.elements.values)\n", + "cell_xy = np.array(interface.lattice.vector_arrays)[:2, :2]\n", + "carbons = coordinates[elements == \"C\"]\n", + "oxygens = coordinates[elements == \"O\"]\n", + "for oxygen in oxygens[np.argsort(-oxygens[:, 2])[:2]]:\n", + " distances = [np.linalg.norm(oxygen[:2] - carbon[:2] - i * cell_xy[0] - j * cell_xy[1])\n", + " for carbon in carbons for i in (-1, 0, 1) for j in (-1, 0, 1)]\n", + " print(f\"surface O at z = {oxygen[2]:.3f} Å: nearest C in the plane {min(distances):.3f} Å\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 3.6. Set the cell to the standard hexagonal setting\n", "The ZSL interface cell comes out with γ = 60°. The cell is put in the 120° hexagonal setting (same atoms) and typed HEX, so the symbolic K point of the band path in [interface_2d_3d_graphene_silicon_dioxide_SIMULATION.ipynb](interface_2d_3d_graphene_silicon_dioxide_SIMULATION.ipynb) is graphene's K.\n" ] }, diff --git a/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide_SIMULATION.ipynb b/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide_SIMULATION.ipynb index cd36d1b97..c119b4aba 100644 --- a/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide_SIMULATION.ipynb +++ b/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide_SIMULATION.ipynb @@ -18,7 +18,7 @@ "

Usage

\n", "\n", "1. Create the interface in the [structure notebook](interface_2d_3d_graphene_silicon_dioxide.ipynb), which saves it to the `uploads` folder under the name used in cell 1.2 below.\n", - "1. Set the material name and parameters in cells 1.2-1.4, or use the defaults; `RELAX = True` relaxes the interface once (fixed-cell, whole slab) and saves it as ` relaxed` for reuse.\n", + "1. Set the material name and parameters in cells 1.2-1.4, or use the defaults; `RELAX = True` (the default) relaxes the interface once (fixed cell, all atoms) and saves it as ` relaxed` for reuse.\n", "1. Click \"Run\" > \"Run All\" to run all cells.\n", "1. Wait for the job to complete.\n", "1. Scroll down to view the results; the last section prints the comparison with the manuscript.\n", @@ -96,14 +96,16 @@ "MY_WORKFLOW_NAME = \"Band Structure\"\n", "APPLICATION_NAME = \"espresso\"\n", "\n", - "# NOTE: False reads the band structure as built; True relaxes the interface once (fixed cell, whole\n", - "# slab) and reads the band structure off the relaxed structure.\n", - "RELAX = False\n", + "# NOTE: True relaxes the interface once (fixed cell, all atoms, to 0.03 eV/Å as in Kang et al. Sec. II)\n", + "# and reads the band structure of the relaxed structure: TODO(measured) E_D - E_F and gap at K.\n", + "# False reads the band structure as built: TODO(measured) E_D - E_F and gap at K.\n", + "RELAX = True\n", "\n", "CLUSTER_NAME = \"001\" # specify full or partial name i.e. \"cluster-001\" to select\n", "QUEUE_NAME = QueueName.OR\n", "PPN = 16 # queue OR on cluster-001 allows at most 16 cores per node\n", - "TIME_LIMIT = \"04:00:00\"\n", + "TIME_LIMIT = \"04:00:00\" # band structure job\n", + "RELAX_TIME_LIMIT = \"24:00:00\" # relaxation job\n", "\n", "timestamp = datetime.now().strftime(\"%Y-%m-%d %H:%M\")\n", "POLL_INTERVAL = 60 # seconds" @@ -140,7 +142,8 @@ "RELAX_UNIT = \"pw_relax\"\n", "RELAXATION_SETTINGS = {\"forc_conv_thr\": 1.17e-3, \"nstep\": 100} # 0.03 eV/Å, Kang et al. 2008 Sec. II\n", "# Names the relaxation job; the relaxed structure itself is found by content hash.\n", - "RELAX_TAG = f\"{MODEL_TAG} f{RELAXATION_SETTINGS['forc_conv_thr']}\"\n", + "RELAX_TAG = (f\"{FUNCTIONAL}-{PSEUDOPOTENTIAL_TYPE} {ECUTWFC}-{ECUTRHO}Ry k{KPOINT_DENSITY} \"\n", + " f\"g{SMEARING_SETTINGS['degauss']} f{RELAXATION_SETTINGS['forc_conv_thr']}\")\n", "\n", "KPATH = [\n", " {\"point\": \"K\", \"steps\": KPATH_STEPS},\n", @@ -399,8 +402,9 @@ "else:\n", " cluster = clusters[0]\n", "compute = Compute(cluster=cluster, queue=QUEUE_NAME, ppn=PPN, timeLimit=TIME_LIMIT)\n", + "relax_compute = Compute(cluster=cluster, queue=QUEUE_NAME, ppn=PPN, timeLimit=RELAX_TIME_LIMIT)\n", "print(f\"Using cluster: {compute.cluster.hostname}, queue: {QUEUE_NAME}, ppn: {PPN}, \"\n", - " f\"time limit: {TIME_LIMIT}\")" + " f\"time limit: {TIME_LIMIT} (relaxation: {RELAX_TIME_LIMIT})\")" ] }, { @@ -454,7 +458,7 @@ " if relax_job is None:\n", " relax_job = create_job(\n", " api_client=client, materials=[saved_interface], workflow=relax_workflow,\n", - " project_id=project_id, owner_id=ACCOUNT_ID, compute=compute.to_dict(),\n", + " project_id=project_id, owner_id=ACCOUNT_ID, compute=relax_compute.to_dict(),\n", " prefix=f\"{relax_workflow_name} {timestamp}\",\n", " )\n", " submit_jobs(client.jobs, [relax_job[\"_id\"]])\n", @@ -614,17 +618,14 @@ "metadata": {}, "outputs": [], "source": [ - "KANG_DIRAC_MINUS_FERMI = 1.2 # eV, Fig. 3(a) read-off\n", + "KANG_DIRAC_MINUS_FERMI = 1.28 # eV, midpoint of the two Dirac bands at K on Fig. 3(a): +1.21 and +1.35 eV\n", "KANG_GAP = 0.13 # eV, Sec. III text\n", "\n", "print(f\"Regime: {'relaxed interface' if RELAX else 'unrelaxed SCF'}\")\n", - "print(f\"Doping: {'p-type' if dirac_minus_fermi > 0 else 'n-type'} (Kang et al., 2008: p-type)\")\n", - "print(f\"{'E_D - E_F (Kang et al., 2008):':<32}{KANG_DIRAC_MINUS_FERMI:+.3f} eV\")\n", - "print(f\"{'E_D - E_F (this notebook):':<32}{dirac_minus_fermi:+.3f} eV \"\n", - " f\"({100 * (dirac_minus_fermi - KANG_DIRAC_MINUS_FERMI) / KANG_DIRAC_MINUS_FERMI:+.1f} % deviation)\")\n", - "print(f\"{'Gap at K (Kang et al., 2008):':<32}{KANG_GAP:.3f} eV\")\n", - "print(f\"{'Gap at K (this notebook):':<32}{gap:.3f} eV \"\n", - " f\"({100 * (gap - KANG_GAP) / KANG_GAP:+.1f} % deviation)\")" + "print(f\"{'':<14}{'this notebook':>16}{'Kang et al. (2008)':>22}\")\n", + "print(f\"{'Doping':<14}{'p-type' if dirac_minus_fermi > 0 else 'n-type':>16}{'p-type':>22}\")\n", + "print(f\"{'E_D - E_F':<14}{dirac_minus_fermi:>13.3f} eV{KANG_DIRAC_MINUS_FERMI:>19.3f} eV\")\n", + "print(f\"{'Gap at K':<14}{gap:>13.3f} eV{KANG_GAP:>19.3f} eV\")" ] }, { From 6cd55459e55188adcc67c48f3d1de0b894565a5a Mon Sep 17 00:00:00 2001 From: VsevolodX Date: Wed, 7 Oct 2026 13:01:50 -0700 Subject: [PATCH 12/18] SOF-8065: one time limit for both jobs Co-Authored-By: Claude Sonnet 5.5 --- ...erface_2d_3d_graphene_silicon_dioxide_SIMULATION.ipynb | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide_SIMULATION.ipynb b/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide_SIMULATION.ipynb index c119b4aba..411e5f915 100644 --- a/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide_SIMULATION.ipynb +++ b/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide_SIMULATION.ipynb @@ -104,8 +104,7 @@ "CLUSTER_NAME = \"001\" # specify full or partial name i.e. \"cluster-001\" to select\n", "QUEUE_NAME = QueueName.OR\n", "PPN = 16 # queue OR on cluster-001 allows at most 16 cores per node\n", - "TIME_LIMIT = \"04:00:00\" # band structure job\n", - "RELAX_TIME_LIMIT = \"24:00:00\" # relaxation job\n", + "TIME_LIMIT = \"04:00:00\"\n", "\n", "timestamp = datetime.now().strftime(\"%Y-%m-%d %H:%M\")\n", "POLL_INTERVAL = 60 # seconds" @@ -402,9 +401,8 @@ "else:\n", " cluster = clusters[0]\n", "compute = Compute(cluster=cluster, queue=QUEUE_NAME, ppn=PPN, timeLimit=TIME_LIMIT)\n", - "relax_compute = Compute(cluster=cluster, queue=QUEUE_NAME, ppn=PPN, timeLimit=RELAX_TIME_LIMIT)\n", "print(f\"Using cluster: {compute.cluster.hostname}, queue: {QUEUE_NAME}, ppn: {PPN}, \"\n", - " f\"time limit: {TIME_LIMIT} (relaxation: {RELAX_TIME_LIMIT})\")" + " f\"time limit: {TIME_LIMIT}\")" ] }, { @@ -458,7 +456,7 @@ " if relax_job is None:\n", " relax_job = create_job(\n", " api_client=client, materials=[saved_interface], workflow=relax_workflow,\n", - " project_id=project_id, owner_id=ACCOUNT_ID, compute=relax_compute.to_dict(),\n", + " project_id=project_id, owner_id=ACCOUNT_ID, compute=compute.to_dict(),\n", " prefix=f\"{relax_workflow_name} {timestamp}\",\n", " )\n", " submit_jobs(client.jobs, [relax_job[\"_id\"]])\n", From d9968a826a50c2a34b99d88f003610f24ca15f28 Mon Sep 17 00:00:00 2001 From: VsevolodX Date: Wed, 7 Oct 2026 15:14:43 -0700 Subject: [PATCH 13/18] SOF-8065: center the slab along z Co-Authored-By: Claude Sonnet 5.5 --- .../interface_2d_3d_graphene_silicon_dioxide.ipynb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide.ipynb b/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide.ipynb index 720b3bdd9..478751284 100644 --- a/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide.ipynb +++ b/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide.ipynb @@ -375,7 +375,7 @@ "metadata": {}, "source": [ "### 3.6. Set the cell to the standard hexagonal setting\n", - "The ZSL interface cell comes out with γ = 60°. The cell is put in the 120° hexagonal setting (same atoms) and typed HEX, so the symbolic K point of the band path in [interface_2d_3d_graphene_silicon_dioxide_SIMULATION.ipynb](interface_2d_3d_graphene_silicon_dioxide_SIMULATION.ipynb) is graphene's K.\n" + "The ZSL interface cell comes out with γ = 60°. The cell is put in the 120° hexagonal setting (same atoms) and typed HEX, so the symbolic K point of the band path in [interface_2d_3d_graphene_silicon_dioxide_SIMULATION.ipynb](interface_2d_3d_graphene_silicon_dioxide_SIMULATION.ipynb) is graphene's K. The slab is then centered along z, so that no atom sits at z = 0, where a relaxation would wrap it to the top of the cell.\n" ] }, { @@ -385,8 +385,10 @@ "outputs": [], "source": [ "from mat3ra.made.tools.helpers import create_supercell\n", + "from mat3ra.made.tools.modify import translate_to_center\n", "\n", "interface = create_supercell(interface, supercell_matrix=[[1, 0, 0], [-1, 1, 0], [0, 0, 1]])\n", + "interface = translate_to_center(interface, axes=[\"z\"])\n", "interface.lattice.type = \"HEX\"\n", "print(f\"{interface.basis.number_of_atoms} atoms, a = {interface.lattice.a:.4f} Å, \"\n", " f\"gamma = {interface.lattice.gamma:.1f}°\")" From 399dcda1eb0cc4eafc8887bc6d9cf4b4f315c022 Mon Sep 17 00:00:00 2001 From: VsevolodX Date: Wed, 7 Oct 2026 15:25:39 -0700 Subject: [PATCH 14/18] SOF-8065: coarser relaxation k-mesh and a fixed deep substrate Co-Authored-By: Claude Sonnet 5.5 --- ..._graphene_silicon_dioxide_SIMULATION.ipynb | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide_SIMULATION.ipynb b/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide_SIMULATION.ipynb index 411e5f915..9fa251642 100644 --- a/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide_SIMULATION.ipynb +++ b/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide_SIMULATION.ipynb @@ -99,6 +99,9 @@ "# NOTE: True relaxes the interface once (fixed cell, all atoms, to 0.03 eV/Å as in Kang et al. Sec. II)\n", "# and reads the band structure of the relaxed structure: TODO(measured) E_D - E_F and gap at K.\n", "# False reads the band structure as built: TODO(measured) E_D - E_F and gap at K.\n", + "# The relaxation uses a coarser k-mesh (RELAX_KPOINT_DENSITY, K stays on the mesh; the band structure\n", + "# uses the full one): TODO(measured) effect. Substrate atoms deeper than RELAX_FREE_DEPTH below the\n", + "# topmost substrate atom are held fixed, graphene and the surface region relax: TODO(measured) effect.\n", "RELAX = True\n", "\n", "CLUSTER_NAME = \"001\" # specify full or partial name i.e. \"cluster-001\" to select\n", @@ -131,6 +134,8 @@ "ECUTRHO = 200 # Ry, GBRV's recommended charge-density cutoff\n", "\n", "KPOINT_DENSITY = 4 # gives 6 x 6 x 1 on the 1x1 quartz cell, Kang et al. Sec. II\n", + "RELAX_KPOINT_DENSITY = 2 # gives 3 x 3 x 1, K is on the mesh; the band structure uses the full mesh\n", + "RELAX_FREE_DEPTH = 4.0 # Å below the topmost substrate atom; deeper substrate atoms are fixed in the relaxation\n", "SMEARING_SETTINGS = {\"degauss\": 0.01} # Ry; the doped graphene has no gap at E_F\n", "KPATH_STEPS = 20\n", "MODEL_TAG = (f\"{FUNCTIONAL}-{PSEUDOPOTENTIAL_TYPE} {ECUTWFC}-{ECUTRHO}Ry k{KPOINT_DENSITY} \"\n", @@ -141,8 +146,8 @@ "RELAX_UNIT = \"pw_relax\"\n", "RELAXATION_SETTINGS = {\"forc_conv_thr\": 1.17e-3, \"nstep\": 100} # 0.03 eV/Å, Kang et al. 2008 Sec. II\n", "# Names the relaxation job; the relaxed structure itself is found by content hash.\n", - "RELAX_TAG = (f\"{FUNCTIONAL}-{PSEUDOPOTENTIAL_TYPE} {ECUTWFC}-{ECUTRHO}Ry k{KPOINT_DENSITY} \"\n", - " f\"g{SMEARING_SETTINGS['degauss']} f{RELAXATION_SETTINGS['forc_conv_thr']}\")\n", + "RELAX_TAG = (f\"{FUNCTIONAL}-{PSEUDOPOTENTIAL_TYPE} {ECUTWFC}-{ECUTRHO}Ry k{RELAX_KPOINT_DENSITY} \"\n", + " f\"g{SMEARING_SETTINGS['degauss']} f{RELAXATION_SETTINGS['forc_conv_thr']} d{RELAX_FREE_DEPTH}\")\n", "\n", "KPATH = [\n", " {\"point\": \"K\", \"steps\": KPATH_STEPS},\n", @@ -358,7 +363,8 @@ "from mat3ra.notebooks_utils.workflow import kgrid_from_density\n", "\n", "kgrid = kgrid_from_density(interface, KPOINT_DENSITY, periodic_dims=(0, 1))\n", - "print(f\"k-grid {kgrid}\")" + "relax_kgrid = kgrid_from_density(interface, RELAX_KPOINT_DENSITY, periodic_dims=(0, 1))\n", + "print(f\"k-grid {kgrid}, relaxation k-grid {relax_kgrid}\")" ] }, { @@ -422,6 +428,7 @@ "metadata": {}, "outputs": [], "source": [ + "from mat3ra.code.array_with_ids import ArrayWithIds\n", "from mat3ra.made.material import Material\n", "from mat3ra.standata.workflows import WorkflowStandata\n", "from mat3ra.wode.workflows import Workflow\n", @@ -445,17 +452,23 @@ " relax_workflow.name = relax_workflow_name\n", " relax_workflow.subworkflows[0].model = model\n", " apply_planewave_cutoffs(relax_workflow, ECUTWFC, ECUTRHO, unit_name=RELAX_UNIT)\n", - " apply_scf_kgrid(relax_workflow, kgrid, material=interface, unit_name=RELAX_UNIT)\n", + " apply_scf_kgrid(relax_workflow, relax_kgrid, material=interface, unit_name=RELAX_UNIT)\n", " patch_workflow_qe_input(relax_workflow, {\"system\": SMEARING_SETTINGS}, [RELAX_UNIT])\n", " patch_workflow_qe_input(relax_workflow, {\"control\": RELAXATION_SETTINGS}, [RELAX_UNIT])\n", "\n", + " is_free = is_carbon | (z_coordinates >= z_coordinates[~is_carbon].max() - RELAX_FREE_DEPTH)\n", + " relax_interface = interface.clone()\n", + " relax_interface.basis.constraints = ArrayWithIds.from_values([[bool(free)] * 3 for free in is_free])\n", + " print(f\"Free atoms in the relaxation: {dict(Counter(np.array(interface.basis.elements.values)[is_free]))}\")\n", + " saved_relax_interface = client.materials.create(relax_interface.to_dict(), owner_id=ACCOUNT_ID)\n", + "\n", " relax_job = find_job_for_material(\n", - " client, saved_interface[\"_id\"], relax_workflow_name, ACCOUNT_ID,\n", + " client, saved_relax_interface[\"_id\"], relax_workflow_name, ACCOUNT_ID,\n", " statuses=(\"submitted\", \"queued\", \"active\", \"finished\"),\n", " )\n", " if relax_job is None:\n", " relax_job = create_job(\n", - " api_client=client, materials=[saved_interface], workflow=relax_workflow,\n", + " api_client=client, materials=[saved_relax_interface], workflow=relax_workflow,\n", " project_id=project_id, owner_id=ACCOUNT_ID, compute=compute.to_dict(),\n", " prefix=f\"{relax_workflow_name} {timestamp}\",\n", " )\n", From c57844f5045568061088345f1e15ee0e59cb6f12 Mon Sep 17 00:00:00 2001 From: VsevolodX Date: Wed, 7 Oct 2026 15:28:39 -0700 Subject: [PATCH 15/18] SOF-8065: relaxation in the same job through add_relaxation Co-Authored-By: Claude Sonnet 5.5 --- ..._graphene_silicon_dioxide_SIMULATION.ipynb | 174 ++++++------------ 1 file changed, 54 insertions(+), 120 deletions(-) diff --git a/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide_SIMULATION.ipynb b/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide_SIMULATION.ipynb index 9fa251642..9cbbe8c11 100644 --- a/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide_SIMULATION.ipynb +++ b/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide_SIMULATION.ipynb @@ -18,7 +18,7 @@ "

Usage

\n", "\n", "1. Create the interface in the [structure notebook](interface_2d_3d_graphene_silicon_dioxide.ipynb), which saves it to the `uploads` folder under the name used in cell 1.2 below.\n", - "1. Set the material name and parameters in cells 1.2-1.4, or use the defaults; `RELAX = True` (the default) relaxes the interface once (fixed cell, all atoms) and saves it as ` relaxed` for reuse.\n", + "1. Set the material name and parameters in cells 1.2-1.4, or use the defaults; `RELAX = True` (the default) relaxes the interface (fixed cell) in the same job, before the band structure.\n", "1. Click \"Run\" > \"Run All\" to run all cells.\n", "1. Wait for the job to complete.\n", "1. Scroll down to view the results; the last section prints the comparison with the manuscript.\n", @@ -27,11 +27,10 @@ "\n", "1. Set up the environment and parameters: install packages (JupyterLite only) and configure the material name, model and compute parameters.\n", "1. Authenticate and initialize API client: authenticate via browser, initialize the client, then select account and project.\n", - "1. Load the interface by name from the `uploads` folder, print its provenance and save it to the platform.\n", + "1. Load the interface by name from the `uploads` folder, print its provenance.\n", "1. Configure the DFT model and k-grid.\n", "1. Configure compute: get the list of clusters and create a compute configuration.\n", - "1. Relax the interface if `RELAX`, reusing a saved relaxed structure when one already exists.\n", - "1. Configure the Band Structure workflow and run the job, re-using a job that already ran under the same workflow name.\n", + "1. Configure the Band Structure workflow, with a relaxation in front of it if `RELAX`, and run the job, re-using a job that already ran under the same workflow name.\n", "1. Retrieve the band structure, the Fermi level and the Dirac point's position and gap at K.\n", "1. Compare with Kang et al. (2008).\n" ] @@ -91,7 +90,6 @@ "ORGANIZATION_NAME = None # set to your organization name (full or partial); otherwise, your default one is used\n", "FOLDER = \"./uploads\"\n", "\n", - "RELAX_WORKFLOW_SEARCH_TERM = \"fixed_cell_relaxation.json\"\n", "BAND_STRUCTURE_WORKFLOW_SEARCH_TERM = \"band_structure.json\"\n", "MY_WORKFLOW_NAME = \"Band Structure\"\n", "APPLICATION_NAME = \"espresso\"\n", @@ -143,11 +141,10 @@ "\n", "SCF_UNIT = \"pw_scf\"\n", "BANDS_UNIT = \"pw_bands\"\n", - "RELAX_UNIT = \"pw_relax\"\n", + "RELAX_UNIT = \"pw_vc-relax\" # the relaxation unit add_relaxation() prepends; calculation is set to \"relax\" below\n", "RELAXATION_SETTINGS = {\"forc_conv_thr\": 1.17e-3, \"nstep\": 100} # 0.03 eV/Å, Kang et al. 2008 Sec. II\n", - "# Names the relaxation job; the relaxed structure itself is found by content hash.\n", - "RELAX_TAG = (f\"{FUNCTIONAL}-{PSEUDOPOTENTIAL_TYPE} {ECUTWFC}-{ECUTRHO}Ry k{RELAX_KPOINT_DENSITY} \"\n", - " f\"g{SMEARING_SETTINGS['degauss']} f{RELAXATION_SETTINGS['forc_conv_thr']} d{RELAX_FREE_DEPTH}\")\n", + "WORKFLOW_TAG = MODEL_TAG + (f\" relax k{RELAX_KPOINT_DENSITY} d{RELAX_FREE_DEPTH} f{RELAXATION_SETTINGS['forc_conv_thr']}\"\n", + " if RELAX else \"\")\n", "\n", "KPATH = [\n", " {\"point\": \"K\", \"steps\": KPATH_STEPS},\n", @@ -301,24 +298,6 @@ "visualize_materials([{\"material\": interface, \"title\": INTERFACE_NAME}], viewer=\"wave\")" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 3.3. Save the material to the platform" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from mat3ra.notebooks_utils.core.entity.material.api import get_or_create_material\n", - "\n", - "saved_interface = get_or_create_material(client, interface, ACCOUNT_ID)" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -415,11 +394,10 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## 6. Relax the interface (optional)\n", + "## 6. Band structure job\n", + "### 6.1. Configure the workflow\n", "\n", - "Runs only if `RELAX`: finds any relaxed version of this structure already on the account,\n", - "regardless of who relaxed it or with what, before spending any compute on a new one. The band\n", - "structure is then read off that geometry." + "If `RELAX`, `add_relaxation()` puts a relaxation in front of the band structure in the same job; the platform runs the band structure on the relaxed structure. The relaxation is set to a fixed cell, with its own coarser k-mesh, and with the atoms deeper than `RELAX_FREE_DEPTH` in the substrate fixed." ] }, { @@ -429,98 +407,41 @@ "outputs": [], "source": [ "from mat3ra.code.array_with_ids import ArrayWithIds\n", - "from mat3ra.made.material import Material\n", "from mat3ra.standata.workflows import WorkflowStandata\n", "from mat3ra.wode.workflows import Workflow\n", - "from mat3ra.notebooks_utils.workflow import apply_planewave_cutoffs, apply_scf_kgrid, patch_workflow_qe_input\n", - "from mat3ra.notebooks_utils.core.entity.job.api import find_job_for_material\n", - "from mat3ra.notebooks_utils.core.entity.material.api import find_relaxed_material, get_final_structure_for_job\n", - "from mat3ra.notebooks_utils.core.entity.property.api import get_properties_for_job\n", - "from mat3ra.notebooks_utils.job import create_job\n", - "from mat3ra.notebooks_utils.api.job import submit_jobs, wait_for_jobs_to_finish_async\n", - "\n", - "relaxed_interface = None\n", - "if RELAX:\n", - " relax_workflow_name = f\"Fixed-cell Relaxation {INTERFACE_NAME} {RELAX_TAG}\"\n", - " relaxed_interface = find_relaxed_material(client, interface, ACCOUNT_ID)\n", - " if relaxed_interface is not None:\n", - " print(f\"♻️ Relaxed interface: {relaxed_interface.name} ({relaxed_interface.id})\")\n", - " else:\n", - " relax_workflow = Workflow.create(\n", - " WorkflowStandata.filter_by_application(app.name).get_by_name_first_match(RELAX_WORKFLOW_SEARCH_TERM)\n", - " )\n", - " relax_workflow.name = relax_workflow_name\n", - " relax_workflow.subworkflows[0].model = model\n", - " apply_planewave_cutoffs(relax_workflow, ECUTWFC, ECUTRHO, unit_name=RELAX_UNIT)\n", - " apply_scf_kgrid(relax_workflow, relax_kgrid, material=interface, unit_name=RELAX_UNIT)\n", - " patch_workflow_qe_input(relax_workflow, {\"system\": SMEARING_SETTINGS}, [RELAX_UNIT])\n", - " patch_workflow_qe_input(relax_workflow, {\"control\": RELAXATION_SETTINGS}, [RELAX_UNIT])\n", - "\n", - " is_free = is_carbon | (z_coordinates >= z_coordinates[~is_carbon].max() - RELAX_FREE_DEPTH)\n", - " relax_interface = interface.clone()\n", - " relax_interface.basis.constraints = ArrayWithIds.from_values([[bool(free)] * 3 for free in is_free])\n", - " print(f\"Free atoms in the relaxation: {dict(Counter(np.array(interface.basis.elements.values)[is_free]))}\")\n", - " saved_relax_interface = client.materials.create(relax_interface.to_dict(), owner_id=ACCOUNT_ID)\n", - "\n", - " relax_job = find_job_for_material(\n", - " client, saved_relax_interface[\"_id\"], relax_workflow_name, ACCOUNT_ID,\n", - " statuses=(\"submitted\", \"queued\", \"active\", \"finished\"),\n", - " )\n", - " if relax_job is None:\n", - " relax_job = create_job(\n", - " api_client=client, materials=[saved_relax_interface], workflow=relax_workflow,\n", - " project_id=project_id, owner_id=ACCOUNT_ID, compute=compute.to_dict(),\n", - " prefix=f\"{relax_workflow_name} {timestamp}\",\n", - " )\n", - " submit_jobs(client.jobs, [relax_job[\"_id\"]])\n", - " await wait_for_jobs_to_finish_async(client.jobs, [relax_job[\"_id\"]], poll_interval=POLL_INTERVAL)\n", - "\n", - " relaxed_material = get_final_structure_for_job(client, relax_job[\"_id\"])\n", - " client.materials.update(relaxed_material.id, {\"name\": f\"{INTERFACE_NAME} relaxed\"})\n", - " relaxed_interface = Material.create(client.materials.get(relaxed_material.id))\n", - " print(f\"✅ Relaxed interface: {relaxed_interface.name} ({relaxed_interface.id})\")\n", - "\n", - " total_force = get_properties_for_job(client, relax_job[\"_id\"], \"total_force\")[0]\n", - " print(f\"Residual force after relaxation (norm over all atoms): \"\n", - " f\"{total_force['value']:.4f} {total_force['units']}\")\n", - "\n", - "interface_for_job = relaxed_interface if RELAX else interface\n", - "interface_for_job.lattice.type = \"HEX\" # the symbolic K-Γ-M-K path needs a named lattice type\n", - "saved_interface_for_job = get_or_create_material(client, interface_for_job, ACCOUNT_ID)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 7. Band structure job\n", - "### 7.1. Configure the workflow" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ "from mat3ra.wode.context.providers import PointsPathDataProvider\n", + "from mat3ra.notebooks_utils.workflow import apply_planewave_cutoffs, apply_scf_kgrid, patch_workflow_qe_input\n", "from mat3ra.notebooks_utils.ipython.entity.workflow.visualize import visualize_workflow\n", "\n", "workflow = Workflow.create(\n", " WorkflowStandata.filter_by_application(app.name).get_by_name_first_match(BAND_STRUCTURE_WORKFLOW_SEARCH_TERM)\n", ")\n", - "workflow.name = f\"{MY_WORKFLOW_NAME} {INTERFACE_NAME} {MODEL_TAG}\"\n", - "workflow.subworkflows[0].model = model\n", - "apply_planewave_cutoffs(workflow, ECUTWFC, ECUTRHO, unit_name=SCF_UNIT)\n", - "apply_planewave_cutoffs(workflow, ECUTWFC, ECUTRHO, unit_name=BANDS_UNIT)\n", - "apply_scf_kgrid(workflow, kgrid, material=interface_for_job)\n", - "patch_workflow_qe_input(workflow, {\"system\": SMEARING_SETTINGS}, [SCF_UNIT, BANDS_UNIT])\n", - "\n", - "bands_subworkflow = workflow.subworkflows[0]\n", + "workflow.name = f\"{MY_WORKFLOW_NAME} {INTERFACE_NAME} {WORKFLOW_TAG}\"\n", + "if RELAX:\n", + " workflow.add_relaxation()\n", + "for subworkflow in workflow.subworkflows:\n", + " subworkflow.model = model\n", + "\n", + "for unit_name in [SCF_UNIT, BANDS_UNIT] + ([RELAX_UNIT] if RELAX else []):\n", + " apply_planewave_cutoffs(workflow, ECUTWFC, ECUTRHO, unit_name=unit_name)\n", + " patch_workflow_qe_input(workflow, {\"system\": SMEARING_SETTINGS}, [unit_name])\n", + "apply_scf_kgrid(workflow, kgrid, material=interface)\n", + "bands_subworkflow = workflow.subworkflows[-1]\n", "bands_unit = bands_subworkflow.get_unit_by_name(name=BANDS_UNIT)\n", "bands_unit.add_context(PointsPathDataProvider(path=KPATH, isEdited=True).get_context_item_data())\n", "bands_subworkflow.set_unit(bands_unit)\n", "\n", + "job_interface = interface\n", + "if RELAX:\n", + " apply_scf_kgrid(workflow, relax_kgrid, material=interface, unit_name=RELAX_UNIT)\n", + " patch_workflow_qe_input(workflow, {\"control\": {\"calculation\": \"relax\", **RELAXATION_SETTINGS}}, [RELAX_UNIT])\n", + "\n", + " is_free = is_carbon | (z_coordinates >= z_coordinates[~is_carbon].max() - RELAX_FREE_DEPTH)\n", + " job_interface = interface.clone()\n", + " job_interface.basis.constraints = ArrayWithIds.from_values([[bool(free)] * 3 for free in is_free])\n", + " print(f\"Free atoms in the relaxation: {dict(Counter(np.array(interface.basis.elements.values)[is_free]))}\")\n", + "\n", + "print([[unit.name for unit in subworkflow.units] for subworkflow in workflow.subworkflows])\n", "visualize_workflow(workflow)" ] }, @@ -528,9 +449,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### 7.2. Run the job\n", + "### 6.2. Save the material and run the job\n", "\n", - "A job is found by its material and its workflow name, which carries the model tag, and re-used if it exists; otherwise it is created and submitted." + "The material is saved with the relaxation's constraints, which its hash does not include, so a saved copy is taken only if it carries the same ones. A job is found by its material and its workflow name, which carries the regime, and re-used if it exists; otherwise it is created and submitted." ] }, { @@ -539,13 +460,23 @@ "metadata": {}, "outputs": [], "source": [ + "from mat3ra.notebooks_utils.core.entity.job.api import find_job_for_material\n", + "from mat3ra.notebooks_utils.core.entity.property.api import get_properties_for_job\n", + "from mat3ra.notebooks_utils.job import create_job\n", + "from mat3ra.notebooks_utils.api.job import submit_jobs, wait_for_jobs_to_finish_async\n", + "\n", + "saved_materials = client.materials.list({\"hash\": job_interface.hash, \"owner._id\": ACCOUNT_ID})\n", + "saved_materials = [m for m in saved_materials if bool(m[\"basis\"].get(\"constraints\")) == RELAX]\n", + "saved_interface = saved_materials[0] if saved_materials else client.materials.create(\n", + " job_interface.to_dict(), owner_id=ACCOUNT_ID)\n", + "\n", "job = find_job_for_material(\n", - " client, saved_interface_for_job[\"_id\"], workflow.name, ACCOUNT_ID,\n", + " client, saved_interface[\"_id\"], workflow.name, ACCOUNT_ID,\n", " statuses=(\"submitted\", \"queued\", \"active\", \"finished\"),\n", ")\n", "if job is None:\n", " job = create_job(\n", - " api_client=client, materials=[saved_interface_for_job], workflow=workflow, project_id=project_id,\n", + " api_client=client, materials=[saved_interface], workflow=workflow, project_id=project_id,\n", " owner_id=ACCOUNT_ID, compute=compute.to_dict(), prefix=f\"{workflow.name} {timestamp}\",\n", " )\n", " submit_jobs(client.jobs, [job[\"_id\"]])\n", @@ -560,8 +491,8 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## 8. Retrieve the results\n", - "### 8.1. Band structure" + "## 7. Retrieve the results\n", + "### 7.1. Band structure" ] }, { @@ -574,14 +505,17 @@ "\n", "band_structure = get_properties_for_job(client, job_id, property_name=\"band_structure\")\n", "visualize_properties(band_structure, title=\"Band Structure\",\n", - " extra_config={\"material\": interface_for_job.to_dict()})" + " extra_config={\"material\": interface.to_dict()})\n", + "if RELAX:\n", + " total_force = get_properties_for_job(client, job_id, \"total_force\")[0]\n", + " print(f\"Residual force after relaxation: {total_force['value']:.4f} {total_force['units']}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### 8.2. Dirac point and gap at K\n", + "### 7.2. Dirac point and gap at K\n", "\n", "With `number_of_occupied_bands` doubly occupied bands, the two Dirac bands at K are bands\n", "`number_of_occupied_bands - 1` and `number_of_occupied_bands` (0-based) for neutral graphene. If a\n", @@ -615,7 +549,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## 9. Compare with Kang et al. (2008)\n", + "## 8. Compare with Kang et al. (2008)\n", "\n", "Settings that differ from the manuscript: GBRV ultrasoft LDA pseudopotentials against VASP at 396 eV;\n", "the standata quartz cell is +2.3 % on a against the manuscript's 4.91 Å; the substrate is 5\n", From ae1e1b79da0194b322e52cf6839de7ca8c6266b7 Mon Sep 17 00:00:00 2001 From: VsevolodX Date: Wed, 7 Oct 2026 18:52:10 -0700 Subject: [PATCH 16/18] SOF-8065: relaxation on the same k-mesh, all atoms free, RELAX False by default Co-Authored-By: Claude Sonnet 5.5 --- ..._graphene_silicon_dioxide_SIMULATION.ipynb | 41 ++++++------------- 1 file changed, 13 insertions(+), 28 deletions(-) diff --git a/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide_SIMULATION.ipynb b/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide_SIMULATION.ipynb index 9cbbe8c11..3eeb7e48c 100644 --- a/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide_SIMULATION.ipynb +++ b/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide_SIMULATION.ipynb @@ -18,7 +18,7 @@ "

Usage

\n", "\n", "1. Create the interface in the [structure notebook](interface_2d_3d_graphene_silicon_dioxide.ipynb), which saves it to the `uploads` folder under the name used in cell 1.2 below.\n", - "1. Set the material name and parameters in cells 1.2-1.4, or use the defaults; `RELAX = True` (the default) relaxes the interface (fixed cell) in the same job, before the band structure.\n", + "1. Set the material name and parameters in cells 1.2-1.4, or use the defaults; `RELAX = True` relaxes the interface (fixed cell) in the same job, before the band structure.\n", "1. Click \"Run\" > \"Run All\" to run all cells.\n", "1. Wait for the job to complete.\n", "1. Scroll down to view the results; the last section prints the comparison with the manuscript.\n", @@ -94,13 +94,11 @@ "MY_WORKFLOW_NAME = \"Band Structure\"\n", "APPLICATION_NAME = \"espresso\"\n", "\n", - "# NOTE: True relaxes the interface once (fixed cell, all atoms, to 0.03 eV/Å as in Kang et al. Sec. II)\n", - "# and reads the band structure of the relaxed structure: TODO(measured) E_D - E_F and gap at K.\n", - "# False reads the band structure as built: TODO(measured) E_D - E_F and gap at K.\n", - "# The relaxation uses a coarser k-mesh (RELAX_KPOINT_DENSITY, K stays on the mesh; the band structure\n", - "# uses the full one): TODO(measured) effect. Substrate atoms deeper than RELAX_FREE_DEPTH below the\n", - "# topmost substrate atom are held fixed, graphene and the surface region relax: TODO(measured) effect.\n", - "RELAX = True\n", + "# NOTE: False reads the band structure of the structure as built: E_D - E_F +1.171 eV, gap at K\n", + "# 0.062 eV, about 1 h on OR/16. True relaxes all atoms at fixed cell to 0.03 eV/Å (Kang et al.\n", + "# Sec. II) before the band structure, in the same job; on OR/16 it did 5 BFGS steps in the 4 h\n", + "# TIME_LIMIT without converging, so it needs a longer TIME_LIMIT.\n", + "RELAX = False\n", "\n", "CLUSTER_NAME = \"001\" # specify full or partial name i.e. \"cluster-001\" to select\n", "QUEUE_NAME = QueueName.OR\n", @@ -132,8 +130,6 @@ "ECUTRHO = 200 # Ry, GBRV's recommended charge-density cutoff\n", "\n", "KPOINT_DENSITY = 4 # gives 6 x 6 x 1 on the 1x1 quartz cell, Kang et al. Sec. II\n", - "RELAX_KPOINT_DENSITY = 2 # gives 3 x 3 x 1, K is on the mesh; the band structure uses the full mesh\n", - "RELAX_FREE_DEPTH = 4.0 # Å below the topmost substrate atom; deeper substrate atoms are fixed in the relaxation\n", "SMEARING_SETTINGS = {\"degauss\": 0.01} # Ry; the doped graphene has no gap at E_F\n", "KPATH_STEPS = 20\n", "MODEL_TAG = (f\"{FUNCTIONAL}-{PSEUDOPOTENTIAL_TYPE} {ECUTWFC}-{ECUTRHO}Ry k{KPOINT_DENSITY} \"\n", @@ -143,8 +139,7 @@ "BANDS_UNIT = \"pw_bands\"\n", "RELAX_UNIT = \"pw_vc-relax\" # the relaxation unit add_relaxation() prepends; calculation is set to \"relax\" below\n", "RELAXATION_SETTINGS = {\"forc_conv_thr\": 1.17e-3, \"nstep\": 100} # 0.03 eV/Å, Kang et al. 2008 Sec. II\n", - "WORKFLOW_TAG = MODEL_TAG + (f\" relax k{RELAX_KPOINT_DENSITY} d{RELAX_FREE_DEPTH} f{RELAXATION_SETTINGS['forc_conv_thr']}\"\n", - " if RELAX else \"\")\n", + "WORKFLOW_TAG = MODEL_TAG + (f\" relax f{RELAXATION_SETTINGS['forc_conv_thr']}\" if RELAX else \"\")\n", "\n", "KPATH = [\n", " {\"point\": \"K\", \"steps\": KPATH_STEPS},\n", @@ -342,8 +337,7 @@ "from mat3ra.notebooks_utils.workflow import kgrid_from_density\n", "\n", "kgrid = kgrid_from_density(interface, KPOINT_DENSITY, periodic_dims=(0, 1))\n", - "relax_kgrid = kgrid_from_density(interface, RELAX_KPOINT_DENSITY, periodic_dims=(0, 1))\n", - "print(f\"k-grid {kgrid}, relaxation k-grid {relax_kgrid}\")" + "print(f\"k-grid {kgrid}\")" ] }, { @@ -397,7 +391,7 @@ "## 6. Band structure job\n", "### 6.1. Configure the workflow\n", "\n", - "If `RELAX`, `add_relaxation()` puts a relaxation in front of the band structure in the same job; the platform runs the band structure on the relaxed structure. The relaxation is set to a fixed cell, with its own coarser k-mesh, and with the atoms deeper than `RELAX_FREE_DEPTH` in the substrate fixed." + "If `RELAX`, `add_relaxation()` puts a relaxation in front of the band structure in the same job; the platform runs the band structure on the relaxed structure. The relaxation is set to a fixed cell, with the same k-mesh as the SCF." ] }, { @@ -406,7 +400,6 @@ "metadata": {}, "outputs": [], "source": [ - "from mat3ra.code.array_with_ids import ArrayWithIds\n", "from mat3ra.standata.workflows import WorkflowStandata\n", "from mat3ra.wode.workflows import Workflow\n", "from mat3ra.wode.context.providers import PointsPathDataProvider\n", @@ -431,16 +424,10 @@ "bands_unit.add_context(PointsPathDataProvider(path=KPATH, isEdited=True).get_context_item_data())\n", "bands_subworkflow.set_unit(bands_unit)\n", "\n", - "job_interface = interface\n", "if RELAX:\n", - " apply_scf_kgrid(workflow, relax_kgrid, material=interface, unit_name=RELAX_UNIT)\n", + " apply_scf_kgrid(workflow, kgrid, material=interface, unit_name=RELAX_UNIT)\n", " patch_workflow_qe_input(workflow, {\"control\": {\"calculation\": \"relax\", **RELAXATION_SETTINGS}}, [RELAX_UNIT])\n", "\n", - " is_free = is_carbon | (z_coordinates >= z_coordinates[~is_carbon].max() - RELAX_FREE_DEPTH)\n", - " job_interface = interface.clone()\n", - " job_interface.basis.constraints = ArrayWithIds.from_values([[bool(free)] * 3 for free in is_free])\n", - " print(f\"Free atoms in the relaxation: {dict(Counter(np.array(interface.basis.elements.values)[is_free]))}\")\n", - "\n", "print([[unit.name for unit in subworkflow.units] for subworkflow in workflow.subworkflows])\n", "visualize_workflow(workflow)" ] @@ -451,7 +438,7 @@ "source": [ "### 6.2. Save the material and run the job\n", "\n", - "The material is saved with the relaxation's constraints, which its hash does not include, so a saved copy is taken only if it carries the same ones. A job is found by its material and its workflow name, which carries the regime, and re-used if it exists; otherwise it is created and submitted." + "A job is found by its material and its workflow name, which carries the regime, and re-used if it exists; otherwise it is created and submitted." ] }, { @@ -461,14 +448,12 @@ "outputs": [], "source": [ "from mat3ra.notebooks_utils.core.entity.job.api import find_job_for_material\n", + "from mat3ra.notebooks_utils.core.entity.material.api import get_or_create_material\n", "from mat3ra.notebooks_utils.core.entity.property.api import get_properties_for_job\n", "from mat3ra.notebooks_utils.job import create_job\n", "from mat3ra.notebooks_utils.api.job import submit_jobs, wait_for_jobs_to_finish_async\n", "\n", - "saved_materials = client.materials.list({\"hash\": job_interface.hash, \"owner._id\": ACCOUNT_ID})\n", - "saved_materials = [m for m in saved_materials if bool(m[\"basis\"].get(\"constraints\")) == RELAX]\n", - "saved_interface = saved_materials[0] if saved_materials else client.materials.create(\n", - " job_interface.to_dict(), owner_id=ACCOUNT_ID)\n", + "saved_interface = get_or_create_material(client, interface, ACCOUNT_ID)\n", "\n", "job = find_job_for_material(\n", " client, saved_interface[\"_id\"], workflow.name, ACCOUNT_ID,\n", From a2be5a381d11c056483396cb7186b8445345ced7 Mon Sep 17 00:00:00 2001 From: VsevolodX Date: Wed, 7 Oct 2026 19:40:05 -0700 Subject: [PATCH 17/18] SOF-8065: review fixes - structure comments and heading, one relaxation branch Co-Authored-By: Claude Sonnet 5.5 --- ...rface_2d_3d_graphene_silicon_dioxide.ipynb | 10 ++++++---- ..._graphene_silicon_dioxide_SIMULATION.ipynb | 19 ++++++++----------- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide.ipynb b/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide.ipynb index 478751284..b789d049f 100644 --- a/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide.ipynb +++ b/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide.ipynb @@ -18,7 +18,7 @@ "\n", "\n", "\n", - "Replicating the materials from the manuscript, FIG. 1. (b):\n", + "Replicating the metastable geometry of Kang et al. Sec. III: graphene 2.58 Å above the O-terminated surface, shifted from the C-over-O registry of Fig. 1(b):\n", "\n", "\n" ] @@ -50,8 +50,10 @@ "SUBSTRATE_VACUUM = 0.0 # in angstroms\n", "\n", "INTERFACE_DISTANCE = 2.58 # Gap between substrate and film, in Angstrom -- from manuscript\n", - "INTERFACE_VACUUM = 17.5 # Angstrom; gives about 20 A above graphene, as the builder adds INTERFACE_DISTANCE above the film too\n", - "REGISTRY_SHIFT = [-1.011, -0.725, 0.0] # Å, in-plane shift of graphene to the manuscript's metastable registry, Sec. III\n", + "INTERFACE_VACUUM = 17.5 # Å; the slab is centered, so about 20 Å between periodic images, 10 Å on each side, as in the manuscript\n", + "# Å, in-plane; the closest a rigid shift gets to the manuscript's metastable registry (Sec. III)\n", + "# 3.5 prints each surface O's distance to the nearest C (0.354 / 1.095 Å)\n", + "REGISTRY_SHIFT = [-1.011, -0.725, 0.0]\n", "\n", "# Whether to convert materials to conventional cells before creating slabs.\n", "USE_CONVENTIONAL_CELL = True\n", @@ -374,7 +376,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### 3.6. Set the cell to the standard hexagonal setting\n", + "### 3.6. Set the cell to the standard hexagonal setting and center the slab\n", "The ZSL interface cell comes out with γ = 60°. The cell is put in the 120° hexagonal setting (same atoms) and typed HEX, so the symbolic K point of the band path in [interface_2d_3d_graphene_silicon_dioxide_SIMULATION.ipynb](interface_2d_3d_graphene_silicon_dioxide_SIMULATION.ipynb) is graphene's K. The slab is then centered along z, so that no atom sits at z = 0, where a relaxation would wrap it to the top of the cell.\n" ] }, diff --git a/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide_SIMULATION.ipynb b/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide_SIMULATION.ipynb index 3eeb7e48c..37c565235 100644 --- a/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide_SIMULATION.ipynb +++ b/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide_SIMULATION.ipynb @@ -97,7 +97,7 @@ "# NOTE: False reads the band structure of the structure as built: E_D - E_F +1.171 eV, gap at K\n", "# 0.062 eV, about 1 h on OR/16. True relaxes all atoms at fixed cell to 0.03 eV/Å (Kang et al.\n", "# Sec. II) before the band structure, in the same job; on OR/16 it did 5 BFGS steps in the 4 h\n", - "# TIME_LIMIT without converging, so it needs a longer TIME_LIMIT.\n", + "# TIME_LIMIT with the force still falling (job 25yp4K2SMNJgJMmBy).\n", "RELAX = False\n", "\n", "CLUSTER_NAME = \"001\" # specify full or partial name i.e. \"cluster-001\" to select\n", @@ -137,8 +137,8 @@ "\n", "SCF_UNIT = \"pw_scf\"\n", "BANDS_UNIT = \"pw_bands\"\n", - "RELAX_UNIT = \"pw_vc-relax\" # the relaxation unit add_relaxation() prepends; calculation is set to \"relax\" below\n", - "RELAXATION_SETTINGS = {\"forc_conv_thr\": 1.17e-3, \"nstep\": 100} # 0.03 eV/Å, Kang et al. 2008 Sec. II\n", + "RELAX_UNIT = \"pw_vc-relax\"\n", + "RELAXATION_SETTINGS = {\"calculation\": \"relax\", \"forc_conv_thr\": 1.17e-3, \"nstep\": 100} # fixed cell, 0.03 eV/Å, Kang et al. 2008 Sec. II\n", "WORKFLOW_TAG = MODEL_TAG + (f\" relax f{RELAXATION_SETTINGS['forc_conv_thr']}\" if RELAX else \"\")\n", "\n", "KPATH = [\n", @@ -415,20 +415,17 @@ "for subworkflow in workflow.subworkflows:\n", " subworkflow.model = model\n", "\n", - "for unit_name in [SCF_UNIT, BANDS_UNIT] + ([RELAX_UNIT] if RELAX else []):\n", + "for unit_name in [RELAX_UNIT, SCF_UNIT, BANDS_UNIT]:\n", " apply_planewave_cutoffs(workflow, ECUTWFC, ECUTRHO, unit_name=unit_name)\n", " patch_workflow_qe_input(workflow, {\"system\": SMEARING_SETTINGS}, [unit_name])\n", - "apply_scf_kgrid(workflow, kgrid, material=interface)\n", + "for unit_name in [RELAX_UNIT, SCF_UNIT]:\n", + " apply_scf_kgrid(workflow, kgrid, material=interface, unit_name=unit_name)\n", + "patch_workflow_qe_input(workflow, {\"control\": RELAXATION_SETTINGS}, [RELAX_UNIT])\n", "bands_subworkflow = workflow.subworkflows[-1]\n", "bands_unit = bands_subworkflow.get_unit_by_name(name=BANDS_UNIT)\n", "bands_unit.add_context(PointsPathDataProvider(path=KPATH, isEdited=True).get_context_item_data())\n", "bands_subworkflow.set_unit(bands_unit)\n", "\n", - "if RELAX:\n", - " apply_scf_kgrid(workflow, kgrid, material=interface, unit_name=RELAX_UNIT)\n", - " patch_workflow_qe_input(workflow, {\"control\": {\"calculation\": \"relax\", **RELAXATION_SETTINGS}}, [RELAX_UNIT])\n", - "\n", - "print([[unit.name for unit in subworkflow.units] for subworkflow in workflow.subworkflows])\n", "visualize_workflow(workflow)" ] }, @@ -539,7 +536,7 @@ "Settings that differ from the manuscript: GBRV ultrasoft LDA pseudopotentials against VASP at 396 eV;\n", "the standata quartz cell is +2.3 % on a against the manuscript's 4.91 Å; the substrate is 5\n", "conventional quartz cells (15 Si planes) where the manuscript has 14 bilayers; the back surface is the bare cut, not\n", - "H-passivated." + "H-passivated; the default run is unrelaxed." ] }, { From 13607b18154125d67cce265f97f16e250aeeb5e8 Mon Sep 17 00:00:00 2001 From: VsevolodX Date: Wed, 7 Oct 2026 19:41:16 -0700 Subject: [PATCH 18/18] SOF-8065: keep code lines under 120 characters Co-Authored-By: Claude Sonnet 5.5 --- .../interface_2d_3d_graphene_silicon_dioxide.ipynb | 3 ++- ...ce_2d_3d_graphene_silicon_dioxide_SIMULATION.ipynb | 11 +++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide.ipynb b/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide.ipynb index b789d049f..69546b8a4 100644 --- a/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide.ipynb +++ b/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide.ipynb @@ -50,7 +50,8 @@ "SUBSTRATE_VACUUM = 0.0 # in angstroms\n", "\n", "INTERFACE_DISTANCE = 2.58 # Gap between substrate and film, in Angstrom -- from manuscript\n", - "INTERFACE_VACUUM = 17.5 # Å; the slab is centered, so about 20 Å between periodic images, 10 Å on each side, as in the manuscript\n", + "# The slab is centered, so 17.5 gives about 20 Å between periodic images, 10 Å on each side, as in the manuscript\n", + "INTERFACE_VACUUM = 17.5 # in Angstrom\n", "# Å, in-plane; the closest a rigid shift gets to the manuscript's metastable registry (Sec. III)\n", "# 3.5 prints each surface O's distance to the nearest C (0.354 / 1.095 Å)\n", "REGISTRY_SHIFT = [-1.011, -0.725, 0.0]\n", diff --git a/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide_SIMULATION.ipynb b/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide_SIMULATION.ipynb index 37c565235..5aabf84f8 100644 --- a/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide_SIMULATION.ipynb +++ b/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide_SIMULATION.ipynb @@ -138,7 +138,8 @@ "SCF_UNIT = \"pw_scf\"\n", "BANDS_UNIT = \"pw_bands\"\n", "RELAX_UNIT = \"pw_vc-relax\"\n", - "RELAXATION_SETTINGS = {\"calculation\": \"relax\", \"forc_conv_thr\": 1.17e-3, \"nstep\": 100} # fixed cell, 0.03 eV/Å, Kang et al. 2008 Sec. II\n", + "# Fixed cell, 0.03 eV/Å, Kang et al. 2008 Sec. II\n", + "RELAXATION_SETTINGS = {\"calculation\": \"relax\", \"forc_conv_thr\": 1.17e-3, \"nstep\": 100}\n", "WORKFLOW_TAG = MODEL_TAG + (f\" relax f{RELAXATION_SETTINGS['forc_conv_thr']}\" if RELAX else \"\")\n", "\n", "KPATH = [\n", @@ -515,11 +516,13 @@ "bands = np.array(band_structure[0][\"yDataSeries\"])\n", "fermi_energy = band_structure[0][\"fermiEnergy\"]\n", "energies_at_k = bands[:, K_INDEX]\n", - "print(f\"Bands at K relative to E_F (eV), 0-based indices {number_of_occupied_bands - 2}..{number_of_occupied_bands + 2}:\")\n", - "for index in range(number_of_occupied_bands - 2, number_of_occupied_bands + 3):\n", + "first, last = number_of_occupied_bands - 2, number_of_occupied_bands + 2\n", + "print(f\"Bands at K relative to E_F (eV), 0-based indices {first}..{last}:\")\n", + "for index in range(first, last + 1):\n", " print(f\" {index}: {energies_at_k[index] - fermi_energy:+.4f}\")\n", "\n", - "candidates = [(number_of_occupied_bands - 1, number_of_occupied_bands), (number_of_occupied_bands, number_of_occupied_bands + 1)]\n", + "candidates = [(number_of_occupied_bands - 1, number_of_occupied_bands),\n", + " (number_of_occupied_bands, number_of_occupied_bands + 1)]\n", "below, above = min(candidates, key=lambda pair: energies_at_k[pair[1]] - energies_at_k[pair[0]])\n", "gap = energies_at_k[above] - energies_at_k[below]\n", "dirac_minus_fermi = (energies_at_k[above] + energies_at_k[below]) / 2 - fermi_energy\n",