@mat3ra/made 2026.5.21-1 → 2026.5.21-2
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/package.json +1 -1
- package/src/py/mat3ra/made/basis/__init__.py +4 -0
- package/src/py/mat3ra/made/tools/build/defective_structures/zero_dimensional/point_defect/substitutional/configuration.py +12 -0
- package/src/py/mat3ra/made/tools/build_components/entities/auxiliary/zero_dimensional/point_defect_site/builder.py +1 -0
- package/src/py/mat3ra/made/tools/build_components/entities/auxiliary/zero_dimensional/point_defect_site/configuration.py +2 -1
- package/tests/py/unit/test_tools_build_defect/test_point_defect.py +33 -0
package/package.json
CHANGED
|
@@ -132,6 +132,7 @@ class Basis(BasisSchema, InMemoryEntityPydantic):
|
|
|
132
132
|
coordinate: Optional[List[float]] = None,
|
|
133
133
|
use_cartesian_coordinates: bool = False,
|
|
134
134
|
force: bool = False,
|
|
135
|
+
label: Optional[Union[int, str]] = None,
|
|
135
136
|
):
|
|
136
137
|
"""
|
|
137
138
|
Add an atom to the basis at a specified coordinate. Check that no other atom is overlapping with it.
|
|
@@ -141,6 +142,7 @@ class Basis(BasisSchema, InMemoryEntityPydantic):
|
|
|
141
142
|
coordinate (List[float]): Coordinate of the atom to be added.
|
|
142
143
|
use_cartesian_coordinates (bool): Whether the coordinate is in Cartesian units (or crystal by default).
|
|
143
144
|
force (bool): Whether to force adding the atom even if it overlaps with another atom.
|
|
145
|
+
label (int|str|None): Per-atom label when the basis already uses labels; omit if the basis has none.
|
|
144
146
|
"""
|
|
145
147
|
if coordinate is None:
|
|
146
148
|
coordinate = [0, 0, 0]
|
|
@@ -162,6 +164,8 @@ class Basis(BasisSchema, InMemoryEntityPydantic):
|
|
|
162
164
|
return
|
|
163
165
|
self.elements.add_item(element)
|
|
164
166
|
self.coordinates.add_item(coordinate)
|
|
167
|
+
if label is not None:
|
|
168
|
+
self.labels.add_item(label)
|
|
165
169
|
|
|
166
170
|
def add_atoms_from_another_basis(self, other_basis: "Basis"):
|
|
167
171
|
"""
|
|
@@ -7,6 +7,7 @@ from mat3ra.esse.models.materials_category_components.entities.core.zero_dimensi
|
|
|
7
7
|
from mat3ra.esse.models.materials_category_components.operations.core.combinations.merge import MergeMethodsEnum
|
|
8
8
|
|
|
9
9
|
from mat3ra.made.material import Material
|
|
10
|
+
from mat3ra.made.tools.analyze.other import get_closest_site_id_from_coordinate
|
|
10
11
|
from ..base.configuration import PointDefectConfiguration
|
|
11
12
|
from ......build_components import MaterialWithBuildMetadata
|
|
12
13
|
from ......build_components.entities.auxiliary.zero_dimensional.point_defect_site.configuration import (
|
|
@@ -14,6 +15,16 @@ from ......build_components.entities.auxiliary.zero_dimensional.point_defect_sit
|
|
|
14
15
|
)
|
|
15
16
|
|
|
16
17
|
|
|
18
|
+
def _host_atom_label_at_coordinate(
|
|
19
|
+
crystal: Union[Material, MaterialWithBuildMetadata], coordinate: List[float]
|
|
20
|
+
) -> Union[int, str, None]:
|
|
21
|
+
host_labels = crystal.basis.labels.values
|
|
22
|
+
if not host_labels:
|
|
23
|
+
return None
|
|
24
|
+
site_index = get_closest_site_id_from_coordinate(crystal, coordinate)
|
|
25
|
+
return host_labels[site_index]
|
|
26
|
+
|
|
27
|
+
|
|
17
28
|
class SubstitutionalDefectConfiguration(PointDefectConfiguration, SubstitutionalPointDefectSchema):
|
|
18
29
|
type: str = "SubstitutionalDefectConfiguration"
|
|
19
30
|
|
|
@@ -25,5 +36,6 @@ class SubstitutionalDefectConfiguration(PointDefectConfiguration, Substitutional
|
|
|
25
36
|
crystal=crystal,
|
|
26
37
|
element=AtomSchema(chemical_element=element),
|
|
27
38
|
coordinate=coordinate,
|
|
39
|
+
host_atom_label=_host_atom_label_at_coordinate(crystal, coordinate),
|
|
28
40
|
)
|
|
29
41
|
return cls(merge_components=[crystal, substitution_site], merge_method=MergeMethodsEnum.REPLACE, **kwargs)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import Union
|
|
1
|
+
from typing import Optional, Union
|
|
2
2
|
|
|
3
3
|
from mat3ra.esse.models.materials_category.defective_structures.two_dimensional.adatom.configuration import (
|
|
4
4
|
PointDefectSiteSchema,
|
|
@@ -11,3 +11,4 @@ from ..crystal_site import CrystalSite
|
|
|
11
11
|
|
|
12
12
|
class PointDefectSiteConfiguration(CrystalSite, PointDefectSiteSchema):
|
|
13
13
|
element: Union[VacancySchema, AtomSchema]
|
|
14
|
+
host_atom_label: Optional[Union[int, str]] = None
|
|
@@ -16,6 +16,7 @@ from mat3ra.made.tools.build.defective_structures.zero_dimensional.point_defect.
|
|
|
16
16
|
)
|
|
17
17
|
from mat3ra.made.tools.build.defective_structures.zero_dimensional.point_defect.types import PointDefectDict
|
|
18
18
|
from unit.fixtures.bulk import BULK_Si_CONVENTIONAL, BULK_Si_PRIMITIVE
|
|
19
|
+
from unit.fixtures.interface.zsl import GRAPHENE_NICKEL_INTERFACE
|
|
19
20
|
from unit.fixtures.point_defects import (
|
|
20
21
|
INTERSTITIAL_DEFECT_BULK_PRIMITIVE_Si,
|
|
21
22
|
INTERSTITIAL_VORONOI_DEFECT_BULK_PRIMITIVE_Si,
|
|
@@ -25,6 +26,19 @@ from unit.fixtures.point_defects import (
|
|
|
25
26
|
)
|
|
26
27
|
from unit.utils import assert_two_entities_deep_almost_equal, get_platform_specific_value
|
|
27
28
|
|
|
29
|
+
FILM_LABEL = 1
|
|
30
|
+
SUBSTRATE_LABEL = 0
|
|
31
|
+
SUBSTITUTION_ELEMENT = "Ge"
|
|
32
|
+
INTERFACE_SUBSTITUTION_PLACEMENT_METHOD = SubstitutionPlacementMethodEnum.CLOSEST_SITE.value
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _film_atom_coordinate(material: Material) -> list:
|
|
36
|
+
labels_by_id = {entry["id"]: entry["value"] for entry in material.basis.labels.to_dict()}
|
|
37
|
+
for coordinate_entry in material.basis.coordinates.to_dict():
|
|
38
|
+
if labels_by_id.get(coordinate_entry["id"]) == FILM_LABEL:
|
|
39
|
+
return coordinate_entry["value"]
|
|
40
|
+
raise ValueError("No film atom found in fixture material")
|
|
41
|
+
|
|
28
42
|
|
|
29
43
|
@pytest.mark.parametrize(
|
|
30
44
|
"material_config, defect_params, expected_material_config",
|
|
@@ -91,6 +105,25 @@ def test_point_defect_helpers(material_config, defect_params, expected_material_
|
|
|
91
105
|
assert_two_entities_deep_almost_equal(defect, expected_material_config)
|
|
92
106
|
|
|
93
107
|
|
|
108
|
+
@pytest.mark.parametrize(
|
|
109
|
+
"expected_label_count, expected_unique_labels",
|
|
110
|
+
[(5, {SUBSTRATE_LABEL, FILM_LABEL})],
|
|
111
|
+
)
|
|
112
|
+
def test_create_defect_point_substitution_preserves_interface_labels(
|
|
113
|
+
expected_label_count, expected_unique_labels
|
|
114
|
+
):
|
|
115
|
+
material = Material.create(GRAPHENE_NICKEL_INTERFACE)
|
|
116
|
+
coordinate = _film_atom_coordinate(material)
|
|
117
|
+
|
|
118
|
+
defect_material = create_defect_point_substitution(
|
|
119
|
+
material, coordinate, SUBSTITUTION_ELEMENT, INTERFACE_SUBSTITUTION_PLACEMENT_METHOD
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
assert len(defect_material.basis.elements.values) == expected_label_count
|
|
123
|
+
assert len(defect_material.basis.labels.values) == expected_label_count
|
|
124
|
+
assert set(defect_material.basis.labels.values) == expected_unique_labels
|
|
125
|
+
|
|
126
|
+
|
|
94
127
|
@pytest.mark.parametrize(
|
|
95
128
|
"material_config, defect_params_list, expected_material_config",
|
|
96
129
|
[
|