@mat3ra/made 2025.8.23-0 → 2025.8.24-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/src/py/mat3ra/made/tools/analyze/lattice/__init__.py +2 -0
- package/src/py/mat3ra/made/tools/analyze/lattice/analyzer.py +120 -0
- package/src/py/mat3ra/made/tools/analyze/lattice/helpers.py +31 -0
- package/src/py/mat3ra/made/tools/helpers.py +6 -0
- package/tests/py/unit/test_tools_analyze.py +1 -20
- package/tests/py/unit/test_tools_analyze_lattice.py +38 -0
- package/src/py/mat3ra/made/tools/analyze/lattice.py +0 -82
package/package.json
CHANGED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
from ....lattice import LatticeTypeEnum
|
|
2
|
+
from ...build_components.metadata import MaterialWithBuildMetadata
|
|
3
|
+
from ...convert import from_pymatgen, to_pymatgen
|
|
4
|
+
from ...operations.core.unary import rotate
|
|
5
|
+
from ...third_party import PymatgenSpacegroupAnalyzer
|
|
6
|
+
from .. import BaseMaterialAnalyzer
|
|
7
|
+
from ..basis import BasisMaterialAnalyzer
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class LatticeMaterialAnalyzer(BaseMaterialAnalyzer):
|
|
11
|
+
precision: float = 0.1
|
|
12
|
+
angle_tolerance: float = 5.0
|
|
13
|
+
|
|
14
|
+
@property
|
|
15
|
+
def spacegroup_analyzer(self):
|
|
16
|
+
return PymatgenSpacegroupAnalyzer(
|
|
17
|
+
to_pymatgen(self.material), symprec=self.precision, angle_tolerance=self.angle_tolerance
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
def detect_lattice_type(self, precision=0.1, angle_tolerance=5) -> LatticeTypeEnum:
|
|
21
|
+
"""
|
|
22
|
+
Detects the lattice type of the material.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
precision (float): Tolerance for lattice parameter comparison, in Angstroms.
|
|
26
|
+
angle_tolerance (float): Tolerance for angle comparisons, in degrees.
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
LatticeTypeEnum: The detected lattice type.
|
|
30
|
+
"""
|
|
31
|
+
self.precision = precision
|
|
32
|
+
self.angle_tolerance = angle_tolerance
|
|
33
|
+
try:
|
|
34
|
+
lattice_type = self.spacegroup_analyzer.get_lattice_type()
|
|
35
|
+
spg_symbol = self.spacegroup_analyzer.get_space_group_symbol()
|
|
36
|
+
|
|
37
|
+
# Enhanced detection using space group symbol
|
|
38
|
+
if lattice_type == "cubic":
|
|
39
|
+
if "P" in spg_symbol:
|
|
40
|
+
return LatticeTypeEnum.CUB
|
|
41
|
+
elif "F" in spg_symbol:
|
|
42
|
+
return LatticeTypeEnum.FCC
|
|
43
|
+
elif "I" in spg_symbol:
|
|
44
|
+
return LatticeTypeEnum.BCC
|
|
45
|
+
elif lattice_type == "tetragonal":
|
|
46
|
+
if "P" in spg_symbol:
|
|
47
|
+
return LatticeTypeEnum.TET
|
|
48
|
+
elif "I" in spg_symbol:
|
|
49
|
+
return LatticeTypeEnum.BCT
|
|
50
|
+
elif lattice_type == "orthorhombic":
|
|
51
|
+
if "P" in spg_symbol:
|
|
52
|
+
return LatticeTypeEnum.ORC
|
|
53
|
+
elif "F" in spg_symbol:
|
|
54
|
+
return LatticeTypeEnum.ORCF
|
|
55
|
+
elif "I" in spg_symbol:
|
|
56
|
+
return LatticeTypeEnum.ORCI
|
|
57
|
+
elif "C" in spg_symbol:
|
|
58
|
+
return LatticeTypeEnum.ORCC
|
|
59
|
+
elif lattice_type == "hexagonal":
|
|
60
|
+
return LatticeTypeEnum.HEX
|
|
61
|
+
elif lattice_type == "rhombohedral":
|
|
62
|
+
return LatticeTypeEnum.RHL
|
|
63
|
+
elif lattice_type == "monoclinic":
|
|
64
|
+
if "P" in spg_symbol:
|
|
65
|
+
return LatticeTypeEnum.MCL
|
|
66
|
+
elif "C" in spg_symbol:
|
|
67
|
+
return LatticeTypeEnum.MCLC
|
|
68
|
+
|
|
69
|
+
except Exception:
|
|
70
|
+
return LatticeTypeEnum.TRI
|
|
71
|
+
|
|
72
|
+
@property
|
|
73
|
+
def material_with_primitive_lattice(self: MaterialWithBuildMetadata) -> MaterialWithBuildMetadata:
|
|
74
|
+
"""
|
|
75
|
+
Convert a structure to its primitive cell.
|
|
76
|
+
"""
|
|
77
|
+
return MaterialWithBuildMetadata.create(
|
|
78
|
+
from_pymatgen(self.spacegroup_analyzer.get_primitive_standard_structure())
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
@property
|
|
82
|
+
def material_with_conventional_lattice(self: MaterialWithBuildMetadata) -> MaterialWithBuildMetadata:
|
|
83
|
+
"""
|
|
84
|
+
Convert a structure to its conventional cell.
|
|
85
|
+
"""
|
|
86
|
+
return MaterialWithBuildMetadata.create(
|
|
87
|
+
from_pymatgen(self.spacegroup_analyzer.get_conventional_standard_structure())
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
def get_material_with_primitive_lattice_standard(
|
|
91
|
+
self, return_original_if_not_reduced: bool = False, keep_orientation: bool = True, layer_thickness: float = 1.0
|
|
92
|
+
) -> MaterialWithBuildMetadata:
|
|
93
|
+
"""
|
|
94
|
+
Get material with primitive lattice and optional orientation correction to be standardized.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
return_original_if_not_reduced: If True, return original material when no reduction occurs
|
|
98
|
+
keep_orientation: If True, correct orientation after primitive conversion
|
|
99
|
+
layer_thickness: Thickness of layers for orientation detection
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
MaterialWithBuildMetadata: Material with primitive lattice
|
|
103
|
+
"""
|
|
104
|
+
material_with_primitive_lattice = self.material_with_primitive_lattice
|
|
105
|
+
original_number_of_atoms = self.material.basis.number_of_atoms
|
|
106
|
+
primitive_structure_number_of_atoms = material_with_primitive_lattice.basis.number_of_atoms
|
|
107
|
+
|
|
108
|
+
if original_number_of_atoms == primitive_structure_number_of_atoms:
|
|
109
|
+
if return_original_if_not_reduced:
|
|
110
|
+
return self.material
|
|
111
|
+
|
|
112
|
+
if keep_orientation:
|
|
113
|
+
basis_analyzer = BasisMaterialAnalyzer(material=material_with_primitive_lattice)
|
|
114
|
+
if basis_analyzer.is_orientation_flipped(self.material, layer_thickness):
|
|
115
|
+
material_with_primitive_lattice = rotate(
|
|
116
|
+
material_with_primitive_lattice, axis=[1, 0, 0], angle=180, rotate_cell=False
|
|
117
|
+
)
|
|
118
|
+
print("Orientation corrected after primitive conversion.")
|
|
119
|
+
|
|
120
|
+
return material_with_primitive_lattice
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from ....lattice import LatticeTypeEnum
|
|
2
|
+
from ...build_components.metadata import MaterialWithBuildMetadata
|
|
3
|
+
from .analyzer import LatticeMaterialAnalyzer
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def get_material_with_conventional_lattice(material: MaterialWithBuildMetadata) -> MaterialWithBuildMetadata:
|
|
7
|
+
analyzer = LatticeMaterialAnalyzer(material=material)
|
|
8
|
+
return analyzer.material_with_conventional_lattice
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def get_material_with_primitive_lattice(
|
|
12
|
+
material: MaterialWithBuildMetadata, return_original_if_not_reduced=False, keep_orientation=True
|
|
13
|
+
) -> MaterialWithBuildMetadata:
|
|
14
|
+
analyzer = LatticeMaterialAnalyzer(material=material)
|
|
15
|
+
return analyzer.get_material_with_primitive_lattice_standard(return_original_if_not_reduced, keep_orientation)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def get_lattice_type(material: MaterialWithBuildMetadata, tolerance=0.2, angle_tolerance=5) -> LatticeTypeEnum:
|
|
19
|
+
"""
|
|
20
|
+
Detects the lattice type of the material.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
material (MaterialWithBuildMetadata): The material to analyze.
|
|
24
|
+
tolerance (float): Tolerance for lattice parameter comparisons.
|
|
25
|
+
angle_tolerance (float): Tolerance for angle comparisons.
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
LatticeTypeEnum: The detected lattice type.
|
|
29
|
+
"""
|
|
30
|
+
analyzer = LatticeMaterialAnalyzer(material=material)
|
|
31
|
+
return analyzer.detect_lattice_type(precision=tolerance, angle_tolerance=angle_tolerance)
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
# Analyzers
|
|
2
|
+
from .analyze.lattice import get_material_with_conventional_lattice, get_material_with_primitive_lattice
|
|
3
|
+
|
|
1
4
|
# Defective Structures
|
|
2
5
|
from .build.compound_pristine_structures.two_dimensional.heterostructure import create_heterostructure
|
|
3
6
|
from .build.compound_pristine_structures.two_dimensional.heterostructure.types import StackComponentDict
|
|
@@ -87,6 +90,9 @@ from .build_components.operations.core.modifications.perturb.helpers import crea
|
|
|
87
90
|
from .entities.coordinate import CoordinateCondition
|
|
88
91
|
|
|
89
92
|
__all__ = [
|
|
93
|
+
# Crystal and related analyzer functions
|
|
94
|
+
"get_material_with_primitive_lattice",
|
|
95
|
+
"get_material_with_conventional_lattice",
|
|
90
96
|
# Slab and related Functions
|
|
91
97
|
"create_slab",
|
|
92
98
|
"create_slab_if_not",
|
|
@@ -7,7 +7,6 @@ from mat3ra.made.tools.analyze.crystal_site.adatom_crystal_site_material_analyze
|
|
|
7
7
|
from mat3ra.made.tools.analyze.crystal_site.adatom_material_analyzer import AdatomMaterialAnalyzer
|
|
8
8
|
from mat3ra.made.tools.analyze.crystal_site.crystal_site_analyzer import CrystalSiteAnalyzer
|
|
9
9
|
from mat3ra.made.tools.analyze.crystal_site.voronoi_crystal_site_analyzer import VoronoiCrystalSiteAnalyzer
|
|
10
|
-
from mat3ra.made.tools.analyze.lattice import LatticeMaterialAnalyzer
|
|
11
10
|
from mat3ra.made.tools.analyze.other import (
|
|
12
11
|
SurfaceTypesEnum,
|
|
13
12
|
get_average_interlayer_distance,
|
|
@@ -23,10 +22,9 @@ from mat3ra.made.tools.build_components.operations.core.combinations.enums impor
|
|
|
23
22
|
from unit.fixtures.nanoribbon.nanoribbon import GRAPHENE_ZIGZAG_NANORIBBON
|
|
24
23
|
from unit.utils import OSPlatform, get_platform_specific_value
|
|
25
24
|
|
|
26
|
-
from .fixtures.bulk import BULK_Si_CONVENTIONAL, BULK_Si_PRIMITIVE
|
|
25
|
+
from .fixtures.bulk import BULK_Si_CONVENTIONAL, BULK_Si_PRIMITIVE
|
|
27
26
|
from .fixtures.interface.zsl import GRAPHENE_NICKEL_INTERFACE
|
|
28
27
|
from .fixtures.slab import SI_CONVENTIONAL_SLAB_001
|
|
29
|
-
from .utils import assert_two_entities_deep_almost_equal
|
|
30
28
|
|
|
31
29
|
COMPARISON_PRECISION = 1e-4
|
|
32
30
|
|
|
@@ -88,23 +86,6 @@ def test_radial_distribution_function(material_config, rdf_params, expected_firs
|
|
|
88
86
|
assert np.isclose(rdf.first_peak_distance, expected_first_peak_distance, atol=0.1)
|
|
89
87
|
|
|
90
88
|
|
|
91
|
-
@pytest.mark.parametrize(
|
|
92
|
-
"primitive_material_config, expected_conventional_material_config, expected_primitive_material_config",
|
|
93
|
-
[(BULK_Si_PRIMITIVE, BULK_Si_CONVENTIONAL, BULK_Si_PRIMITIVIZED)],
|
|
94
|
-
)
|
|
95
|
-
def test_lattice_material_analyzer(
|
|
96
|
-
primitive_material_config, expected_conventional_material_config, expected_primitive_material_config
|
|
97
|
-
):
|
|
98
|
-
primitive_cell = Material.create(primitive_material_config)
|
|
99
|
-
lattice_material_analyzer = LatticeMaterialAnalyzer(material=primitive_cell)
|
|
100
|
-
|
|
101
|
-
conventional_cell = lattice_material_analyzer.material_with_conventional_lattice
|
|
102
|
-
assert_two_entities_deep_almost_equal(conventional_cell, expected_conventional_material_config)
|
|
103
|
-
|
|
104
|
-
primitive_cell_generated = lattice_material_analyzer.material_with_primitive_lattice
|
|
105
|
-
assert_two_entities_deep_almost_equal(primitive_cell_generated, expected_primitive_material_config)
|
|
106
|
-
|
|
107
|
-
|
|
108
89
|
VORONOI_SITE_EXPECTED = {OSPlatform.DARWIN: [0.625, 0.625, 0.125], OSPlatform.OTHER: [0.5, 0.5, 0.5]}
|
|
109
90
|
|
|
110
91
|
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from mat3ra.made.material import Material
|
|
3
|
+
from mat3ra.made.tools.analyze.lattice import LatticeMaterialAnalyzer, get_lattice_type
|
|
4
|
+
|
|
5
|
+
from .fixtures.bulk import BULK_GRAPHITE, BULK_Hf2O_MCL, BULK_Si_CONVENTIONAL, BULK_Si_PRIMITIVE, BULK_Si_PRIMITIVIZED
|
|
6
|
+
from .fixtures.interface.gr_ni_111_top_hcp import GRAPHENE_NICKEL_INTERFACE_TOP_HCP
|
|
7
|
+
from .utils import assert_two_entities_deep_almost_equal
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@pytest.mark.parametrize(
|
|
11
|
+
"primitive_material_config, expected_conventional_material_config, expected_primitive_material_config",
|
|
12
|
+
[(BULK_Si_PRIMITIVE, BULK_Si_CONVENTIONAL, BULK_Si_PRIMITIVIZED)],
|
|
13
|
+
)
|
|
14
|
+
def test_lattice_material_analyzer(
|
|
15
|
+
primitive_material_config, expected_conventional_material_config, expected_primitive_material_config
|
|
16
|
+
):
|
|
17
|
+
primitive_cell = Material.create(primitive_material_config)
|
|
18
|
+
lattice_material_analyzer = LatticeMaterialAnalyzer(material=primitive_cell)
|
|
19
|
+
conventional_cell = lattice_material_analyzer.material_with_conventional_lattice
|
|
20
|
+
assert_two_entities_deep_almost_equal(conventional_cell, expected_conventional_material_config)
|
|
21
|
+
|
|
22
|
+
primitive_cell_generated = lattice_material_analyzer.material_with_primitive_lattice
|
|
23
|
+
assert_two_entities_deep_almost_equal(primitive_cell_generated, expected_primitive_material_config)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@pytest.mark.parametrize(
|
|
27
|
+
"material, expected_lattice_type",
|
|
28
|
+
[
|
|
29
|
+
(BULK_Si_PRIMITIVE, "FCC"),
|
|
30
|
+
(BULK_Si_CONVENTIONAL, "FCC"),
|
|
31
|
+
(GRAPHENE_NICKEL_INTERFACE_TOP_HCP, "HEX"),
|
|
32
|
+
(BULK_Hf2O_MCL, "MCL"),
|
|
33
|
+
(BULK_GRAPHITE, "HEX"),
|
|
34
|
+
],
|
|
35
|
+
)
|
|
36
|
+
def test_analyze_lattice_type(material, expected_lattice_type):
|
|
37
|
+
result = get_lattice_type(material)
|
|
38
|
+
assert result.value == expected_lattice_type
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
from ..build_components.metadata import MaterialWithBuildMetadata
|
|
2
|
-
from ..convert import from_pymatgen, to_pymatgen
|
|
3
|
-
from ..operations.core.unary import rotate
|
|
4
|
-
from ..third_party import PymatgenSpacegroupAnalyzer
|
|
5
|
-
from . import BaseMaterialAnalyzer
|
|
6
|
-
from .basis import BasisMaterialAnalyzer
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class LatticeMaterialAnalyzer(BaseMaterialAnalyzer):
|
|
10
|
-
@property
|
|
11
|
-
def spacegroup_analyzer(self):
|
|
12
|
-
return PymatgenSpacegroupAnalyzer(to_pymatgen(self.material))
|
|
13
|
-
|
|
14
|
-
@property
|
|
15
|
-
def material_with_primitive_lattice(self: MaterialWithBuildMetadata) -> MaterialWithBuildMetadata:
|
|
16
|
-
"""
|
|
17
|
-
Convert a structure to its primitive cell.
|
|
18
|
-
"""
|
|
19
|
-
return MaterialWithBuildMetadata.create(
|
|
20
|
-
from_pymatgen(self.spacegroup_analyzer.get_primitive_standard_structure())
|
|
21
|
-
)
|
|
22
|
-
|
|
23
|
-
@property
|
|
24
|
-
def material_with_primitive_lattice_standard(self) -> MaterialWithBuildMetadata:
|
|
25
|
-
"""
|
|
26
|
-
Get material with primitive lattice and standard orientation correction.
|
|
27
|
-
Uses default parameters: return_original_if_not_reduced=False, keep_orientation=True
|
|
28
|
-
"""
|
|
29
|
-
return self.get_material_with_primitive_lattice_standard()
|
|
30
|
-
|
|
31
|
-
@property
|
|
32
|
-
def material_with_conventional_lattice(self: MaterialWithBuildMetadata) -> MaterialWithBuildMetadata:
|
|
33
|
-
"""
|
|
34
|
-
Convert a structure to its conventional cell.
|
|
35
|
-
"""
|
|
36
|
-
return MaterialWithBuildMetadata.create(
|
|
37
|
-
from_pymatgen(self.spacegroup_analyzer.get_conventional_standard_structure())
|
|
38
|
-
)
|
|
39
|
-
|
|
40
|
-
def get_material_with_primitive_lattice_standard(
|
|
41
|
-
self, return_original_if_not_reduced: bool = False, keep_orientation: bool = True, layer_thickness: float = 1.0
|
|
42
|
-
) -> MaterialWithBuildMetadata:
|
|
43
|
-
"""
|
|
44
|
-
Get material with primitive lattice and optional orientation correction to be standardized.
|
|
45
|
-
|
|
46
|
-
Args:
|
|
47
|
-
return_original_if_not_reduced: If True, return original material when no reduction occurs
|
|
48
|
-
keep_orientation: If True, correct orientation after primitive conversion
|
|
49
|
-
layer_thickness: Thickness of layers for orientation detection
|
|
50
|
-
|
|
51
|
-
Returns:
|
|
52
|
-
MaterialWithBuildMetadata: Material with primitive lattice
|
|
53
|
-
"""
|
|
54
|
-
material_with_primitive_lattice = self.material_with_primitive_lattice
|
|
55
|
-
original_number_of_atoms = self.material.basis.number_of_atoms
|
|
56
|
-
primitive_structure_number_of_atoms = material_with_primitive_lattice.basis.number_of_atoms
|
|
57
|
-
|
|
58
|
-
if original_number_of_atoms == primitive_structure_number_of_atoms:
|
|
59
|
-
if return_original_if_not_reduced:
|
|
60
|
-
return self.material
|
|
61
|
-
|
|
62
|
-
if keep_orientation:
|
|
63
|
-
basis_analyzer = BasisMaterialAnalyzer(material=material_with_primitive_lattice)
|
|
64
|
-
if basis_analyzer.is_orientation_flipped(self.material, layer_thickness):
|
|
65
|
-
material_with_primitive_lattice = rotate(
|
|
66
|
-
material_with_primitive_lattice, axis=[1, 0, 0], angle=180, rotate_cell=False
|
|
67
|
-
)
|
|
68
|
-
print("Orientation corrected after primitive conversion.")
|
|
69
|
-
|
|
70
|
-
return material_with_primitive_lattice
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
def get_material_with_conventional_lattice(material: MaterialWithBuildMetadata) -> MaterialWithBuildMetadata:
|
|
74
|
-
analyzer = LatticeMaterialAnalyzer(material=material)
|
|
75
|
-
return analyzer.material_with_conventional_lattice
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
def get_material_with_primitive_lattice(
|
|
79
|
-
material: MaterialWithBuildMetadata, return_original_if_not_reduced=False, keep_orientation=True
|
|
80
|
-
) -> MaterialWithBuildMetadata:
|
|
81
|
-
analyzer = LatticeMaterialAnalyzer(material=material)
|
|
82
|
-
return analyzer.get_material_with_primitive_lattice_standard(return_original_if_not_reduced, keep_orientation)
|