Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
SlabStrainedSupercellConfiguration,
)
from ......analyze.interface import CommensurateLatticeInterfaceAnalyzer
from ......analyze.lattice import get_material_with_conventional_lattice
from mat3ra.made.tools.build.pristine_structures.two_dimensional.slab.configuration import SlabConfiguration
from ......build_components.metadata import MaterialWithBuildMetadata

Expand Down Expand Up @@ -53,9 +52,6 @@ def get_commensurate_strained_configurations(
Raises:
ValueError: If no commensurate lattice matches are found.
"""
if use_conventional_cell:
material = get_material_with_conventional_lattice(material)

slab_config = SlabConfiguration.from_parameters(
material_or_dict=material,
miller_indices=miller_indices,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

from mat3ra.made.material import Material
from ......analyze.interface import ZSLInterfaceAnalyzer
from ......analyze.lattice import get_material_with_conventional_lattice
from ......analyze.slab import SlabMaterialAnalyzer
from ......build.compound_pristine_structures.two_dimensional.interface import (
InterfaceBuilderParameters,
Expand Down Expand Up @@ -37,10 +36,6 @@ def create_interface_zsl(
reduce_result_cell: bool = True,
reduce_result_cell_to_primitive: bool = False,
) -> MaterialWithBuildMetadata:
if use_conventional_cell:
substrate_crystal = get_material_with_conventional_lattice(substrate_crystal)
film_crystal = get_material_with_conventional_lattice(film_crystal)

substrate_slab = create_slab(
crystal=substrate_crystal,
miller_indices=substrate_miller_indices,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
from .builder import GrainBoundaryLinearBuilder
from .configuration import GrainBoundaryLinearConfiguration
from ....compound_pristine_structures.two_dimensional.interface import get_commensurate_strained_configurations
from .....analyze.lattice import get_material_with_conventional_lattice
from .....build_components import MaterialWithBuildMetadata


Expand Down Expand Up @@ -53,9 +52,6 @@ def create_grain_boundary_linear(
Raises:
ValueError: If no commensurate lattice matches are found.
"""
if use_conventional_cell:
material = get_material_with_conventional_lattice(material)

strained_configs, actual_angle = get_commensurate_strained_configurations(
material=material,
target_angle=target_angle,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
from .builder import GrainBoundaryPlanarBuilder
from .configuration import GrainBoundaryPlanarConfiguration
from .....analyze.interface import GrainBoundaryPlanarAnalyzer
from .....analyze.lattice import get_material_with_conventional_lattice
from .....build_components import MaterialWithBuildMetadata


Expand Down Expand Up @@ -45,10 +44,6 @@ def create_grain_boundary_planar(
Material: The grain boundary material
"""
phase_2_material = phase_2_material or phase_1_material
if use_conventional_cell:
phase_1_material = get_material_with_conventional_lattice(phase_1_material)
phase_2_material = get_material_with_conventional_lattice(phase_2_material)

analyzer = GrainBoundaryPlanarAnalyzer(
phase_1_material=phase_1_material,
phase_2_material=phase_2_material,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,10 @@ def from_parameters(
termination_top = select_slab_termination(terminations, termination_top_formula)
termination_bottom = select_slab_termination(terminations, termination_bottom_formula)

if use_conventional_cell:
material = crystal_lattice_planes_analyzer.material_with_conventional_lattice
atomic_layers_repeated_configuration = AtomicLayersUniqueRepeatedConfiguration(
crystal=material,
miller_indices=miller_indices,
use_conventional_cell=use_conventional_cell,
termination_top=termination_top,
termination_bottom=termination_bottom,
number_of_repetitions=number_of_layers,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ def create_atomic_layers(
atomic_layers_config = AtomicLayersUniqueRepeatedConfiguration(
crystal=material,
miller_indices=miller_indices,
use_conventional_cell=False,
termination_top=termination,
number_of_repetitions=number_of_layers,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ def get_analyzer(self, configuration: CrystalLatticePlanesConfiguration) -> Crys
def _generate(self, configuration: CrystalLatticePlanesConfiguration) -> MaterialWithBuildMetadata:
crystal_lattice_planes_analyzer = self.get_analyzer(configuration)
miller_supercell_matrix = crystal_lattice_planes_analyzer.miller_supercell_matrix
miller_supercell_material = supercell(configuration.crystal, miller_supercell_matrix)
crystal = configuration.crystal
if configuration.use_conventional_cell:
crystal = crystal_lattice_planes_analyzer.material_with_conventional_lattice
miller_supercell_material = supercell(crystal, miller_supercell_matrix)
return miller_supercell_material

def _enforce_convention(self, material: MaterialWithBuildMetadata) -> MaterialWithBuildMetadata:
Expand Down
22 changes: 22 additions & 0 deletions tests/py/unit/fixtures/slab.py
Original file line number Diff line number Diff line change
Expand Up @@ -604,3 +604,25 @@
"type": "TRI",
},
}


ATOMIC_LAYERS_NI_001: Dict[str, Any] = {
"name": "Ni(001), termination Ni_P4/mmm_2",
"basis": {
"elements": [{"id": 0, "value": "Ni"}],
"coordinates": [{"id": 0, "value": [0.0, 0.0, 1e-06]}],
"units": "crystal",
"labels": [],
"constraints": [],
},
"lattice": {
"a": 2.478974,
"b": 2.478974,
"c": 2.478974,
"alpha": 60.0,
"beta": 60.0,
"gamma": 60.0,
"units": {"length": "angstrom", "angle": "degree"},
"type": "TRI",
},
}
93 changes: 93 additions & 0 deletions tests/py/unit/test_build_metadata_records_input_crystal.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
from typing import Any, Dict, List

import pytest
from mat3ra.made.material import Material
from mat3ra.made.tools.helpers import (
create_atomic_layers,
create_defect_point_vacancy,
create_grain_boundary_linear,
create_grain_boundary_planar,
create_interface_commensurate,
create_interface_twisted,
create_interface_zsl,
create_slab,
get_slab_terminations,
)

from .fixtures.bulk import BULK_Ni_PRIMITIVE
from .fixtures.slab import ATOMIC_LAYERS_NI_001
from .utils import assert_two_entities_deep_almost_equal

MILLER_INDICES = (0, 0, 1)
BULK_Ni_PRIMITIVE_WITH_ID: Dict[str, Any] = {**BULK_Ni_PRIMITIVE, "_id": "platform-id-abc"}

BUILDERS = {
"slab": lambda crystal: create_slab(crystal=crystal, miller_indices=MILLER_INDICES, number_of_layers=3),
"interface_zsl": lambda crystal: create_interface_zsl(
substrate_crystal=crystal, film_crystal=crystal, max_area=50.0
),
"interface_commensurate": lambda crystal: create_interface_commensurate(
material=crystal, target_angle=13.0, angle_tolerance=0.5, max_repetition_int=5
),
"grain_boundary_planar": lambda crystal: create_grain_boundary_planar(
phase_1_material=crystal,
phase_1_miller_indices=MILLER_INDICES,
phase_2_miller_indices=(0, 1, 1),
max_area=100.0,
),
"grain_boundary_linear": lambda crystal: create_grain_boundary_linear(
material=crystal, target_angle=13.0, angle_tolerance=0.5, max_repetition_int=5, gap=1.0
),
"point_defect_vacancy": lambda crystal: create_defect_point_vacancy(crystal, [0.0, 0.0, 0.0], "closest_site"),
}


def get_recorded_source_crystals(node: Any) -> List[Dict[str, Any]]:
"""Crystals recorded next to Miller indices, plus defect hosts. A vacuum's is a built intermediate."""
if isinstance(node, list):
return [crystal for item in node for crystal in get_recorded_source_crystals(item)]
if not isinstance(node, dict):
return []
crystals = []
collected = set()
if isinstance(node.get("crystal"), dict) and "miller_indices" in node:
crystals.append(node["crystal"])
collected.add("crystal")
if node.get("merge_components"):
crystals.append(node["merge_components"][0])
collected.add("merge_components")
return crystals + [
found for key, value in node.items() if key not in collected for found in get_recorded_source_crystals(value)
]


@pytest.mark.parametrize("build", BUILDERS.values(), ids=BUILDERS.keys())
def test_recorded_crystal_is_the_input(build):
material = Material.create(BULK_Ni_PRIMITIVE_WITH_ID)

built = build(material)

recorded = get_recorded_source_crystals(built.model_dump()["metadata"]["build"])
assert recorded
for crystal in recorded:
assert_two_entities_deep_almost_equal(crystal, material)


@pytest.mark.parametrize("material_config, expected_number_of_atoms, expected_gamma", [(BULK_Ni_PRIMITIVE, 8, 90.0)])
def test_create_interface_twisted_uses_conventional_cell(material_config, expected_number_of_atoms, expected_gamma):
crystal = Material.create(material_config)

interface = create_interface_twisted(material1=crystal, material2=crystal, angle=10.0, use_conventional_cell=True)

assert len(interface.basis.elements.values) == expected_number_of_atoms
assert interface.lattice.gamma == pytest.approx(expected_gamma)


@pytest.mark.parametrize("material_config, expected_material_config", [(BULK_Ni_PRIMITIVE, ATOMIC_LAYERS_NI_001)])
def test_create_atomic_layers(material_config, expected_material_config):
material = Material.create(material_config)
termination = get_slab_terminations(material, MILLER_INDICES)[0]

atomic_layers = create_atomic_layers(material, MILLER_INDICES, termination=termination)

assert_two_entities_deep_almost_equal(atomic_layers, expected_material_config)
8 changes: 2 additions & 6 deletions tests/py/unit/test_tools_build_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
from mat3ra.esse.models.core.reusable.axis_enum import AxisEnum
from mat3ra.made.material import Material
from mat3ra.made.tools.analyze.interface.simple import InterfaceAnalyzer
from mat3ra.made.tools.analyze.lattice_planes import CrystalLatticePlanesMaterialAnalyzer
from mat3ra.made.tools.build import MaterialWithBuildMetadata
from mat3ra.made.tools.build.compound_pristine_structures.two_dimensional.interface.base.build_parameters import (
InterfaceBuilderParameters,
Expand Down Expand Up @@ -259,9 +258,6 @@ def test_commensurate_interface_creation(material_config, analyzer_params, direc
def test_create_slab_with_conventional_cell_stores_crystal_hashes_in_metadata():
miller_indices = (0, 0, 1)
material = Material.create(BULK_Ni_PRIMITIVE)
expected_crystal = CrystalLatticePlanesMaterialAnalyzer(
material=material, miller_indices=miller_indices
).material_with_conventional_lattice

slab = create_slab(
crystal=material,
Expand All @@ -274,8 +270,8 @@ def test_create_slab_with_conventional_cell_stores_crystal_hashes_in_metadata():
serialized_slab = slab.model_dump()
crystal = serialized_slab["metadata"]["build"][-1]["configuration"]["stack_components"][0]["crystal"]

assert crystal[HASH_KEY] == expected_crystal.hash
assert crystal[SCALED_HASH_KEY] == expected_crystal.scaled_hash
assert crystal[HASH_KEY] == material.hash
assert crystal[SCALED_HASH_KEY] == material.scaled_hash
assert "bulkId" not in serialized_slab["metadata"]


Expand Down
1 change: 1 addition & 0 deletions tests/py/unit/test_tools_build_slab.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ def get_slab_with_builder(
atomic_layers_repeated_configuration = AtomicLayersUniqueRepeatedConfiguration(
crystal=material,
miller_indices=miller_indices,
use_conventional_cell=False,
termination_top=termination_top,
termination_bottom=termination_bottom,
number_of_repetitions=number_of_layers,
Expand Down