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..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
@@ -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"
]
@@ -45,12 +45,16 @@
"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: 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",
"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",
+ "# 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",
"\n",
"# Whether to convert materials to conventional cells before creating slabs.\n",
"USE_CONVENTIONAL_CELL = True\n",
@@ -337,6 +341,62 @@
")\n"
]
},
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### 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 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"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "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}°\")"
+ ]
+ },
{
"cell_type": "markdown",
"metadata": {},
@@ -367,7 +427,8 @@
"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",
"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..5aabf84f8
--- /dev/null
+++ b/other/materials_designer/specific_examples/interface_2d_3d_graphene_silicon_dioxide_SIMULATION.ipynb
@@ -0,0 +1,594 @@
+{
+ "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 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 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 (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",
+ "\n",
+ "## Summary\n",
+ "\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.\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. 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"
+ ]
+ },
+ {
+ "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 name"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Name saved by interface_2d_3d_graphene_silicon_dioxide.ipynb.\n",
+ "INTERFACE_NAME = \"C(001)-O2Si(001), Interface, Strain 1.875pct\""
+ ]
+ },
+ {
+ "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",
+ "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 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 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",
+ "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",
+ "\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 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",
+ "KPOINT_DENSITY = 4 # gives 6 x 6 x 1 on the 1x1 quartz cell, Kang et al. Sec. II\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_vc-relax\"\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",
+ " {\"point\": \"K\", \"steps\": KPATH_STEPS},\n",
+ " {\"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"
+ ]
+ },
+ {
+ "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 material\n",
+ "### 3.1. Load from the uploads folder and print provenance\n",
+ "\n",
+ "The structure, its registry and its 120° hexagonal cell come from the structure notebook; this one\n",
+ "only loads it by name."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import numpy as np\n",
+ "from collections import Counter\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",
+ "\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",
+ "\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_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\")"
+ ]
+ },
+ {
+ "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": [
+ "## 4. Configure the 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"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "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}\")"
+ ]
+ },
+ {
+ "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. 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 the same k-mesh as the SCF."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from mat3ra.standata.workflows import WorkflowStandata\n",
+ "from mat3ra.wode.workflows import Workflow\n",
+ "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} {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 [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",
+ "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",
+ "visualize_workflow(workflow)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### 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 regime, and re-used if it exists; otherwise it is created and submitted."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "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_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",
+ " statuses=(\"submitted\", \"queued\", \"active\", \"finished\"),\n",
+ ")\n",
+ "if job is None:\n",
+ " job = create_job(\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",
+ " 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\"]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## 7. Retrieve the results\n",
+ "### 7.1. Band structure"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from mat3ra.notebooks_utils.ipython.entity.property.visualize import visualize_properties\n",
+ "\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.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": [
+ "### 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",
+ "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."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "bands = np.array(band_structure[0][\"yDataSeries\"])\n",
+ "fermi_energy = band_structure[0][\"fermiEnergy\"]\n",
+ "energies_at_k = bands[:, K_INDEX]\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),\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",
+ "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\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## 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",
+ "conventional quartz cells (15 Si planes) where the manuscript has 14 bilayers; the back surface is the bare cut, not\n",
+ "H-passivated; the default run is unrelaxed."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "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\"{'':<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\")"
+ ]
+ },
+ {
+ "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"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "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.11.2"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}