@mat3ra/made 2024.5.15-1 → 2024.6.4-0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/dist/js/cell/cell.d.ts +3 -2
  2. package/dist/js/lattice/lattice.d.ts +1 -1
  3. package/dist/js/material.d.ts +3 -8
  4. package/dist/js/math.d.ts +2 -2
  5. package/dist/js/tools/material.js +3 -6
  6. package/dist/js/types.d.ts +1 -1
  7. package/package.json +4 -6
  8. package/src/js/cell/cell.ts +3 -3
  9. package/src/js/material.ts +1 -1
  10. package/src/js/{math.js → math.ts} +2 -2
  11. package/src/js/tools/material.js +1 -1
  12. package/src/js/types.ts +1 -1
  13. package/src/py/mat3ra/made/tools/analyze.py +14 -0
  14. package/src/py/mat3ra/made/tools/build/__init__.py +98 -77
  15. package/src/py/mat3ra/made/tools/build/interface/__init__.py +26 -0
  16. package/src/py/mat3ra/made/tools/build/interface/builders.py +185 -0
  17. package/src/py/mat3ra/made/tools/build/interface/configuration.py +18 -0
  18. package/src/py/mat3ra/made/tools/build/interface/enums.py +7 -0
  19. package/src/py/mat3ra/made/tools/build/interface/termination_pair.py +59 -0
  20. package/src/py/mat3ra/made/tools/build/interface/utils.py +36 -0
  21. package/src/py/mat3ra/made/tools/build/mixins.py +13 -0
  22. package/src/py/mat3ra/made/tools/build/slab/__init__.py +15 -0
  23. package/src/py/mat3ra/made/tools/build/slab/builders.py +75 -0
  24. package/src/py/mat3ra/made/tools/build/slab/configuration.py +56 -0
  25. package/src/py/mat3ra/made/tools/build/slab/termination.py +27 -0
  26. package/src/py/mat3ra/made/tools/build/supercell.py +25 -0
  27. package/src/py/mat3ra/made/tools/convert.py +9 -2
  28. package/src/py/mat3ra/made/tools/utils.py +17 -0
  29. package/tests/py/unit/fixtures.py +115 -10
  30. package/tests/py/unit/test_tools_build_interface.py +30 -17
  31. package/tests/py/unit/test_tools_build_slab.py +21 -0
  32. package/tests/py/unit/test_tools_build_supercell.py +15 -0
  33. package/src/py/mat3ra/made/tools/build/interface.py +0 -268
  34. package/tests/py/unit/test_tools_build.py +0 -20
@@ -1,6 +1,7 @@
1
+ import CodeMath from "@mat3ra/code/dist/js/math";
1
2
  import { Coordinate } from "../basis/types";
2
3
  import { Vector, VectorsAsArray } from "../lattice/types";
3
- type Point = Coordinate | math.Matrix | math.MathType;
4
+ type Point = Coordinate | CodeMath.Matrix | CodeMath.MathType;
4
5
  export declare class Cell {
5
6
  tolerance: number;
6
7
  vector1: Vector;
@@ -21,7 +22,7 @@ export declare class Cell {
21
22
  /**
22
23
  * Convert a point (in crystal coordinates) to cartesian.
23
24
  */
24
- convertPointToCartesian(point: Point): import("mathjs").MathType;
25
+ convertPointToCartesian(point: Point): CodeMath.MathType;
25
26
  /**
26
27
  * Convert a point (in cartesian coordinates) to crystal (fractional).
27
28
  */
@@ -83,7 +83,7 @@ export declare class Lattice extends LatticeBravais implements LatticeSchema {
83
83
  beta: number;
84
84
  gamma: number;
85
85
  units: {
86
- length?: "bohr" | "angstrom" | undefined;
86
+ length?: "angstrom" | "bohr" | undefined;
87
87
  angle?: "degree" | "radian" | undefined;
88
88
  };
89
89
  type: "CUB" | "BCC" | "FCC" | "TET" | "MCL" | "ORC" | "ORCC" | "ORCF" | "ORCI" | "HEX" | "BCT" | "TRI" | "MCLC" | "RHL";
@@ -1,5 +1,5 @@
1
1
  import { HasConsistencyChecksHasMetadataNamedDefaultableInMemoryEntity } from "@mat3ra/code/dist/js/entity";
2
- import { AnyObject } from "@mat3ra/code/dist/js/entity/in_memory";
2
+ import { AnyObject } from "@mat3ra/esse/dist/js/esse/types";
3
3
  import { ConsistencyCheck, DerivedPropertiesSchema, FileSourceSchema, MaterialSchema } from "@mat3ra/esse/dist/js/types";
4
4
  import { ConstrainedBasis } from "./basis/constrained_basis";
5
5
  import { Constraint } from "./constraints/constraints";
@@ -483,9 +483,7 @@ export declare const Material: {
483
483
  setProps(json?: AnyObject | undefined): any;
484
484
  toJSON(exclude?: string[] | undefined): AnyObject;
485
485
  toJSONSafe(exclude?: string[] | undefined): AnyObject;
486
- toJSONQuick(exclude?: string[] | undefined): AnyObject; /**
487
- * Returns a copy of the material with conventional cell constructed instead of primitive.
488
- */
486
+ toJSONQuick(exclude?: string[] | undefined): AnyObject;
489
487
  clone(extraContext?: object | undefined): any;
490
488
  validate(): void;
491
489
  clean(config: AnyObject): AnyObject;
@@ -511,10 +509,7 @@ export declare const Material: {
511
509
  toJSONQuick(exclude?: string[] | undefined): AnyObject;
512
510
  clone(extraContext?: object | undefined): any;
513
511
  validate(): void;
514
- clean(config: AnyObject): AnyObject; /**
515
- * @summary a series of checks for the material's basis and returns an array of results in ConsistencyChecks format.
516
- * @returns Array of checks results
517
- */
512
+ clean(config: AnyObject): AnyObject;
518
513
  isValid(): boolean;
519
514
  id: string;
520
515
  readonly cls: string;
package/dist/js/math.d.ts CHANGED
@@ -35,7 +35,7 @@ declare const _default: {
35
35
  uninitialized: any;
36
36
  version: string;
37
37
  expression: import("mathjs").MathNode;
38
- json: mathjs.MathJsJson;
38
+ json: import("mathjs").MathJsJson;
39
39
  config: (options: any) => void;
40
40
  lsolve(L: import("mathjs").Matrix | import("mathjs").MathArray, b: import("mathjs").Matrix | import("mathjs").MathArray): import("mathjs").Matrix | import("mathjs").MathArray;
41
41
  lup(A?: import("mathjs").Matrix | import("mathjs").MathArray | undefined): import("mathjs").MathArray;
@@ -184,7 +184,7 @@ declare const _default: {
184
184
  re(x: number | import("mathjs").Matrix | import("mathjs").BigNumber | import("mathjs").Complex | import("mathjs").MathArray): number | import("mathjs").Matrix | import("mathjs").BigNumber | import("mathjs").MathArray;
185
185
  bignumber(x?: string | number | boolean | import("mathjs").Matrix | import("mathjs").MathArray | undefined): import("mathjs").BigNumber;
186
186
  boolean(x: string | number | boolean | import("mathjs").Matrix | import("mathjs").MathArray): boolean | import("mathjs").Matrix | import("mathjs").MathArray;
187
- chain(value?: any): any;
187
+ chain(value?: any): import("mathjs").MathJsChain;
188
188
  complex(arg?: string | import("mathjs").Complex | import("mathjs").MathArray | import("mathjs").PolarCoordinates | undefined): import("mathjs").Complex;
189
189
  complex(re: number, im: number): import("mathjs").Complex;
190
190
  fraction(numerator: string | number | import("mathjs").Matrix | import("mathjs").MathArray, denominator?: string | number | import("mathjs").Matrix | import("mathjs").MathArray | undefined): import("mathjs").Matrix | import("mathjs").Fraction | import("mathjs").MathArray;
@@ -1,9 +1,6 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
- const mathjs_1 = __importDefault(require("mathjs"));
3
+ const math_1 = require("@mat3ra/code/dist/js/math");
7
4
  const constants_1 = require("../constants");
8
5
  const lattice_1 = require("../lattice/lattice");
9
6
  /**
@@ -40,8 +37,8 @@ function getBasisConfigTranslatedToCenter(material) {
40
37
  material.toCartesian();
41
38
  const updatedBasis = material.Basis;
42
39
  const centerOfCoordinates = updatedBasis.centerOfCoordinatesPoint;
43
- const centerOfLattice = mathjs_1.default.multiply(0.5, material.Lattice.vectorArrays.reduce((a, b) => mathjs_1.default.add(a, b)));
44
- const translationVector = mathjs_1.default.subtract(centerOfLattice, centerOfCoordinates);
40
+ const centerOfLattice = math_1.math.multiply(0.5, material.Lattice.vectorArrays.reduce((a, b) => math_1.math.add(a, b)));
41
+ const translationVector = math_1.math.subtract(centerOfLattice, centerOfCoordinates);
45
42
  updatedBasis.translateByVector(translationVector);
46
43
  material.setBasis(updatedBasis.toJSON());
47
44
  if (originalUnits !== constants_1.ATOMIC_COORD_UNITS.cartesian)
@@ -1,3 +1,3 @@
1
- import { AnyObject } from "@mat3ra/code/dist/js/entity/in_memory";
1
+ import { AnyObject } from "@mat3ra/esse/dist/js/esse/types";
2
2
  import { MaterialSchema } from "@mat3ra/esse/dist/js/types";
3
3
  export type MaterialJSON = MaterialSchema & AnyObject;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mat3ra/made",
3
- "version": "2024.5.15-1",
3
+ "version": "2024.6.4-0",
4
4
  "description": "MAterials DEsign library",
5
5
  "scripts": {
6
6
  "lint": "eslint --cache src/js tests/js && prettier --write src/js tests/js",
@@ -33,11 +33,10 @@
33
33
  "@babel/register": "^7.22.15",
34
34
  "@babel/runtime-corejs3": "^7.16.8",
35
35
  "@exabyte-io/eslint-config": "^2023.8.29-1",
36
- "@mat3ra/code": "2024.3.25-3",
37
- "@mat3ra/esse": "2024.3.25-6",
38
- "@mat3ra/tsconfig": "^2024.3.25-5",
36
+ "@mat3ra/code": "2024.6.4-0",
37
+ "@mat3ra/esse": "2024.6.4-1",
38
+ "@mat3ra/tsconfig": "2024.6.3-0",
39
39
  "@types/crypto-js": "^4.2.2",
40
- "@types/mathjs": "^3.21.1",
41
40
  "@typescript-eslint/eslint-plugin": "^5.9.1",
42
41
  "@typescript-eslint/parser": "^5.9.1",
43
42
  "chai": "^4.3.4",
@@ -72,7 +71,6 @@
72
71
  "array-almost-equal": "^1.0.0",
73
72
  "crypto-js": "4.2.0",
74
73
  "lodash": "^4.17.*",
75
- "mathjs": "12.4.1",
76
74
  "ts-node": "^10.9.1",
77
75
  "typescript": "^4.5.5",
78
76
  "underscore": "^1.8.3",
@@ -1,4 +1,4 @@
1
- import { math } from "@mat3ra/code/dist/js/math";
1
+ import CodeMath, { math } from "@mat3ra/code/dist/js/math";
2
2
 
3
3
  import { Coordinate } from "../basis/types";
4
4
  import constants from "../constants";
@@ -10,7 +10,7 @@ const INV = math.inv;
10
10
  // @ts-ignore
11
11
  const MATRIX_MULT = (...args) => MULT(...args.map((x) => MATRIX(x))).toArray();
12
12
 
13
- type Point = Coordinate | math.Matrix | math.MathType;
13
+ type Point = Coordinate | CodeMath.Matrix | CodeMath.MathType;
14
14
 
15
15
  /*
16
16
  * Cell represents a unit cell in geometrical form: 3x3 matrix where rows are cell vectors.
@@ -64,7 +64,7 @@ export class Cell {
64
64
  /**
65
65
  * Convert a point (in crystal coordinates) to cartesian.
66
66
  */
67
- convertPointToCartesian(point: Point) {
67
+ convertPointToCartesian(point: Point): CodeMath.MathType {
68
68
  return MULT(point, this.vectorsAsArray);
69
69
  }
70
70
 
@@ -1,5 +1,5 @@
1
1
  import { HasConsistencyChecksHasMetadataNamedDefaultableInMemoryEntity } from "@mat3ra/code/dist/js/entity";
2
- import { AnyObject } from "@mat3ra/code/dist/js/entity/in_memory";
2
+ import { AnyObject } from "@mat3ra/esse/dist/js/esse/types";
3
3
  import {
4
4
  ConsistencyCheck,
5
5
  DerivedPropertiesSchema,
@@ -1,6 +1,6 @@
1
1
  // TODO: adjust the imports and remove the need for re-exporting
2
- import { math as codeJSMath } from "@mat3ra/code/dist/js/math";
2
+ import { math } from "@mat3ra/code/dist/js/math";
3
3
 
4
4
  export default {
5
- ...codeJSMath,
5
+ ...math,
6
6
  };
@@ -1,4 +1,4 @@
1
- import math from "mathjs";
1
+ import { math } from "@mat3ra/code/dist/js/math";
2
2
 
3
3
  import { ATOMIC_COORD_UNITS } from "../constants";
4
4
  import { Lattice } from "../lattice/lattice";
package/src/js/types.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { AnyObject } from "@mat3ra/code/dist/js/entity/in_memory";
1
+ import { AnyObject } from "@mat3ra/esse/dist/js/esse/types";
2
2
  import { MaterialSchema } from "@mat3ra/esse/dist/js/types";
3
3
 
4
4
  export type MaterialJSON = MaterialSchema & AnyObject;
@@ -55,3 +55,17 @@ def get_surface_area(atoms: Atoms):
55
55
  matrix = atoms.cell
56
56
  cross_product = np.cross(matrix[0], matrix[1])
57
57
  return np.linalg.norm(cross_product)
58
+
59
+
60
+ @decorator_convert_material_args_kwargs_to_atoms
61
+ def get_chemical_formula(atoms: Atoms):
62
+ """
63
+ Calculate the formula of the atoms structure.
64
+
65
+ Args:
66
+ atoms (ase.Atoms): The Atoms object to calculate the formula of.
67
+
68
+ Returns:
69
+ str: The formula of the atoms.
70
+ """
71
+ return atoms.get_chemical_formula()
@@ -1,80 +1,101 @@
1
+ from typing import List, Optional, Any
2
+
3
+ from pydantic import BaseModel
4
+
1
5
  from ...material import Material
2
- from .interface import InterfaceDataHolder, CoherentInterfaceBuilder, TerminationType
3
- from .interface import InterfaceSettings as Settings
4
- from .interface import interface_init_zsl_builder, interface_patch_with_mean_abs_strain
5
- from ..convert import decorator_convert_material_args_kwargs_to_structure
6
- from ..modify import translate_to_bottom, wrap_to_unit_cell
7
- from typing import Optional
8
-
9
-
10
- @decorator_convert_material_args_kwargs_to_structure
11
- def create_interfaces(
12
- substrate: Optional[Material] = None,
13
- layer: Optional[Material] = None,
14
- settings: Settings = Settings(),
15
- sort_by_strain_and_size: bool = True,
16
- remove_duplicates: bool = True,
17
- is_logging_enabled: bool = True,
18
- interface_builder: Optional[CoherentInterfaceBuilder] = None,
19
- termination: Optional[TerminationType] = None,
20
- ) -> InterfaceDataHolder:
6
+
7
+
8
+ class BaseBuilder(BaseModel):
21
9
  """
22
- Create all interfaces between the substrate and layer structures using ZSL algorithm provided by pymatgen.
23
-
24
- Args:
25
- substrate (Material): The substrate structure.
26
- layer (Material): The layer structure.
27
- settings: The settings for the interface generation.
28
- sort_by_strain_and_size (bool): Whether to sort the interfaces by strain and size.
29
- remove_duplicates (bool): Whether to remove duplicate interfaces.
30
- is_logging_enabled (bool): Whether to enable debug print.
31
- Returns:
32
- InterfaceDataHolder.
10
+ Base class for material builders.
11
+ This class provides an interface for generating materials and getter functions.
12
+ The builder is meant as a description of the process, while its functions require a
13
+ "Configuration" class instance to perform the generation.
14
+
15
+ The class is designed to be subclassed and the subclass should implement the following methods:
16
+
17
+ - `_generate`: Generate the material items, possibly using third-party tools/implementation for items.
18
+ - `_sort`: Sort the items.
19
+ - `_select`: Select a subset of the items.
20
+ - `_post_process`: Post-process the items to convert them to materials (Material class).
21
+ - `_finalize`: Finalize the materials.
22
+
23
+ The subclass should also define the following attributes:
24
+
25
+ - `_BuildParametersType`: The data structure model for the build parameters.
26
+ - `_DefaultBuildParameters`: The default build parameters.
27
+ - `_ConfigurationType`: The data structure model for the Configuration used during the build.
28
+ - `_GeneratedItemType`: The type of the generated item.
29
+ - `_SelectorParametersType`: The data structure model for the selector parameters.
30
+ - `_PostProcessParametersType`: The data structure model for the post-process parameters.
33
31
  """
34
- if is_logging_enabled:
35
- print("Creating interfaces...")
36
-
37
- builder = interface_builder or init_interface_builder(substrate, layer, settings)
38
- interfaces_data = InterfaceDataHolder()
39
-
40
- if termination is not None:
41
- builder.terminations = [termination]
42
-
43
- for termination in builder.terminations:
44
- all_interfaces_for_termination = builder.get_interfaces(
45
- termination,
46
- gap=settings.distance_z,
47
- film_thickness=settings.LayerParameters.thickness,
48
- substrate_thickness=settings.SubstrateParameters.thickness,
49
- in_layers=True,
50
- )
51
-
52
- all_interfaces_for_termination_patched_wrapped = list(
53
- map(
54
- lambda i: wrap_to_unit_cell(interface_patch_with_mean_abs_strain(i)),
55
- all_interfaces_for_termination,
56
- )
57
- )
58
-
59
- interfaces_data.add_data_entries(
60
- all_interfaces_for_termination_patched_wrapped,
61
- sort_interfaces_by_strain_and_size=sort_by_strain_and_size,
62
- remove_duplicates=remove_duplicates,
63
- )
64
-
65
- if is_logging_enabled:
66
- unique_str = "unique" if remove_duplicates else ""
67
- print(f"Found {len(interfaces_data.get_interfaces_for_termination(0))} {unique_str} interfaces.")
68
-
69
- return interfaces_data
70
-
71
-
72
- @decorator_convert_material_args_kwargs_to_structure
73
- def init_interface_builder(
74
- substrate: Material,
75
- layer: Material,
76
- settings: Settings,
77
- ) -> CoherentInterfaceBuilder:
78
- substrate = translate_to_bottom(substrate, settings.use_conventional_cell)
79
- layer = translate_to_bottom(layer, settings.use_conventional_cell)
80
- return interface_init_zsl_builder(substrate, layer, settings)
32
+
33
+ build_parameters: Any = None
34
+ _BuildParametersType: Any = None
35
+ _DefaultBuildParameters: Any = None
36
+
37
+ _ConfigurationType: Any = Any
38
+ _GeneratedItemType: Any = Any
39
+ _SelectorParametersType: Any = None
40
+ _PostProcessParametersType: Any = None
41
+
42
+ def __init__(self, build_parameters: _BuildParametersType = None):
43
+ super().__init__(build_parameters=build_parameters)
44
+ self.build_parameters = build_parameters or self._DefaultBuildParameters
45
+ self.__generated_items: List[List[BaseBuilder._GeneratedItemType]] = []
46
+ self.__configurations: List[BaseBuilder._ConfigurationType] = []
47
+
48
+ def _generate(self, configuration: _ConfigurationType) -> List[_GeneratedItemType]:
49
+ return []
50
+
51
+ def _generate_or_get_from_cache(self, configuration: _ConfigurationType) -> List[_GeneratedItemType]:
52
+ if configuration not in self.__configurations:
53
+ self.__configurations.append(configuration)
54
+ self.__generated_items.append(self._generate(configuration))
55
+ return self.__generated_items[self.__configurations.index(configuration)]
56
+
57
+ def _sort(self, items: List[_GeneratedItemType]) -> List[_GeneratedItemType]:
58
+ return items
59
+
60
+ def _select(
61
+ self, items: List[_GeneratedItemType], selector_parameters: Optional[_SelectorParametersType]
62
+ ) -> List[_GeneratedItemType]:
63
+ return items
64
+
65
+ def _post_process(
66
+ self, items: List[_GeneratedItemType], post_process_parameters: Optional[_PostProcessParametersType]
67
+ ) -> List[Material]:
68
+ return [Material(self._convert_generated_item(item)) for item in items]
69
+
70
+ @staticmethod
71
+ def _convert_generated_item(item: _GeneratedItemType):
72
+ material_config = item
73
+ return material_config
74
+
75
+ def _finalize(self, materials: List[Material], configuration: _ConfigurationType) -> List[Material]:
76
+ return [self._update_material_name(material, configuration) for material in materials]
77
+
78
+ def get_materials(
79
+ self,
80
+ configuration: _ConfigurationType,
81
+ selector_parameters: Optional[_SelectorParametersType] = None,
82
+ post_process_parameters: Optional[_PostProcessParametersType] = None,
83
+ ) -> List[Material]:
84
+ generated_items = self._generate_or_get_from_cache(configuration)
85
+ sorted_items = self._sort(generated_items)
86
+ selected_items = self._select(sorted_items, selector_parameters)
87
+ materials = self._post_process(selected_items, post_process_parameters)
88
+ finalized_materials = self._finalize(materials, configuration)
89
+ return finalized_materials
90
+
91
+ def get_material(
92
+ self,
93
+ configuration: _ConfigurationType,
94
+ selector_parameters: Optional[_SelectorParametersType] = None,
95
+ post_process_parameters: Optional[_PostProcessParametersType] = None,
96
+ ) -> Material:
97
+ return self.get_materials(configuration, selector_parameters, post_process_parameters)[0]
98
+
99
+ def _update_material_name(self, material, configuration):
100
+ # Do nothing by default
101
+ return material
@@ -0,0 +1,26 @@
1
+ from typing import Union, List, Optional
2
+
3
+ from mat3ra.made.material import Material
4
+ from .builders import (
5
+ SimpleInterfaceBuilder,
6
+ SimpleInterfaceBuilderParameters,
7
+ ZSLStrainMatchingParameters,
8
+ ZSLStrainMatchingInterfaceBuilder,
9
+ ZSLStrainMatchingInterfaceBuilderParameters,
10
+ )
11
+ from .configuration import InterfaceConfiguration
12
+
13
+
14
+ def create_interfaces(
15
+ builder: Union[SimpleInterfaceBuilder, ZSLStrainMatchingInterfaceBuilder], configuration: InterfaceConfiguration
16
+ ) -> List[Material]:
17
+ return builder.get_materials(configuration)
18
+
19
+
20
+ def create_interface(
21
+ configuration: InterfaceConfiguration,
22
+ builder: Optional[Union[SimpleInterfaceBuilder, ZSLStrainMatchingInterfaceBuilder]] = None,
23
+ ) -> Material:
24
+ if builder is None:
25
+ builder = SimpleInterfaceBuilder(build_parameters=SimpleInterfaceBuilderParameters())
26
+ return builder.get_material(configuration)
@@ -0,0 +1,185 @@
1
+ from typing import Any, List, Optional
2
+
3
+ import numpy as np
4
+ from pydantic import BaseModel
5
+ from ase.build.tools import niggli_reduce
6
+ from pymatgen.analysis.interfaces.coherent_interfaces import (
7
+ CoherentInterfaceBuilder,
8
+ ZSLGenerator,
9
+ )
10
+
11
+ from mat3ra.made.material import Material
12
+ from .enums import StrainModes
13
+ from .configuration import InterfaceConfiguration
14
+ from .termination_pair import TerminationPair, safely_select_termination_pair
15
+ from .utils import interface_patch_with_mean_abs_strain, remove_duplicate_interfaces
16
+ from ..mixins import (
17
+ ConvertGeneratedItemsASEAtomsMixin,
18
+ ConvertGeneratedItemsPymatgenStructureMixin,
19
+ )
20
+ from ..slab import create_slab, Termination
21
+ from ..slab.configuration import SlabConfiguration
22
+ from ...analyze import get_chemical_formula
23
+ from ...convert import to_ase, from_ase, to_pymatgen, PymatgenInterface, ASEAtoms
24
+ from ...build import BaseBuilder
25
+
26
+
27
+ class InterfaceBuilderParameters(BaseModel):
28
+ pass
29
+
30
+
31
+ class InterfaceBuilder(BaseBuilder):
32
+ _BuildParametersType = InterfaceBuilderParameters
33
+ _ConfigurationType: type(InterfaceConfiguration) = InterfaceConfiguration # type: ignore
34
+
35
+ def _update_material_name(self, material: Material, configuration: InterfaceConfiguration) -> Material:
36
+ film_formula = get_chemical_formula(configuration.film_configuration.bulk)
37
+ substrate_formula = get_chemical_formula(configuration.substrate_configuration.bulk)
38
+ film_miller_indices = "".join([str(i) for i in configuration.film_configuration.miller_indices])
39
+ substrate_miller_indices = "".join([str(i) for i in configuration.substrate_configuration.miller_indices])
40
+ new_name = f"{film_formula}({film_miller_indices})-{substrate_formula}({substrate_miller_indices}), Interface"
41
+ material.name = new_name
42
+ return material
43
+
44
+
45
+ ########################################################################################
46
+ # Simple Interface Builder #
47
+ ########################################################################################
48
+ class SimpleInterfaceBuilderParameters(InterfaceBuilderParameters):
49
+ scale_film: bool = True
50
+
51
+
52
+ class SimpleInterfaceBuilder(ConvertGeneratedItemsASEAtomsMixin, InterfaceBuilder):
53
+ """
54
+ Creates matching interface between substrate and film by straining the film to match the substrate.
55
+ """
56
+
57
+ _BuildParametersType = Optional[SimpleInterfaceBuilderParameters]
58
+ _DefaultBuildParameters = SimpleInterfaceBuilderParameters(scale_film=True)
59
+ _GeneratedItemType: type(ASEAtoms) = ASEAtoms # type: ignore
60
+
61
+ @staticmethod
62
+ def __preprocess_slab_configuration(configuration: SlabConfiguration, termination: Termination):
63
+ slab = create_slab(configuration, termination)
64
+ ase_slab = to_ase(slab)
65
+ niggli_reduce(ase_slab)
66
+ return ase_slab
67
+
68
+ @staticmethod
69
+ def __combine_two_slabs_ase(substrate_slab_ase: ASEAtoms, film_slab_ase: ASEAtoms, distance_z: float) -> ASEAtoms:
70
+ max_z_substrate = max(substrate_slab_ase.positions[:, 2])
71
+ min_z_film = min(film_slab_ase.positions[:, 2])
72
+ shift_z = max_z_substrate - min_z_film + distance_z
73
+
74
+ film_slab_ase.translate([0, 0, shift_z])
75
+
76
+ return substrate_slab_ase + film_slab_ase
77
+
78
+ @staticmethod
79
+ def __add_vacuum_along_c_ase(interface_ase: ASEAtoms, vacuum: float) -> ASEAtoms:
80
+ cell_c_with_vacuum = max(interface_ase.positions[:, 2]) + vacuum
81
+ interface_ase.cell[2, 2] = cell_c_with_vacuum
82
+ return interface_ase
83
+
84
+ def _generate(self, configuration: InterfaceBuilder._ConfigurationType) -> List[_GeneratedItemType]: # type: ignore
85
+ film_slab_ase = self.__preprocess_slab_configuration(
86
+ configuration.film_configuration, configuration.film_termination
87
+ )
88
+ substrate_slab_ase = self.__preprocess_slab_configuration(
89
+ configuration.substrate_configuration, configuration.substrate_termination
90
+ )
91
+
92
+ if self.build_parameters.scale_film:
93
+ film_slab_ase.set_cell(substrate_slab_ase.cell, scale_atoms=True)
94
+ film_slab_ase.wrap()
95
+
96
+ interface_ase = self.__combine_two_slabs_ase(substrate_slab_ase, film_slab_ase, configuration.distance_z)
97
+ interface_ase_with_vacuum = self.__add_vacuum_along_c_ase(interface_ase, configuration.vacuum)
98
+
99
+ return [interface_ase_with_vacuum]
100
+
101
+ def _post_process(self, items: List[_GeneratedItemType], post_process_parameters=None) -> List[Material]:
102
+ return [Material(from_ase(slab)) for slab in items]
103
+
104
+
105
+ ########################################################################################
106
+ # Strain Matching Interface Builders #
107
+ ########################################################################################
108
+ class StrainMatchingInterfaceBuilderParameters(BaseModel):
109
+ strain_matching_parameters: Optional[Any] = None
110
+
111
+
112
+ class StrainMatchingInterfaceBuilder(InterfaceBuilder):
113
+ _BuildParametersType = StrainMatchingInterfaceBuilderParameters # type: ignore
114
+
115
+ def _update_material_name(self, material: Material, configuration: InterfaceConfiguration) -> Material:
116
+ updated_material = super()._update_material_name(material, configuration)
117
+ if StrainModes.mean_abs_strain in material.metadata:
118
+ strain = material.metadata[StrainModes.mean_abs_strain]
119
+ new_name = f"{updated_material.name}, Strain {strain*100:.3f}%"
120
+ updated_material.name = new_name
121
+ return material
122
+
123
+
124
+ class ZSLStrainMatchingParameters(BaseModel):
125
+ max_area: float = 50.0
126
+ max_area_ratio_tol: float = 0.09
127
+ max_length_tol: float = 0.03
128
+ max_angle_tol: float = 0.01
129
+
130
+
131
+ class ZSLStrainMatchingInterfaceBuilderParameters(StrainMatchingInterfaceBuilderParameters):
132
+ strain_matching_parameters: ZSLStrainMatchingParameters
133
+
134
+
135
+ class ZSLStrainMatchingInterfaceBuilder(ConvertGeneratedItemsPymatgenStructureMixin, StrainMatchingInterfaceBuilder):
136
+ """
137
+ Creates matching interface between substrate and film using the ZSL algorithm.
138
+ """
139
+
140
+ _BuildParametersType: type( # type: ignore
141
+ ZSLStrainMatchingInterfaceBuilderParameters
142
+ ) = ZSLStrainMatchingInterfaceBuilderParameters # type: ignore
143
+ _GeneratedItemType: PymatgenInterface = PymatgenInterface # type: ignore
144
+
145
+ def _generate(self, configuration: InterfaceConfiguration) -> List[PymatgenInterface]:
146
+ generator = ZSLGenerator(**self.build_parameters.strain_matching_parameters.dict())
147
+ builder = CoherentInterfaceBuilder(
148
+ substrate_structure=to_pymatgen(configuration.substrate_configuration.bulk),
149
+ film_structure=to_pymatgen(configuration.film_configuration.bulk),
150
+ substrate_miller=configuration.substrate_configuration.miller_indices,
151
+ film_miller=configuration.film_configuration.miller_indices,
152
+ zslgen=generator,
153
+ )
154
+
155
+ generated_termination_pairs = [
156
+ TerminationPair.from_pymatgen(pymatgen_termination) for pymatgen_termination in builder.terminations
157
+ ]
158
+ termination_pair = safely_select_termination_pair(configuration.termination_pair, generated_termination_pairs)
159
+ interfaces = builder.get_interfaces(
160
+ termination=termination_pair.to_pymatgen(),
161
+ gap=configuration.distance_z,
162
+ film_thickness=configuration.film_configuration.thickness,
163
+ substrate_thickness=configuration.substrate_configuration.thickness,
164
+ in_layers=True,
165
+ )
166
+
167
+ return list([interface_patch_with_mean_abs_strain(interface) for interface in interfaces])
168
+
169
+ def _sort(self, items: List[_GeneratedItemType]):
170
+ sorted_by_num_sites = sorted(items, key=lambda x: x.num_sites)
171
+ sorted_by_num_sites_and_strain = sorted(
172
+ sorted_by_num_sites, key=lambda x: np.mean(x.interface_properties[StrainModes.mean_abs_strain])
173
+ )
174
+ unique_sorted_interfaces = remove_duplicate_interfaces(
175
+ sorted_by_num_sites_and_strain, strain_mode=StrainModes.mean_abs_strain
176
+ )
177
+ return unique_sorted_interfaces
178
+
179
+ def _post_process(self, items: List[_GeneratedItemType], post_process_parameters=None) -> List[Material]:
180
+ materials = super()._post_process(items, post_process_parameters)
181
+ strains = [interface.interface_properties[StrainModes.mean_abs_strain] for interface in items]
182
+
183
+ for material, strain in zip(materials, strains):
184
+ material.metadata["mean_abs_strain"] = strain
185
+ return materials
@@ -0,0 +1,18 @@
1
+ from pydantic import BaseModel
2
+
3
+ from .termination_pair import TerminationPair
4
+ from ..slab import Termination
5
+ from ..slab.configuration import SlabConfiguration
6
+
7
+
8
+ class InterfaceConfiguration(BaseModel):
9
+ film_configuration: SlabConfiguration
10
+ substrate_configuration: SlabConfiguration
11
+ film_termination: Termination
12
+ substrate_termination: Termination
13
+ distance_z: float = 3.0
14
+ vacuum: float = 5.0
15
+
16
+ @property
17
+ def termination_pair(self):
18
+ return TerminationPair(self.film_termination, self.substrate_termination)
@@ -0,0 +1,7 @@
1
+ from enum import Enum
2
+
3
+
4
+ class StrainModes(str, Enum):
5
+ strain = "strain"
6
+ von_mises_strain = "von_mises_strain"
7
+ mean_abs_strain = "mean_abs_strain"