@mat3ra/made 2024.6.11-0 → 2024.6.12-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.
- package/package.json +1 -1
- package/pyproject.toml +1 -0
- package/src/py/mat3ra/made/material.py +5 -0
- package/src/py/mat3ra/made/tools/analyze.py +20 -0
- package/src/py/mat3ra/made/tools/build/defect/__init__.py +36 -0
- package/src/py/mat3ra/made/tools/build/defect/builders.py +69 -0
- package/src/py/mat3ra/made/tools/build/defect/configuration.py +44 -0
- package/src/py/mat3ra/made/tools/build/defect/enums.py +7 -0
- package/src/py/mat3ra/made/tools/build/interface/builders.py +3 -3
- package/src/py/mat3ra/made/utils.py +4 -0
- package/tests/py/unit/fixtures.py +1 -1
- package/tests/py/unit/test_tools_build_defect.py +51 -0
package/package.json
CHANGED
package/pyproject.toml
CHANGED
|
@@ -3,6 +3,7 @@ from typing import Any, Dict, List, Union
|
|
|
3
3
|
from mat3ra.code.constants import AtomicCoordinateUnits, Units
|
|
4
4
|
from mat3ra.code.entity import HasDescriptionHasMetadataNamedDefaultableInMemoryEntity
|
|
5
5
|
from mat3ra.esse.models.material import MaterialSchema
|
|
6
|
+
from mat3ra.made.utils import map_array_with_id_value_to_array
|
|
6
7
|
|
|
7
8
|
defaultMaterialConfig = {
|
|
8
9
|
"name": "Silicon FCC",
|
|
@@ -57,3 +58,7 @@ class Material(HasDescriptionHasMetadataNamedDefaultableInMemoryEntity):
|
|
|
57
58
|
|
|
58
59
|
def to_json(self, exclude: List[str] = []) -> MaterialSchemaJSON:
|
|
59
60
|
return {**super().to_json()}
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def coordinates_array(self) -> List[List[float]]:
|
|
64
|
+
return map_array_with_id_value_to_array(self.basis["coordinates"])
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
|
|
1
3
|
import numpy as np
|
|
2
4
|
from ase import Atoms
|
|
3
5
|
|
|
6
|
+
from ..material import Material
|
|
4
7
|
from .convert import decorator_convert_material_args_kwargs_to_atoms
|
|
5
8
|
|
|
6
9
|
|
|
@@ -69,3 +72,20 @@ def get_chemical_formula(atoms: Atoms):
|
|
|
69
72
|
str: The formula of the atoms.
|
|
70
73
|
"""
|
|
71
74
|
return atoms.get_chemical_formula()
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def get_closest_site_id_from_position(material: Material, position: List[float]) -> int:
|
|
78
|
+
"""
|
|
79
|
+
Get the site ID of the closest site to a given position in the crystal.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
material (Material): The material object to find the closest site in.
|
|
83
|
+
position (List[float]): The position to find the closest site to.
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
int: The site ID of the closest site.
|
|
87
|
+
"""
|
|
88
|
+
coordinates = np.array(material.coordinates_array)
|
|
89
|
+
position = np.array(position) # type: ignore
|
|
90
|
+
distances = np.linalg.norm(coordinates - position, axis=1)
|
|
91
|
+
return int(np.argmin(distances))
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
from mat3ra.utils.factory import BaseFactory
|
|
4
|
+
from mat3ra.made.material import Material
|
|
5
|
+
|
|
6
|
+
from .builders import PointDefectBuilderParameters
|
|
7
|
+
from .configuration import PointDefectConfiguration
|
|
8
|
+
from .enums import PointDefectTypeEnum
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class DefectBuilderFactory(BaseFactory):
|
|
12
|
+
__class_registry__ = {
|
|
13
|
+
PointDefectTypeEnum.VACANCY: "mat3ra.made.tools.build.defect.builders.VacancyPointDefectBuilder",
|
|
14
|
+
PointDefectTypeEnum.SUBSTITUTION: "mat3ra.made.tools.build.defect.builders.SubstitutionPointDefectBuilder",
|
|
15
|
+
PointDefectTypeEnum.INTERSTITIAL: "mat3ra.made.tools.build.defect.builders.InterstitialPointDefectBuilder",
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def create_defect(
|
|
20
|
+
configuration: PointDefectConfiguration,
|
|
21
|
+
builder_parameters: Optional[PointDefectBuilderParameters] = None,
|
|
22
|
+
) -> Material:
|
|
23
|
+
"""
|
|
24
|
+
Return a material with a selected defect added.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
configuration: The configuration of the defect to be added.
|
|
28
|
+
builder_parameters: The parameters to be used by the defect builder.
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
The material with the defect added.
|
|
32
|
+
"""
|
|
33
|
+
BuilderClass = DefectBuilderFactory.get_class_by_name(configuration.defect_type)
|
|
34
|
+
builder = BuilderClass(builder_parameters)
|
|
35
|
+
|
|
36
|
+
return builder.get_material(configuration) if builder else configuration.crystal
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
from typing import List, Callable
|
|
2
|
+
|
|
3
|
+
from mat3ra.made.material import Material
|
|
4
|
+
from pydantic import BaseModel
|
|
5
|
+
from pymatgen.analysis.defects.core import (
|
|
6
|
+
Substitution as PymatgenSubstitution,
|
|
7
|
+
Vacancy as PymatgenVacancy,
|
|
8
|
+
Interstitial as PymatgenInterstitial,
|
|
9
|
+
)
|
|
10
|
+
from pymatgen.core import PeriodicSite as PymatgenPeriodicSite
|
|
11
|
+
|
|
12
|
+
from ...build import BaseBuilder
|
|
13
|
+
from ...convert import PymatgenStructure, to_pymatgen
|
|
14
|
+
from ..mixins import ConvertGeneratedItemsPymatgenStructureMixin
|
|
15
|
+
from .configuration import PointDefectConfiguration
|
|
16
|
+
from mat3ra.made.utils import get_array_with_id_value_element_value_by_index
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class PointDefectBuilderParameters(BaseModel):
|
|
20
|
+
center_defect: bool = False
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class PointDefectBuilder(ConvertGeneratedItemsPymatgenStructureMixin, BaseBuilder):
|
|
24
|
+
"""
|
|
25
|
+
Builder class for generating point defects.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
_BuildParametersType = PointDefectBuilderParameters
|
|
29
|
+
_DefaultBuildParameters = PointDefectBuilderParameters()
|
|
30
|
+
_GeneratedItemType: PymatgenStructure = PymatgenStructure
|
|
31
|
+
_ConfigurationType = PointDefectConfiguration
|
|
32
|
+
_generator: Callable
|
|
33
|
+
|
|
34
|
+
def _get_species(self, configuration: BaseBuilder._ConfigurationType):
|
|
35
|
+
crystal_elements = configuration.crystal.basis["elements"]
|
|
36
|
+
placeholder_specie = get_array_with_id_value_element_value_by_index(crystal_elements, 0)
|
|
37
|
+
return configuration.chemical_element or placeholder_specie
|
|
38
|
+
|
|
39
|
+
def _generate(self, configuration: BaseBuilder._ConfigurationType) -> List[_GeneratedItemType]:
|
|
40
|
+
pymatgen_structure = to_pymatgen(configuration.crystal)
|
|
41
|
+
pymatgen_periodic_site = PymatgenPeriodicSite(
|
|
42
|
+
species=self._get_species(configuration),
|
|
43
|
+
coords=configuration.position,
|
|
44
|
+
lattice=pymatgen_structure.lattice,
|
|
45
|
+
)
|
|
46
|
+
defect = self._generator(pymatgen_structure, pymatgen_periodic_site)
|
|
47
|
+
defect_structure = defect.defect_structure.copy()
|
|
48
|
+
defect_structure.remove_oxidation_states()
|
|
49
|
+
return [defect_structure]
|
|
50
|
+
|
|
51
|
+
def _update_material_name(self, material: Material, configuration: BaseBuilder._ConfigurationType) -> Material:
|
|
52
|
+
updated_material = super()._update_material_name(material, configuration)
|
|
53
|
+
capitalized_defect_type = configuration.defect_type.name.capitalize()
|
|
54
|
+
chemical_element = configuration.chemical_element if configuration.chemical_element else ""
|
|
55
|
+
new_name = f"{updated_material.name}, {capitalized_defect_type} {chemical_element} Defect"
|
|
56
|
+
updated_material.name = new_name
|
|
57
|
+
return updated_material
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class VacancyPointDefectBuilder(PointDefectBuilder):
|
|
61
|
+
_generator: PymatgenVacancy = PymatgenVacancy
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class SubstitutionPointDefectBuilder(PointDefectBuilder):
|
|
65
|
+
_generator: PymatgenSubstitution = PymatgenSubstitution
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class InterstitialPointDefectBuilder(PointDefectBuilder):
|
|
69
|
+
_generator: PymatgenInterstitial = PymatgenInterstitial
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from typing import Optional, List, Any
|
|
2
|
+
from pydantic import BaseModel
|
|
3
|
+
|
|
4
|
+
from mat3ra.code.entity import InMemoryEntity
|
|
5
|
+
from mat3ra.made.material import Material
|
|
6
|
+
|
|
7
|
+
from ...analyze import get_closest_site_id_from_position
|
|
8
|
+
from .enums import PointDefectTypeEnum
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class BaseDefectConfiguration(BaseModel):
|
|
12
|
+
# TODO: fix arbitrary_types_allowed error and set Material class type
|
|
13
|
+
crystal: Any = None
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class PointDefectConfiguration(BaseDefectConfiguration, InMemoryEntity):
|
|
17
|
+
defect_type: PointDefectTypeEnum
|
|
18
|
+
position: Optional[List[float]] = [0, 0, 0] # fractional coordinates
|
|
19
|
+
site_id: Optional[int] = None
|
|
20
|
+
chemical_element: Optional[str] = None
|
|
21
|
+
|
|
22
|
+
def __init__(self, position=position, site_id=None, **data):
|
|
23
|
+
super().__init__(**data)
|
|
24
|
+
if site_id is not None:
|
|
25
|
+
self.position = self.crystal.coordinates_array[site_id]
|
|
26
|
+
else:
|
|
27
|
+
self.site_id = get_closest_site_id_from_position(self.crystal, position)
|
|
28
|
+
|
|
29
|
+
@classmethod
|
|
30
|
+
def from_site_id(cls, site_id: int, crystal: Material, **data):
|
|
31
|
+
if crystal:
|
|
32
|
+
position = crystal.coordinates_array[site_id]
|
|
33
|
+
else:
|
|
34
|
+
RuntimeError("Crystal is not defined")
|
|
35
|
+
return cls(crystal=crystal, position=position, site_id=site_id, **data)
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def _json(self):
|
|
39
|
+
return {
|
|
40
|
+
"type": "PointDefectConfiguration",
|
|
41
|
+
"defect_type": self.defect_type.name,
|
|
42
|
+
"position": self.position,
|
|
43
|
+
"chemical_element": self.chemical_element,
|
|
44
|
+
}
|
|
@@ -54,7 +54,7 @@ class SimpleInterfaceBuilder(ConvertGeneratedItemsASEAtomsMixin, InterfaceBuilde
|
|
|
54
54
|
Creates matching interface between substrate and film by straining the film to match the substrate.
|
|
55
55
|
"""
|
|
56
56
|
|
|
57
|
-
_BuildParametersType =
|
|
57
|
+
_BuildParametersType = SimpleInterfaceBuilderParameters
|
|
58
58
|
_DefaultBuildParameters = SimpleInterfaceBuilderParameters(scale_film=True)
|
|
59
59
|
_GeneratedItemType: type(ASEAtoms) = ASEAtoms # type: ignore
|
|
60
60
|
|
|
@@ -116,9 +116,9 @@ class StrainMatchingInterfaceBuilder(InterfaceBuilder):
|
|
|
116
116
|
updated_material = super()._update_material_name(material, configuration)
|
|
117
117
|
if StrainModes.mean_abs_strain in material.metadata:
|
|
118
118
|
strain = material.metadata[StrainModes.mean_abs_strain]
|
|
119
|
-
new_name = f"{updated_material.name}, Strain {strain*100:.3f}
|
|
119
|
+
new_name = f"{updated_material.name}, Strain {strain*100:.3f}pct"
|
|
120
120
|
updated_material.name = new_name
|
|
121
|
-
return
|
|
121
|
+
return updated_material
|
|
122
122
|
|
|
123
123
|
|
|
124
124
|
class ZSLStrainMatchingParameters(BaseModel):
|
|
@@ -15,6 +15,10 @@ def map_array_with_id_value_to_array(array: List[Dict[str, Any]]) -> List[Any]:
|
|
|
15
15
|
return [item["value"] for item in array]
|
|
16
16
|
|
|
17
17
|
|
|
18
|
+
def get_array_with_id_value_element_value_by_index(array: List[Dict[str, Any]], index: int = 0) -> List[Any]:
|
|
19
|
+
return map_array_with_id_value_to_array(array)[index]
|
|
20
|
+
|
|
21
|
+
|
|
18
22
|
def filter_array_with_id_value_by_values(
|
|
19
23
|
array: List[Dict[str, Any]], values: Union[List[Any], Any]
|
|
20
24
|
) -> List[Dict[str, Any]]:
|
|
@@ -56,7 +56,7 @@ INTERFACE_PROPERTIES_JSON = {
|
|
|
56
56
|
|
|
57
57
|
# Add properties to interface structure
|
|
58
58
|
INTERFACE_STRUCTURE.interface_properties = INTERFACE_PROPERTIES_MOCK
|
|
59
|
-
INTERFACE_NAME = "Cu4(001)-Si8(001), Interface, Strain 0.
|
|
59
|
+
INTERFACE_NAME = "Cu4(001)-Si8(001), Interface, Strain 0.062pct"
|
|
60
60
|
|
|
61
61
|
# TODO: Use fixtures package when available
|
|
62
62
|
SI_CONVENTIONAL_CELL = {
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
from mat3ra.made.material import Material
|
|
2
|
+
from mat3ra.made.tools.build.defect import PointDefectBuilderParameters, PointDefectConfiguration, create_defect
|
|
3
|
+
|
|
4
|
+
clean_material = Material.create(Material.default_config)
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def test_create_vacancy():
|
|
8
|
+
# vacancy in place of 0 element
|
|
9
|
+
configuration = PointDefectConfiguration(crystal=clean_material, defect_type="vacancy", site_id=0)
|
|
10
|
+
defect = create_defect(configuration)
|
|
11
|
+
|
|
12
|
+
assert len(defect.basis["elements"]) == 1
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def test_create_substitution():
|
|
16
|
+
# Substitution of Ge in place of Si at default site_id=0
|
|
17
|
+
configuration = PointDefectConfiguration(crystal=clean_material, defect_type="substitution", chemical_element="Ge")
|
|
18
|
+
defect = create_defect(configuration)
|
|
19
|
+
|
|
20
|
+
assert defect.basis["elements"] == [{"id": 0, "value": "Ge"}, {"id": 1, "value": "Si"}]
|
|
21
|
+
assert defect.basis["coordinates"][0] == {"id": 0, "value": [0.0, 0.0, 0.0]}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_create_interstitial():
|
|
25
|
+
# Interstitial Ge at 0.5, 0.5, 0.5 position
|
|
26
|
+
configuration = PointDefectConfiguration(
|
|
27
|
+
crystal=clean_material, defect_type="interstitial", chemical_element="Ge", position=[0.5, 0.5, 0.5]
|
|
28
|
+
)
|
|
29
|
+
defect = create_defect(configuration)
|
|
30
|
+
|
|
31
|
+
assert defect.basis["elements"] == [
|
|
32
|
+
{"id": 0, "value": "Ge"},
|
|
33
|
+
{"id": 1, "value": "Si"},
|
|
34
|
+
{"id": 2, "value": "Si"},
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_create_defect_from_site_id():
|
|
39
|
+
# Substitution of Ge in place of Si at site_id=1
|
|
40
|
+
defect_configuration = PointDefectConfiguration.from_site_id(
|
|
41
|
+
crystal=clean_material, defect_type="substitution", chemical_element="Ge", site_id=1
|
|
42
|
+
)
|
|
43
|
+
defect_builder_parameters = PointDefectBuilderParameters(center_defect=False)
|
|
44
|
+
material_with_defect = create_defect(
|
|
45
|
+
builder_parameters=defect_builder_parameters, configuration=defect_configuration
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
assert material_with_defect.basis["elements"] == [
|
|
49
|
+
{"id": 0, "value": "Si"},
|
|
50
|
+
{"id": 1, "value": "Ge"},
|
|
51
|
+
]
|