@mat3ra/made 2026.4.2-1 → 2026.5.7-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": "2026.4.2-1",
3
+ "version": "2026.5.7-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",
@@ -4,7 +4,7 @@ from typing import Any, List, Optional, Union
4
4
  from mat3ra.code.constants import AtomicCoordinateUnits, Units
5
5
  from mat3ra.code.entity import HasDescriptionHasMetadataNamedDefaultableInMemoryEntityPydantic
6
6
  from mat3ra.esse.models.material import MaterialSchema
7
- from pydantic import ConfigDict, SkipValidation
7
+ from pydantic import ConfigDict, SkipValidation, computed_field, field_serializer
8
8
 
9
9
  from .basis import Basis
10
10
  from .lattice import Lattice
@@ -120,10 +120,15 @@ class Material(MaterialSchema, HasDescriptionHasMetadataNamedDefaultableInMemory
120
120
  message = f"{self.basis.hash_string}#{self.lattice.get_hash_string(is_scaled)}#{salt}"
121
121
  return hashlib.md5(message.encode()).hexdigest()
122
122
 
123
+ @computed_field
123
124
  @property
124
125
  def hash(self) -> str:
125
126
  return self.calculate_hash()
126
127
 
128
+ @field_serializer("scaledHash")
129
+ def serialize_scaled_hash(self, _scaled_hash: Optional[str]) -> str:
130
+ return self.scaled_hash
131
+
127
132
  @property
128
133
  def scaled_hash(self) -> str:
129
134
  return self.calculate_hash(is_scaled=True)
@@ -40,14 +40,6 @@ def create_slab(
40
40
  Returns:
41
41
  Material: The generated slab material.
42
42
  """
43
- material_to_use = crystal
44
-
45
- if use_conventional_cell:
46
- crystal_lattice_planes_analyzer = CrystalLatticePlanesMaterialAnalyzer(
47
- material=crystal, miller_indices=miller_indices
48
- )
49
- material_to_use = crystal_lattice_planes_analyzer.material_with_conventional_lattice
50
-
51
43
  if termination_top is not None:
52
44
  termination_top_formula = termination_top.formula
53
45
  if termination_bottom is not None:
@@ -58,7 +50,7 @@ def create_slab(
58
50
  use_orthogonal_c=use_orthogonal_c,
59
51
  )
60
52
  slab_configuration = SlabConfiguration.from_parameters(
61
- material_or_dict=material_to_use,
53
+ material_or_dict=crystal,
62
54
  miller_indices=miller_indices,
63
55
  number_of_layers=number_of_layers,
64
56
  termination_top_formula=termination_top_formula,
@@ -12,6 +12,8 @@ from unit.fixtures.slab import BULK_Si_CONVENTIONAL
12
12
  from unit.utils import assert_two_entities_deep_almost_equal
13
13
 
14
14
  FIXTURES_DIR = Path(__file__).parents[2] / "fixtures"
15
+ HASH_KEY = "hash"
16
+ SCALED_HASH_KEY = "scaledHash"
15
17
 
16
18
 
17
19
  def load_fixture(name: str) -> dict:
@@ -136,3 +138,11 @@ def test_calculate_hash(fixture_file):
136
138
  material = Material.create(fixture)
137
139
  assert material.hash == fixture["hash"]
138
140
  assert material.scaled_hash == fixture["scaledHash"]
141
+
142
+
143
+ def test_model_dump_includes_hashes():
144
+ material = Material.create_default()
145
+ serialized_material = material.model_dump()
146
+
147
+ assert serialized_material[HASH_KEY] == material.hash
148
+ assert serialized_material[SCALED_HASH_KEY] == material.scaled_hash
@@ -5,6 +5,7 @@ import pytest
5
5
  from mat3ra.esse.models.core.reusable.axis_enum import AxisEnum
6
6
  from mat3ra.made.material import Material
7
7
  from mat3ra.made.tools.analyze.interface.simple import InterfaceAnalyzer
8
+ from mat3ra.made.tools.analyze.lattice_planes import CrystalLatticePlanesMaterialAnalyzer
8
9
  from mat3ra.made.tools.build import MaterialWithBuildMetadata
9
10
  from mat3ra.made.tools.build.compound_pristine_structures.two_dimensional.interface.base.build_parameters import (
10
11
  InterfaceBuilderParameters,
@@ -39,6 +40,9 @@ from .fixtures.interface.twisted_nanoribbons import TWISTED_INTERFACE_GRAPHENE_G
39
40
  from .fixtures.monolayer import GRAPHENE
40
41
  from .utils import OSPlatform, assert_two_entities_deep_almost_equal
41
42
 
43
+ HASH_KEY = "hash"
44
+ SCALED_HASH_KEY = "scaledHash"
45
+
42
46
  Si_Ge_SIMPLE_INTERFACE_TEST_CASE = (
43
47
  SimpleNamespace(
44
48
  bulk_config=BULK_Si_CONVENTIONAL,
@@ -253,6 +257,29 @@ def test_commensurate_interface_creation(material_config, analyzer_params, direc
253
257
  assert_two_entities_deep_almost_equal(interface, expected_interface, atol=PRECISION)
254
258
 
255
259
 
260
+ def test_create_slab_with_conventional_cell_stores_crystal_hashes_in_metadata():
261
+ miller_indices = (0, 0, 1)
262
+ material = Material.create(BULK_Ni_PRIMITIVE)
263
+ expected_crystal = CrystalLatticePlanesMaterialAnalyzer(
264
+ material=material, miller_indices=miller_indices
265
+ ).material_with_conventional_lattice
266
+
267
+ slab = create_slab(
268
+ crystal=material,
269
+ miller_indices=miller_indices,
270
+ use_conventional_cell=True,
271
+ use_orthogonal_c=True,
272
+ number_of_layers=1,
273
+ vacuum=0.0,
274
+ )
275
+ serialized_slab = slab.model_dump()
276
+ crystal = serialized_slab["metadata"]["build"][-1]["configuration"]["stack_components"][0]["crystal"]
277
+
278
+ assert crystal[HASH_KEY] == expected_crystal.hash
279
+ assert crystal[SCALED_HASH_KEY] == expected_crystal.scaled_hash
280
+ assert "bulkId" not in serialized_slab["metadata"]
281
+
282
+
256
283
  @pytest.mark.parametrize(
257
284
  "interface_config, expected_coordinate_level",
258
285
  [(GRAPHENE_NICKEL_INTERFACE_TOP_HCP, 12.048)],