@mat3ra/made 2025.7.29-0 → 2025.8.1-1
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.
- package/README.md +7 -0
- package/dist/js/material.d.ts +7 -2
- package/package.json +1 -1
- package/pyproject.toml +4 -2
- package/src/py/mat3ra/made/basis/__init__.py +87 -8
- package/src/py/mat3ra/made/lattice.py +19 -7
- package/src/py/mat3ra/made/material.py +20 -3
- package/src/py/mat3ra/made/metadata.py +45 -0
- package/src/py/mat3ra/made/tools/analyze/__init__.py +14 -4
- package/src/py/mat3ra/made/tools/analyze/adatom.py +75 -0
- package/src/py/mat3ra/made/tools/analyze/crystal_site.py +108 -0
- package/src/py/mat3ra/made/tools/analyze/interface/__init__.py +24 -0
- package/src/py/mat3ra/made/tools/analyze/interface/commensurate.py +155 -0
- package/src/py/mat3ra/made/tools/analyze/interface/enums.py +60 -0
- package/src/py/mat3ra/made/tools/analyze/interface/grain_boundary.py +79 -0
- package/src/py/mat3ra/made/tools/analyze/interface/simple.py +165 -0
- package/src/py/mat3ra/made/tools/analyze/interface/twisted_nanoribbons.py +89 -0
- package/src/py/mat3ra/made/tools/analyze/interface/utils/__init__.py +5 -0
- package/src/py/mat3ra/made/tools/analyze/interface/utils/holders.py +11 -0
- package/src/py/mat3ra/made/tools/analyze/interface/utils/vector.py +56 -0
- package/src/py/mat3ra/made/tools/analyze/interface/zsl.py +190 -0
- package/src/py/mat3ra/made/tools/analyze/lattice.py +32 -8
- package/src/py/mat3ra/made/tools/analyze/lattice_lines.py +29 -0
- package/src/py/mat3ra/made/tools/analyze/lattice_planes.py +192 -0
- package/src/py/mat3ra/made/tools/analyze/other.py +26 -42
- package/src/py/mat3ra/made/tools/analyze/rdf.py +1 -1
- package/src/py/mat3ra/made/tools/analyze/slab.py +89 -0
- package/src/py/mat3ra/made/tools/analyze/terrace.py +105 -0
- package/src/py/mat3ra/made/tools/analyze/utils.py +51 -0
- package/src/py/mat3ra/made/tools/build/__init__.py +85 -18
- package/src/py/mat3ra/made/tools/build/defect/__init__.py +0 -111
- package/src/py/mat3ra/made/tools/build/defect/adatom/builders.py +11 -0
- package/src/py/mat3ra/made/tools/build/defect/adatom/configuration.py +18 -0
- package/src/py/mat3ra/made/tools/build/defect/adatom/helpers.py +128 -0
- package/src/py/mat3ra/made/tools/build/defect/enums.py +21 -7
- package/src/py/mat3ra/made/tools/build/defect/factories.py +68 -12
- package/src/py/mat3ra/made/tools/build/defect/island/builders.py +15 -0
- package/src/py/mat3ra/made/tools/build/defect/island/configuration.py +16 -0
- package/src/py/mat3ra/made/tools/build/defect/island/helpers.py +113 -0
- package/src/py/mat3ra/made/tools/build/defect/pair_defect/__init__.py +0 -0
- package/src/py/mat3ra/made/tools/build/defect/pair_defect/analyzer.py +15 -0
- package/src/py/mat3ra/made/tools/build/defect/pair_defect/builders.py +25 -0
- package/src/py/mat3ra/made/tools/build/defect/pair_defect/configuration.py +41 -0
- package/src/py/mat3ra/made/tools/build/defect/pair_defect/helpers.py +65 -0
- package/src/py/mat3ra/made/tools/build/defect/point/builders.py +109 -0
- package/src/py/mat3ra/made/tools/build/defect/point/configuration.py +82 -0
- package/src/py/mat3ra/made/tools/build/defect/point/helpers.py +211 -0
- package/src/py/mat3ra/made/tools/build/defect/slab/__init__.py +0 -0
- package/src/py/mat3ra/made/tools/build/defect/slab/builders.py +32 -0
- package/src/py/mat3ra/made/tools/build/defect/slab/configuration.py +33 -0
- package/src/py/mat3ra/made/tools/build/defect/slab/helpers.py +78 -0
- package/src/py/mat3ra/made/tools/build/defect/terrace/builders.py +25 -0
- package/src/py/mat3ra/made/tools/build/defect/terrace/configuration.py +8 -0
- package/src/py/mat3ra/made/tools/build/defect/terrace/helpers.py +76 -0
- package/src/py/mat3ra/made/tools/build/defect/terrace/parameters.py +10 -0
- package/src/py/mat3ra/made/tools/build/grain_boundary/__init__.py +10 -23
- package/src/py/mat3ra/made/tools/build/grain_boundary/builders.py +36 -110
- package/src/py/mat3ra/made/tools/build/grain_boundary/configuration.py +76 -48
- package/src/py/mat3ra/made/tools/build/grain_boundary/helpers.py +153 -0
- package/src/py/mat3ra/made/tools/build/interface/__init__.py +12 -78
- package/src/py/mat3ra/made/tools/build/interface/builders.py +135 -390
- package/src/py/mat3ra/made/tools/build/interface/configuration.py +45 -94
- package/src/py/mat3ra/made/tools/build/interface/enums.py +0 -59
- package/src/py/mat3ra/made/tools/build/interface/helpers.py +445 -0
- package/src/py/mat3ra/made/tools/build/interface/utils.py +4 -15
- package/src/py/mat3ra/made/tools/build/lattice_lines/__init__.py +52 -0
- package/src/py/mat3ra/made/tools/build/lattice_lines/builders.py +75 -0
- package/src/py/mat3ra/made/tools/build/lattice_lines/configuration.py +62 -0
- package/src/py/mat3ra/made/tools/build/merge/__init__.py +6 -0
- package/src/py/mat3ra/made/tools/build/merge/builders.py +93 -0
- package/src/py/mat3ra/made/tools/build/merge/configuration.py +21 -0
- package/src/py/mat3ra/made/tools/build/metadata.py +39 -0
- package/src/py/mat3ra/made/tools/build/monolayer/__init__.py +0 -0
- package/src/py/mat3ra/made/tools/build/monolayer/builders.py +75 -0
- package/src/py/mat3ra/made/tools/build/monolayer/configurations.py +21 -0
- package/src/py/mat3ra/made/tools/build/monolayer/helpers.py +40 -0
- package/src/py/mat3ra/made/tools/build/nanoparticle/__init__.py +0 -19
- package/src/py/mat3ra/made/tools/build/nanoparticle/analyzer.py +86 -0
- package/src/py/mat3ra/made/tools/build/nanoparticle/builders.py +27 -64
- package/src/py/mat3ra/made/tools/build/nanoparticle/configuration.py +18 -76
- package/src/py/mat3ra/made/tools/build/nanoparticle/enums.py +1 -1
- package/src/py/mat3ra/made/tools/build/nanoparticle/helpers.py +119 -0
- package/src/py/mat3ra/made/tools/build/nanoribbon/__init__.py +21 -7
- package/src/py/mat3ra/made/tools/build/nanoribbon/builders.py +48 -150
- package/src/py/mat3ra/made/tools/build/nanoribbon/configuration.py +24 -28
- package/src/py/mat3ra/made/tools/build/nanoribbon/helpers.py +73 -0
- package/src/py/mat3ra/made/tools/build/nanotape/__init__.py +10 -0
- package/src/py/mat3ra/made/tools/build/nanotape/builders.py +83 -0
- package/src/py/mat3ra/made/tools/build/nanotape/configuration.py +35 -0
- package/src/py/mat3ra/made/tools/build/nanotape/helpers.py +60 -0
- package/src/py/mat3ra/made/tools/build/passivation/__init__.py +0 -60
- package/src/py/mat3ra/made/tools/build/passivation/analyzer.py +150 -0
- package/src/py/mat3ra/made/tools/build/passivation/builders.py +14 -218
- package/src/py/mat3ra/made/tools/build/passivation/configuration.py +16 -15
- package/src/py/mat3ra/made/tools/build/passivation/helpers.py +151 -0
- package/src/py/mat3ra/made/tools/build/perturbation/__init__.py +0 -37
- package/src/py/mat3ra/made/tools/build/perturbation/builders.py +33 -58
- package/src/py/mat3ra/made/tools/build/perturbation/configuration.py +5 -19
- package/src/py/mat3ra/made/tools/build/perturbation/helpers.py +42 -0
- package/src/py/mat3ra/made/tools/build/perturbation/parameters.py +5 -0
- package/src/py/mat3ra/made/tools/build/slab/__init__.py +0 -47
- package/src/py/mat3ra/made/tools/build/slab/build_parameters.py +8 -0
- package/src/py/mat3ra/made/tools/build/slab/builders.py +117 -99
- package/src/py/mat3ra/made/tools/build/slab/configurations/__init__.py +15 -0
- package/src/py/mat3ra/made/tools/build/slab/configurations/base_configurations.py +33 -0
- package/src/py/mat3ra/made/tools/build/slab/configurations/slab_configuration.py +89 -0
- package/src/py/mat3ra/made/tools/build/slab/configurations/strained_configurations.py +12 -0
- package/src/py/mat3ra/made/tools/build/slab/entities.py +57 -0
- package/src/py/mat3ra/made/tools/build/slab/helpers.py +140 -0
- package/src/py/mat3ra/made/tools/build/slab/termination_utils.py +14 -0
- package/src/py/mat3ra/made/tools/build/slab/utils.py +49 -0
- package/src/py/mat3ra/made/tools/build/stack/builders.py +69 -0
- package/src/py/mat3ra/made/tools/build/stack/configuration.py +25 -0
- package/src/py/mat3ra/made/tools/build/supercell.py +26 -14
- package/src/py/mat3ra/made/tools/build/utils.py +21 -108
- package/src/py/mat3ra/made/tools/build/vacuum/builders.py +36 -0
- package/src/py/mat3ra/made/tools/build/vacuum/configuration.py +16 -0
- package/src/py/mat3ra/made/tools/build/void_region/builders.py +29 -0
- package/src/py/mat3ra/made/tools/build/void_region/configuration.py +19 -0
- package/src/py/mat3ra/made/tools/calculate/__init__.py +15 -11
- package/src/py/mat3ra/made/tools/calculate/calculators.py +3 -3
- package/src/py/mat3ra/made/tools/convert/__init__.py +8 -7
- package/src/py/mat3ra/made/tools/enums.py +1 -1
- package/src/py/mat3ra/made/tools/modify.py +30 -24
- package/src/py/mat3ra/made/tools/operations/core/binary.py +106 -0
- package/src/py/mat3ra/made/tools/operations/core/unary.py +101 -0
- package/src/py/mat3ra/made/tools/operations/core/utils.py +138 -0
- package/src/py/mat3ra/made/tools/operations/reusable/unary.py +28 -0
- package/src/py/mat3ra/made/tools/site.py +8 -3
- package/src/py/mat3ra/made/tools/utils/__init__.py +42 -17
- package/src/py/mat3ra/made/tools/utils/coordinate.py +2 -2
- package/src/py/mat3ra/made/tools/utils/functions.py +73 -23
- package/src/py/mat3ra/made/tools/utils/perturbation.py +3 -71
- package/src/py/mat3ra/made/utils.py +63 -48
- package/{src/py/mat3ra/made → tests/py/mat3ra}/debug_utils.py +5 -3
- package/tests/py/unit/fixtures/adatom.py +60 -0
- package/tests/py/unit/fixtures/bulk.py +152 -0
- package/tests/py/unit/fixtures/generated/fixtures.py +72 -74
- package/tests/py/unit/fixtures/grain_boundary.py +563 -0
- package/tests/py/unit/fixtures/interface/commensurate.py +498 -0
- package/tests/py/unit/fixtures/interface/gr_ni_111_top_hcp.py +102 -0
- package/tests/py/unit/fixtures/interface/simple.py +84 -0
- package/tests/py/unit/fixtures/interface/twisted_nanoribbons.py +131 -0
- package/tests/py/unit/fixtures/interface/zsl.py +540 -0
- package/tests/py/unit/fixtures/island.py +336 -0
- package/tests/py/unit/fixtures/merge.py +147 -0
- package/tests/py/unit/fixtures/monolayer.py +26 -0
- package/tests/py/unit/fixtures/nanoparticle.py +116 -0
- package/tests/py/unit/fixtures/nanoribbon/__init__.py +0 -0
- package/tests/py/unit/fixtures/nanoribbon/armchair.py +102 -0
- package/tests/py/unit/fixtures/{nanoribbon.py → nanoribbon/nanoribbon.py} +22 -103
- package/tests/py/unit/fixtures/nanoribbon/zigzag.py +54 -0
- package/tests/py/unit/fixtures/pair_defects.py +41 -0
- package/tests/py/unit/fixtures/passivated/nanoribbon.py +69 -0
- package/tests/py/unit/fixtures/passivated/slab.py +58 -0
- package/tests/py/unit/fixtures/point_defects.py +158 -0
- package/tests/py/unit/fixtures/slab.py +433 -127
- package/tests/py/unit/fixtures/{cell.py → strain.py} +4 -31
- package/tests/py/unit/fixtures/supercell.py +22 -0
- package/tests/py/unit/fixtures/terrace.py +70 -0
- package/tests/py/unit/test_analyze_lattice_planes.py +131 -0
- package/tests/py/unit/test_material.py +3 -3
- package/tests/py/unit/test_operations.py +80 -0
- package/tests/py/unit/test_tools_analyze.py +176 -17
- package/tests/py/unit/test_tools_analyze_interface.py +130 -0
- package/tests/py/unit/test_tools_analyze_interface_zsl.py +165 -0
- package/tests/py/unit/test_tools_build.py +135 -16
- package/tests/py/unit/test_tools_build_defect/__init__.py +0 -0
- package/tests/py/unit/test_tools_build_defect/test_adatom.py +72 -0
- package/tests/py/unit/test_tools_build_defect/test_island.py +43 -0
- package/tests/py/unit/test_tools_build_defect/test_pair_defect.py +80 -0
- package/tests/py/unit/test_tools_build_defect/test_point_defect.py +139 -0
- package/tests/py/unit/test_tools_build_defect/test_slab_stack.py +27 -0
- package/tests/py/unit/test_tools_build_defect/test_terrace.py +42 -0
- package/tests/py/unit/test_tools_build_grain_boundary.py +97 -75
- package/tests/py/unit/test_tools_build_interface.py +198 -68
- package/tests/py/unit/test_tools_build_interface_zsl.py +156 -0
- package/tests/py/unit/test_tools_build_metadata.py +80 -0
- package/tests/py/unit/test_tools_build_monolayer.py +34 -0
- package/tests/py/unit/test_tools_build_nanoparticle.py +39 -0
- package/tests/py/unit/test_tools_build_nanoribbon.py +31 -22
- package/tests/py/unit/test_tools_build_passivation.py +75 -32
- package/tests/py/unit/test_tools_build_perturbation.py +80 -28
- package/tests/py/unit/test_tools_build_slab.py +335 -25
- package/tests/py/unit/test_tools_build_supercell.py +20 -4
- package/tests/py/unit/test_tools_calculate.py +3 -2
- package/tests/py/unit/test_tools_convert.py +46 -9
- package/tests/py/unit/test_tools_modify.py +35 -27
- package/tests/py/unit/utils.py +39 -4
- package/src/py/mat3ra/made/tools/build/defect/builders.py +0 -767
- package/src/py/mat3ra/made/tools/build/defect/configuration.py +0 -342
- package/src/py/mat3ra/made/tools/build/interface/commensurate_lattice_pair.py +0 -26
- package/src/py/mat3ra/made/tools/build/interface/termination_pair.py +0 -63
- package/src/py/mat3ra/made/tools/build/nanoribbon/enums.py +0 -10
- package/src/py/mat3ra/made/tools/build/slab/configuration.py +0 -42
- package/src/py/mat3ra/made/tools/build/slab/termination.py +0 -24
- package/tests/py/unit/fixtures/interface.py +0 -121
- package/tests/py/unit/test_tools_build_defect.py +0 -224
package/README.md
CHANGED
|
@@ -178,6 +178,13 @@ pip install ".[tests]"
|
|
|
178
178
|
pytest tests/py
|
|
179
179
|
```
|
|
180
180
|
|
|
181
|
+
To visualize material from a debugger, run the following command in the "Evaluate expression" console:
|
|
182
|
+
|
|
183
|
+
```python
|
|
184
|
+
from mat3ra.debug_utils import debug_visualize_material; debug_visualize_material(material)
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
|
|
181
188
|
#### 5.2.2. Important Notes
|
|
182
189
|
|
|
183
190
|
Conventions:
|
package/dist/js/material.d.ts
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
|
-
import { HasConsistencyChecksHasMetadataNamedDefaultableInMemoryEntity } from "@mat3ra/code/dist/js/entity";
|
|
2
1
|
import type { ConsistencyCheck, MaterialSchema } from "@mat3ra/esse/dist/js/types";
|
|
3
2
|
import { type MaterialMixinConstructor, defaultMaterialConfig } from "./materialMixin";
|
|
4
3
|
export { defaultMaterialConfig };
|
|
5
|
-
declare const BaseInMemoryEntity: typeof
|
|
4
|
+
declare const BaseInMemoryEntity: typeof import("@mat3ra/code/dist/js/entity").InMemoryEntity & import("@mat3ra/code/dist/js/entity/mixins/DefaultableMixin").DefaultableInMemoryEntityConstructor & {
|
|
5
|
+
createDefault<T extends import("@mat3ra/code/dist/js/utils/types").Constructor<import("@mat3ra/code/dist/js/entity").InMemoryEntity> & {
|
|
6
|
+
defaultConfig?: object | null | undefined;
|
|
7
|
+
}>(this: T): InstanceType<T> & {
|
|
8
|
+
isDefault: boolean;
|
|
9
|
+
};
|
|
10
|
+
} & import("@mat3ra/code/dist/js/entity/mixins/NamedEntityMixin").NamedInMemoryEntityConstructor & import("@mat3ra/code/dist/js/entity/mixins/HasMetadataMixin").HasMetadataInMemoryEntityConstructor & import("@mat3ra/code/dist/js/entity/mixins/HasConsistencyChecksMixin").HasConsistencyChecksInMemoryEntityConstructor;
|
|
6
11
|
type BaseMaterial = MaterialMixinConstructor & typeof BaseInMemoryEntity;
|
|
7
12
|
type MaterialSchemaWithConsistencyChecksAsString = Omit<MaterialSchema, "consistencyChecks"> & {
|
|
8
13
|
consistencyChecks?: ConsistencyCheck[];
|
package/package.json
CHANGED
package/pyproject.toml
CHANGED
|
@@ -19,8 +19,9 @@ dependencies = [
|
|
|
19
19
|
# new verison of numpy==2.0.0 is not handled by pymatgen yet
|
|
20
20
|
"numpy<=1.26.4",
|
|
21
21
|
"mat3ra-utils",
|
|
22
|
-
"mat3ra-esse
|
|
23
|
-
"mat3ra-code
|
|
22
|
+
"mat3ra-esse>=2025.7.29.post0",
|
|
23
|
+
"mat3ra-code"
|
|
24
|
+
|
|
24
25
|
]
|
|
25
26
|
|
|
26
27
|
[project.optional-dependencies]
|
|
@@ -47,6 +48,7 @@ tests = [
|
|
|
47
48
|
"gradio",
|
|
48
49
|
"pydantic",
|
|
49
50
|
"mat3ra-made[tools]",
|
|
51
|
+
"mat3ra-standata"
|
|
50
52
|
]
|
|
51
53
|
all = [
|
|
52
54
|
"mat3ra-made[tests]",
|
|
@@ -1,12 +1,33 @@
|
|
|
1
1
|
from typing import Any, Dict, List, Optional, Union
|
|
2
2
|
|
|
3
|
+
import numpy as np
|
|
3
4
|
from mat3ra.code.array_with_ids import ArrayWithIds
|
|
4
5
|
from mat3ra.code.entity import InMemoryEntityPydantic
|
|
6
|
+
from mat3ra.esse.models.core.abstract.matrix_3x3 import Matrix3x3Schema
|
|
5
7
|
from mat3ra.esse.models.material import BasisSchema, BasisUnitsEnum
|
|
6
8
|
from mat3ra.made.basis.coordinates import Coordinates
|
|
7
9
|
from mat3ra.made.cell import Cell
|
|
8
|
-
from mat3ra.made.utils import get_overlapping_coordinates
|
|
9
10
|
from pydantic import Field
|
|
11
|
+
from scipy.spatial import cKDTree
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def get_overlapping_coordinates(
|
|
15
|
+
coordinate: List[float],
|
|
16
|
+
coordinates: List[List[float]],
|
|
17
|
+
threshold: float = 0.01,
|
|
18
|
+
) -> List[List[float]]:
|
|
19
|
+
"""
|
|
20
|
+
Find coordinates that are within a certain threshold of a given coordinate.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
coordinate (List[float]): The coordinate.
|
|
24
|
+
coordinates (List[List[float]]): The list of coordinates.
|
|
25
|
+
threshold (float): The threshold for the distance, in the units of the coordinates.
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
List[List[float]]: The list of overlapping coordinates.
|
|
29
|
+
"""
|
|
30
|
+
return [c for c in coordinates if np.linalg.norm(np.array(c) - np.array(coordinate)) < threshold]
|
|
10
31
|
|
|
11
32
|
|
|
12
33
|
class Basis(BasisSchema, InMemoryEntityPydantic):
|
|
@@ -15,6 +36,9 @@ class Basis(BasisSchema, InMemoryEntityPydantic):
|
|
|
15
36
|
cell: Cell = Field(Cell(), exclude=True)
|
|
16
37
|
labels: ArrayWithIds = Field(ArrayWithIds.from_values([]))
|
|
17
38
|
constraints: ArrayWithIds = Field(ArrayWithIds.from_values([]))
|
|
39
|
+
DEFAULT_COORDINATE_PROXIMITY_TOLERANCE: float = Field(
|
|
40
|
+
0.1, exclude=True
|
|
41
|
+
) # Angstroms, used for checking overlapping coordinates
|
|
18
42
|
|
|
19
43
|
def __convert_kwargs__(self, **kwargs: Any) -> Dict[str, Any]:
|
|
20
44
|
if isinstance(kwargs.get("elements"), list):
|
|
@@ -33,6 +57,17 @@ class Basis(BasisSchema, InMemoryEntityPydantic):
|
|
|
33
57
|
kwargs = self.__convert_kwargs__(**kwargs)
|
|
34
58
|
super().__init__(*args, **kwargs)
|
|
35
59
|
|
|
60
|
+
@property
|
|
61
|
+
def coordinates_as_kdtree(self):
|
|
62
|
+
return cKDTree(np.array(self.coordinates.values))
|
|
63
|
+
|
|
64
|
+
def get_coordinates_colliding_pairs(self, tolerance=DEFAULT_COORDINATE_PROXIMITY_TOLERANCE):
|
|
65
|
+
return self.coordinates_as_kdtree.query_pairs(r=tolerance)
|
|
66
|
+
|
|
67
|
+
@property
|
|
68
|
+
def number_of_atoms(self) -> int:
|
|
69
|
+
return len(self.elements.values)
|
|
70
|
+
|
|
36
71
|
@classmethod
|
|
37
72
|
def from_dict(
|
|
38
73
|
cls,
|
|
@@ -80,9 +115,7 @@ class Basis(BasisSchema, InMemoryEntityPydantic):
|
|
|
80
115
|
force: bool = False,
|
|
81
116
|
):
|
|
82
117
|
"""
|
|
83
|
-
Add an atom to the basis.
|
|
84
|
-
|
|
85
|
-
Before adding the atom at the specified coordinate, checks that no other atom is overlapping within a threshold.
|
|
118
|
+
Add an atom to the basis at a specified coordinate. Check that no other atom is overlapping with it.
|
|
86
119
|
|
|
87
120
|
Args:
|
|
88
121
|
element (str): Element symbol of the atom to be added.
|
|
@@ -128,15 +161,61 @@ class Basis(BasisSchema, InMemoryEntityPydantic):
|
|
|
128
161
|
self.coordinates.remove_item(id)
|
|
129
162
|
self.labels.remove_item(id)
|
|
130
163
|
|
|
131
|
-
def
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
164
|
+
def remove_atoms_by_elements(self, values: Union[List[str], str]) -> "Basis":
|
|
165
|
+
if isinstance(values, str):
|
|
166
|
+
values = [values]
|
|
167
|
+
ids_to_remove = [
|
|
168
|
+
id_ for value in values for id_, v in zip(self.elements.ids, self.elements.values) if v == value
|
|
169
|
+
]
|
|
170
|
+
self.filter_atoms_by_ids(ids_to_remove, invert=True)
|
|
171
|
+
return self
|
|
172
|
+
|
|
173
|
+
def filter_atoms_by_ids(self, ids: Union[List[int], int], invert: bool = False, reset_ids=False) -> "Basis":
|
|
174
|
+
self.elements.filter_by_ids(ids, invert, reset_ids=reset_ids)
|
|
175
|
+
self.coordinates.filter_by_ids(ids, invert, reset_ids=reset_ids)
|
|
176
|
+
self.labels.filter_by_ids(ids, invert, reset_ids=reset_ids)
|
|
135
177
|
return self
|
|
136
178
|
|
|
137
179
|
def filter_atoms_by_labels(self, labels: Union[List[str], str]) -> "Basis":
|
|
180
|
+
labels = [int(label) if isinstance(label, str) else label for label in labels]
|
|
138
181
|
self.labels.filter_by_values(labels)
|
|
139
182
|
ids = self.labels.ids
|
|
140
183
|
self.elements.filter_by_ids(ids)
|
|
141
184
|
self.coordinates.filter_by_ids(ids)
|
|
142
185
|
return self
|
|
186
|
+
|
|
187
|
+
def set_labels_from_list(self, labels: List[Union[int, str]]) -> None:
|
|
188
|
+
num_atoms = len(self.elements.values)
|
|
189
|
+
|
|
190
|
+
if len(labels) != num_atoms:
|
|
191
|
+
raise ValueError(f"Number of labels ({len(labels)}) must match number of atoms ({num_atoms})")
|
|
192
|
+
|
|
193
|
+
self.labels = ArrayWithIds.from_values(values=list(labels))
|
|
194
|
+
|
|
195
|
+
def transform_by_matrix(self, matrix: Matrix3x3Schema) -> None:
|
|
196
|
+
original_is_in_crystal_units = self.is_in_crystal_units
|
|
197
|
+
self.to_crystal()
|
|
198
|
+
matrix_np = np.array(matrix)
|
|
199
|
+
new_coordinates = np.dot(self.coordinates.values, matrix_np)
|
|
200
|
+
self.coordinates.values = new_coordinates.tolist()
|
|
201
|
+
if not original_is_in_crystal_units:
|
|
202
|
+
self.to_cartesian()
|
|
203
|
+
|
|
204
|
+
# TODO: add/update test for this method
|
|
205
|
+
def resolve_colliding_coordinates(self, tolerance=DEFAULT_COORDINATE_PROXIMITY_TOLERANCE):
|
|
206
|
+
"""
|
|
207
|
+
Find all atoms that are within distance tolerance and only keep the last one, remove other sites.
|
|
208
|
+
|
|
209
|
+
Args:
|
|
210
|
+
tolerance (float): The distance tolerance in angstroms.
|
|
211
|
+
"""
|
|
212
|
+
original_is_in_crystal = self.is_in_crystal_units
|
|
213
|
+
self.to_cartesian()
|
|
214
|
+
ids_to_remove = set()
|
|
215
|
+
atom_ids = self.coordinates.ids
|
|
216
|
+
for index_1, index_2 in self.get_coordinates_colliding_pairs(tolerance):
|
|
217
|
+
ids_to_remove.add(atom_ids[index_1]) # Keep the last one in the pair
|
|
218
|
+
|
|
219
|
+
self.filter_atoms_by_ids(list(ids_to_remove), invert=True, reset_ids=True)
|
|
220
|
+
if original_is_in_crystal:
|
|
221
|
+
self.to_crystal()
|
|
@@ -3,14 +3,13 @@ from typing import List, Optional
|
|
|
3
3
|
|
|
4
4
|
import numpy as np
|
|
5
5
|
from mat3ra.code.entity import InMemoryEntityPydantic
|
|
6
|
-
from mat3ra.esse.models.properties_directory.structural.lattice
|
|
7
|
-
|
|
8
|
-
)
|
|
9
|
-
from mat3ra.esse.models.properties_directory.structural.lattice.lattice_bravais import (
|
|
6
|
+
from mat3ra.esse.models.properties_directory.structural.lattice import (
|
|
7
|
+
LatticeSchema,
|
|
10
8
|
LatticeTypeEnum,
|
|
11
9
|
LatticeUnitsSchema,
|
|
12
10
|
)
|
|
13
11
|
from mat3ra.utils.mixins import RoundNumericValuesMixin
|
|
12
|
+
from pydantic import BaseModel
|
|
14
13
|
|
|
15
14
|
from .cell import Cell
|
|
16
15
|
|
|
@@ -21,10 +20,23 @@ class LatticeVectors(Cell):
|
|
|
21
20
|
pass
|
|
22
21
|
|
|
23
22
|
|
|
24
|
-
class
|
|
23
|
+
class LatticeSchemaVectorless(BaseModel):
|
|
24
|
+
"""LatticeSchema without the vectors field to avoid conflicts."""
|
|
25
|
+
|
|
26
|
+
a: float
|
|
27
|
+
b: float
|
|
28
|
+
c: float
|
|
29
|
+
alpha: float
|
|
30
|
+
beta: float
|
|
31
|
+
gamma: float
|
|
32
|
+
units: LatticeUnitsSchema = LatticeSchema.model_fields["units"].default_factory()
|
|
33
|
+
type: LatticeTypeEnum = LatticeSchema.model_fields["type"].default
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class Lattice(RoundNumericValuesMixin, LatticeSchemaVectorless, InMemoryEntityPydantic):
|
|
25
37
|
__types__ = LatticeTypeEnum
|
|
26
|
-
__type_default__ =
|
|
27
|
-
__units_default__ =
|
|
38
|
+
__type_default__ = LatticeSchema.model_fields["type"].default
|
|
39
|
+
__units_default__ = LatticeSchema.model_fields["units"].default_factory()
|
|
28
40
|
|
|
29
41
|
a: float = 1.0
|
|
30
42
|
b: float = a
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import Any, List
|
|
1
|
+
from typing import Any, List, Union
|
|
2
2
|
|
|
3
3
|
from mat3ra.code.constants import AtomicCoordinateUnits, Units
|
|
4
4
|
from mat3ra.code.entity import HasDescriptionHasMetadataNamedDefaultableInMemoryEntityPydantic
|
|
@@ -63,6 +63,12 @@ class Material(MaterialSchema, HasDescriptionHasMetadataNamedDefaultableInMemory
|
|
|
63
63
|
self.name: str = self.formula
|
|
64
64
|
self.basis.cell = self.lattice.vectors
|
|
65
65
|
|
|
66
|
+
@classmethod
|
|
67
|
+
def create_from_config_or_class_instance(cls, config_or_instance: Union[dict, "Material"]) -> "Material":
|
|
68
|
+
if isinstance(config_or_instance, cls):
|
|
69
|
+
return config_or_instance
|
|
70
|
+
return cls.create(config_or_instance)
|
|
71
|
+
|
|
66
72
|
@property
|
|
67
73
|
def coordinates_array(self) -> List[List[float]]:
|
|
68
74
|
return self.basis.coordinates.values
|
|
@@ -76,7 +82,7 @@ class Material(MaterialSchema, HasDescriptionHasMetadataNamedDefaultableInMemory
|
|
|
76
82
|
def set_coordinates(self, coordinates: List[List[float]]) -> None:
|
|
77
83
|
self.basis.coordinates.values = coordinates
|
|
78
84
|
|
|
79
|
-
def
|
|
85
|
+
def set_lattice_vectors(
|
|
80
86
|
self, lattice_vector1: List[float], lattice_vector2: List[float], lattice_vector3: List[float]
|
|
81
87
|
) -> None:
|
|
82
88
|
original_is_in_crystal_units = self.basis.is_in_crystal_units
|
|
@@ -86,8 +92,19 @@ class Material(MaterialSchema, HasDescriptionHasMetadataNamedDefaultableInMemory
|
|
|
86
92
|
if original_is_in_crystal_units:
|
|
87
93
|
self.to_crystal()
|
|
88
94
|
|
|
95
|
+
def set_lattice_vectors_from_array(self, lattice_vectors: List[List[float]]) -> None:
|
|
96
|
+
if len(lattice_vectors) != 3:
|
|
97
|
+
raise ValueError("Lattice vectors array must contain exactly three vectors.")
|
|
98
|
+
self.set_lattice_vectors(*lattice_vectors)
|
|
99
|
+
|
|
89
100
|
def set_lattice(self, lattice: Lattice) -> None:
|
|
90
|
-
self.
|
|
101
|
+
self.set_lattice_vectors(*lattice.vector_arrays)
|
|
91
102
|
|
|
92
103
|
def add_atom(self, element: str, coordinate: List[float], use_cartesian_coordinates: bool = False) -> None:
|
|
93
104
|
self.basis.add_atom(element, coordinate, use_cartesian_coordinates)
|
|
105
|
+
|
|
106
|
+
def set_labels_from_list(self, labels: List[Union[int, str]]) -> None:
|
|
107
|
+
self.basis.set_labels_from_list(labels)
|
|
108
|
+
|
|
109
|
+
def set_labels_from_value(self, value: Union[int, str]) -> None:
|
|
110
|
+
self.basis.set_labels_from_list([value] * self.basis.number_of_atoms)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from mat3ra.code.entity import InMemoryEntityPydantic
|
|
5
|
+
from pydantic import model_validator
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def to_dict(obj: Any) -> dict:
|
|
9
|
+
if isinstance(obj, dict):
|
|
10
|
+
return obj
|
|
11
|
+
if hasattr(obj, "to_dict") and callable(obj.to_dict):
|
|
12
|
+
return obj.to_dict()
|
|
13
|
+
# TODO: remove conditional checks below when all configurations moved to Pydantic
|
|
14
|
+
if hasattr(obj, "to_json") and callable(obj.to_json):
|
|
15
|
+
data = obj.to_json()
|
|
16
|
+
if isinstance(data, str):
|
|
17
|
+
return json.loads(data)
|
|
18
|
+
return data
|
|
19
|
+
return {}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class BaseMetadata(InMemoryEntityPydantic):
|
|
23
|
+
model_config = {"arbitrary_types_allowed": True, "extra": "allow"}
|
|
24
|
+
|
|
25
|
+
@model_validator(mode="before")
|
|
26
|
+
def convert_fields_to_dict(cls, values):
|
|
27
|
+
for key, value in values.items():
|
|
28
|
+
field = cls.model_fields.get(key)
|
|
29
|
+
if field and field.annotation is dict and value is None:
|
|
30
|
+
values[key] = {}
|
|
31
|
+
elif field and field.annotation is dict and value is not None and not isinstance(value, dict):
|
|
32
|
+
values[key] = to_dict(value)
|
|
33
|
+
elif isinstance(value, InMemoryEntityPydantic):
|
|
34
|
+
values[key] = to_dict(value)
|
|
35
|
+
elif hasattr(value, "to_dict") and callable(value.to_dict):
|
|
36
|
+
values[key] = to_dict(value)
|
|
37
|
+
# TODO: remove when Pydantic configurations are fully implemented
|
|
38
|
+
elif hasattr(value, "to_json") and callable(value.to_json):
|
|
39
|
+
values[key] = to_dict(value)
|
|
40
|
+
return values
|
|
41
|
+
|
|
42
|
+
def update(self, **kwargs: Any):
|
|
43
|
+
for key, value in kwargs.items():
|
|
44
|
+
if hasattr(self, key) and isinstance(getattr(self, key), dict):
|
|
45
|
+
getattr(self, key).update(to_dict(value))
|
|
@@ -1,12 +1,17 @@
|
|
|
1
|
+
from typing import Union
|
|
2
|
+
|
|
1
3
|
import numpy as np
|
|
2
4
|
from mat3ra.made.material import Material
|
|
5
|
+
from pydantic import BaseModel
|
|
3
6
|
from scipy.spatial.distance import pdist
|
|
4
7
|
|
|
8
|
+
from ..build import MaterialWithBuildMetadata
|
|
9
|
+
from .other import get_chemical_formula_empirical
|
|
10
|
+
from .utils import decorator_perform_operation_in_cartesian_coordinates
|
|
11
|
+
|
|
5
12
|
|
|
6
|
-
class BaseMaterialAnalyzer:
|
|
7
|
-
|
|
8
|
-
self.material = material.clone()
|
|
9
|
-
self.material.to_cartesian()
|
|
13
|
+
class BaseMaterialAnalyzer(BaseModel):
|
|
14
|
+
material: Union[Material, MaterialWithBuildMetadata]
|
|
10
15
|
|
|
11
16
|
@property
|
|
12
17
|
def volume(self):
|
|
@@ -17,5 +22,10 @@ class BaseMaterialAnalyzer:
|
|
|
17
22
|
return len(self.material.coordinates_array) / self.volume
|
|
18
23
|
|
|
19
24
|
@property
|
|
25
|
+
@decorator_perform_operation_in_cartesian_coordinates
|
|
20
26
|
def pairwise_distances(self):
|
|
21
27
|
return pdist(np.array(self.material.coordinates_array))
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def formula(self):
|
|
31
|
+
return get_chemical_formula_empirical(self.material)
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
|
|
3
|
+
from mat3ra.esse.models.materials_category_components.entities.core.zero_dimensional.atom import AtomSchema
|
|
4
|
+
from mat3ra.made.material import Material
|
|
5
|
+
from mat3ra.made.tools.analyze.crystal_site import CrystalSiteAnalyzer
|
|
6
|
+
from mat3ra.made.tools.analyze.slab import SlabMaterialAnalyzer
|
|
7
|
+
from mat3ra.made.tools.build import MaterialWithBuildMetadata
|
|
8
|
+
from mat3ra.made.tools.build.defect.point.builders import AtomAtCoordinateBuilder, AtomAtCoordinateConfiguration
|
|
9
|
+
from mat3ra.made.tools.build.defect.slab.helpers import recreate_slab_with_fractional_layers
|
|
10
|
+
from mat3ra.made.tools.build.slab.builders import SlabBuilder
|
|
11
|
+
from mat3ra.made.tools.build.vacuum.builders import VacuumBuilder
|
|
12
|
+
from mat3ra.made.tools.build.vacuum.configuration import VacuumConfiguration
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class AdatomMaterialAnalyzer(SlabMaterialAnalyzer):
|
|
16
|
+
distance_z: float
|
|
17
|
+
coordinate_2d: List[float] # Add coordinate property
|
|
18
|
+
element: str
|
|
19
|
+
|
|
20
|
+
@property
|
|
21
|
+
def added_component_height(self) -> float:
|
|
22
|
+
return self.layer_thickness
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def added_component_prototype(self) -> MaterialWithBuildMetadata:
|
|
26
|
+
vacuum_configuration = VacuumConfiguration(crystal=self.material, size=self.added_component_height)
|
|
27
|
+
vacuum_material = VacuumBuilder().get_material(vacuum_configuration)
|
|
28
|
+
return vacuum_material
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def coordinate_in_added_component(self) -> List[float]:
|
|
32
|
+
coordinate_3d = self.coordinate_2d + [0]
|
|
33
|
+
coordinate_3d_cartesian = self.material.basis.cell.convert_point_to_cartesian(coordinate_3d)
|
|
34
|
+
coordinate_3d_cartesian[2] = self.distance_z
|
|
35
|
+
coordinate_3d_crystal = self.added_component_prototype.basis.cell.convert_point_to_crystal(
|
|
36
|
+
coordinate_3d_cartesian
|
|
37
|
+
)
|
|
38
|
+
return coordinate_3d_crystal
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def added_component(self) -> MaterialWithBuildMetadata:
|
|
42
|
+
atom_configuration = AtomAtCoordinateConfiguration(
|
|
43
|
+
crystal=self.added_component_prototype,
|
|
44
|
+
element=AtomSchema(chemical_element=self.element),
|
|
45
|
+
coordinate=self.coordinate_in_added_component,
|
|
46
|
+
)
|
|
47
|
+
return AtomAtCoordinateBuilder().get_material(atom_configuration)
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def slab_material_or_configuration_for_stacking(self) -> MaterialWithBuildMetadata:
|
|
51
|
+
return self._slab_with_no_gap
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class AdatomCrystalSiteMaterialAnalyzer(AdatomMaterialAnalyzer):
|
|
55
|
+
DEFAULT_NUMBER_OF_LAYERS: float = 1
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def added_component_prototype(self) -> Material:
|
|
59
|
+
# Recreate the slab with a single layer to ensure the adatom is placed correctly
|
|
60
|
+
return recreate_slab_with_fractional_layers(self.material, self.DEFAULT_NUMBER_OF_LAYERS)
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def coordinate_in_added_component(self) -> List[float]:
|
|
64
|
+
approximate_coordinate_3d = super().coordinate_in_added_component
|
|
65
|
+
crystal_site_analyzer = CrystalSiteAnalyzer(
|
|
66
|
+
material=self.added_component_prototype,
|
|
67
|
+
coordinate=approximate_coordinate_3d,
|
|
68
|
+
)
|
|
69
|
+
return crystal_site_analyzer.closest_site_coordinate
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def slab_material_or_configuration_for_stacking(self) -> MaterialWithBuildMetadata:
|
|
73
|
+
config = self.slab_configuration_with_no_vacuum
|
|
74
|
+
params = self.build_parameters
|
|
75
|
+
return SlabBuilder(build_parameters=params).get_material(config)
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
|
|
3
|
+
from ...utils import get_center_of_coordinates
|
|
4
|
+
from ..build.supercell import create_supercell
|
|
5
|
+
from ..convert import to_pymatgen
|
|
6
|
+
from ..modify import filter_by_condition_on_coordinates
|
|
7
|
+
from ..third_party import PymatgenVoronoiInterstitialGenerator
|
|
8
|
+
from ..utils import get_distance_between_coordinates, transform_coordinate_to_supercell
|
|
9
|
+
from . import BaseMaterialAnalyzer
|
|
10
|
+
from .coordination import get_voronoi_nearest_neighbors_atom_indices
|
|
11
|
+
from .other import get_closest_site_id_from_coordinate
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class CrystalSiteAnalyzer(BaseMaterialAnalyzer):
|
|
15
|
+
coordinate: List[float] = [0.0, 0.0, 0.0]
|
|
16
|
+
|
|
17
|
+
@property
|
|
18
|
+
def exact_coordinate(self) -> List[float]:
|
|
19
|
+
return self.coordinate
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
def closest_site_coordinate(self) -> List[float]:
|
|
23
|
+
site_id = get_closest_site_id_from_coordinate(self.material, self.coordinate)
|
|
24
|
+
return self.material.coordinates_array[site_id]
|
|
25
|
+
|
|
26
|
+
def get_equidistant_coordinate(self, coordinate=None) -> List[float]:
|
|
27
|
+
"""
|
|
28
|
+
Compute a coordinate that is equidistant from the nearest atoms to the target coordinate. Useful for adatom.
|
|
29
|
+
|
|
30
|
+
This method works by:
|
|
31
|
+
1. Creating a 3x3x1 supercell of the original material to include atoms in PBC.
|
|
32
|
+
2. Transforming the target coordinate into the supercell's coordinate system.
|
|
33
|
+
3. Using Voronoi tessellation to find the indices of the nearest atoms in the supercell.
|
|
34
|
+
4. Taking the geometric center (average) of these neighboring atoms' coordinates as the equidistant point.
|
|
35
|
+
5. Setting the z-coordinate to the original value (useful for 2D/slab systems).
|
|
36
|
+
6. Transforming the equidistant coordinate back to the original cell's coordinate system.
|
|
37
|
+
|
|
38
|
+
The [3, 3, 1] supercell ensures robust neighbor search in x and y, but not in z (for 2D/slab systems).
|
|
39
|
+
"""
|
|
40
|
+
if coordinate is None:
|
|
41
|
+
coordinate = self.coordinate
|
|
42
|
+
scaling_factor = [3, 3, 1]
|
|
43
|
+
translation_vector = [1 / 3, 1 / 3, 0]
|
|
44
|
+
supercell_material = create_supercell(self.material, scaling_factor=scaling_factor)
|
|
45
|
+
|
|
46
|
+
coordinate_in_supercell = transform_coordinate_to_supercell(
|
|
47
|
+
coordinate=coordinate, scaling_factor=scaling_factor, translation_vector=translation_vector
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
neighboring_atoms_ids_in_supercell = get_voronoi_nearest_neighbors_atom_indices(
|
|
51
|
+
material=supercell_material, coordinate=coordinate_in_supercell
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
if neighboring_atoms_ids_in_supercell is None:
|
|
55
|
+
raise ValueError("No neighboring atoms found for equidistant calculation.")
|
|
56
|
+
|
|
57
|
+
# Filter out atoms that are too close to the z boundaries of the supercell
|
|
58
|
+
supercell_material = filter_by_condition_on_coordinates(supercell_material, lambda c: 1e-2 < c[2] < 1 - 1e-2)
|
|
59
|
+
isolated_neighboring_atoms_basis = supercell_material.basis.model_copy()
|
|
60
|
+
isolated_neighboring_atoms_basis.coordinates.filter_by_ids(neighboring_atoms_ids_in_supercell)
|
|
61
|
+
equidistant_coordinate_in_supercell = get_center_of_coordinates(
|
|
62
|
+
isolated_neighboring_atoms_basis.coordinates.values
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
return transform_coordinate_to_supercell(
|
|
66
|
+
equidistant_coordinate_in_supercell, scaling_factor, translation_vector, reverse=True
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class VoronoiCrystalSiteAnalyzer(CrystalSiteAnalyzer):
|
|
71
|
+
"""
|
|
72
|
+
Attributes:
|
|
73
|
+
clustering_tol: Tolerance for clustering the Voronoi nodes.
|
|
74
|
+
min_dist: Minimum distance between an interstitial and the nearest atom.
|
|
75
|
+
ltol: Tolerance for lattice matching.
|
|
76
|
+
stol: Tolerance for structure matching.
|
|
77
|
+
angle_tol: Angle tolerance for structure matching.
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
clustering_tol: float = 0.5
|
|
81
|
+
min_dist: float = 0.9
|
|
82
|
+
ltol: float = 0.2
|
|
83
|
+
stol: float = 0.3
|
|
84
|
+
angle_tol: float = 5
|
|
85
|
+
|
|
86
|
+
@property
|
|
87
|
+
def voronoi_site_coordinate(self) -> List[float]:
|
|
88
|
+
pymatgen_structure = to_pymatgen(self.material)
|
|
89
|
+
|
|
90
|
+
voronoi_gen = PymatgenVoronoiInterstitialGenerator(
|
|
91
|
+
clustering_tol=self.clustering_tol,
|
|
92
|
+
min_dist=self.min_dist,
|
|
93
|
+
ltol=self.ltol,
|
|
94
|
+
stol=self.stol,
|
|
95
|
+
angle_tol=self.angle_tol,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
interstitials = list(voronoi_gen.generate(structure=pymatgen_structure, insert_species=["Si"]))
|
|
99
|
+
|
|
100
|
+
if not interstitials:
|
|
101
|
+
raise ValueError("No Voronoi interstitial sites found.")
|
|
102
|
+
|
|
103
|
+
closest_interstitial = min(
|
|
104
|
+
interstitials,
|
|
105
|
+
key=lambda interstitial: get_distance_between_coordinates(interstitial.site.frac_coords, self.coordinate),
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
return closest_interstitial.site.frac_coords.tolist()
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from mat3ra.made.tools.analyze.interface.commensurate import (
|
|
2
|
+
CommensurateLatticeInterfaceAnalyzer,
|
|
3
|
+
CommensurateLatticeMatchHolder,
|
|
4
|
+
)
|
|
5
|
+
from mat3ra.made.tools.analyze.interface.grain_boundary import (
|
|
6
|
+
GrainBoundaryPlanarAnalyzer,
|
|
7
|
+
GrainBoundaryPlanarMatchHolder,
|
|
8
|
+
)
|
|
9
|
+
from mat3ra.made.tools.analyze.interface.simple import InterfaceAnalyzer
|
|
10
|
+
from mat3ra.made.tools.analyze.interface.twisted_nanoribbons import TwistedNanoribbonsInterfaceAnalyzer
|
|
11
|
+
from mat3ra.made.tools.analyze.interface.utils.holders import MatchedSubstrateFilmConfigurationHolder
|
|
12
|
+
from mat3ra.made.tools.analyze.interface.zsl import ZSLInterfaceAnalyzer, ZSLMatchHolder
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"InterfaceAnalyzer",
|
|
16
|
+
"ZSLInterfaceAnalyzer",
|
|
17
|
+
"ZSLMatchHolder",
|
|
18
|
+
"CommensurateLatticeInterfaceAnalyzer",
|
|
19
|
+
"CommensurateLatticeMatchHolder",
|
|
20
|
+
"GrainBoundaryPlanarAnalyzer",
|
|
21
|
+
"GrainBoundaryPlanarMatchHolder",
|
|
22
|
+
"TwistedNanoribbonsInterfaceAnalyzer",
|
|
23
|
+
"MatchedSubstrateFilmConfigurationHolder",
|
|
24
|
+
]
|