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