diff --git a/other/materials_designer/workflows/Introduction.ipynb b/other/materials_designer/workflows/Introduction.ipynb index c5987b4d..e2510604 100644 --- a/other/materials_designer/workflows/Introduction.ipynb +++ b/other/materials_designer/workflows/Introduction.ipynb @@ -51,7 +51,7 @@ "## 5. Vibrational\n", "\n", "### 5.1. Phonons\n", - "#### 5.1.1. Phonon DOS and Dispersion. *(to be added)*\n", + "#### [5.1.1. Phonon DOS and Dispersion.](phonon_dos_dispersion.ipynb)\n", "\n", "\n", "## 6. Thermodynamics\n", diff --git a/other/materials_designer/workflows/phonon_dos_dispersion.ipynb b/other/materials_designer/workflows/phonon_dos_dispersion.ipynb new file mode 100644 index 00000000..47b96ebf --- /dev/null +++ b/other/materials_designer/workflows/phonon_dos_dispersion.ipynb @@ -0,0 +1,685 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "0", + "metadata": {}, + "source": [ + "# Phonon Density of States + Dispersions\n", + "\n", + "Compute the phonon **dispersion curves** and phonon **density of states (DOS)** of a single material using Quantum ESPRESSO on the Mat3ra platform.\n", + "\n", + "The workflow runs, in sequence: SCF (`pw.x`) → DFPT on a q-grid (`ph.x`) → interatomic force constants (`q2r.x`) → phonon DOS on a q-grid and dispersion along a high-symmetry path (`matdyn.x`).\n", + "\n", + "Single material; no reference materials or prerequisites. Phonon DFPT cost scales steeply with the number of atoms, so use a **small primitive cell**.\n", + "\n", + "

Usage

\n", + "\n", + "1. Set the material and parameters in cells 1.2 / 1.3 below (default: **Silicon**, which gives clean phonons out of the box). For harder cases (e.g. Graphene / 2D) use the controls in 1.3 — relaxation, denser k/q grids, higher cutoffs — to avoid imaginary modes.\n", + "2. Click \"Run\" > \"Run All\".\n", + "3. Inspect the phonon dispersion curves and DOS at the end. Acoustic branches should go to 0 at Γ, with no large imaginary (negative) modes.\n", + "\n", + "## Summary\n", + "\n", + "1. Set up the environment and parameters.\n", + "2. Authenticate and initialize API client.\n", + "3. Load the material.\n", + "4. Configure the Phonon DOS + Dispersions workflow.\n", + "5. Configure compute.\n", + "6. Create the job.\n", + "7. Submit and monitor the job.\n", + "8. Retrieve and visualize the phonon dispersion and DOS." + ] + }, + { + "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 = \"Silicon\" # small primitive cell (FCC, mp-149); phonon DFPT is expensive\n", + "\n", + "# 4. Workflow parameters\n", + "WORKFLOW_SEARCH_TERM = \"phonon_dos_dispersion.json\"\n", + "APPLICATION_NAME = \"espresso\"\n", + "MY_WORKFLOW_NAME = \"Phonon Density of States + Dispersions\"\n", + "\n", + "# Model parameters\n", + "MODEL_SUBTYPE = \"gga\" # \"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 phonon 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", + "# Relax the structure before phonons. Phonons require a force-minimized geometry —\n", + "# an unrelaxed structure produces imaginary (negative) modes. Recommended for any\n", + "# material not already at its equilibrium geometry (e.g. Graphene / 2D).\n", + "ADD_RELAXATION = False\n", + "\n", + "# Electronic k-grids; if None, the workflow default is used\n", + "SCF_KGRID = None # e.g. [8, 8, 8] (3D) or [12, 12, 1] (2D)\n", + "RELAXATION_KGRID = None # e.g. [8, 8, 8] (used only when ADD_RELAXATION)\n", + "\n", + "# Phonon q-grid for the DFPT run (ph.x); if None, the workflow default is used\n", + "PHONON_QGRID = None # e.g. [4, 4, 4] (3D) or [6, 6, 1] (2D)\n", + "\n", + "# Phonon DOS interpolation grid (matdyn.x); if None, the workflow default is used\n", + "DOS_QGRID = None # e.g. [12, 12, 12]\n", + "\n", + "# Phonon dispersion path (matdyn.x); if None, the workflow default is used\n", + "QPATH = None # e.g. [{\"point\": \"G\", \"steps\": 20}, {\"point\": \"M\", \"steps\": 20},\n", + " # {\"point\": \"K\", \"steps\": 20}, {\"point\": \"G\", \"steps\": 20}]\n", + "\n", + "# Energy cutoffs (raise for harder cases — e.g. 60 / 480 for Graphene)\n", + "ECUTWFC = 40\n", + "ECUTRHO = 200" + ] + }, + { + "cell_type": "markdown", + "id": "7", + "metadata": {}, + "source": [ + "## 2. Authenticate and initialize API client\n", + "### 2.1. Authenticate" + ] + }, + { + "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\"✅ 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\"✅ 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", + "material.basis.set_labels_from_list([])\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)\n", + "Prepend a relaxation subworkflow so the phonons are computed on a force-minimized structure." + ] + }, + { + "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 import ModelFactory\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 = ModelFactory.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)\n", + "Optional overrides — each applies only if its parameter is set above. Electronic k-grid + cutoffs on `pw.x`; phonon q-grid on `ph.x`; DOS interpolation grid and dispersion path on `matdyn.x`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "29", + "metadata": {}, + "outputs": [], + "source": [ + "from mat3ra.wode.context.providers import (\n", + " PlanewaveCutoffsContextProvider, PointsGridDataProvider, PointsPathDataProvider,\n", + ")\n", + "\n", + "# The phonon subworkflow is index 0, or 1 when a relaxation subworkflow is prepended.\n", + "phonon_subworkflow = workflow.subworkflows[1 if ADD_RELAXATION else 0]\n", + "\n", + "\n", + "def set_grid(subworkflow, unit_name, dimensions, name=None):\n", + " unit = subworkflow.get_unit_by_name(name=unit_name)\n", + " if unit:\n", + " unit.add_context(PointsGridDataProvider(dimensions=dimensions, isEdited=True, **({\"name\": name} if name else {})).get_context_item_data())\n", + " subworkflow.set_unit(unit)\n", + "\n", + "\n", + "def set_path(subworkflow, unit_name, path, name):\n", + " unit = subworkflow.get_unit_by_name(name=unit_name)\n", + " if unit:\n", + " unit.add_context(PointsPathDataProvider(path=path, isEdited=True, name=name).get_context_item_data())\n", + " subworkflow.set_unit(unit)\n", + "\n", + "\n", + "# Relaxation electronic k-grid\n", + "if RELAXATION_KGRID is not None and ADD_RELAXATION:\n", + " unit = workflow.subworkflows[0].get_unit_by_name(name_regex=\"relax\")\n", + " if unit:\n", + " unit.add_context(PointsGridDataProvider(dimensions=RELAXATION_KGRID, isEdited=True).get_context_item_data())\n", + " workflow.subworkflows[0].set_unit(unit)\n", + "\n", + "# SCF electronic k-grid (pw.x)\n", + "if SCF_KGRID is not None:\n", + " set_grid(phonon_subworkflow, \"pw_scf\", SCF_KGRID)\n", + "\n", + "# Phonon q-grid for DFPT (ph.x)\n", + "if PHONON_QGRID is not None:\n", + " set_grid(phonon_subworkflow, \"ph_grid\", PHONON_QGRID, name=\"qgrid\")\n", + "\n", + "# Phonon DOS interpolation grid (matdyn.x)\n", + "if DOS_QGRID is not None:\n", + " set_grid(phonon_subworkflow, \"matdyn_grid\", DOS_QGRID, name=\"igrid\")\n", + "\n", + "# Phonon dispersion path (matdyn.x)\n", + "if QPATH is not None:\n", + " set_path(phonon_subworkflow, \"matdyn_path\", QPATH, name=\"ipath\")\n", + "\n", + "# Energy cutoffs on every pw.x unit\n", + "if ECUTWFC is not None:\n", + " cutoffs_context = PlanewaveCutoffsContextProvider(\n", + " wavefunction=ECUTWFC, density=ECUTRHO, isEdited=True\n", + " ).get_context_item_data()\n", + " for unit_name in [\"pw_relax\", \"pw_vc-relax\", \"pw_scf\"]:\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)" + ] + }, + { + "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(cluster=cluster, queue=QUEUE_NAME, ppn=PPN)\n", + "print(f\"Using cluster: {compute.cluster.hostname}, queue: {QUEUE_NAME}, ppn: {PPN}\")" + ] + }, + { + "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", + "job_name = f\"{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(f\"✅ Job created: {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\"✅ 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. Phonon dispersion" + ] + }, + { + "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", + "phonon_dispersions_data = get_properties_for_job(\n", + " client, job_id, property_name=PropertyName.non_scalar.phonon_dispersions.value\n", + ")\n", + "visualize_properties(phonon_dispersions_data, title=\"Phonon Dispersion\", extra_config={\"material\": material.to_dict()})\n", + "\n", + "# Sanity check: confirm real, non-trivial values were computed (not an empty/failed result)\n", + "_freqs = [f for band in phonon_dispersions_data[0][\"yDataSeries\"] for f in band]\n", + "print(f\"Phonon dispersion: {len(phonon_dispersions_data[0]['yDataSeries'])} bands, frequency range {min(_freqs):.1f} to {max(_freqs):.1f} cm^-1\")" + ] + }, + { + "cell_type": "markdown", + "id": "45", + "metadata": {}, + "source": [ + "### 8.2. Phonon density of states" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "46", + "metadata": {}, + "outputs": [], + "source": [ + "phonon_dos_data = get_properties_for_job(\n", + " client, job_id, property_name=PropertyName.non_scalar.phonon_dos.value\n", + ")\n", + "visualize_properties(phonon_dos_data, title=\"Phonon DOS\", extra_config={\"material\": material.to_dict()})\n", + "\n", + "# Sanity check: confirm real, non-trivial values were computed (not an empty/failed result)\n", + "_dos_freqs = phonon_dos_data[0][\"xDataArray\"][0]\n", + "_dos_vals = phonon_dos_data[0][\"yDataSeries\"][0]\n", + "print(f\"Phonon DOS: frequency range {min(_dos_freqs):.1f} to {max(_dos_freqs):.1f} cm^-1, peak DOS {max(_dos_vals):.4f} states/cm^-1\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python", + "pygments_lexer": "ipython3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}