@mat3ra/made 2025.8.7-1 → 2025.8.8-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.7-1",
3
+ "version": "2025.8.8-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",
package/pyproject.toml CHANGED
@@ -19,7 +19,7 @@ dependencies = [
19
19
  # new verison of numpy==2.0.0 is not handled by pymatgen yet
20
20
  "numpy<=1.26.4",
21
21
  "mat3ra-utils",
22
- "mat3ra-esse @ git+https://github.com/Exabyte-io/esse.git@fe1c134459a1a1fe6646bb0d82a075db256cb09c",
22
+ "mat3ra-esse",
23
23
  "mat3ra-code"
24
24
 
25
25
  ]
@@ -110,7 +110,7 @@ def create_interface_simple(
110
110
  miller_indices=substrate_miller_indices,
111
111
  number_of_layers=substrate_number_of_layers,
112
112
  vacuum=0,
113
- termination_formula=substrate_termination_formula,
113
+ termination_top_formula=substrate_termination_formula,
114
114
  xy_supercell_matrix=substrate_xy_supercell_matrix,
115
115
  use_conventional_cell=use_conventional_cell,
116
116
  )
@@ -119,7 +119,7 @@ def create_interface_simple(
119
119
  miller_indices=film_miller_indices,
120
120
  number_of_layers=film_number_of_layers,
121
121
  vacuum=0,
122
- termination_formula=film_termination_formula,
122
+ termination_top_formula=film_termination_formula,
123
123
  xy_supercell_matrix=film_xy_supercell_matrix,
124
124
  use_conventional_cell=use_conventional_cell,
125
125
  )
@@ -46,7 +46,7 @@ def create_interface_zsl(
46
46
  miller_indices=substrate_miller_indices,
47
47
  number_of_layers=substrate_number_of_layers,
48
48
  vacuum=0,
49
- termination_formula=substrate_termination_formula,
49
+ termination_bottom_formula=substrate_termination_formula,
50
50
  use_conventional_cell=use_conventional_cell,
51
51
  )
52
52
  film_slab = create_slab(
@@ -54,7 +54,7 @@ def create_interface_zsl(
54
54
  miller_indices=film_miller_indices,
55
55
  number_of_layers=film_number_of_layers,
56
56
  vacuum=0,
57
- termination_formula=film_termination_formula,
57
+ termination_top_formula=film_termination_formula,
58
58
  use_conventional_cell=use_conventional_cell,
59
59
  )
60
60
 
@@ -12,6 +12,7 @@ from .....build_components.metadata import MaterialWithBuildMetadata
12
12
  from .....build_components import select_slab_termination
13
13
  from .....build_components.operations.core.combinations.stack.configuration import StackConfiguration
14
14
  from .....build_components.entities.core.two_dimensional.vacuum.configuration import VacuumConfiguration
15
+ from .....build_components.entities.auxiliary.two_dimensional.termination import Termination
15
16
 
16
17
 
17
18
  class SlabConfiguration(StackConfiguration, SlabConfigurationSchema):
@@ -19,6 +20,9 @@ class SlabConfiguration(StackConfiguration, SlabConfigurationSchema):
19
20
  stack_components: List[Union[AtomicLayersUniqueRepeatedConfiguration, VacuumConfiguration]] # No Materials!
20
21
  direction: AxisEnum = AxisEnum.z
21
22
 
23
+ termination_top: Optional[Termination] = None
24
+ termination_bottom: Optional[Termination] = None
25
+
22
26
  @property
23
27
  def number_of_layers(self):
24
28
  return self.atomic_layers.number_of_repetitions
@@ -46,7 +50,8 @@ class SlabConfiguration(StackConfiguration, SlabConfigurationSchema):
46
50
  material_or_dict: Union[Material, dict],
47
51
  miller_indices: Tuple[int, int, int],
48
52
  number_of_layers: int,
49
- termination_formula: Optional[str] = None,
53
+ termination_top_formula: Optional[str] = None,
54
+ termination_bottom_formula: Optional[str] = None,
50
55
  vacuum: float = 10.0,
51
56
  use_conventional_cell: bool = True,
52
57
  ) -> "SlabConfiguration":
@@ -56,7 +61,9 @@ class SlabConfiguration(StackConfiguration, SlabConfigurationSchema):
56
61
  material_or_dict (Union[Material, dict]): Material or dictionary representation of the material.
57
62
  miller_indices (Tuple[int, int, int]): Miller indices for the slab surface.
58
63
  number_of_layers (int): Number of atomic layers in the slab, in the number of unit cells.
59
- termination_formula (Optional[str]): Formula of the termination to use for the slab (i.e. "SrTiO").
64
+ termination_top_formula (Optional[str]): Formula of the top termination to use for the slab (i.e. "SrTiO").
65
+ termination_bottom_formula (Optional[str]): Formula of the bottom termination to use for the slab.
66
+
60
67
  vacuum (float): Size of the vacuum layer in Angstroms.
61
68
 
62
69
  Returns:
@@ -71,14 +78,16 @@ class SlabConfiguration(StackConfiguration, SlabConfigurationSchema):
71
78
  material=material, miller_indices=miller_indices
72
79
  )
73
80
  terminations = crystal_lattice_planes_analyzer.terminations
74
- termination = select_slab_termination(terminations, termination_formula)
81
+ termination_top = select_slab_termination(terminations, termination_top_formula)
82
+ termination_bottom = select_slab_termination(terminations, termination_bottom_formula)
75
83
 
76
84
  if use_conventional_cell:
77
85
  material = crystal_lattice_planes_analyzer.material_with_conventional_lattice
78
86
  atomic_layers_repeated_configuration = AtomicLayersUniqueRepeatedConfiguration(
79
87
  crystal=material,
80
88
  miller_indices=miller_indices,
81
- termination_top=termination,
89
+ termination_top=termination_top,
90
+ termination_bottom=termination_bottom,
82
91
  number_of_repetitions=number_of_layers,
83
92
  )
84
93
 
@@ -15,8 +15,10 @@ def create_slab(
15
15
  miller_indices: Tuple[int, int, int] = (0, 0, 1),
16
16
  use_conventional_cell=True,
17
17
  use_orthogonal_c: bool = True,
18
- termination: Optional[Termination] = None,
19
- termination_formula: Optional[str] = None,
18
+ termination_top: Optional[Termination] = None,
19
+ termination_bottom: Optional[Termination] = None,
20
+ termination_top_formula: Optional[str] = None,
21
+ termination_bottom_formula: Optional[str] = None,
20
22
  number_of_layers=1,
21
23
  vacuum=10.0,
22
24
  xy_supercell_matrix=DEFAULT_XY_SUPERCELL_MATRIX,
@@ -46,8 +48,10 @@ def create_slab(
46
48
  )
47
49
  material_to_use = crystal_lattice_planes_analyzer.material_with_conventional_lattice
48
50
 
49
- if termination is not None:
50
- termination_formula = termination.formula
51
+ if termination_top is not None:
52
+ termination_top_formula = termination_top.formula
53
+ if termination_bottom is not None:
54
+ termination_bottom_formula = termination_bottom.formula
51
55
 
52
56
  slab_builder_parameters = SlabBuilderParameters(
53
57
  xy_supercell_matrix=xy_supercell_matrix,
@@ -57,7 +61,8 @@ def create_slab(
57
61
  material_or_dict=material_to_use,
58
62
  miller_indices=miller_indices,
59
63
  number_of_layers=number_of_layers,
60
- termination_formula=termination_formula,
64
+ termination_top_formula=termination_top_formula,
65
+ termination_bottom_formula=termination_bottom_formula,
61
66
  vacuum=vacuum,
62
67
  use_conventional_cell=use_conventional_cell,
63
68
  )
@@ -2,7 +2,7 @@ from typing import Type
2
2
 
3
3
  from ......analyze import BaseMaterialAnalyzer
4
4
  from ......modify import wrap_to_unit_cell
5
- from ......operations.core.unary import supercell, translate
5
+ from ......operations.core.unary import rotate, supercell, translate
6
6
  from ..... import MaterialWithBuildMetadata
7
7
  from ....auxiliary.two_dimensional.miller_indices import MillerIndices
8
8
  from ..crystal_lattice_planes.builder import CrystalLatticePlanesBuilder
@@ -16,15 +16,28 @@ class AtomicLayersUniqueRepeatedBuilder(CrystalLatticePlanesBuilder):
16
16
  crystal_lattice_planes_material = super()._generate(configuration)
17
17
 
18
18
  crystal_lattice_planes_material_analyzer = self.get_analyzer(configuration)
19
- if configuration.termination_top is None:
20
- raise ValueError("termination_top is required for AtomicLayersUniqueRepeatedBuilder")
21
- translation_vector = (
22
- crystal_lattice_planes_material_analyzer.get_translation_vector_for_termination_without_vacuum(
23
- configuration.termination_top
19
+
20
+ if configuration.termination_top is not None:
21
+ termination = configuration.termination_top
22
+ should_rotate = False
23
+ elif configuration.termination_bottom is not None:
24
+ termination = configuration.termination_bottom
25
+ should_rotate = True
26
+ else:
27
+ raise ValueError(
28
+ "Either termination_top or termination_bottom is required for AtomicLayersUniqueRepeatedBuilder"
24
29
  )
30
+
31
+ translation_vector = (
32
+ crystal_lattice_planes_material_analyzer.get_translation_vector_for_termination_without_vacuum(termination)
25
33
  )
26
34
  material_translated = translate(crystal_lattice_planes_material, translation_vector)
27
35
  material_translated_wrapped = wrap_to_unit_cell(material_translated)
36
+
37
+ if should_rotate:
38
+ # Rotation of basis around [1,0,0] yielded the same material, probably due to the symmetry of unit cell.
39
+ # rotation around X and Z simultaneously gives the mirroring effect. (x,y,z) ⟶ (z,−y,x)
40
+ material_translated_wrapped = rotate(material_translated_wrapped, angle=180, axis=[1, 0, 1])
28
41
  material_translated_wrapped_layered = supercell(
29
42
  material_translated_wrapped, [[1, 0, 0], [0, 1, 0], [0, 0, configuration.number_of_repetitions]]
30
43
  )
@@ -35,9 +48,15 @@ class AtomicLayersUniqueRepeatedBuilder(CrystalLatticePlanesBuilder):
35
48
  ) -> MaterialWithBuildMetadata:
36
49
  material_analyzer = BaseMaterialAnalyzer(material=material)
37
50
  material.formula = material_analyzer.formula
38
- termination = configuration.termination_top
39
51
  miller_indices_str = str(MillerIndices(root=configuration.miller_indices))
40
- # for example: "Si(001), termination Si_P4/mmm_1"
41
- new_name = f"{material.formula}{miller_indices_str}, termination {termination}"
52
+
53
+ if configuration.termination_top is not None:
54
+ termination_str = f"termination {configuration.termination_top}"
55
+ elif configuration.termination_bottom is not None:
56
+ termination_str = f"bottom termination {configuration.termination_bottom}"
57
+ else:
58
+ termination_str = ""
59
+
60
+ new_name = f"{material.formula}{miller_indices_str}, {termination_str}"
42
61
  material.name = new_name
43
62
  return material
@@ -9,5 +9,6 @@ from ..crystal_lattice_planes.configuration import CrystalLatticePlanesConfigura
9
9
 
10
10
 
11
11
  class AtomicLayersUniqueRepeatedConfiguration(CrystalLatticePlanesConfiguration, AtomicLayersUniqueRepeatedSchema):
12
- termination_top: Optional[Termination]
12
+ termination_top: Optional[Termination] = None
13
+ termination_bottom: Optional[Termination] = None
13
14
  number_of_repetitions: int
@@ -63,7 +63,10 @@ def recreate_slab_with_fractional_layers(
63
63
  slab_with_int_layers_without_vacuum = create_slab(
64
64
  crystal=slab_without_vacuum.atomic_layers.crystal,
65
65
  miller_indices=slab_without_vacuum.atomic_layers.miller_indices,
66
- termination=slab_without_vacuum.atomic_layers.termination_top,
66
+ termination_top_formula=slab_without_vacuum.atomic_layers.termination_top.formula
67
+ if slab_without_vacuum.atomic_layers.termination_top
68
+ else None,
69
+ termination_bottom_formula=None,
67
70
  number_of_layers=ceiling_number_of_layers,
68
71
  vacuum=0,
69
72
  xy_supercell_matrix=build_parameters.xy_supercell_matrix,
@@ -560,3 +560,47 @@ SLAB_SrTiO3_011_TERMINATION_SrTiO = {
560
560
  "type": "TRI",
561
561
  },
562
562
  }
563
+
564
+
565
+ SLAB_SrTiO3_011_TERMINATION_O2_BOTTOM = {
566
+ "name": "O3SrTi(011), bottom termination O2_Pmmm_2, Slab",
567
+ "basis": {
568
+ "elements": [
569
+ {"id": 0, "value": "Sr"},
570
+ {"id": 1, "value": "Ti"},
571
+ {"id": 2, "value": "O"},
572
+ {"id": 3, "value": "O"},
573
+ {"id": 4, "value": "O"},
574
+ {"id": 5, "value": "Sr"},
575
+ {"id": 6, "value": "Ti"},
576
+ {"id": 7, "value": "O"},
577
+ {"id": 8, "value": "O"},
578
+ {"id": 9, "value": "O"},
579
+ ],
580
+ "coordinates": [
581
+ {"id": 0, "value": [0.5, 0.749999, 0.152537778]},
582
+ {"id": 1, "value": [0, 0.249999, 0.152537778]},
583
+ {"id": 2, "value": [0.5, 0.249999, 0.152537778]},
584
+ {"id": 3, "value": [0, 0.999999, 6.1e-7]},
585
+ {"id": 4, "value": [0, 0.499999, 6.1e-7]},
586
+ {"id": 5, "value": [0.5, 0.249999, 0.457612114]},
587
+ {"id": 6, "value": [0, 0.749999, 0.457612114]},
588
+ {"id": 7, "value": [0.5, 0.749999, 0.457612114]},
589
+ {"id": 8, "value": [0, 0.499999, 0.305074946]},
590
+ {"id": 9, "value": [0, 0.999999, 0.305074946]},
591
+ ],
592
+ "units": "crystal",
593
+ "labels": [],
594
+ "constraints": [],
595
+ },
596
+ "lattice": {
597
+ "a": 3.912701,
598
+ "b": 5.53339482,
599
+ "c": 9.068928726,
600
+ "alpha": 90,
601
+ "beta": 90,
602
+ "gamma": 90,
603
+ "units": {"length": "angstrom", "angle": "degree"},
604
+ "type": "TRI",
605
+ },
606
+ }
@@ -38,10 +38,20 @@ TEST_CASES = [(SUBSTRATE_SI_001, FILM_GE_001, EXPECTED_PROPERTIES_SI_GE_001)]
38
38
  @pytest.mark.parametrize("substrate, film, expected", TEST_CASES)
39
39
  def test_interface_analyzer(substrate, film, expected):
40
40
  substrate_slab_config = SlabConfiguration.from_parameters(
41
- substrate.bulk_config, substrate.miller_indices, substrate.number_of_layers, vacuum=substrate.vacuum
41
+ substrate.bulk_config,
42
+ substrate.miller_indices,
43
+ substrate.number_of_layers,
44
+ vacuum=substrate.vacuum,
45
+ termination_top_formula=None,
46
+ termination_bottom_formula=None,
42
47
  )
43
48
  film_slab_config = SlabConfiguration.from_parameters(
44
- film.bulk_config, film.miller_indices, film.number_of_layers, vacuum=film.vacuum
49
+ film.bulk_config,
50
+ film.miller_indices,
51
+ film.number_of_layers,
52
+ vacuum=film.vacuum,
53
+ termination_top_formula=None,
54
+ termination_bottom_formula=None,
45
55
  )
46
56
 
47
57
  interface_analyzer = InterfaceAnalyzer(
@@ -88,7 +98,12 @@ def test_commensurate_analyzer_functionality(
88
98
  material_config, analyzer_params, expected_matches_len, expected_angle_range
89
99
  ):
90
100
  slab_config = SlabConfiguration.from_parameters(
91
- material_config, miller_indices=(0, 0, 1), number_of_layers=1, vacuum=0.0
101
+ material_config,
102
+ miller_indices=(0, 0, 1),
103
+ number_of_layers=1,
104
+ vacuum=0.0,
105
+ termination_top_formula=None,
106
+ termination_bottom_formula=None,
92
107
  )
93
108
 
94
109
  analyzer = CommensurateLatticeInterfaceAnalyzer(substrate_slab_configuration=slab_config, **analyzer_params)
@@ -67,7 +67,12 @@ EXPECTED_PROPERTIES_SI_GE_001: Final = SimpleNamespace(
67
67
  )
68
68
  def test_zsl_interface_analyzer(substrate, film, zsl_params, expected_matches_min):
69
69
  substrate_slab_config = SlabConfiguration.from_parameters(
70
- substrate.bulk_config, substrate.miller_indices, substrate.number_of_layers, vacuum=0.0
70
+ substrate.bulk_config,
71
+ substrate.miller_indices,
72
+ substrate.number_of_layers,
73
+ vacuum=0.0,
74
+ termination_top_formula=None,
75
+ termination_bottom_formula=None,
71
76
  )
72
77
  film_slab_config = SlabConfiguration.from_parameters(
73
78
  film.bulk_config, film.miller_indices, film.number_of_layers, vacuum=0.0
@@ -150,7 +155,12 @@ def test_zsl_interface_analyzer_sort_by_strain_then_area(
150
155
 
151
156
  analyzer = ZSLInterfaceAnalyzer(
152
157
  substrate_slab_configuration=SlabConfiguration.from_parameters(
153
- substrate.bulk_config, substrate.miller_indices, substrate.number_of_layers, vacuum=0.0
158
+ substrate.bulk_config,
159
+ substrate.miller_indices,
160
+ substrate.number_of_layers,
161
+ vacuum=0.0,
162
+ termination_top_formula=None,
163
+ termination_bottom_formula=None,
154
164
  ),
155
165
  film_slab_configuration=SlabConfiguration.from_parameters(
156
166
  film.bulk_config, film.miller_indices, film.number_of_layers, vacuum=0.0
@@ -47,6 +47,8 @@ def test_create_adatom(
47
47
  crystal=Material.create(crystal_config),
48
48
  number_of_layers=2,
49
49
  xy_supercell_matrix=[[2, 0], [0, 2]],
50
+ termination_top_formula=None,
51
+ termination_bottom_formula=None,
50
52
  )
51
53
  defect = create_defect_adatom(slab, position_on_surface, distance_z, adatom_placement_method, chemical_element)
52
54
 
@@ -25,6 +25,8 @@ def test_create_island_defect(slab_parameters, condition_class, condition_params
25
25
  Material.create(slab_parameters["crystal"]),
26
26
  number_of_layers=slab_parameters["number_of_layers"],
27
27
  xy_supercell_matrix=slab_parameters["xy_supercell_matrix"],
28
+ termination_top_formula=None,
29
+ termination_bottom_formula=None,
28
30
  )
29
31
  condition = condition_class(
30
32
  center_position=condition_params["center_position"],
@@ -30,6 +30,8 @@ def test_create_terrace(
30
30
  Material.create(slab_parameters["crystal"]),
31
31
  number_of_layers=slab_parameters["number_of_layers"],
32
32
  xy_supercell_matrix=slab_parameters["xy_supercell_matrix"],
33
+ termination_top_formula=None,
34
+ termination_bottom_formula=None,
33
35
  )
34
36
  terrace = create_defect_terrace(
35
37
  slab=slab,
@@ -82,10 +82,16 @@ PRECISION = 1e-3
82
82
  def test_simple_interface_builder(substrate, film, expected_interface):
83
83
  builder = InterfaceBuilder(build_parameters=InterfaceBuilderParameters(make_primitive=False))
84
84
  substrate_slab_config = SlabConfiguration.from_parameters(
85
- substrate.bulk_config, substrate.miller_indices, substrate.number_of_layers, vacuum=substrate.vacuum
85
+ substrate.bulk_config,
86
+ substrate.miller_indices,
87
+ substrate.number_of_layers,
88
+ vacuum=substrate.vacuum,
86
89
  )
87
90
  film_slab_config = SlabConfiguration.from_parameters(
88
- film.bulk_config, film.miller_indices, film.number_of_layers, vacuum=film.vacuum
91
+ film.bulk_config,
92
+ film.miller_indices,
93
+ film.number_of_layers,
94
+ vacuum=film.vacuum,
89
95
  )
90
96
 
91
97
  analyzer = InterfaceAnalyzer(
@@ -111,14 +117,12 @@ def test_create_simple_interface_between_slabs(substrate, film, expected_interfa
111
117
  miller_indices=substrate.miller_indices,
112
118
  number_of_layers=substrate.number_of_layers,
113
119
  vacuum=0,
114
- termination_formula=None,
115
120
  )
116
121
  film_slab_config = SlabConfiguration.from_parameters(
117
122
  material_or_dict=film.bulk_config,
118
123
  miller_indices=film.miller_indices,
119
124
  number_of_layers=film.number_of_layers,
120
125
  vacuum=0,
121
- termination_formula=None,
122
126
  )
123
127
 
124
128
  substrate_slab = SlabBuilder().get_material(substrate_slab_config)
@@ -129,7 +129,7 @@ def test_create_zsl_interface_between_slabs(substrate, film, gap, vacuum, max_ar
129
129
  miller_indices=substrate.miller_indices,
130
130
  number_of_layers=substrate.number_of_layers,
131
131
  vacuum=0.0,
132
- termination_formula=None,
132
+ termination_top_formula=None,
133
133
  use_conventional_cell=True,
134
134
  )
135
135
  film_slab_config = SlabConfiguration.from_parameters(
@@ -137,7 +137,7 @@ def test_create_zsl_interface_between_slabs(substrate, film, gap, vacuum, max_ar
137
137
  miller_indices=film.miller_indices,
138
138
  number_of_layers=film.number_of_layers,
139
139
  vacuum=0.0,
140
- termination_formula=None,
140
+ termination_bottom_formula=None,
141
141
  use_conventional_cell=True,
142
142
  )
143
143
 
@@ -17,7 +17,7 @@ from mat3ra.made.tools.build.pristine_structures.two_dimensional.slab import (
17
17
  SlabBuilderParameters,
18
18
  SlabConfiguration,
19
19
  )
20
- from mat3ra.made.tools.build.pristine_structures.two_dimensional.slab.helpers import create_slab, get_slab_terminations
20
+ from mat3ra.made.tools.build.pristine_structures.two_dimensional.slab.helpers import create_slab
21
21
  from mat3ra.made.tools.build.pristine_structures.two_dimensional.slab.termination_utils import select_slab_termination
22
22
  from mat3ra.made.tools.build.pristine_structures.two_dimensional.slab_strained_supercell.builder import (
23
23
  SlabStrainedSupercellBuilder,
@@ -39,6 +39,7 @@ from unit.fixtures.slab import (
39
39
  SI_PRIMITIVE_SLAB_001,
40
40
  SLAB_SI_CONVENTIONAL_001_NO_VACUUM,
41
41
  SLAB_SrTiO3_011_TERMINATION_O2,
42
+ SLAB_SrTiO3_011_TERMINATION_O2_BOTTOM,
42
43
  SLAB_SrTiO3_011_TERMINATION_SrTiO,
43
44
  )
44
45
 
@@ -79,6 +80,7 @@ PARAMS_BUILD_SLAB_CONVENTIONAL_SrTiO_SrTiO: Final = (
79
80
  BULK_SrTiO3,
80
81
  (0, 1, 1),
81
82
  "SrTiO",
83
+ None,
82
84
  2,
83
85
  5.0,
84
86
  [[1, 0], [0, 1]],
@@ -88,6 +90,18 @@ PARAMS_BUILD_SLAB_CONVENTIONAL_SrTiO_O2: Final = (
88
90
  BULK_SrTiO3,
89
91
  (0, 1, 1),
90
92
  "O2",
93
+ None,
94
+ 2,
95
+ 5.0,
96
+ [[1, 0], [0, 1]],
97
+ )
98
+
99
+
100
+ PARAMS_BUILD_SLAB_CONVENTIONAL_SrTiO_O2_BOTTOM: Final = (
101
+ BULK_SrTiO3,
102
+ (0, 1, 1),
103
+ None,
104
+ "O2",
91
105
  2,
92
106
  5.0,
93
107
  [[1, 0], [0, 1]],
@@ -97,6 +111,7 @@ PARAMS_CREATE_SLAB: Final = (
97
111
  BULK_Si_CONVENTIONAL,
98
112
  (0, 0, 1),
99
113
  "Si",
114
+ None,
100
115
  2,
101
116
  5,
102
117
  [[1, 0], [0, 1]],
@@ -107,7 +122,8 @@ PARAMS_CREATE_SLAB: Final = (
107
122
  def get_slab_with_builder(
108
123
  material: Material,
109
124
  miller_indices: Tuple[int, int, int],
110
- termination_formula: str,
125
+ termination_top_formula: str,
126
+ termination_bottom_formula: str,
111
127
  number_of_layers: int,
112
128
  vacuum: float,
113
129
  xy_supercell_matrix: list,
@@ -116,12 +132,20 @@ def get_slab_with_builder(
116
132
  material=material, miller_indices=miller_indices
117
133
  )
118
134
  terminations = crystal_lattice_planes_analyzer.terminations
119
- termination = select_slab_termination(terminations, termination_formula)
135
+ termination_top = (
136
+ select_slab_termination(terminations, termination_top_formula) if termination_top_formula is not None else None
137
+ )
138
+ termination_bottom = (
139
+ select_slab_termination(terminations, termination_bottom_formula)
140
+ if termination_bottom_formula is not None
141
+ else None
142
+ )
120
143
 
121
144
  atomic_layers_repeated_configuration = AtomicLayersUniqueRepeatedConfiguration(
122
145
  crystal=material,
123
146
  miller_indices=miller_indices,
124
- termination_top=termination,
147
+ termination_top=termination_top,
148
+ termination_bottom=termination_bottom,
125
149
  number_of_repetitions=number_of_layers,
126
150
  )
127
151
  atomic_layers_repeated_orthogonal_c = AtomicLayersUniqueRepeatedBuilder().get_material(
@@ -159,7 +183,7 @@ def test_build_slab_primitive(
159
183
  ):
160
184
  material = MaterialWithBuildMetadata.create(material_config)
161
185
  slab = get_slab_with_builder(
162
- material, miller_indices, termination_formula, number_of_layers, vacuum, xy_supercell_matrix
186
+ material, miller_indices, termination_formula, None, number_of_layers, vacuum, xy_supercell_matrix
163
187
  )
164
188
  slab.metadata.build = [] # Remove build metadata for comparison
165
189
  expected_slab_config.get("metadata", {}).pop("build", None) # Remove build metadata for comparison
@@ -198,6 +222,7 @@ def test_build_slab_conventional(
198
222
  conventional_material,
199
223
  miller_indices,
200
224
  termination_formula,
225
+ None,
201
226
  number_of_layers,
202
227
  vacuum,
203
228
  xy_supercell_matrix,
@@ -207,7 +232,7 @@ def test_build_slab_conventional(
207
232
 
208
233
 
209
234
  @pytest.mark.parametrize(
210
- "material_config, miller_indices, termination_formula, number_of_layers,"
235
+ "material_config, miller_indices, termination_top_formula,termination_bottom_formula, number_of_layers,"
211
236
  + " vacuum, xy_supercell_matrix, expected_slab_config",
212
237
  [
213
238
  (
@@ -218,12 +243,17 @@ def test_build_slab_conventional(
218
243
  *PARAMS_BUILD_SLAB_CONVENTIONAL_SrTiO_O2,
219
244
  SLAB_SrTiO3_011_TERMINATION_O2,
220
245
  ),
246
+ (
247
+ *PARAMS_BUILD_SLAB_CONVENTIONAL_SrTiO_O2_BOTTOM,
248
+ SLAB_SrTiO3_011_TERMINATION_O2_BOTTOM,
249
+ ),
221
250
  ],
222
251
  )
223
252
  def test_build_slab_conventional_with_multiple_terminations(
224
253
  material_config,
225
254
  miller_indices,
226
- termination_formula,
255
+ termination_top_formula,
256
+ termination_bottom_formula,
227
257
  number_of_layers,
228
258
  vacuum,
229
259
  xy_supercell_matrix,
@@ -236,7 +266,8 @@ def test_build_slab_conventional_with_multiple_terminations(
236
266
  slab = get_slab_with_builder(
237
267
  conventional_material,
238
268
  miller_indices,
239
- termination_formula,
269
+ termination_top_formula,
270
+ termination_bottom_formula,
240
271
  number_of_layers,
241
272
  vacuum,
242
273
  xy_supercell_matrix,
@@ -248,7 +279,7 @@ def test_build_slab_conventional_with_multiple_terminations(
248
279
 
249
280
 
250
281
  @pytest.mark.parametrize(
251
- "material_config, miller_indices, termination_formula, number_of_layers,"
282
+ "material_config, miller_indices, termination_top_formula, termination_bottom_formula, number_of_layers,"
252
283
  + " vacuum, xy_supercell, use_conventional_cell, expected_slab_config",
253
284
  [
254
285
  (
@@ -260,7 +291,8 @@ def test_build_slab_conventional_with_multiple_terminations(
260
291
  def test_create_slab(
261
292
  material_config,
262
293
  miller_indices,
263
- termination_formula,
294
+ termination_top_formula,
295
+ termination_bottom_formula,
264
296
  number_of_layers,
265
297
  vacuum,
266
298
  xy_supercell,
@@ -268,13 +300,12 @@ def test_create_slab(
268
300
  expected_slab_config,
269
301
  ):
270
302
  crystal = Material.create(material_config)
271
- terminations = get_slab_terminations(material=crystal, miller_indices=miller_indices)
272
- termination = select_slab_termination(terminations, termination_formula)
273
303
  slab = create_slab(
274
304
  crystal=crystal,
275
305
  miller_indices=miller_indices,
276
306
  use_conventional_cell=use_conventional_cell,
277
- termination=termination,
307
+ termination_top_formula=termination_top_formula,
308
+ termination_bottom_formula=termination_bottom_formula,
278
309
  number_of_layers=number_of_layers,
279
310
  vacuum=vacuum,
280
311
  xy_supercell_matrix=xy_supercell,
@@ -326,7 +357,7 @@ def test_build_slab_strained(
326
357
  config = SlabStrainedSupercellConfiguration.from_parameters(
327
358
  material_or_dict=material,
328
359
  miller_indices=miller_indices,
329
- termination_formula=termination_formula,
360
+ termination_top_formula=termination_formula,
330
361
  number_of_layers=number_of_layers,
331
362
  vacuum=vacuum,
332
363
  )