diff --git a/other/materials_designer/workflows/band_structure.ipynb b/other/materials_designer/workflows/band_structure.ipynb
index 0217ebd5..7d01c487 100644
--- a/other/materials_designer/workflows/band_structure.ipynb
+++ b/other/materials_designer/workflows/band_structure.ipynb
@@ -118,13 +118,19 @@
"\n",
"# K-grids and k-path\n",
"RELAXATION_KGRID = None # e.g. [4, 4, 4]\n",
- "SCF_KGRID = None # e.g. [8, 8, 8]\n",
- "NSCF_KGRID = None # e.g. [12, 12, 12] — used for band_structure_dos\n",
- "KPATH = [{\"point\": \"Г\", \"steps\": 10}, {\"point\": \"M\", \"steps\": 10}, {\"point\": \"K\", \"steps\": 10}, {\"point\": \"Г\", \"steps\": 10}] # e.g. [{\"point\": \"Г\", \"steps\": 10}, {\"point\": \"M\", \"steps\": 10}, {\"point\": \"K\", \"steps\": 10}, {\"point\": \"Г\", \"steps\": 10}]\n",
+ "SCF_KGRID = None # e.g. [8, 8, 8]\n",
+ "NSCF_KGRID = None # e.g. [12, 12, 12] — used for band_structure_dos\n",
+ "KPATH = None # [{\"point\": \"Г\", \"steps\": 10}, {\"point\": \"M\", \"steps\": 10}, {\"point\": \"K\", \"steps\": 10}, {\"point\": \"Г\", \"steps\": 10}]\n",
"\n",
"# Energy cutoffs\n",
"ECUTWFC = 40\n",
- "ECUTRHO = 200"
+ "ECUTRHO = 200\n",
+ "\n",
+ "# Additional QE input parameters per unit\n",
+ "# Format: {\"unit_name\": {\"namelist\": {\"parameter\": value}}}\n",
+ "ADDITIONAL_PARAMETERS = {\n",
+ " # \"pw_nscf\": {\"system\": {\"occupations\": \"tetrahedra_opt\"}},\n",
+ "}"
]
},
{
@@ -390,6 +396,8 @@
"source": [
"from mat3ra.wode.context.providers import PlanewaveCutoffsContextProvider, PointsGridDataProvider, \\\n",
" PointsPathDataProvider\n",
+ "from mat3ra.notebooks_utils.workflow import patch_workflow_qe_input\n",
+ "\n",
"\n",
"bs_subworkflow = workflow.subworkflows[1 if ADD_RELAXATION else 0]\n",
"\n",
@@ -423,7 +431,11 @@
" unit = swf.get_unit_by_name(name=unit_name)\n",
" if unit:\n",
" unit.add_context(cutoffs_context)\n",
- " swf.set_unit(unit)"
+ " swf.set_unit(unit)\n",
+ "\n",
+ "if ADDITIONAL_PARAMETERS:\n",
+ " for unit_name, parameters in ADDITIONAL_PARAMETERS.items():\n",
+ " patch_workflow_qe_input(workflow, parameters, unit_names=[unit_name])"
]
},
{
@@ -513,8 +525,7 @@
" queue=QUEUE_NAME,\n",
" ppn=PPN\n",
")\n",
- "print(f\"Using cluster: {compute.cluster.hostname}, queue: {QUEUE_NAME}, ppn: {PPN}\")\n",
- "compute.cluster"
+ "print(f\"Using cluster: {compute.cluster.hostname}, queue: {QUEUE_NAME}, ppn: {PPN}\")"
]
},
{
diff --git a/other/materials_designer/workflows/band_structure_hse.ipynb b/other/materials_designer/workflows/band_structure_hse.ipynb
index 244d47fc..0d30400b 100644
--- a/other/materials_designer/workflows/band_structure_hse.ipynb
+++ b/other/materials_designer/workflows/band_structure_hse.ipynb
@@ -120,9 +120,9 @@
"# K-grids and k-path\n",
"RELAXATION_KGRID = None # e.g. [4, 4, 4]\n",
"# Required: uniform Gamma-centered grid — sets both k-mesh and exchange q-grid (nqx1/2/3)\n",
- "SCF_KGRID = [1, 1, 1]\n",
- "KPATH = None # e.g. [{\"point\": \"Г\", \"steps\": 20}, {\"point\": \"X\", \"steps\": 20}]\n",
- "\n",
+ "SCF_KGRID = None # [1, 1, 1]\n",
+ "KPATH = None # e.g. [{\"point\": \"Г\", \"steps\": 10},{\"point\": \"M\", \"steps\": 10}, {\"point\": \"K\", \"steps\": 10}, {\"point\": \"Г\", \"steps\": 10}]\n",
+ "QGRID = None # [1, 1, 1]\n",
"# Energy cutoffs\n",
"ECUTWFC = 40\n",
"ECUTRHO = 200"
@@ -436,7 +436,25 @@
" unit = swf.get_unit_by_name(name=unit_name)\n",
" if unit:\n",
" unit.add_context(cutoffs_context)\n",
- " swf.set_unit(unit)"
+ " swf.set_unit(unit)\n",
+ "\n",
+ "if QGRID is not None:\n",
+ " unit = main_hse_subworkflow.get_unit_by_name(name=\"pw_scf_bands_hse\")\n",
+ " if unit:\n",
+ " unit.add_context({\n",
+ " \"qgrid\": {\n",
+ " \"dimensions\": QGRID,\n",
+ " \"shifts\": [0, 0, 0],\n",
+ " \"preferGridMetric\": False,\n",
+ " },\n",
+ " \"isQgridEdited\": True,\n",
+ " })\n",
+ " main_hse_subworkflow.set_unit(unit)\n",
+ "\n",
+ "bands_unit = main_hse_subworkflow.get_unit_by_name(name=\"bands\")\n",
+ "if bands_unit:\n",
+ " bands_unit.results = [{\"name\": \"band_structure\"},{\"name\": \"band_gaps\"}]\n",
+ " main_hse_subworkflow.set_unit(bands_unit)"
]
},
{
diff --git a/other/materials_designer/workflows/band_structure_magn.ipynb b/other/materials_designer/workflows/band_structure_magn.ipynb
new file mode 100644
index 00000000..2dcadffa
--- /dev/null
+++ b/other/materials_designer/workflows/band_structure_magn.ipynb
@@ -0,0 +1,704 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "0",
+ "metadata": {},
+ "source": [
+ "# Spin-Magnetic Band Structure (QE)\n",
+ "\n",
+ "Calculate the spin-resolved electronic band structure of a material using a spin-polarized DFT workflow on the Mat3ra platform. Optionally enable DFT+U and per-species starting magnetization.\n",
+ "\n",
+ "For antiferromagnetic or otherwise labeled structures, assign numeric labels to atoms in Materials Designer (e.g. `Fe1`, `Fe2`) before running. See the [Magnetic Properties (QE) tutorial](https://docs.mat3ra.com/guide/tutorials/dft/electronic/spin-magnetic-qe/).\n",
+ "\n",
+ "
Usage
\n",
+ "\n",
+ "1. Set material and calculation parameters in cells 1.2 and 1.3 below (or use the default values).\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 result.\n",
+ "\n",
+ "## Summary\n",
+ "\n",
+ "1. Set up the environment and parameters: install packages (JupyterLite only) and configure parameters for material, workflow, compute resources, and job.\n",
+ "1. Authenticate and initialize API client: authenticate via browser, initialize the client, then select account and project.\n",
+ "1. Create material: materials are read from the `../uploads` folder — place files there manually or run a material creation notebook first. If the material is not found by name, Standata is used as a fallback. The material is then saved to the platform.\n",
+ "1. Configure workflow: select application, load spin-magnetic band structure workflow from Standata, set model and computational parameters, starting magnetization, optional DFT+U, and ELECTRONS settings, and save the workflow.\n",
+ "1. Configure compute: get list of clusters and create compute configuration with selected cluster, queue, and number of processors.\n",
+ "1. Create the job with material and workflow configuration: assemble the job from material, workflow, project, and compute configuration.\n",
+ "1. Submit the job and monitor the status: submit the job and wait for completion.\n",
+ "1. Retrieve results: get and display the spin-resolved band structure."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "1",
+ "metadata": {},
+ "source": [
+ "## 1. Set up the environment and parameters\n",
+ "### 1.1. Install packages (JupyterLite)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "2",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from mat3ra.notebooks_utils.packages import install_packages\n",
+ "\n",
+ "await install_packages(\"made|api_examples\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "3",
+ "metadata": {},
+ "source": [
+ "### 1.2. Set parameters"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "4",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from datetime import datetime\n",
+ "from mat3ra.ide.compute import QueueName\n",
+ "\n",
+ "# 2. Auth and organization parameters\n",
+ "ORGANIZATION_NAME = None\n",
+ "\n",
+ "# 3. Material parameters\n",
+ "FOLDER = \"../uploads\"\n",
+ "MATERIAL_NAME = \"Ni\"\n",
+ "\n",
+ "# 4. Workflow parameters\n",
+ "APPLICATION_NAME = \"espresso\"\n",
+ "ADD_RELAXATION = False\n",
+ "USE_DFT_U = False\n",
+ "\n",
+ "WORKFLOW_SEARCH_TERM = \"band_structure_magn.json\"\n",
+ "MY_WORKFLOW_NAME = \"IDE Band Structure (spin magnetism)\" + (\" + DFT+U\" if USE_DFT_U else \"\")\n",
+ "\n",
+ "# Model parameters\n",
+ "MODEL_SUBTYPE = \"gga\" # or \"lda\"\n",
+ "\n",
+ "# 5. Compute parameters\n",
+ "CLUSTER_NAME = None # specify full or partial name i.e. \"cluster-001\" to select\n",
+ "QUEUE_NAME = QueueName.D\n",
+ "PPN = 1\n",
+ "\n",
+ "# 6. Job parameters\n",
+ "timestamp = datetime.now().strftime(\"%Y-%m-%d %H:%M\")\n",
+ "POLL_INTERVAL = 30 # seconds"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "5",
+ "metadata": {},
+ "source": [
+ "### 1.3. Set specific spin-magnetic band structure parameters"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "6",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Method parameters\n",
+ "PSEUDOPOTENTIAL_TYPE = \"us\" # \"us\" (ultrasoft), \"nc\" (norm-conserving), \"paw\"\n",
+ "FUNCTIONAL = \"pbe\" # for gga: \"pbe\", \"pbesol\"; for lda: \"pz\"\n",
+ "\n",
+ "# K-grids and k-path\n",
+ "RELAXATION_KGRID = None # e.g. [2, 2, 2]\n",
+ "SCF_KGRID = None # e.g. [2, 2, 2]\n",
+ "KPATH = None\n",
+ "# [\n",
+ "# {\"point\": \"Г\", \"steps\": 10},\n",
+ "# {\"point\": \"L\", \"steps\": 10},\n",
+ "# {\"point\": \"Y\", \"steps\": 10},\n",
+ "# {\"point\": \"Z\", \"steps\": 10},\n",
+ "# {\"point\": \"Г\", \"steps\": 10},\n",
+ "# ]\n",
+ "\n",
+ "# Collinear magnetization by atomic species (with labels), e.g. antiferromagnetic Fe2O3\n",
+ "STARTING_MAGNETIZATION = {\"Fe1\": -1, \"Fe2\": 1}\n",
+ "IS_TOTAL_MAGNETIZATION = False\n",
+ "TOTAL_MAGNETIZATION = 0\n",
+ "\n",
+ "# DFT+U Hubbard U values by atomic species (with labels); set USE_DFT_U = False to disable\n",
+ "HUBBARD_U = {\"Fe1\": 5.3, \"Fe2\": 5.3}\n",
+ "\n",
+ "# Patch &CONTROL and &ELECTRONS (recommended for magnetic insulators)\n",
+ "ADDITIONAL_PARAMETERS = {\n",
+ " # \"control\": {\n",
+ " # \"tstress\": False,\n",
+ " # \"tprnfor\": False,\n",
+ " # },\n",
+ " # \"electrons\": {\n",
+ " # \"mixing_mode\": \"plain\",\n",
+ " # \"mixing_beta\": 0.2,\n",
+ " # \"mixing_ndim\": 10,\n",
+ " # \"conv_thr\": \"1.0d-8\",\n",
+ " # \"diagonalization\": \"cg\",\n",
+ " # \"electron_maxstep\": 200,\n",
+ " # \"diago_david_ndim\": 4,\n",
+ " # \"diago_full_acc\": True,\n",
+ " # \"startingwfc\": \"atomic+random\",\n",
+ " # }\n",
+ "}\n",
+ "\n",
+ "# Energy cutoffs\n",
+ "ECUTWFC = 40\n",
+ "ECUTRHO = 200"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "7",
+ "metadata": {},
+ "source": [
+ "## 2. Authenticate and initialize API client\n",
+ "### 2.1. Authenticate\n",
+ "Authenticate in the browser and have credentials stored in environment variable \"OIDC_ACCESS_TOKEN\"."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "8",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from mat3ra.notebooks_utils.auth import authenticate\n",
+ "\n",
+ "await authenticate()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "9",
+ "metadata": {},
+ "source": [
+ "### 2.2. Initialize API client"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "10",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from mat3ra.api_client import APIClient\n",
+ "\n",
+ "client = APIClient.authenticate()\n",
+ "client"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "11",
+ "metadata": {},
+ "source": [
+ "### 2.3. Select account"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "12",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "client.list_accounts()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "13",
+ "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\"\\u2705 Selected account ID: {ACCOUNT_ID}, name: {selected_account.name}\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "14",
+ "metadata": {},
+ "source": [
+ "### 2.4. Select project"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "15",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "projects = client.projects.list({\"isDefault\": True, \"owner._id\": ACCOUNT_ID})\n",
+ "project_id = projects[0][\"_id\"]\n",
+ "print(f\"\\u2705 Using project: {projects[0]['name']} ({project_id})\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "16",
+ "metadata": {},
+ "source": [
+ "## 3. Create material\n",
+ "### 3.1. Load material from local file (or Standata)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "17",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from mat3ra.made.material import Material\n",
+ "from mat3ra.standata.materials import Materials\n",
+ "from mat3ra.notebooks_utils.ipython.entity.material.visualize import visualize_materials as visualize\n",
+ "from mat3ra.notebooks_utils.material import load_material_from_folder\n",
+ "\n",
+ "material = load_material_from_folder(FOLDER, MATERIAL_NAME) or Material.create(\n",
+ " Materials.get_by_name_first_match(MATERIAL_NAME))\n",
+ "\n",
+ "visualize(material)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "18",
+ "metadata": {},
+ "source": [
+ "### 3.2. Save material to the platform"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "19",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from mat3ra.notebooks_utils.core.entity.material.api import get_or_create_material\n",
+ "\n",
+ "saved_material_response = get_or_create_material(client, material, ACCOUNT_ID)\n",
+ "saved_material = Material.create(saved_material_response)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "20",
+ "metadata": {},
+ "source": [
+ "## 4. Configure workflow\n",
+ "### 4.1. Select application"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "21",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from mat3ra.standata.applications import ApplicationStandata\n",
+ "from mat3ra.ade.application import Application\n",
+ "\n",
+ "app_config = ApplicationStandata.get_by_name_first_match(APPLICATION_NAME)\n",
+ "app = Application(**app_config)\n",
+ "print(f\"Using application: {app.name}\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "22",
+ "metadata": {},
+ "source": [
+ "### 4.2. Load workflow from Standata and preview it"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "23",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from mat3ra.standata.workflows import WorkflowStandata\n",
+ "from mat3ra.wode.workflows import Workflow\n",
+ "from mat3ra.notebooks_utils.ipython.entity.workflow.visualize import visualize_workflow\n",
+ "\n",
+ "workflow_config = WorkflowStandata.filter_by_application(app.name).get_by_name_first_match(WORKFLOW_SEARCH_TERM)\n",
+ "workflow = Workflow.create(workflow_config)\n",
+ "workflow.name = MY_WORKFLOW_NAME\n",
+ "\n",
+ "visualize_workflow(workflow)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "24",
+ "metadata": {},
+ "source": [
+ "### 4.3. Add relaxation (optional)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "25",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "if ADD_RELAXATION:\n",
+ " workflow.add_relaxation()\n",
+ " visualize_workflow(workflow)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "26",
+ "metadata": {},
+ "source": [
+ "### 4.4. Set Model and its parameters (physics)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "27",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from mat3ra.standata.model_tree import ModelTreeStandata\n",
+ "from mat3ra.mode.model import Model\n",
+ "\n",
+ "model_config = ModelTreeStandata.get_model_by_parameters(\n",
+ " type=\"dft\", subtype=MODEL_SUBTYPE, functional=FUNCTIONAL\n",
+ ")\n",
+ "model_config[\"method\"] = {\"type\": \"pseudopotential\", \"subtype\": PSEUDOPOTENTIAL_TYPE}\n",
+ "model = Model.create(model_config)\n",
+ "\n",
+ "for subworkflow in workflow.subworkflows:\n",
+ " subworkflow.model = model\n",
+ "\n",
+ "visualize_workflow(workflow)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "28",
+ "metadata": {},
+ "source": [
+ "### 4.5. Modify Method (computational parameters): k-grid, k-path, cutoffs, magnetization, DFT+U, and ELECTRONS"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "29",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from mat3ra.wode.context.providers import PlanewaveCutoffsContextProvider, PointsGridDataProvider, \\\n",
+ " PointsPathDataProvider\n",
+ "from mat3ra.notebooks_utils.workflow import patch_workflow_qe_input\n",
+ "\n",
+ "bs_subworkflow = workflow.subworkflows[1 if ADD_RELAXATION else 0]\n",
+ "magn_unit_names = [\"pw_scf_magn\", \"pw_bands_magn\"]\n",
+ "\n",
+ "if RELAXATION_KGRID is not None and ADD_RELAXATION:\n",
+ " unit = workflow.subworkflows[0].get_unit_by_name(name_regex=\"relax\")\n",
+ " unit.add_context(PointsGridDataProvider(dimensions=RELAXATION_KGRID, isEdited=True).yield_data())\n",
+ " workflow.subworkflows[0].set_unit(unit)\n",
+ "\n",
+ "if SCF_KGRID is not None:\n",
+ " unit = bs_subworkflow.get_unit_by_name(name=\"pw_scf_magn\")\n",
+ " if unit:\n",
+ " unit.add_context(PointsGridDataProvider(dimensions=SCF_KGRID, isEdited=True).yield_data())\n",
+ " bs_subworkflow.set_unit(unit)\n",
+ "\n",
+ "if KPATH is not None:\n",
+ " unit = bs_subworkflow.get_unit_by_name(name=\"pw_bands_magn\")\n",
+ " if unit:\n",
+ " unit.add_context(PointsPathDataProvider(path=KPATH, isEdited=True).yield_data())\n",
+ " bs_subworkflow.set_unit(unit)\n",
+ "\n",
+ "if ECUTWFC is not None:\n",
+ " cutoffs_context = PlanewaveCutoffsContextProvider(wavefunction=ECUTWFC, density=ECUTRHO, isEdited=True).yield_data()\n",
+ " for unit_name in [\"pw_relax\", \"pw_vc-relax\", \"pw_scf_magn\", \"pw_bands_magn\"]:\n",
+ " for swf in workflow.subworkflows:\n",
+ " unit = swf.get_unit_by_name(name=unit_name)\n",
+ " if unit:\n",
+ " unit.add_context(cutoffs_context)\n",
+ " swf.set_unit(unit)\n",
+ "\n",
+ "# Build species names from material labels (e.g. Fe1, Fe2, O)\n",
+ "basis = material.basis\n",
+ "labels_map = {item[\"id\"]: str(item[\"value\"]) for item in basis.labels.to_dict()} if basis.labels else {}\n",
+ "species_names = []\n",
+ "seen = set()\n",
+ "for element in basis.elements.to_dict():\n",
+ " label = labels_map.get(element[\"id\"], \"\")\n",
+ " name = f\"{element['value']}{label}\" if label else element[\"value\"]\n",
+ " if name not in seen:\n",
+ " seen.add(name)\n",
+ " species_names.append(name)\n",
+ "\n",
+ "# Remove the Jinja collinearMagnetization block from templates and\n",
+ "# add starting_magnetization directly via patch (context rendering is unreliable)\n",
+ "import re\n",
+ "JINJA_MAGN_BLOCK = re.compile(\n",
+ " r'\\{%-?\\s*if collinearMagnetization\\.isTotalMagnetization.*?\\{%-?\\s*endif\\s*%\\}',\n",
+ " re.DOTALL\n",
+ ")\n",
+ "for unit_name in magn_unit_names:\n",
+ " unit = bs_subworkflow.get_unit_by_name(name=unit_name)\n",
+ " if unit:\n",
+ " unit.input[0][\"content\"] = JINJA_MAGN_BLOCK.sub(\"\", unit.input[0][\"content\"])\n",
+ " bs_subworkflow.set_unit(unit)\n",
+ "\n",
+ "# Build system_patch with starting_magnetization, DFT+U, etc.\n",
+ "system_patch = {}\n",
+ "for atomic_species, value in STARTING_MAGNETIZATION.items():\n",
+ " index = species_names.index(atomic_species) + 1\n",
+ " system_patch[f\"starting_magnetization({index})\"] = value\n",
+ "\n",
+ "if USE_DFT_U and HUBBARD_U:\n",
+ " system_patch[\"lda_plus_u\"] = True\n",
+ " system_patch[\"lda_plus_u_kind\"] = 0\n",
+ " system_patch[\"U_projection_type\"] = \"ortho-atomic\"\n",
+ " for atomic_species, hubbard_u_value in HUBBARD_U.items():\n",
+ " index = species_names.index(atomic_species) + 1\n",
+ " system_patch[f\"Hubbard_U({index})\"] = hubbard_u_value\n",
+ "\n",
+ "if system_patch:\n",
+ " patch_workflow_qe_input(workflow, {\"system\": system_patch}, unit_names=magn_unit_names)\n",
+ "\n",
+ "if ADDITIONAL_PARAMETERS:\n",
+ " patch_workflow_qe_input(workflow, ADDITIONAL_PARAMETERS, unit_names=magn_unit_names)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "30",
+ "metadata": {},
+ "source": [
+ "### 4.6. Preview final workflow"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "31",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "visualize_workflow(workflow)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "32",
+ "metadata": {},
+ "source": [
+ "### 4.7. Save workflow to collection"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "33",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from mat3ra.notebooks_utils.core.entity.workflow.api import get_or_create_workflow\n",
+ "\n",
+ "saved_workflow_response = get_or_create_workflow(client, workflow, ACCOUNT_ID)\n",
+ "saved_workflow = Workflow.create(saved_workflow_response)\n",
+ "print(f\"Workflow ID: {saved_workflow.id}\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "34",
+ "metadata": {},
+ "source": [
+ "## 5. Create the compute configuration\n",
+ "### 5.1. Get list of clusters"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "35",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "clusters = client.clusters.list()\n",
+ "print(f\"Available clusters: {[c['hostname'] for c in clusters]}\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "36",
+ "metadata": {},
+ "source": [
+ "### 5.2. Create compute configuration"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "37",
+ "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",
+ "else:\n",
+ " cluster = clusters[0]\n",
+ "\n",
+ "compute = Compute(\n",
+ " cluster=cluster,\n",
+ " queue=QUEUE_NAME,\n",
+ " ppn=PPN\n",
+ ")\n",
+ "print(f\"Using cluster: {compute.cluster.hostname}, queue: {QUEUE_NAME}, ppn: {PPN}\")\n",
+ "compute.cluster"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "38",
+ "metadata": {},
+ "source": [
+ "## 6. Create the job\n",
+ "### 6.1. Create job"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "39",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from mat3ra.notebooks_utils.job import create_job\n",
+ "from mat3ra.utils.namespace import dict_to_namespace_recursive\n",
+ "from mat3ra.notebooks_utils.ui import display_JSON\n",
+ "\n",
+ "print(f\"Material: {saved_material.id}\")\n",
+ "print(f\"Workflow: {saved_workflow.id}\")\n",
+ "print(f\"Project: {project_id}\")\n",
+ "\n",
+ "job_name = MY_WORKFLOW_NAME + \" \" + saved_material.formula + \" \" + timestamp\n",
+ "job_response = create_job(\n",
+ " api_client=client,\n",
+ " materials=[saved_material],\n",
+ " workflow=workflow,\n",
+ " project_id=project_id,\n",
+ " owner_id=ACCOUNT_ID,\n",
+ " prefix=job_name,\n",
+ " compute=compute.to_dict(),\n",
+ ")\n",
+ "\n",
+ "job = dict_to_namespace_recursive(job_response)\n",
+ "job_id = job._id\n",
+ "print(\"\\u2705 Job created successfully!\")\n",
+ "print(f\"Job ID: {job_id}\")\n",
+ "display_JSON(job_response)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "40",
+ "metadata": {},
+ "source": [
+ "## 7. Submit the job and monitor the status"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "41",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "client.jobs.submit(job_id)\n",
+ "print(f\"\\u2705 Job {job_id} submitted successfully!\")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "42",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from mat3ra.notebooks_utils.api.job import wait_for_jobs_to_finish_async\n",
+ "\n",
+ "await wait_for_jobs_to_finish_async(client.jobs, [job_id], poll_interval=POLL_INTERVAL)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "43",
+ "metadata": {},
+ "source": [
+ "## 8. Retrieve and visualize results\n",
+ "### 8.1. Spin-Resolved Band Structure"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "44",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from mat3ra.prode import PropertyName\n",
+ "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_structure_data = get_properties_for_job(client, job_id, property_name=PropertyName.non_scalar.band_structure.value)\n",
+ "visualize_properties(band_structure_data, title=\"Spin-Resolved Band Structure\", extra_config={\"material\": material.to_dict()})"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "name": "python",
+ "version": "3.11.2"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/pyproject.toml b/pyproject.toml
index 66f63f5d..4e0badc7 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -17,10 +17,11 @@ jupyterlite = [
"matplotlib>=3.4.1",
"plotly>=5.18",
"ipython>=8.0",
+ "ipywidgets>=8.0",
]
utils_standata = [
"mat3ra-utils",
- "mat3ra-standata",
+ "mat3ra-standata==2026.5.15.post0",
]
api = [
"mat3ra-api-client",
@@ -34,19 +35,23 @@ materials = [
"mat3ra-notebooks-utils[auxilary]",
"mat3ra-notebooks-utils[utils_standata]",
"pymatgen==2024.4.13",
+ "pymatgen-analysis-defects<=2024.4.23",
"mat3ra-made>=2026.4.2.post0",
+ "mat3ra-periodic-table"
]
workflows = [
"mat3ra-notebooks-utils[materials]",
- "mat3ra-mode",
- "mat3ra-wode",
- "mat3ra-ade>=2026.5.29.post0",
+ "mat3ra-esse==2026.4.29.post0",
+ "mat3ra-mode==2026.3.4.post0",
+ "mat3ra-wode==2026.5.23.post0",
+ "mat3ra-ade==2026.4.29.post0",
"mat3ra-prode",
"mat3ra-ide",
"mat3ra-notebooks-utils[api]"
]
all = [
- "mat3ra-notebooks-utils[workflows]"
+ "mat3ra-notebooks-utils[workflows]",
+ "mat3ra-notebooks-utils[jupyterlite]",
]
# Install all above dependencies in colab
diff --git a/src/py/mat3ra/notebooks_utils/workflow.py b/src/py/mat3ra/notebooks_utils/workflow.py
index d2ada0db..c01695dc 100644
--- a/src/py/mat3ra/notebooks_utils/workflow.py
+++ b/src/py/mat3ra/notebooks_utils/workflow.py
@@ -3,6 +3,16 @@
from mat3ra.wode import Workflow
+FORTRAN_NUMBER_PATTERN = re.compile(r"^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[de][+-]?\d+)?$", re.IGNORECASE)
+
+
+def _format_fortran_value(value: object) -> str:
+ if isinstance(value, bool):
+ return f".{str(value).lower()}."
+ if isinstance(value, str) and not FORTRAN_NUMBER_PATTERN.match(value):
+ return repr(value)
+ return str(value)
+
def patch_workflow_qe_input(
workflow: Workflow,
@@ -22,18 +32,25 @@ def patch_workflow_qe_input(
Example:
patch_workflow_qe_input(workflow, {"system": {"vdw_corr": "d3_grimme"}}, ["pw_relax"])
"""
- f90 = lambda value: ( # noqa: E731
- f".{str(value).lower()}." if isinstance(value, bool) else repr(value) if isinstance(value, str) else str(value)
- )
+ # TODO: remove the dict handling when WA is updated, and use latest Standata/Wode/Ade here
for subworkflow in workflow.subworkflows:
for unit_name in unit_names:
if not (unit := subworkflow.get_unit_by_name(name=unit_name)):
continue
for input_item in getattr(unit, "input", []):
- template = input_item.template
- if input_name not in (None, template.name):
+ if isinstance(input_item, dict):
+ template_name = input_item.get("name")
+ content = input_item.get("content")
+ else:
+ template = input_item.template
+ template_name = template.name
+ content = template.content
+
+ if input_name not in (None, template_name):
continue
- content = template.content
+ if content is None:
+ continue
+
for section, updates in parameters.items():
name = section.lstrip("&")
match = re.search(rf"(?ims)(^&{re.escape(name)}\s*\n)(.*?)(^/\s*$)", content)
@@ -41,9 +58,14 @@ def patch_workflow_qe_input(
raise ValueError(f"Namelist '&{name.upper()}' not found.")
header, body, footer = match.groups()
for key, value in updates.items():
- line, pattern = f" {key} = {f90(value)}", rf"(?im)^\s*{re.escape(key)}\s*=.*$"
+ line = f" {key} = {_format_fortran_value(value)}"
+ pattern = rf"(?im)^\s*{re.escape(key)}\s*=.*$"
body = re.sub(pattern, line, body) if re.search(pattern, body) else f"{body.rstrip()}\n{line}\n"
content = content[: match.start()] + header + body + footer + content[match.end() :]
- template.set_content(content)
+
+ if isinstance(input_item, dict):
+ input_item["content"] = content
+ else:
+ template.set_content(content)
subworkflow.set_unit(unit)
return workflow