Skip to content

Commit 5e01095

Browse files
authored
Merge pull request #149 from Exabyte-io/hotfix/revert-src-to-experiments
chore: revert src folder and place into experiments where used
2 parents 634c54c + 9971403 commit 5e01095

7 files changed

+690
-1
lines changed

other/experiments/create_interface_with_min_energy_by_miller_indices.ipynb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@
9595
"from IPython.display import HTML\n",
9696
"import base64\n",
9797
"import io\n",
98+
"# TODO: use Made package instead\n",
9899
"from src.pymatgen_coherent_interface_builder import CoherentInterfaceBuilder, ZSLGenerator\n",
99100
"from src.utils import ase_to_poscar, pymatgen_to_ase, to_pymatgen, get_interfacial_energy, get_adhesion_energy\n",
100101
"from ase.io import write\n",
@@ -366,6 +367,7 @@
366367
"source": [
367368
"from pymatgen.core.surface import SlabGenerator\n",
368369
"from pymatgen.symmetry.analyzer import SpacegroupAnalyzer\n",
370+
"# TODO: use Made package instead\n",
369371
"from src.utils import ase_to_pymatgen\n",
370372
"import hashlib\n",
371373
"\n",
@@ -519,6 +521,7 @@
519521
"cell_type": "code",
520522
"outputs": [],
521523
"source": [
524+
"# TODO: use Made package instead\n",
522525
"from src.utils import calculate_average_interlayer_distance\n",
523526
"\n",
524527
"# Find the most optimal interface\n",
@@ -575,6 +578,7 @@
575578
"cell_type": "code",
576579
"outputs": [],
577580
"source": [
581+
"# TODO: use Made package instead\n",
578582
"from src.utils import from_ase\n",
579583
"\n",
580584
"poscar = ase_to_poscar(ase_final_interface)\n",

other/experiments/create_interface_with_relaxation_alignn.ipynb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@
148148
"outputs": [],
149149
"source": [
150150
"import json\n",
151+
"# TODO: use Made package instead\n",
151152
"from src.pymatgen_coherent_interface_builder import CoherentInterfaceBuilder, ZSLGenerator\n",
152153
"from src.utils import to_pymatgen\n",
153154
"\n",
@@ -418,6 +419,7 @@
418419
"import plotly.graph_objs as go\n",
419420
"from IPython.display import display\n",
420421
"from plotly.subplots import make_subplots\n",
422+
"# TODO: use Made package instead\n",
421423
"from src.utils import ase_to_poscar, pymatgen_to_ase\n",
422424
"from ase.optimize import BFGS\n",
423425
"from alignn.ff.ff import AlignnAtomwiseCalculator as Calculator, default_path, wt1_path\n",

other/experiments/create_interface_with_relaxation_ase_emt.ipynb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@
147147
"outputs": [],
148148
"source": [
149149
"import json\n",
150+
"# TODO: use Made package instead\n",
150151
"from src.pymatgen_coherent_interface_builder import CoherentInterfaceBuilder, ZSLGenerator\n",
151152
"from src.utils import to_pymatgen\n",
152153
"\n",
@@ -417,6 +418,7 @@
417418
"import plotly.graph_objs as go\n",
418419
"from IPython.display import display\n",
419420
"from plotly.subplots import make_subplots\n",
421+
"# TODO: use Made package instead\n",
420422
"from src.utils import ase_to_poscar, pymatgen_to_ase\n",
421423
"from ase.optimize import BFGS\n",
422424
"from ase.calculators.emt import EMT\n",

other/experiments/create_interface_with_relaxation_matgl_m3gnet.ipynb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@
148148
"outputs": [],
149149
"source": [
150150
"import json\n",
151+
"# TODO: use Made package instead\n",
151152
"from src.pymatgen_coherent_interface_builder import CoherentInterfaceBuilder, ZSLGenerator\n",
152153
"from src.utils import to_pymatgen\n",
153154
"\n",
@@ -415,6 +416,7 @@
415416
"metadata": {},
416417
"outputs": [],
417418
"source": [
419+
"# TODO: use Made package instead\n",
418420
"from src.utils import ase_to_poscar, pymatgen_to_ase\n",
419421
"import matgl\n",
420422
"from pymatgen.io.ase import AseAtomsAdaptor\n",

other/experiments/src

Lines changed: 0 additions & 1 deletion
This file was deleted.
Lines changed: 311 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,311 @@
1+
from __future__ import annotations
2+
3+
# TODO: Remove this file and use Made.Tools instead
4+
"""
5+
This module is a modified version of the pymatgen CoherentInterfaceBuilder
6+
that adds strain information to the generated interfaces.
7+
"""
8+
from itertools import product
9+
from typing import TYPE_CHECKING
10+
11+
import numpy as np
12+
from numpy.testing import assert_allclose
13+
from scipy.linalg import polar
14+
15+
import pymatgen
16+
from pymatgen.analysis.interfaces.zsl import ZSLGenerator
17+
from pymatgen.core.structure import Structure
18+
from pymatgen.analysis.elasticity.strain import Deformation
19+
from pymatgen.analysis.interfaces.zsl import ZSLGenerator, fast_norm
20+
from pymatgen.core.interface import Interface, label_termination
21+
from pymatgen.core.surface import SlabGenerator
22+
23+
if TYPE_CHECKING:
24+
from collections.abc import Iterator, Sequence
25+
26+
from pymatgen.core import Structure
27+
28+
# This is a modified version of the pymatgen CoherentInterfaceBuilder
29+
# that adds strain information to the generated interfaces (line 249).
30+
# The original code can be found at: https://github.com/materialsproject/pymatgen/blob/v2024.1.27/pymatgen/analysis/interfaces/coherent_interfaces.py
31+
32+
33+
class CoherentInterfaceBuilder:
34+
"""
35+
This class constructs the coherent interfaces between two crystalline slabs
36+
Coherency is defined by matching lattices not sub-planes.
37+
"""
38+
39+
def __init__(
40+
self,
41+
substrate_structure: Structure,
42+
film_structure: Structure,
43+
film_miller: tuple[int, int, int],
44+
substrate_miller: tuple[int, int, int],
45+
zslgen: ZSLGenerator | None = None,
46+
strain_tol: float = 1e-6,
47+
):
48+
"""
49+
Args:
50+
substrate_structure: structure of substrate
51+
film_structure: structure of film
52+
film_miller: miller index of the film layer
53+
substrate_miller: miller index for the substrate layer
54+
zslgen: BiDirectionalZSL if you want custom lattice matching tolerances for coherency.
55+
"""
56+
# Bulk structures
57+
self.substrate_structure = substrate_structure
58+
self.film_structure = film_structure
59+
self.film_miller = film_miller
60+
self.substrate_miller = substrate_miller
61+
self.zslgen = zslgen or ZSLGenerator(bidirectional=True)
62+
self.strain_tol = strain_tol
63+
64+
self._find_matches()
65+
self._find_terminations()
66+
67+
def _find_matches(self) -> None:
68+
"""
69+
Finds and stores the ZSL matches.
70+
"""
71+
self.zsl_matches = []
72+
73+
film_sg = SlabGenerator(
74+
self.film_structure,
75+
self.film_miller,
76+
min_slab_size=1,
77+
min_vacuum_size=3,
78+
in_unit_planes=True,
79+
center_slab=True,
80+
primitive=True,
81+
reorient_lattice=False, # This is necessary to not screw up the lattice
82+
)
83+
84+
sub_sg = SlabGenerator(
85+
self.substrate_structure,
86+
self.substrate_miller,
87+
min_slab_size=1,
88+
min_vacuum_size=3,
89+
in_unit_planes=True,
90+
center_slab=True,
91+
primitive=True,
92+
reorient_lattice=False, # This is necessary to not screw up the lattice
93+
)
94+
95+
film_slab = film_sg.get_slab(shift=0)
96+
sub_slab = sub_sg.get_slab(shift=0)
97+
98+
film_vectors = film_slab.lattice.matrix
99+
substrate_vectors = sub_slab.lattice.matrix
100+
101+
# Generate all possible interface matches
102+
self.zsl_matches = list(self.zslgen(film_vectors[:2], substrate_vectors[:2], lowest=False))
103+
104+
for match in self.zsl_matches:
105+
xform = get_2d_transform(film_vectors, match.film_vectors)
106+
strain, rot = polar(xform)
107+
assert_allclose(
108+
strain, np.round(strain), atol=1e-12
109+
), "Film lattice vectors changed during ZSL match, check your ZSL Generator parameters"
110+
111+
xform = get_2d_transform(substrate_vectors, match.substrate_vectors)
112+
strain, rot = polar(xform)
113+
assert_allclose(
114+
strain, strain.astype(int), atol=1e-12
115+
), "Substrate lattice vectors changed during ZSL match, check your ZSL Generator parameters"
116+
117+
def _find_terminations(self):
118+
"""
119+
Finds all terminations.
120+
"""
121+
film_sg = SlabGenerator(
122+
self.film_structure,
123+
self.film_miller,
124+
min_slab_size=1,
125+
min_vacuum_size=3,
126+
in_unit_planes=True,
127+
center_slab=True,
128+
primitive=True,
129+
reorient_lattice=False, # This is necessary to not screw up the lattice
130+
)
131+
132+
sub_sg = SlabGenerator(
133+
self.substrate_structure,
134+
self.substrate_miller,
135+
min_slab_size=1,
136+
min_vacuum_size=3,
137+
in_unit_planes=True,
138+
center_slab=True,
139+
primitive=True,
140+
reorient_lattice=False, # This is necessary to not screw up the lattice
141+
)
142+
143+
film_slabs = film_sg.get_slabs()
144+
sub_slabs = sub_sg.get_slabs()
145+
146+
film_shifts = [s.shift for s in film_slabs]
147+
film_terminations = [label_termination(s) for s in film_slabs]
148+
149+
sub_shifts = [s.shift for s in sub_slabs]
150+
sub_terminations = [label_termination(s) for s in sub_slabs]
151+
152+
self._terminations = {
153+
(film_label, sub_label): (film_shift, sub_shift)
154+
for (film_label, film_shift), (sub_label, sub_shift) in product(
155+
zip(film_terminations, film_shifts), zip(sub_terminations, sub_shifts)
156+
)
157+
}
158+
self.terminations = list(self._terminations)
159+
160+
def get_interfaces(
161+
self,
162+
termination: tuple[str, str],
163+
gap: float = 2.0,
164+
vacuum_over_film: float = 20.0,
165+
film_thickness: float = 1,
166+
substrate_thickness: float = 1,
167+
in_layers: bool = True,
168+
) -> Iterator[Interface]:
169+
"""
170+
Generates interface structures given the film and substrate structure
171+
as well as the desired terminations.
172+
173+
Args:
174+
termination (tuple[str, str]): termination from self.termination list
175+
gap (float, optional): gap between film and substrate. Defaults to 2.0.
176+
vacuum_over_film (float, optional): vacuum over the top of the film. Defaults to 20.0.
177+
film_thickness (float, optional): the film thickness. Defaults to 1.
178+
substrate_thickness (float, optional): substrate thickness. Defaults to 1.
179+
in_layers (bool, optional): set the thickness in layer units. Defaults to True.
180+
181+
Yields:
182+
Iterator[Interface]: interfaces from slabs
183+
"""
184+
film_sg = SlabGenerator(
185+
self.film_structure,
186+
self.film_miller,
187+
min_slab_size=film_thickness,
188+
min_vacuum_size=3,
189+
in_unit_planes=in_layers,
190+
center_slab=True,
191+
primitive=True,
192+
reorient_lattice=False, # This is necessary to not screw up the lattice
193+
)
194+
195+
sub_sg = SlabGenerator(
196+
self.substrate_structure,
197+
self.substrate_miller,
198+
min_slab_size=substrate_thickness,
199+
min_vacuum_size=3,
200+
in_unit_planes=in_layers,
201+
center_slab=True,
202+
primitive=True,
203+
reorient_lattice=False, # This is necessary to not screw up the lattice
204+
)
205+
206+
film_shift, sub_shift = self._terminations[termination]
207+
208+
film_slab = film_sg.get_slab(shift=film_shift)
209+
sub_slab = sub_sg.get_slab(shift=sub_shift)
210+
211+
for match in self.zsl_matches:
212+
# Build film superlattice
213+
super_film_transform = np.round(
214+
from_2d_to_3d(get_2d_transform(film_slab.lattice.matrix[:2], match.film_sl_vectors))
215+
).astype(int)
216+
film_sl_slab = film_slab.copy()
217+
film_sl_slab.make_supercell(super_film_transform)
218+
assert_allclose(
219+
film_sl_slab.lattice.matrix[2], film_slab.lattice.matrix[2], atol=1e-08
220+
), "2D transformation affected C-axis for Film transformation"
221+
assert_allclose(
222+
film_sl_slab.lattice.matrix[:2], match.film_sl_vectors, atol=1e-08
223+
), "Transformation didn't make proper supercell for film"
224+
225+
# Build substrate superlattice
226+
super_sub_transform = np.round(
227+
from_2d_to_3d(get_2d_transform(sub_slab.lattice.matrix[:2], match.substrate_sl_vectors))
228+
).astype(int)
229+
sub_sl_slab = sub_slab.copy()
230+
sub_sl_slab.make_supercell(super_sub_transform)
231+
assert_allclose(
232+
sub_sl_slab.lattice.matrix[2], sub_slab.lattice.matrix[2], atol=1e-08
233+
), "2D transformation affected C-axis for Film transformation"
234+
assert_allclose(
235+
sub_sl_slab.lattice.matrix[:2], match.substrate_sl_vectors, atol=1e-08
236+
), "Transformation didn't make proper supercell for substrate"
237+
238+
# Add extra info
239+
match_dict = match.as_dict()
240+
interface_properties = {k: match_dict[k] for k in match_dict if not k.startswith("@")}
241+
242+
dfm = Deformation(match.match_transformation)
243+
244+
strain = dfm.green_lagrange_strain
245+
interface_properties["strain"] = strain
246+
interface_properties["von_mises_strain"] = strain.von_mises_strain
247+
interface_properties["termination"] = termination
248+
interface_properties["film_thickness"] = film_thickness
249+
interface_properties["substrate_thickness"] = substrate_thickness
250+
251+
yield {
252+
"interface": Interface.from_slabs(
253+
substrate_slab=sub_sl_slab,
254+
film_slab=film_sl_slab,
255+
gap=gap,
256+
vacuum_over_film=vacuum_over_film,
257+
interface_properties=interface_properties,
258+
center_slab=False, # False -- positions interface at the most bottom of the cell, solving the issue of second iteration not working properly
259+
),
260+
"strain": strain,
261+
"von_mises_strain": strain.von_mises_strain,
262+
"mean_abs_strain": round(np.mean(np.abs(strain)) / self.strain_tol) * self.strain_tol,
263+
"film_sl_vectors": match.film_sl_vectors,
264+
"substrate_sl_vectors": match.substrate_sl_vectors,
265+
"film_transform": super_film_transform,
266+
"substrate_transform": super_sub_transform,
267+
}
268+
269+
270+
def get_rot_3d_for_2d(film_matrix, sub_matrix) -> np.ndarray:
271+
"""
272+
Find transformation matrix that will rotate and strain the film to the substrate while preserving the c-axis.
273+
"""
274+
film_matrix = np.array(film_matrix)
275+
film_matrix = film_matrix.tolist()[:2]
276+
film_matrix.append(np.cross(film_matrix[0], film_matrix[1]))
277+
278+
# Generate 3D lattice vectors for substrate super lattice
279+
# Out of plane substrate super lattice has to be same length as
280+
# Film out of plane vector to ensure no extra deformation in that
281+
# direction
282+
sub_matrix = np.array(sub_matrix)
283+
sub_matrix = sub_matrix.tolist()[:2]
284+
temp_sub = np.cross(sub_matrix[0], sub_matrix[1]).astype(float) # conversion to float necessary if using numba
285+
temp_sub = temp_sub * fast_norm(
286+
np.array(film_matrix[2], dtype=float)
287+
) # conversion to float necessary if using numba
288+
sub_matrix.append(temp_sub)
289+
290+
transform_matrix = np.transpose(np.linalg.solve(film_matrix, sub_matrix))
291+
292+
rot, _ = polar(transform_matrix)
293+
294+
return rot
295+
296+
297+
def get_2d_transform(start: Sequence, end: Sequence) -> np.ndarray:
298+
"""
299+
Gets a 2d transformation matrix
300+
that converts start to end.
301+
"""
302+
return np.dot(end, np.linalg.pinv(start))
303+
304+
305+
def from_2d_to_3d(mat: np.ndarray) -> np.ndarray:
306+
"""
307+
Converts a 2D matrix to a 3D matrix.
308+
"""
309+
new_mat = np.diag([1.0, 1.0, 1.0])
310+
new_mat[:2, :2] = mat
311+
return new_mat

0 commit comments

Comments
 (0)