@mat3ra/made 2026.7.29-0 → 2026.8.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.7.29-0",
3
+ "version": "2026.8.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",
@@ -17,6 +17,47 @@ def translate(material: Material, vector: Vector3D) -> Material:
17
17
  return translate_by_vector(material, vector, use_cartesian_coordinates=True)
18
18
 
19
19
 
20
+ def translate_atoms(
21
+ material: Material,
22
+ atom_ids: List[int],
23
+ vector: Vector3D,
24
+ use_cartesian_coordinates: bool = True,
25
+ ) -> Material:
26
+ """
27
+ Translate the given atoms by a vector.
28
+
29
+ Basis order is preserved.
30
+
31
+ Args:
32
+ material (Material): The material to translate atoms in.
33
+ atom_ids (List[int]): Indices of the atoms to translate.
34
+ vector (Vector3D): The vector to translate the atoms by.
35
+ use_cartesian_coordinates (bool): Whether `vector` is in cartesian coordinates.
36
+
37
+ Returns:
38
+ Material: The material with the given atoms translated.
39
+
40
+ Raises:
41
+ IndexError: If any atom id is out of range for the basis.
42
+ """
43
+ new_material = material.clone()
44
+ if use_cartesian_coordinates:
45
+ new_material.to_cartesian()
46
+ coordinates: List[List[float]] = [list(coordinate) for coordinate in new_material.basis.coordinates.values]
47
+
48
+ out_of_range = [atom_id for atom_id in atom_ids if not -len(coordinates) <= atom_id < len(coordinates)]
49
+ if out_of_range:
50
+ raise IndexError(f"Atom ids {out_of_range} are out of range for a basis of {len(coordinates)} atoms.")
51
+
52
+ for atom_id in atom_ids:
53
+ coordinates[atom_id] = (np.array(coordinates[atom_id]) + np.array(vector)).tolist()
54
+
55
+ new_material.set_coordinates(coordinates)
56
+ if use_cartesian_coordinates:
57
+ new_material.to_crystal()
58
+ return new_material
59
+
60
+
20
61
  @decorator_convert_supercell_matrix_2x2_to_3x3
21
62
  def supercell(material: MaterialWithBuildMetadata, supercell_matrix) -> MaterialWithBuildMetadata:
22
63
  atoms = to_ase(material)
@@ -5,9 +5,11 @@ import pytest
5
5
  from mat3ra.esse.models.core.abstract.matrix_3x3 import Matrix3x3Schema
6
6
  from mat3ra.esse.models.core.reusable.axis_enum import AxisEnum
7
7
  from mat3ra.made.material import Material
8
- from mat3ra.made.tools.build_components.operations.core.modifications.perturb import SineWavePerturbationFunctionHolder
8
+ from mat3ra.made.tools.build_components.operations.core.modifications.perturb import (
9
+ SineWavePerturbationFunctionHolder,
10
+ )
9
11
  from mat3ra.made.tools.operations.core.binary import stack_two_materials
10
- from mat3ra.made.tools.operations.core.unary import perturb, strain
12
+ from mat3ra.made.tools.operations.core.unary import perturb, strain, translate_atoms
11
13
  from mat3ra.made.tools.operations.reusable.unary import transform_material_by_matrix
12
14
  from unit.fixtures.bulk import BULK_Si_CONVENTIONAL
13
15
  from unit.fixtures.strain import BULK_Si_CONVENTIONAL_STRAINED
@@ -100,3 +102,25 @@ def test_transform_material_by_matrix(matrix, expected_a, expected_b, expected_c
100
102
  assert transformed_material.lattice.a == pytest.approx(expected_a, abs=1e-6)
101
103
  assert transformed_material.lattice.b == pytest.approx(expected_b, abs=1e-6)
102
104
  assert transformed_material.lattice.c == pytest.approx(expected_c, abs=1e-6)
105
+
106
+
107
+ @pytest.mark.parametrize(
108
+ "material_config, atom_ids, vector, expected_coord_change",
109
+ [
110
+ (SI_CONVENTIONAL_SLAB_001, [3], [0.0, 0.0, -1.5], [0.0, 0.0, -1.5]),
111
+ ],
112
+ )
113
+ def test_translate_atoms(material_config, atom_ids, vector, expected_coord_change):
114
+ material = Material.create(material_config)
115
+ original_coords = [coord[:] for coord in material.basis.coordinates.values]
116
+
117
+ translated_material = translate_atoms(material, atom_ids, vector)
118
+ lattice_vectors = np.array(material.lattice.vector_arrays)
119
+
120
+ actual_delta = (
121
+ np.array(translated_material.basis.coordinates.values[atom_ids[0]]) - np.array(original_coords[atom_ids[0]])
122
+ ) @ lattice_vectors
123
+ assert np.isclose(actual_delta, expected_coord_change, atol=1e-4).all()
124
+
125
+ untouched_id = atom_ids[0] - 1
126
+ assert np.allclose(translated_material.basis.coordinates.values[untouched_id], original_coords[untouched_id])