Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/SOF-7339 update: rewrite ZSL notebook to use made-tools #118

Merged
merged 29 commits into from May 3, 2024
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
ecabfdb
update: rewrite using mat3ra made
VsevolodX Apr 25, 2024
9d4815f
chore: import made from github
VsevolodX Apr 25, 2024
78c7e0f
update: use made-tools
VsevolodX Apr 26, 2024
e6abb84
update: export each material separately
VsevolodX Apr 26, 2024
913dae3
chore: clear outputs
VsevolodX Apr 26, 2024
be1e72c
update: adjust
VsevolodX Apr 27, 2024
bd3f6d1
update: move plot to utils
VsevolodX Apr 29, 2024
73f94d8
update: adjust plot
VsevolodX Apr 29, 2024
923d471
update: adjust plot
VsevolodX Apr 29, 2024
bb9a6d6
update: adjust notebook
VsevolodX Apr 29, 2024
7be52d9
chore: run lint
VsevolodX Apr 29, 2024
ecd3857
update: the selection
VsevolodX Apr 29, 2024
1509db8
chore: adjsut usage
VsevolodX Apr 29, 2024
3390054
update: cleanup nb
VsevolodX Apr 30, 2024
49ea2f1
chore: cleanup:
VsevolodX Apr 30, 2024
3ad723a
chore: import made from github
VsevolodX Apr 30, 2024
fafedc6
chore: fix index
VsevolodX May 1, 2024
fcd7582
chore: address PR comments
VsevolodX May 1, 2024
8a5e59d
chore: import made from github
VsevolodX May 1, 2024
72e825c
chore: add types and docstring
VsevolodX May 1, 2024
9782033
chore: use correct conversions
VsevolodX May 1, 2024
0e94158
update: cleanup
VsevolodX May 2, 2024
89acf15
update: add ordering to listdir
VsevolodX May 2, 2024
c3a77a8
chore: add mat3ra-utils + made from gh
VsevolodX May 2, 2024
7c351ea
chore: final cleanups
VsevolodX May 2, 2024
4a5c147
chore: import made from github
VsevolodX May 2, 2024
84a9443
chore: import pydantic for pyodide
VsevolodX May 3, 2024
923bb53
chore: import made
VsevolodX May 3, 2024
d4a07d5
update: cleanups
VsevolodX May 3, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions config.yml
Expand Up @@ -31,6 +31,10 @@ notebooks:
- nbformat>=4.2.0
packages_python:
packages_pyodide:
- annotated_types
- https://files.mat3ra.com/uploads/pydantic_core-2.18.2-py3-none-any.whl
- https://files.mat3ra.com/uploads/pydantic-2.7.1-py3-none-any.whl
- mat3ra-made
- name: import_material_from_jarvis_db_entry.ipynb
packages_common:
- express-py==2024.2.2.post2
Expand Down
226 changes: 41 additions & 185 deletions other/materials_designer/create_interface_with_min_strain_zsl.ipynb
Expand Up @@ -46,13 +46,11 @@
"outputs": [],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Line #6.        await install_packages("create_interface_with_min_strain_zsl.ipynb","../../config.yml")

Are we keeping this change? If yes, then how will the packages be installed in Python (not Pyodide)?


Reply via ReviewNB

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Line #7.    for material in globals()["materials_in"]:

for material_config in ...


Reply via ReviewNB

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Line #4.        materials[0],

Let's clearly isolate layer and substrate


Reply via ReviewNB

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can just pass

substrate = materials[0]
layer = materials[1]

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Line #4.    Interface.from_str(str, "POSCAR")

What's all the above in this cell??


Reply via ReviewNB

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Line #5.    interfaces_range = slice(0, 1)  # select the first interface with the lowest strain

Let's move the comment above and add with the lowest strain and number of atoms

The actual selection by termination and slice functionality can go to the holder class itself too


Reply via ReviewNB

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Line #4.    materials = list(map(lambda interface_config: from_pymatgen(interface_config), selected_interface))

This lambda function can go to interface_data_holder


Reply via ReviewNB

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Line #4.        await micropip.install('mat3ra-api-examples[dev]', deps=False)

Remove [dev]


Reply via ReviewNB

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Line #1.    print(interface_data_holder.terminations)

Maybe we should print the number of interfaces for the first termination, not all of them

Should be:

print(f"Terminations found:  {interface_data_holder.terminations)}")
print(f"Interfaces for the first termination:  {interface_data_holder.get_interfaces_for_termination(0))}")

Reply via ReviewNB

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Line #3.    materials = list(selected_interface)

We should not need list here, just return the list for the get_interfaces_... function


Reply via ReviewNB

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We probably don't need print statement here either. We should instead add a visualization function to plot the selected interface(s) in a subsequent task

"source": [
"SUBSTRATE_PARAMETERS = {\n",
" \"MATERIAL_INDEX\": 0, # the index of the material in the materials_in list\n",
" \"MILLER_INDICES\": (1, 1, 1), # the miller indices of the interfacial plane\n",
" \"THICKNESS\": 3, # in layers\n",
"}\n",
"\n",
"LAYER_PARAMETERS = {\n",
" \"MATERIAL_INDEX\": 1, # the index of the material in the materials_in list\n",
" \"MILLER_INDICES\": (0, 0, 1), # the miller indices of the interfacial plane\n",
" \"THICKNESS\": 1, # in layers\n",
"}\n",
Expand Down Expand Up @@ -107,7 +105,8 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"## 2. Install Packages"
"## 2. Install Packages\n",
"The step executes only in Pyodide environment. For other environments, the packages should be installed via `pip install` as directed in README."
]
},
{
Expand All @@ -120,35 +119,30 @@
"if sys.platform == \"emscripten\":\n",
" import micropip\n",
" await micropip.install('mat3ra-api-examples', deps=False)\n",
"from utils.jupyterlite import install_packages\n",
"await install_packages(\"create_interface_with_min_strain_zsl.ipynb\",\"../../config.yml\")"
" from utils.jupyterlite import install_packages\n",
" await install_packages(\"create_interface_with_min_strain_zsl.ipynb\",\"../../config.yml\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 3. Load and prepare input Materials\n"
"## 3. Load input Materials\n"
]
},
{
"cell_type": "code",
"outputs": [],
"source": [
"from utils.jupyterlite import get_data\n",
"from pymatgen.analysis.structure_analyzer import SpacegroupAnalyzer\n",
"from src.utils import to_pymatgen\n",
"from mat3ra.made.material import Material\n",
"\n",
"# Get the list of input materials and load them into `materials_in` variable\n",
"get_data(\"materials_in\", globals())\n",
"\n",
"if \"materials_in\" in globals():\n",
" pymatgen_materials = [to_pymatgen(item) for item in materials_in]\n",
" if USE_CONVENTIONAL_CELL: pymatgen_materials = [SpacegroupAnalyzer(item).get_conventional_standard_structure() for\n",
" item in pymatgen_materials]\n",
"\n",
" for material in pymatgen_materials:\n",
" print(material, \"\\n\")"
"materials = [] \n",
"for material_config in globals()[\"materials_in\"]:\n",
" print(material_config, \"\\n\")\n",
" materials.append(Material(material_config))"
],
"metadata": {
"collapsed": false
Expand Down Expand Up @@ -177,66 +171,28 @@
},
"outputs": [],
"source": [
"from src.pymatgen_coherent_interface_builder import CoherentInterfaceBuilder, ZSLGenerator\n",
"from src.utils import translate_to_bottom\n",
"\n",
"# Translate the materials to the bottom of the cell to allow for multilayer heterostructures creation\n",
"pymatgen_materials = [translate_to_bottom(item) for item in pymatgen_materials]\n",
" \n",
"def create_interfaces(settings):\n",
" print(\"Creating interfaces...\")\n",
" zsl = ZSLGenerator(\n",
" max_area_ratio_tol=settings[\"ZSL_PARAMETERS\"][\"MAX_AREA_TOL\"],\n",
" max_area=settings[\"ZSL_PARAMETERS\"][\"MAX_AREA\"],\n",
" max_length_tol=settings[\"ZSL_PARAMETERS\"][\"MAX_LENGTH_TOL\"],\n",
" max_angle_tol=settings[\"ZSL_PARAMETERS\"][\"MAX_ANGLE_TOL\"],\n",
" )\n",
"\n",
" cib = CoherentInterfaceBuilder(\n",
" substrate_structure=pymatgen_materials[settings[\"SUBSTRATE_PARAMETERS\"][\"MATERIAL_INDEX\"]],\n",
" film_structure=pymatgen_materials[settings[\"LAYER_PARAMETERS\"][\"MATERIAL_INDEX\"]],\n",
" substrate_miller=settings[\"SUBSTRATE_PARAMETERS\"][\"MILLER_INDICES\"],\n",
" film_miller=settings[\"LAYER_PARAMETERS\"][\"MILLER_INDICES\"],\n",
" zslgen=zsl,\n",
" strain_tol=settings[\"ZSL_PARAMETERS\"][\"STRAIN_TOL\"],\n",
" )\n",
"\n",
" # Find terminations\n",
" cib._find_terminations()\n",
" terminations = cib.terminations\n",
"from mat3ra.made.tools.build import create_interfaces\n",
"\n",
" # Create interfaces for each termination\n",
" interfaces = {}\n",
" for termination in terminations:\n",
" interfaces[termination] = []\n",
" for interface in cib.get_interfaces(\n",
" termination,\n",
" gap=settings[\"INTERFACE_PARAMETERS\"][\"DISTANCE_Z\"],\n",
" film_thickness=settings[\"LAYER_PARAMETERS\"][\"THICKNESS\"],\n",
" substrate_thickness=settings[\"SUBSTRATE_PARAMETERS\"][\"THICKNESS\"],\n",
" in_layers=True,\n",
" ):\n",
" # Wrap atoms to unit cell\n",
" interface[\"interface\"].make_supercell((1, 1, 1), to_unit_cell=True)\n",
" interfaces[termination].append(interface)\n",
" return interfaces, terminations\n",
"\n",
"\n",
"interfaces, terminations = create_interfaces(\n",
"interface_data_holder = create_interfaces(\n",
" substrate=materials[0],\n",
" layer=materials[1],\n",
" settings={\n",
" \"SUBSTRATE_PARAMETERS\": SUBSTRATE_PARAMETERS,\n",
" \"LAYER_PARAMETERS\": LAYER_PARAMETERS,\n",
" \"USE_CONVENTIONAL_CELL\": USE_CONVENTIONAL_CELL,\n",
" \"ZSL_PARAMETERS\": ZSL_PARAMETERS,\n",
" \"INTERFACE_PARAMETERS\": INTERFACE_PARAMETERS,\n",
" }\n",
" },\n",
" sort_by_strain_and_size=True,\n",
" remove_duplicates=True,\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 5.2. Print out the interfaces and terminations"
"### 4.2. Print out the interfaces and terminations"
]
},
{
Expand All @@ -245,18 +201,15 @@
"metadata": {},
"outputs": [],
"source": [
"print(f'Found {len(terminations)} terminations')\n",
"for termination in terminations:\n",
" print(f\"Found {len(interfaces[termination])} interfaces for\", termination, \"termination\")"
"print(\"Found terminations:\", interface_data_holder.terminations)\n",
"print(f\"Number of interfaces for a termination: {len(interface_data_holder.get_interfaces_for_termination(0))}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 5. Sort interfaces by strain\n",
"\n",
"### 5.1. Sort all interfaces"
"### 4.2. Print out interfaces with the lowest strain for each termination"
]
},
{
Expand All @@ -265,48 +218,19 @@
"metadata": {},
"outputs": [],
"source": [
"# Could be \"strain\", \"von_mises_strain\", \"mean_abs_strain\"\n",
"strain_mode = \"mean_abs_strain\"\n",
"\n",
"\n",
"# Sort interfaces by the specified strain mode and number of sites\n",
"def sort_interfaces(interfaces, terminations):\n",
" sorted_interfaces = {}\n",
" for termination in terminations:\n",
" sorted_interfaces[termination] = sorted(\n",
" interfaces[termination], key=lambda x: (x[strain_mode], x[\"interface\"].num_sites)\n",
" )\n",
" return sorted_interfaces\n",
"\n",
"\n",
"sorted_interfaces = sort_interfaces(interfaces, terminations)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 5.2. Print out interfaces with lowest strain for each termination"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"for termination in terminations:\n",
"for termination in interface_data_holder.terminations:\n",
" print(f\"Interface with lowest strain for termination {termination} (index 0):\")\n",
" first_interface = sorted_interfaces[termination][0]\n",
" print(\" strain:\", first_interface[strain_mode] * 100, \"%\")\n",
" print(\" number of atoms:\", first_interface[\"interface\"].num_sites)"
" interfaces = interface_data_holder.get_interfaces_for_termination(termination)\n",
" first_interface = interfaces[0]\n",
" print(f\" strain: {first_interface.get_mean_abs_strain() * 100:.3f}%\")\n",
" print(\" number of atoms:\", first_interface.num_sites)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 6. Plot the results\n",
"## 5. Plot the results\n",
"\n",
"Plot the number of atoms vs strain. Adjust the parameters as needed.\n"
]
Expand All @@ -317,91 +241,26 @@
"metadata": {},
"outputs": [],
"source": [
"import plotly.graph_objs as go\n",
"from collections import defaultdict\n",
"from utils.plot import plot_strain_vs_atoms\n",
"\n",
"PLOT_SETTINGS = {\n",
" \"HEIGHT\": 600,\n",
" \"X_SCALE\": \"log\", # or linear\n",
" \"Y_SCALE\": \"log\", # or linear\n",
"}\n",
"\n",
"plot_strain_vs_atoms(interface_data_holder, PLOT_SETTINGS)\n",
"\n",
"def plot_strain_vs_atoms(sorted_interfaces, terminations, settings):\n",
" # Create a mapping from termination to its index\n",
" termination_to_index = {termination: i for i, termination in enumerate(terminations)}\n",
"\n",
" grouped_interfaces = defaultdict(list)\n",
" for termination, interfaces in sorted_interfaces.items():\n",
" for index, interface_data in enumerate(interfaces):\n",
" strain_percentage = interface_data[\"mean_abs_strain\"] * 100\n",
" num_sites = interface_data[\"interface\"].num_sites\n",
" key = (strain_percentage, num_sites)\n",
" grouped_interfaces[key].append((index, termination))\n",
"\n",
" data = []\n",
" for (strain, num_sites), indices_and_terminations in grouped_interfaces.items():\n",
" termination_indices = defaultdict(list)\n",
" for index, termination in indices_and_terminations:\n",
" termination_indices[termination].append(index)\n",
" all_indices = [index for indices in termination_indices.values() for index in indices]\n",
" index_range = f\"{min(all_indices)}-{max(all_indices)}\" if len(all_indices) > 1 else str(min(all_indices))\n",
"\n",
" hover_text = \"<br>-----<br>\".join(\n",
" f\"Termination: {termination}<br>Termination index: {termination_to_index[termination]}<br>Interfaces Index Range: {index_range}<br>Strain: {strain:.2f}%<br>Atoms: {num_sites}\"\n",
" for termination, indices in termination_indices.items()\n",
" )\n",
" trace = go.Scatter(\n",
" x=[strain],\n",
" y=[num_sites],\n",
" text=[hover_text],\n",
" mode=\"markers\",\n",
" hoverinfo=\"text\",\n",
" name=f\"Indices: {index_range}\",\n",
" )\n",
" data.append(trace)\n",
"\n",
" layout = go.Layout(\n",
" xaxis=dict(title=\"Strain (%)\", type=settings[\"X_SCALE\"]),\n",
" yaxis=dict(title=\"Number of atoms\", type=settings[\"Y_SCALE\"]),\n",
" hovermode=\"closest\",\n",
" height=settings[\"HEIGHT\"],\n",
" legend_title_text=\"Interfaces Index Range\",\n",
" )\n",
" fig = go.Figure(data=data, layout=layout)\n",
" fig.show()\n",
"\n",
"\n",
"plot_strain_vs_atoms(sorted_interfaces, terminations, PLOT_SETTINGS)\n",
"\n",
"for i, termination in enumerate(terminations):\n",
" print(f\"Termination {i}:\", termination)"
"print(\"Terminations: \\n\", interface_data_holder.terminations)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 7. Select the interface to pass outside this kernel\n",
"## 6. Select the interface to pass outside this kernel\n",
"\n",
"### 7.1. Select the interface with the desired termination and strain\n",
"\n",
"The data in `sorted_interfaces` now contains an object with the following structure:\n",
"\n",
"```json\n",
"{\n",
" \"('C_P6/mmm_2', 'Si_R-3m_1')\": [\n",
" { ...interface for ('C_P6/mmm_2', 'Si_R-3m_1') at index 0...},\n",
" { ...interface for ('C_P6/mmm_2', 'Si_R-3m_1') at index 1...},\n",
" ...\n",
" ],\n",
" \"<termination at index 1>\": [\n",
" { ...interface for 'termination at index 1' at index 0...},\n",
" { ...interface for 'termination at index 1' at index 1...},\n",
" ...\n",
" ]\n",
"}\n",
"```\n",
"### 6.1. Select the interface with the desired termination and strain\n",
"\n",
"Select the index for termination first, and for it - the index in the list of corresponding interfaces sorted by strain (index 0 has minimum strain)."
]
Expand All @@ -412,19 +271,18 @@
"metadata": {},
"outputs": [],
"source": [
"termination_index = 0\n",
"number_of_interfaces_to_include = 1\n",
"\n",
"termination = terminations[termination_index]\n",
"\n",
"selected_interfaces = sorted_interfaces[termination][:number_of_interfaces_to_include]"
"# Could be either the termination as tuple, e.g. `('Ni_P6/mmm_1', 'C_C2/m_2')` or its index: `0`\n",
"termination_or_its_index = 0\n",
"# select the first interface with the lowest strain and the smallest number of atoms\n",
"interfaces_slice_range_or_index = 0\n",
"selected_interfaces = interface_data_holder.get_interfaces_as_materials(termination_or_its_index, interfaces_slice_range_or_index)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 7.2. Pass data to the outside runtime\n"
"### 6.2. Pass data to the outside runtime\n"
]
},
{
Expand All @@ -433,12 +291,10 @@
"metadata": {},
"outputs": [],
"source": [
"from src.utils import from_pymatgen\n",
"from utils.jupyterlite import set_data\n",
"\n",
"materials = list(map(lambda interface_config: from_pymatgen(interface_config[\"interface\"]), selected_interfaces))\n",
"\n",
"set_data(\"materials\", materials)"
"set_data(\"materials\", selected_interfaces)\n",
"print(selected_interfaces)"
]
}
],
Expand Down
5 changes: 3 additions & 2 deletions other/materials_designer/src/utils.py
Expand Up @@ -137,8 +137,9 @@ def ase_to_pymatgen(atoms: Atoms):
Returns:
Structure: pymatgen Structure object
"""
poscar = ase_to_poscar(atoms)
structure = Structure.from_str(poscar, fmt="poscar")

adaptor = AseAtomsAdaptor()
structure = adaptor.get_structure(atoms)

return structure

Expand Down
3 changes: 2 additions & 1 deletion pyproject.toml
Expand Up @@ -9,7 +9,8 @@ dependencies = [
"exabyte-api-client>=2023.6.13.post0",
"matplotlib>=3.4.1",
"pandas>=1.5.3",
"pymatgen>=2023.5.31",
"pymatgen>=2024.4.13",
"mat3ra-made@git+ssh://git@github.com/Exabyte-io/made.git@420814974ac465918c9b08f1d43d667a4936dce2",
]

[project.optional-dependencies]
Expand Down