@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mat3ra/made",
3
- "version": "2025.8.23-0",
3
+ "version": "2025.8.24-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",
@@ -0,0 +1,2 @@
1
+ from .analyzer import LatticeMaterialAnalyzer
2
+ from .helpers import get_lattice_type, get_material_with_conventional_lattice, get_material_with_primitive_lattice
@@ -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, BULK_Si_PRIMITIVIZED
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)