diff --git a/other/materials_designer/Introduction.ipynb b/other/materials_designer/Introduction.ipynb new file mode 100644 index 00000000..a9a74cdb --- /dev/null +++ b/other/materials_designer/Introduction.ipynb @@ -0,0 +1,37 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Materials Transformations with Python Notebooks\n", + "\n", + "## Overview\n", + "\n", + "This Jupyter Lite distribution contains pre-configured examples of materials transformations including simple ones like creation of supercell or a surface, and more complex like creating an interface with Zur and McGill Superlattice matching algorithm\n", + "\n", + "## Examples\n", + "\n", + "### 1. Interface creation with Zur and McGill Superlattice algorithm\n", + "\n", + "[This file](zsl_interface_generation.ipynb) helps creating interface between two materials by finding matching superlattices and selecting optimal variants by size-strain trade-off. (Link to the lattice match algorithm: https://doi.org/10.1063/1.333084)\n", + "\n", + "### Definition for communication\n", + "\n", + "[definitions.py](definitions.py)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [] + } + ], + "metadata": { + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/other/materials_designer/definitions.py b/other/materials_designer/definitions.py new file mode 100644 index 00000000..0ca1d758 --- /dev/null +++ b/other/materials_designer/definitions.py @@ -0,0 +1,132 @@ +from IPython.display import display, Javascript +import json + + +def set_materials(materials): + """ + This function takes a Python object, serializes it to JSON, and sends it to the host environment + through a JavaScript function defined in the JupyterLite extension `data_bridge`. + + Args: + materials (object): The Python object to be sent to the host environment. + """ + python_data = materials + serialized_data = json.dumps({"materials": python_data}) + js_code = f""" + (function() {{ + window.sendDataToHost({serialized_data}) + console.log({serialized_data}) + }})(); + """ + + display(Javascript(js_code)) + print("materials sent") + + +def get_materials(): + """ + This function requests materials from the host environment through a JavaScript function defined in the JupyterLite + extension `data_bridge`. The materials are then returned to the Python environment. + """ + js_code = """ + (function() { + if (window.requestDataFromHost) { + window.requestDataFromHost(); + + } else { + console.error('requestDataFromHost function is not defined on the window object.'); + } + })(); + """ + + display(Javascript(js_code)) + print("materials requested") + + +from pymatgen.core import Structure, Lattice + + +def to_pymatgen(material_data): + """ + Convert material object in ESSE format to a pymatgen Structure object. + + Args: + material_data (dict): A dictionary containing the material information in ESSE format. + + Returns: + Structure: A pymatgen Structure object. + """ + + # Extract lattice information + lattice_params = material_data["lattice"] + lattice_vectors = lattice_params["vectors"] + a = lattice_vectors["a"] + b = lattice_vectors["b"] + c = lattice_vectors["c"] + + # Create a Lattice + lattice = Lattice([a, b, c]) + + # Extract the basis information + basis = material_data["basis"] + elements = [element["value"] for element in basis["elements"]] + coordinates = [coord["value"] for coord in basis["coordinates"]] + + # Assuming that the basis units are fractional since it's a crystal basis + coords_are_cartesian = basis["units"].lower() != "crystal" + + # Create the Structure + structure = Structure(lattice, elements, coordinates, coords_are_cartesian=coords_are_cartesian) + + return structure + + +def from_pymatgen(structure: Structure): + """ + Convert a pymatgen Structure object to a material object in ESSE format. + + Args: + structure (Structure): A pymatgen Structure object. + + Returns: + dict: A dictionary containing the material information in ESSE format. + """ + basis = { + "elements": [{"id": i, "value": str(site.specie)} for i, site in enumerate(structure.sites)], + "coordinates": [{"id": i, "value": list(site.frac_coords)} for i, site in enumerate(structure.sites)], + "units": "crystal", + "cell": structure.lattice.matrix.tolist(), + "constraints": [], # Assuming there are no constraints + } + + # Extract lattice information + lattice = { + "a": structure.lattice.a, + "b": structure.lattice.b, + "c": structure.lattice.c, + "alpha": structure.lattice.alpha, + "beta": structure.lattice.beta, + "gamma": structure.lattice.gamma, + "units": {"length": "angstrom", "angle": "degree"}, + "type": "FCC", # You need a way to determine the lattice type + "vectors": { + "a": structure.lattice.matrix[0].tolist(), + "b": structure.lattice.matrix[1].tolist(), + "c": structure.lattice.matrix[2].tolist(), + "alat": 1, # This seems to be a scaling factor; adjust if necessary + "units": "angstrom", + }, + } + + # Combine into a material dictionary + material = { + "name": structure.formula, + "basis": basis, + "lattice": lattice, + "isNonPeriodic": not structure.is_ordered, + "_id": "", + "metadata": {"boundaryConditions": {"type": "bc2", "offset": 0}}, + "isUpdated": True, + } + + return material diff --git a/other/materials_designer/zsl_interface_generation.ipynb b/other/materials_designer/zsl_interface_generation.ipynb new file mode 100644 index 00000000..e16acedb --- /dev/null +++ b/other/materials_designer/zsl_interface_generation.ipynb @@ -0,0 +1,668 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "trusted": true + }, + "outputs": [], + "source": [ + "import micropip\n", + "\n", + "packages = [\n", + " \"https://files.mat3ra.com:44318/uploads/pymatgen-2023.9.10-py3-none-any.whl\",\n", + " \"https://files.mat3ra.com:44318/web/pyodide/spglib-2.0.2-py3-none-any.whl\",\n", + " \"https://files.pythonhosted.org/packages/d9/0e/2a05efa11ea33513fbdf4a2e2576fe94fd8fa5ad226dbb9c660886390974/ruamel.yaml-0.17.32-py3-none-any.whl\",\n", + " \"ase==3.22.1\",\n", + " \"networkx==3.2.1\",\n", + " \"monty==2023.11.3\",\n", + " \"scipy==1.11.2\",\n", + " \"lzma\",\n", + " \"tabulate==0.9.0\",\n", + " \"sqlite3\",\n", + " \"sympy==1.12\",\n", + " \"uncertainties==3.1.6\",\n", + " \"ipywidgets\",\n", + "]\n", + "\n", + "\n", + "async def install_package(pkg):\n", + " \"\"\"\n", + " Installs a package in a Pyodide environment.\n", + " Args:\n", + " pkg: The name of the package to install.\n", + "\n", + " Returns:\n", + " None\n", + " \"\"\"\n", + " is_url = pkg.startswith(\"http://\") or pkg.startswith(\"https://\")\n", + " are_dependencies_installed = not is_url\n", + " await micropip.install(pkg, deps=are_dependencies_installed)\n", + " # Extract package name for printing\n", + " pkg_name = pkg.split(\"/\")[-1].split(\"-\")[0] if is_url else pkg.split(\"==\")[0]\n", + " print(f\"Installed {pkg_name}\")\n", + "\n", + "\n", + "for package in packages:\n", + " await install_package(package)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "trusted": true + }, + "outputs": [], + "source": [ + "from definitions import get_materials, set_materials, to_pymatgen, from_pymatgen" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "trusted": true + }, + "outputs": [], + "source": [ + "# Load materials from Materials Designer list (JSON config format) to `data` variable\n", + "get_materials()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "materials = data\n", + "print(f\"Loaded {len(materials)} materials from Materials Designer\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "trusted": true + }, + "outputs": [], + "source": [ + "# Work with materials\n", + "l = len(materials)\n", + "if l > 0:\n", + " m1 = to_pymatgen(materials[0])\n", + " print(m1)\n", + "if l > 1:\n", + " m2 = to_pymatgen(materials[1])\n", + " print(m2)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [], + "trusted": true + }, + "outputs": [], + "source": [ + "from __future__ import annotations\n", + "\n", + "# Parameters for the ZSL algorithm\n", + "# as defined in: https://pymatgen.org/pymatgen.analysis.interfaces.html#pymatgen.analysis.interfaces.zsl.ZSLGenerator\n", + "MAX_AREA = 400\n", + "MAX_AREA_TOL = 0.09\n", + "MAX_LENGTH_TOL = 0.03\n", + "MAX_ANGLE_TOL = 0.01\n", + "\n", + "# Strains within this tolerance are considered equal\n", + "STRAIN_TOL = 10e-6\n", + "\n", + "\n", + "# Select materials from the list of input materials\n", + "SUBSTRATE_MATERIAL = m1\n", + "LAYER_MATERIAL = m2\n", + "\n", + "# Miller indices and thickness for substrate and layer.\n", + "SUBSTRATE_MILLER = (1, 1, 1)\n", + "SUBSTRATE_THICKNESS = 3\n", + "LAYER_MILLER = (0, 0, 1)\n", + "LAYER_THICKNESS = 1\n", + "\n", + "# Distance between layers, in Angstroms\n", + "DISTANCE = 3.0\n", + "\n", + "\n", + "# Plot settings\n", + "# Strain axis limits in percent\n", + "X_MIN = 0.01\n", + "X_MAX = 100\n", + "\n", + "# Number of atoms axis limits\n", + "Y_MIN = 1\n", + "Y_MAX = 1000\n", + "\n", + "\n", + "\"\"\"\n", + "NOTE: DO NOT edit code below unless you know what you are doing.\n", + "\"\"\"\n", + "\n", + "\"\"\" \n", + "Classes and Definitions\n", + "\"\"\"\n", + "\n", + "from itertools import product\n", + "from typing import TYPE_CHECKING\n", + "\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "from numpy.testing import assert_allclose\n", + "from scipy.linalg import polar\n", + "\n", + "import pymatgen\n", + "from pymatgen.analysis.interfaces.zsl import ZSLGenerator as PymatgenZSL\n", + "from pymatgen.core.structure import Structure\n", + "from operator import itemgetter\n", + "from pymatgen.analysis.elasticity.strain import Deformation\n", + "from pymatgen.analysis.interfaces.zsl import ZSLGenerator, fast_norm\n", + "from pymatgen.core.interface import Interface, label_termination\n", + "from pymatgen.core.surface import SlabGenerator\n", + "\n", + "if TYPE_CHECKING:\n", + " from collections.abc import Iterator, Sequence\n", + "\n", + " from pymatgen.core import Structure\n", + "\n", + "\n", + "class CoherentInterfaceBuilder:\n", + " \"\"\"\n", + " This class constructs the coherent interfaces between two crystalline slabs\n", + " Coherency is defined by matching lattices not sub-planes.\n", + " \"\"\"\n", + "\n", + " def __init__(\n", + " self,\n", + " substrate_structure: Structure,\n", + " film_structure: Structure,\n", + " film_miller: tuple[int, int, int],\n", + " substrate_miller: tuple[int, int, int],\n", + " zslgen: ZSLGenerator | None = None,\n", + " ):\n", + " \"\"\"\n", + " Args:\n", + " substrate_structure: structure of substrate\n", + " film_structure: structure of film\n", + " film_miller: miller index of the film layer\n", + " substrate_miller: miller index for the substrate layer\n", + " zslgen: BiDirectionalZSL if you want custom lattice matching tolerances for coherency.\n", + " \"\"\"\n", + " # Bulk structures\n", + " self.substrate_structure = substrate_structure\n", + " self.film_structure = film_structure\n", + " self.film_miller = film_miller\n", + " self.substrate_miller = substrate_miller\n", + " self.zslgen = zslgen or ZSLGenerator(bidirectional=True)\n", + "\n", + " self._find_matches()\n", + " self._find_terminations()\n", + "\n", + " def _find_matches(self) -> None:\n", + " \"\"\"\n", + " Finds and stores the ZSL matches.\n", + " \"\"\"\n", + " self.zsl_matches = []\n", + "\n", + " film_sg = SlabGenerator(\n", + " self.film_structure,\n", + " self.film_miller,\n", + " min_slab_size=1,\n", + " min_vacuum_size=3,\n", + " in_unit_planes=True,\n", + " center_slab=True,\n", + " primitive=True,\n", + " reorient_lattice=False, # This is necessary to not screw up the lattice\n", + " )\n", + "\n", + " sub_sg = SlabGenerator(\n", + " self.substrate_structure,\n", + " self.substrate_miller,\n", + " min_slab_size=1,\n", + " min_vacuum_size=3,\n", + " in_unit_planes=True,\n", + " center_slab=True,\n", + " primitive=True,\n", + " reorient_lattice=False, # This is necessary to not screw up the lattice\n", + " )\n", + "\n", + " film_slab = film_sg.get_slab(shift=0)\n", + " sub_slab = sub_sg.get_slab(shift=0)\n", + "\n", + " film_vectors = film_slab.lattice.matrix\n", + " substrate_vectors = sub_slab.lattice.matrix\n", + "\n", + " # Generate all possible interface matches\n", + " self.zsl_matches = list(self.zslgen(film_vectors[:2], substrate_vectors[:2], lowest=False))\n", + "\n", + " for match in self.zsl_matches:\n", + " xform = get_2d_transform(film_vectors, match.film_vectors)\n", + " strain, rot = polar(xform)\n", + " assert_allclose(\n", + " strain, np.round(strain), atol=1e-12\n", + " ), \"Film lattice vectors changed during ZSL match, check your ZSL Generator parameters\"\n", + "\n", + " xform = get_2d_transform(substrate_vectors, match.substrate_vectors)\n", + " strain, rot = polar(xform)\n", + " assert_allclose(\n", + " strain, strain.astype(int), atol=1e-12\n", + " ), \"Substrate lattice vectors changed during ZSL match, check your ZSL Generator parameters\"\n", + "\n", + " def _find_terminations(self):\n", + " \"\"\"\n", + " Finds all terminations.\n", + " \"\"\"\n", + " film_sg = SlabGenerator(\n", + " self.film_structure,\n", + " self.film_miller,\n", + " min_slab_size=1,\n", + " min_vacuum_size=3,\n", + " in_unit_planes=True,\n", + " center_slab=True,\n", + " primitive=True,\n", + " reorient_lattice=False, # This is necessary to not screw up the lattice\n", + " )\n", + "\n", + " sub_sg = SlabGenerator(\n", + " self.substrate_structure,\n", + " self.substrate_miller,\n", + " min_slab_size=1,\n", + " min_vacuum_size=3,\n", + " in_unit_planes=True,\n", + " center_slab=True,\n", + " primitive=True,\n", + " reorient_lattice=False, # This is necessary to not screw up the lattice\n", + " )\n", + "\n", + " film_slabs = film_sg.get_slabs()\n", + " sub_slabs = sub_sg.get_slabs()\n", + "\n", + " film_shits = [s.shift for s in film_slabs]\n", + " film_terminations = [label_termination(s) for s in film_slabs]\n", + "\n", + " sub_shifts = [s.shift for s in sub_slabs]\n", + " sub_terminations = [label_termination(s) for s in sub_slabs]\n", + "\n", + " self._terminations = {\n", + " (film_label, sub_label): (film_shift, sub_shift)\n", + " for (film_label, film_shift), (sub_label, sub_shift) in product(\n", + " zip(film_terminations, film_shits), zip(sub_terminations, sub_shifts)\n", + " )\n", + " }\n", + " self.terminations = list(self._terminations)\n", + "\n", + " def get_interfaces(\n", + " self,\n", + " termination: tuple[str, str],\n", + " gap: float = 2.0,\n", + " vacuum_over_film: float = 20.0,\n", + " film_thickness: float = 1,\n", + " substrate_thickness: float = 1,\n", + " in_layers: bool = True,\n", + " ) -> Iterator[Interface]:\n", + " \"\"\"\n", + " Generates interface structures given the film and substrate structure\n", + " as well as the desired terminations.\n", + "\n", + " Args:\n", + " termination (tuple[str, str]): termination from self.termination list\n", + " gap (float, optional): gap between film and substrate. Defaults to 2.0.\n", + " vacuum_over_film (float, optional): vacuum over the top of the film. Defaults to 20.0.\n", + " film_thickness (float, optional): the film thickness. Defaults to 1.\n", + " substrate_thickness (float, optional): substrate thickness. Defaults to 1.\n", + " in_layers (bool, optional): set the thickness in layer units. Defaults to True.\n", + "\n", + " Yields:\n", + " Iterator[Interface]: interfaces from slabs\n", + " \"\"\"\n", + " film_sg = SlabGenerator(\n", + " self.film_structure,\n", + " self.film_miller,\n", + " min_slab_size=film_thickness,\n", + " min_vacuum_size=3,\n", + " in_unit_planes=in_layers,\n", + " center_slab=True,\n", + " primitive=True,\n", + " reorient_lattice=False, # This is necessary to not screw up the lattice\n", + " )\n", + "\n", + " sub_sg = SlabGenerator(\n", + " self.substrate_structure,\n", + " self.substrate_miller,\n", + " min_slab_size=substrate_thickness,\n", + " min_vacuum_size=3,\n", + " in_unit_planes=in_layers,\n", + " center_slab=True,\n", + " primitive=True,\n", + " reorient_lattice=False, # This is necessary to not screw up the lattice\n", + " )\n", + "\n", + " film_shift, sub_shift = self._terminations[termination]\n", + "\n", + " film_slab = film_sg.get_slab(shift=film_shift)\n", + " sub_slab = sub_sg.get_slab(shift=sub_shift)\n", + "\n", + " for match in self.zsl_matches:\n", + " # Build film superlattice\n", + " super_film_transform = np.round(\n", + " from_2d_to_3d(get_2d_transform(film_slab.lattice.matrix[:2], match.film_sl_vectors))\n", + " ).astype(int)\n", + " film_sl_slab = film_slab.copy()\n", + " film_sl_slab.make_supercell(super_film_transform)\n", + " assert_allclose(\n", + " film_sl_slab.lattice.matrix[2], film_slab.lattice.matrix[2], atol=1e-08\n", + " ), \"2D transformation affected C-axis for Film transformation\"\n", + " assert_allclose(\n", + " film_sl_slab.lattice.matrix[:2], match.film_sl_vectors, atol=1e-08\n", + " ), \"Transformation didn't make proper supercell for film\"\n", + "\n", + " # Build substrate superlattice\n", + " super_sub_transform = np.round(\n", + " from_2d_to_3d(get_2d_transform(sub_slab.lattice.matrix[:2], match.substrate_sl_vectors))\n", + " ).astype(int)\n", + " sub_sl_slab = sub_slab.copy()\n", + " sub_sl_slab.make_supercell(super_sub_transform)\n", + " assert_allclose(\n", + " sub_sl_slab.lattice.matrix[2], sub_slab.lattice.matrix[2], atol=1e-08\n", + " ), \"2D transformation affected C-axis for Film transformation\"\n", + " assert_allclose(\n", + " sub_sl_slab.lattice.matrix[:2], match.substrate_sl_vectors, atol=1e-08\n", + " ), \"Transformation didn't make proper supercell for substrate\"\n", + "\n", + " # Add extra info\n", + " match_dict = match.as_dict()\n", + " interface_properties = {k: match_dict[k] for k in match_dict if not k.startswith(\"@\")}\n", + "\n", + " dfm = Deformation(match.match_transformation)\n", + "\n", + " strain = dfm.green_lagrange_strain\n", + " interface_properties[\"strain\"] = strain\n", + " interface_properties[\"von_mises_strain\"] = strain.von_mises_strain\n", + " interface_properties[\"termination\"] = termination\n", + " interface_properties[\"film_thickness\"] = film_thickness\n", + " interface_properties[\"substrate_thickness\"] = substrate_thickness\n", + "\n", + " yield {\n", + " \"interface\": Interface.from_slabs(\n", + " substrate_slab=sub_sl_slab,\n", + " film_slab=film_sl_slab,\n", + " gap=gap,\n", + " vacuum_over_film=vacuum_over_film,\n", + " interface_properties=interface_properties,\n", + " ),\n", + " \"strain\": strain,\n", + " \"von_mises_strain\": strain.von_mises_strain,\n", + " \"mean_abs_strain\": round(np.mean(np.abs(strain)) / STRAIN_TOL) * STRAIN_TOL,\n", + " \"film_sl_vectors\": match.film_sl_vectors,\n", + " \"substrate_sl_vectors\": match.substrate_sl_vectors,\n", + " \"film_transform\": super_film_transform,\n", + " \"substrate_transform\": super_sub_transform,\n", + " }\n", + "\n", + "\n", + "def get_rot_3d_for_2d(film_matrix, sub_matrix) -> np.ndarray:\n", + " \"\"\"\n", + " Find transformation matrix that will rotate and strain the film to the substrate while preserving the c-axis.\n", + " \"\"\"\n", + " film_matrix = np.array(film_matrix)\n", + " film_matrix = film_matrix.tolist()[:2]\n", + " film_matrix.append(np.cross(film_matrix[0], film_matrix[1]))\n", + "\n", + " # Generate 3D lattice vectors for substrate super lattice\n", + " # Out of plane substrate super lattice has to be same length as\n", + " # Film out of plane vector to ensure no extra deformation in that\n", + " # direction\n", + " sub_matrix = np.array(sub_matrix)\n", + " sub_matrix = sub_matrix.tolist()[:2]\n", + " temp_sub = np.cross(sub_matrix[0], sub_matrix[1]).astype(float) # conversion to float necessary if using numba\n", + " temp_sub = temp_sub * fast_norm(\n", + " np.array(film_matrix[2], dtype=float)\n", + " ) # conversion to float necessary if using numba\n", + " sub_matrix.append(temp_sub)\n", + "\n", + " transform_matrix = np.transpose(np.linalg.solve(film_matrix, sub_matrix))\n", + "\n", + " rot, _ = polar(transform_matrix)\n", + "\n", + " return rot\n", + "\n", + "\n", + "def get_2d_transform(start: Sequence, end: Sequence) -> np.ndarray:\n", + " \"\"\"\n", + " Gets a 2d transformation matrix\n", + " that converts start to end.\n", + " \"\"\"\n", + " return np.dot(end, np.linalg.pinv(start))\n", + "\n", + "\n", + "def from_2d_to_3d(mat: np.ndarray) -> np.ndarray:\n", + " \"\"\"\n", + " Converts a 2D matrix to a 3D matrix.\n", + " \"\"\"\n", + " new_mat = np.diag([1.0, 1.0, 1.0])\n", + " new_mat[:2, :2] = mat\n", + " return new_mat\n", + "\n", + "\n", + "def plot_strain_vs_atoms(strain_mode, sorted_interfaces):\n", + " \"\"\"\n", + " Plots the strain vs. the number of atoms in the interface. With hover-over labels.\n", + " \"\"\"\n", + " fig, ax = plt.subplots()\n", + "\n", + " # Scatter plot\n", + " x = [i[strain_mode] * 100 for i in sorted_interfaces] # in precentage\n", + " y = [i[\"interface\"].num_sites for i in sorted_interfaces]\n", + " sc = ax.scatter(x, y)\n", + "\n", + " # Annotation for the hover-over labels\n", + " annot = ax.annotate(\n", + " \"\",\n", + " xy=(0, 0),\n", + " xytext=(20, 20),\n", + " textcoords=\"offset points\",\n", + " bbox=dict(boxstyle=\"round\", fc=\"w\"),\n", + " arrowprops=dict(arrowstyle=\"->\"),\n", + " )\n", + " annot.set_visible(False)\n", + "\n", + " def update_annot(ind):\n", + " pos = sc.get_offsets()[ind[\"ind\"][0]]\n", + " annot.xy = pos\n", + " text = \"{}\".format(\" \".join([str(index) for index in ind[\"ind\"]]))\n", + " annot.set_text(text)\n", + " annot.get_bbox_patch().set_alpha(0.4)\n", + "\n", + " def hover(event):\n", + " vis = annot.get_visible()\n", + " if event.inaxes == ax:\n", + " cont, ind = sc.contains(event)\n", + " if cont:\n", + " update_annot(ind)\n", + " annot.set_visible(True)\n", + " fig.canvas.draw_idle()\n", + " else:\n", + " if vis:\n", + " annot.set_visible(False)\n", + " fig.canvas.draw_idle()\n", + "\n", + " # Connect the hover event\n", + " fig.canvas.mpl_connect(\"motion_notify_event\", hover)\n", + "\n", + " # Set the scale and labels\n", + " plt.xscale(\"log\")\n", + " plt.yscale(\"log\")\n", + " plt.xlim(X_MIN, X_MAX)\n", + " plt.ylim(Y_MIN, Y_MAX)\n", + " plt.xlabel(\"strain in %\")\n", + " plt.ylabel(\"number of atoms\")\n", + "\n", + " plt.show()\n", + "\n", + "\n", + "def output_materials(sorted_interfaces, output_indices, strain_mode):\n", + " \"\"\"\n", + " Outputs the materials in the output range.\n", + " \"\"\"\n", + " # Initialize an empty list to store the materials\n", + " materials_out = []\n", + "\n", + " # Check if output_indices is a single number or a list\n", + " if isinstance(output_indices, int):\n", + " # If it's a single number, output the material with that index\n", + " output_range = [output_indices]\n", + " else:\n", + " # If it's a list, output materials in that range\n", + " output_range = range(*output_indices)\n", + "\n", + " # Loop over the interfaces in the output range\n", + " for index in output_range:\n", + " best_interface = sorted_interfaces[index][\"interface\"]\n", + "\n", + " wrapped_structure = best_interface.copy()\n", + " # This should wrap the atoms inside the unit cell\n", + " wrapped_structure.make_supercell([1, 1, 1])\n", + " interface_poscar = wrapped_structure.to(fmt=\"poscar\")\n", + "\n", + " # Add the strain to the POSCAR first line to use as a name\n", + " strain = sorted_interfaces[index][strain_mode]\n", + " lines = interface_poscar.split(\"\\n\")\n", + " lines[0] = lines[0] + \" strain: \" + \"{:.3f}\".format(strain * 100) + \"%\"\n", + " interface_poscar = \"\\n\".join(lines)\n", + "\n", + " # Append the material to the list\n", + " materials_out.append({\"poscar\": interface_poscar})\n", + "\n", + " return materials_out\n", + "\n", + "\n", + "\"\"\" MAIN \"\"\"\n", + "\n", + "\n", + "def main():\n", + " # Create Interface Builder class\n", + " zsl = PymatgenZSL(\n", + " max_area_ratio_tol=MAX_AREA_TOL,\n", + " max_area=MAX_AREA,\n", + " max_length_tol=MAX_LENGTH_TOL,\n", + " max_angle_tol=MAX_ANGLE_TOL,\n", + " )\n", + " cib = CoherentInterfaceBuilder(\n", + " substrate_structure=SUBSTRATE_MATERIAL,\n", + " film_structure=LAYER_MATERIAL,\n", + " substrate_miller=SUBSTRATE_MILLER,\n", + " film_miller=LAYER_MILLER,\n", + " zslgen=zsl,\n", + " )\n", + "\n", + " # Run the Interface Building process\n", + " cib._find_terminations()\n", + " matches = cib.zsl_matches\n", + " terminations = cib.terminations\n", + "\n", + " # Create interfaces\n", + " interfaces = []\n", + " for termination in terminations:\n", + " interfaces = list(\n", + " cib.get_interfaces(\n", + " termination,\n", + " gap=DISTANCE,\n", + " film_thickness=LAYER_THICKNESS,\n", + " substrate_thickness=SUBSTRATE_THICKNESS,\n", + " in_layers=True,\n", + " )\n", + " )\n", + "\n", + " print(f\"Found {len(matches)} interfaces\")\n", + " print(f\"Found {len(terminations)} terminations:\", terminations)\n", + "\n", + " strain_modes = {\n", + " \"VON_MISES\": \"von_mises_strain\",\n", + " \"STRAIN\": \"strain\",\n", + " \"MEAN\": \"mean_abs_strain\",\n", + " }\n", + " strain_mode = strain_modes[\"MEAN\"]\n", + " interfaces_list = list(interfaces)\n", + "\n", + " # Sort interfaces by ascending strain and then by ascending number of atoms\n", + " sorted_interfaces = sorted(\n", + " interfaces_list,\n", + " key=lambda x: (itemgetter(strain_mode)(x), x[\"interface\"].num_sites),\n", + " )\n", + "\n", + " print(\"Interface with lowest strain (index 0):\")\n", + " print(\" strain:\", sorted_interfaces[0][strain_mode] * 100, \"%\")\n", + " print(\" number of atoms:\", sorted_interfaces[0][\"interface\"].num_sites)\n", + "\n", + " # plot stran vs number of atoms via matplotlib\n", + " plot_strain_vs_atoms(strain_mode, sorted_interfaces)\n", + " return sorted_interfaces\n", + "\n", + "\n", + "interfaces = main()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "trusted": true + }, + "outputs": [], + "source": [ + "m3_esse = from_pymatgen(interfaces[1][\"interface\"])\n", + "print(m3_esse)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "trusted": true + }, + "outputs": [], + "source": [ + "# Submit materials (JSON config format) back to Materials Designer\n", + "set_materials([m3_esse])" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python (Pyodide)", + "language": "python", + "name": "python" + }, + "language_info": { + "codemirror_mode": { + "name": "python", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8" + }, + "widgets": { + "application/vnd.jupyter.widget-state+json": { + "state": {}, + "version_major": 2, + "version_minor": 0 + } + } + }, + "nbformat": 4, + "nbformat_minor": 4 +}