@mat3ra/made 2024.6.25-0 → 2024.7.1-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 +1 -1
- package/src/py/mat3ra/made/tools/analyze.py +39 -11
- package/src/py/mat3ra/made/tools/build/defect/builders.py +8 -7
- package/src/py/mat3ra/made/tools/build/mixins.py +2 -1
- package/src/py/mat3ra/made/tools/build/slab/builders.py +3 -5
- package/src/py/mat3ra/made/tools/build/slab/configuration.py +2 -2
- package/src/py/mat3ra/made/tools/build/supercell.py +3 -4
- package/src/py/mat3ra/made/tools/calculate.py +15 -16
- package/src/py/mat3ra/made/tools/convert/__init__.py +10 -8
- package/src/py/mat3ra/made/tools/convert/utils.py +2 -14
- package/src/py/mat3ra/made/tools/modify.py +185 -19
- package/src/py/mat3ra/made/tools/third_party.py +41 -0
- package/src/py/mat3ra/made/tools/utils.py +71 -4
- package/tests/py/unit/test_tools_build.py +2 -2
- package/tests/py/unit/test_tools_modify.py +34 -9
package/package.json
CHANGED
|
@@ -1,24 +1,21 @@
|
|
|
1
|
-
from typing import List, Optional
|
|
1
|
+
from typing import Callable, List, Optional
|
|
2
2
|
|
|
3
3
|
import numpy as np
|
|
4
|
-
from ase import Atoms
|
|
5
|
-
from pymatgen.core import IStructure as PymatgenIStructure
|
|
6
4
|
|
|
7
5
|
from ..material import Material
|
|
8
6
|
from .convert import decorator_convert_material_args_kwargs_to_atoms, to_pymatgen
|
|
9
|
-
|
|
10
|
-
PymatgenIStructure = PymatgenIStructure
|
|
7
|
+
from .third_party import ASEAtoms, PymatgenIStructure
|
|
11
8
|
|
|
12
9
|
|
|
13
10
|
@decorator_convert_material_args_kwargs_to_atoms
|
|
14
11
|
def get_average_interlayer_distance(
|
|
15
|
-
interface_atoms:
|
|
12
|
+
interface_atoms: ASEAtoms, tag_substrate: str, tag_film: str, threshold: float = 0.5
|
|
16
13
|
) -> float:
|
|
17
14
|
"""
|
|
18
15
|
Calculate the average distance between the top layer of substrate atoms and the bottom layer of film atoms.
|
|
19
16
|
|
|
20
17
|
Args:
|
|
21
|
-
interface_atoms (ase.
|
|
18
|
+
interface_atoms (ase.ASEAtoms): The ASE ASEAtoms object containing both sets of atoms.
|
|
22
19
|
tag_substrate (int): The tag representing the substrate atoms.
|
|
23
20
|
tag_film (int): The tag representing the film atoms.
|
|
24
21
|
threshold (float): The threshold for identifying the top and bottom layers of atoms.
|
|
@@ -48,12 +45,12 @@ def get_average_interlayer_distance(
|
|
|
48
45
|
|
|
49
46
|
|
|
50
47
|
@decorator_convert_material_args_kwargs_to_atoms
|
|
51
|
-
def get_surface_area(atoms:
|
|
48
|
+
def get_surface_area(atoms: ASEAtoms):
|
|
52
49
|
"""
|
|
53
50
|
Calculate the area of the surface perpendicular to the z-axis of the atoms structure.
|
|
54
51
|
|
|
55
52
|
Args:
|
|
56
|
-
atoms (ase.
|
|
53
|
+
atoms (ase.ASEAtoms): The ASEAtoms object to calculate the surface area of.
|
|
57
54
|
|
|
58
55
|
Returns:
|
|
59
56
|
float: The surface area of the atoms.
|
|
@@ -64,12 +61,12 @@ def get_surface_area(atoms: Atoms):
|
|
|
64
61
|
|
|
65
62
|
|
|
66
63
|
@decorator_convert_material_args_kwargs_to_atoms
|
|
67
|
-
def get_chemical_formula(atoms:
|
|
64
|
+
def get_chemical_formula(atoms: ASEAtoms):
|
|
68
65
|
"""
|
|
69
66
|
Calculate the formula of the atoms structure.
|
|
70
67
|
|
|
71
68
|
Args:
|
|
72
|
-
atoms (ase.
|
|
69
|
+
atoms (ase.ASEAtoms): The ASEAtoms object to calculate the formula of.
|
|
73
70
|
|
|
74
71
|
Returns:
|
|
75
72
|
str: The formula of the atoms.
|
|
@@ -201,3 +198,34 @@ def get_atom_indices_within_radius_pbc(
|
|
|
201
198
|
|
|
202
199
|
selected_indices = [site.index for site in sites_within_radius]
|
|
203
200
|
return selected_indices
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def get_atom_indices_with_condition_on_coordinates(
|
|
204
|
+
material: Material,
|
|
205
|
+
condition: Callable[[List[float]], bool],
|
|
206
|
+
use_cartesian_coordinates: bool = False,
|
|
207
|
+
) -> List[int]:
|
|
208
|
+
"""
|
|
209
|
+
Select atoms whose coordinates satisfy the given condition.
|
|
210
|
+
|
|
211
|
+
Args:
|
|
212
|
+
material (Material): Material object
|
|
213
|
+
condition (Callable[List[float], bool]): Function that checks if coordinates satisfy the condition.
|
|
214
|
+
use_cartesian (bool): Whether to use Cartesian coordinates for the condition evaluation.
|
|
215
|
+
|
|
216
|
+
Returns:
|
|
217
|
+
List[int]: List of indices of atoms whose coordinates satisfy the condition.
|
|
218
|
+
"""
|
|
219
|
+
new_material = material.clone()
|
|
220
|
+
if use_cartesian_coordinates:
|
|
221
|
+
new_basis = new_material.basis
|
|
222
|
+
new_basis.to_cartesian()
|
|
223
|
+
new_material.basis = new_basis
|
|
224
|
+
coordinates = new_material.basis.coordinates.to_array_of_values_with_ids()
|
|
225
|
+
|
|
226
|
+
selected_indices = []
|
|
227
|
+
for coord in coordinates:
|
|
228
|
+
if condition(coord.value):
|
|
229
|
+
selected_indices.append(coord.id)
|
|
230
|
+
|
|
231
|
+
return selected_indices
|
|
@@ -2,15 +2,16 @@ from typing import List, Callable
|
|
|
2
2
|
|
|
3
3
|
from mat3ra.made.material import Material
|
|
4
4
|
from pydantic import BaseModel
|
|
5
|
-
from pymatgen.analysis.defects.core import (
|
|
6
|
-
Substitution as PymatgenSubstitution,
|
|
7
|
-
Vacancy as PymatgenVacancy,
|
|
8
|
-
Interstitial as PymatgenInterstitial,
|
|
9
|
-
)
|
|
10
|
-
from pymatgen.core import PeriodicSite as PymatgenPeriodicSite
|
|
11
5
|
|
|
6
|
+
from ...third_party import (
|
|
7
|
+
PymatgenStructure,
|
|
8
|
+
PymatgenPeriodicSite,
|
|
9
|
+
PymatgenVacancy,
|
|
10
|
+
PymatgenSubstitution,
|
|
11
|
+
PymatgenInterstitial,
|
|
12
|
+
)
|
|
12
13
|
from ...build import BaseBuilder
|
|
13
|
-
from ...convert import
|
|
14
|
+
from ...convert import to_pymatgen
|
|
14
15
|
from ..mixins import ConvertGeneratedItemsPymatgenStructureMixin
|
|
15
16
|
from .configuration import PointDefectConfiguration
|
|
16
17
|
|
|
@@ -1,18 +1,16 @@
|
|
|
1
|
-
from pymatgen.core.surface import SlabGenerator as PymatgenSlabGenerator
|
|
2
|
-
from ...convert import label_pymatgen_slab_termination
|
|
3
1
|
from typing import List
|
|
4
2
|
from pydantic import BaseModel
|
|
5
3
|
|
|
6
|
-
|
|
7
4
|
from mat3ra.made.material import Material
|
|
8
5
|
|
|
9
|
-
from
|
|
6
|
+
from ...third_party import PymatgenSlab, PymatgenSlabGenerator, label_pymatgen_slab_termination
|
|
10
7
|
from ...analyze import get_chemical_formula
|
|
11
|
-
from ...convert import to_pymatgen
|
|
8
|
+
from ...convert import to_pymatgen
|
|
12
9
|
from ...build import BaseBuilder
|
|
13
10
|
from ...build.mixins import ConvertGeneratedItemsPymatgenStructureMixin
|
|
14
11
|
from ..supercell import create_supercell
|
|
15
12
|
from .configuration import SlabConfiguration
|
|
13
|
+
from .termination import Termination
|
|
16
14
|
|
|
17
15
|
|
|
18
16
|
class SlabSelectorParameters(BaseModel):
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
from typing import List, Tuple, Any
|
|
2
|
+
from pydantic import BaseModel
|
|
2
3
|
|
|
3
4
|
from mat3ra.code.entity import InMemoryEntity
|
|
4
|
-
from pymatgen.symmetry.analyzer import SpacegroupAnalyzer as PymatgenSpacegroupAnalyzer
|
|
5
|
-
from pydantic import BaseModel
|
|
6
5
|
|
|
7
6
|
from mat3ra.made.material import Material
|
|
7
|
+
from ...third_party import PymatgenSpacegroupAnalyzer
|
|
8
8
|
from ...convert import to_pymatgen, from_pymatgen
|
|
9
9
|
|
|
10
10
|
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
from typing import List
|
|
2
|
-
from ase import Atoms
|
|
3
|
-
from ase.build.supercells import make_supercell
|
|
4
2
|
|
|
5
3
|
from mat3ra.made.material import Material
|
|
4
|
+
from ..third_party import ASEAtoms, ase_make_supercell
|
|
6
5
|
from ..utils import decorator_convert_2x2_to_3x3
|
|
7
6
|
from ..convert import from_ase, decorator_convert_material_args_kwargs_to_atoms
|
|
8
7
|
|
|
9
8
|
|
|
10
9
|
@decorator_convert_2x2_to_3x3
|
|
11
10
|
@decorator_convert_material_args_kwargs_to_atoms
|
|
12
|
-
def create_supercell(atoms:
|
|
11
|
+
def create_supercell(atoms: ASEAtoms, supercell_matrix: List[List[int]]) -> Material:
|
|
13
12
|
"""
|
|
14
13
|
Create a supercell of the atoms.
|
|
15
14
|
|
|
@@ -21,5 +20,5 @@ def create_supercell(atoms: Atoms, supercell_matrix: List[List[int]]) -> Materia
|
|
|
21
20
|
Material: The supercell of the atoms.
|
|
22
21
|
"""
|
|
23
22
|
|
|
24
|
-
supercell_atoms =
|
|
23
|
+
supercell_atoms = ase_make_supercell(atoms, supercell_matrix)
|
|
25
24
|
return Material(from_ase(supercell_atoms))
|
|
@@ -1,22 +1,19 @@
|
|
|
1
1
|
from typing import Optional
|
|
2
2
|
|
|
3
|
-
from ase import Atoms
|
|
4
|
-
from ase.calculators.calculator import Calculator
|
|
5
|
-
from ase.calculators.emt import EMT
|
|
6
|
-
|
|
7
3
|
from ..material import Material
|
|
8
4
|
from .analyze import get_surface_area
|
|
9
5
|
from .build.interface.utils import get_slab
|
|
10
6
|
from .convert import decorator_convert_material_args_kwargs_to_atoms
|
|
7
|
+
from .third_party import ASEAtoms, ASECalculator, ASECalculatorEMT
|
|
11
8
|
|
|
12
9
|
|
|
13
10
|
@decorator_convert_material_args_kwargs_to_atoms
|
|
14
|
-
def calculate_total_energy(atoms:
|
|
11
|
+
def calculate_total_energy(atoms: ASEAtoms, calculator: ASECalculator):
|
|
15
12
|
"""
|
|
16
13
|
Set calculator for ASE Atoms and calculate the total energy.
|
|
17
14
|
|
|
18
15
|
Args:
|
|
19
|
-
atoms (
|
|
16
|
+
atoms (ASEAtoms): The Atoms object to calculate the energy of.
|
|
20
17
|
calculator (ase.calculators.calculator.Calculator): The calculator to use for the energy calculation.
|
|
21
18
|
|
|
22
19
|
Returns:
|
|
@@ -27,12 +24,12 @@ def calculate_total_energy(atoms: Atoms, calculator: Calculator):
|
|
|
27
24
|
|
|
28
25
|
|
|
29
26
|
@decorator_convert_material_args_kwargs_to_atoms
|
|
30
|
-
def calculate_total_energy_per_atom(atoms:
|
|
27
|
+
def calculate_total_energy_per_atom(atoms: ASEAtoms, calculator: ASECalculator):
|
|
31
28
|
"""
|
|
32
29
|
Set calculator for ASE Atoms and calculate the total energy per atom.
|
|
33
30
|
|
|
34
31
|
Args:
|
|
35
|
-
atoms (
|
|
32
|
+
atoms (ASEAtoms): The Atoms object to calculate the energy of.
|
|
36
33
|
calculator (ase.calculators.calculator.Calculator): The calculator to use for the energy calculation.
|
|
37
34
|
|
|
38
35
|
Returns:
|
|
@@ -42,13 +39,13 @@ def calculate_total_energy_per_atom(atoms: Atoms, calculator: Calculator):
|
|
|
42
39
|
|
|
43
40
|
|
|
44
41
|
@decorator_convert_material_args_kwargs_to_atoms
|
|
45
|
-
def calculate_surface_energy(slab:
|
|
42
|
+
def calculate_surface_energy(slab: ASEAtoms, bulk: ASEAtoms, calculator: ASECalculator):
|
|
46
43
|
"""
|
|
47
44
|
Calculate the surface energy by subtracting the weighted bulk energy from the slab energy.
|
|
48
45
|
|
|
49
46
|
Args:
|
|
50
|
-
slab (
|
|
51
|
-
bulk (
|
|
47
|
+
slab (ASEAtoms): The slab Atoms object to calculate the surface energy of.
|
|
48
|
+
bulk (ASEAtoms): The bulk Atoms object to calculate the surface energy of.
|
|
52
49
|
calculator (ase.calculators.calculator.Calculator): The calculator to use for the energy calculation.
|
|
53
50
|
|
|
54
51
|
Returns:
|
|
@@ -62,7 +59,9 @@ def calculate_surface_energy(slab: Atoms, bulk: Atoms, calculator: Calculator):
|
|
|
62
59
|
|
|
63
60
|
|
|
64
61
|
@decorator_convert_material_args_kwargs_to_atoms
|
|
65
|
-
def calculate_adhesion_energy(
|
|
62
|
+
def calculate_adhesion_energy(
|
|
63
|
+
interface: ASEAtoms, substrate_slab: ASEAtoms, film_slab: ASEAtoms, calculator: ASECalculator
|
|
64
|
+
):
|
|
66
65
|
"""
|
|
67
66
|
Calculate the adhesion energy.
|
|
68
67
|
The adhesion energy is the difference between the energy of the interface and
|
|
@@ -70,9 +69,9 @@ def calculate_adhesion_energy(interface: Atoms, substrate_slab: Atoms, film_slab
|
|
|
70
69
|
According to: 10.1088/0953-8984/27/30/305004
|
|
71
70
|
|
|
72
71
|
Args:
|
|
73
|
-
interface (
|
|
74
|
-
substrate_slab (
|
|
75
|
-
film_slab (
|
|
72
|
+
interface (ASEAtoms): The interface ASEAtoms object to calculate the adhesion energy of.
|
|
73
|
+
substrate_slab (ASEAtoms): The substrate slab ASEAtoms object to calculate the adhesion energy of.
|
|
74
|
+
film_slab (ASEAtoms): The film slab ASEAtoms object to calculate the adhesion energy of.
|
|
76
75
|
calculator (ase.calculators.calculator.Calculator): The calculator to use for the energy calculation.
|
|
77
76
|
|
|
78
77
|
Returns:
|
|
@@ -91,7 +90,7 @@ def calculate_interfacial_energy(
|
|
|
91
90
|
substrate_bulk: Optional[Material] = None,
|
|
92
91
|
film_slab: Optional[Material] = None,
|
|
93
92
|
film_bulk: Optional[Material] = None,
|
|
94
|
-
calculator:
|
|
93
|
+
calculator: ASECalculator = ASECalculatorEMT(),
|
|
95
94
|
):
|
|
96
95
|
"""
|
|
97
96
|
Calculate the interfacial energy.
|
|
@@ -5,20 +5,22 @@ from typing import Any, Callable, Dict, Union
|
|
|
5
5
|
from mat3ra.made.material import Material
|
|
6
6
|
from mat3ra.made.utils import map_array_with_id_value_to_array
|
|
7
7
|
from mat3ra.utils.mixins import RoundNumericValuesMixin
|
|
8
|
-
from pymatgen.io.ase import AseAtomsAdaptor
|
|
9
|
-
from pymatgen.io.vasp.inputs import Poscar
|
|
10
8
|
|
|
11
|
-
from
|
|
12
|
-
INTERFACE_LABELS_MAP,
|
|
9
|
+
from ..third_party import (
|
|
13
10
|
ASEAtoms,
|
|
11
|
+
PymatgenAseAtomsAdaptor,
|
|
14
12
|
PymatgenInterface,
|
|
15
13
|
PymatgenLattice,
|
|
14
|
+
PymatgenPoscar,
|
|
16
15
|
PymatgenSlab,
|
|
17
16
|
PymatgenStructure,
|
|
17
|
+
label_pymatgen_slab_termination,
|
|
18
|
+
)
|
|
19
|
+
from .utils import (
|
|
20
|
+
INTERFACE_LABELS_MAP,
|
|
18
21
|
extract_labels_from_pymatgen_structure,
|
|
19
22
|
extract_metadata_from_pymatgen_structure,
|
|
20
23
|
extract_tags_from_ase_atoms,
|
|
21
|
-
label_pymatgen_slab_termination,
|
|
22
24
|
map_array_to_array_with_id_value,
|
|
23
25
|
)
|
|
24
26
|
|
|
@@ -138,7 +140,7 @@ def to_poscar(material_or_material_data: Union[Material, Dict[str, Any]]) -> str
|
|
|
138
140
|
str: A POSCAR string.
|
|
139
141
|
"""
|
|
140
142
|
structure = to_pymatgen(material_or_material_data)
|
|
141
|
-
poscar =
|
|
143
|
+
poscar = PymatgenPoscar(structure)
|
|
142
144
|
# For pymatgen `2023.6.23` supporting py3.8 the method name is "get_string"
|
|
143
145
|
# TODO: cleanup the if statement when dropping support for py3.8
|
|
144
146
|
if hasattr(poscar, "get_string"):
|
|
@@ -175,7 +177,7 @@ def to_ase(material_or_material_data: Union[Material, Dict[str, Any]]) -> ASEAto
|
|
|
175
177
|
else:
|
|
176
178
|
material_config = material_or_material_data
|
|
177
179
|
structure = to_pymatgen(material_config)
|
|
178
|
-
atoms =
|
|
180
|
+
atoms = PymatgenAseAtomsAdaptor.get_atoms(structure)
|
|
179
181
|
|
|
180
182
|
atomic_labels = material_config["basis"].get("labels", [])
|
|
181
183
|
if atomic_labels:
|
|
@@ -196,7 +198,7 @@ def from_ase(ase_atoms: ASEAtoms) -> Dict[str, Any]:
|
|
|
196
198
|
dict: A dictionary containing the material information in ESSE format.
|
|
197
199
|
"""
|
|
198
200
|
# TODO: check that atomic labels/tags are properly handled
|
|
199
|
-
structure =
|
|
201
|
+
structure = PymatgenAseAtomsAdaptor.get_structure(ase_atoms)
|
|
200
202
|
material = from_pymatgen(structure)
|
|
201
203
|
ase_tags = extract_tags_from_ase_atoms(ase_atoms)
|
|
202
204
|
material["basis"]["labels"] = ase_tags
|
|
@@ -1,22 +1,10 @@
|
|
|
1
1
|
import json
|
|
2
2
|
from typing import Any, Dict, List, Union
|
|
3
3
|
|
|
4
|
-
from ase import Atoms as ASEAtoms
|
|
5
4
|
from mat3ra.made.utils import map_array_to_array_with_id_value
|
|
6
5
|
from mat3ra.utils.object import NumpyNDArrayRoundEncoder
|
|
7
|
-
|
|
8
|
-
from
|
|
9
|
-
from pymatgen.core.structure import Lattice as PymatgenLattice
|
|
10
|
-
from pymatgen.core.structure import Structure as PymatgenStructure
|
|
11
|
-
from pymatgen.core.surface import Slab as PymatgenSlab
|
|
12
|
-
|
|
13
|
-
# Re-exported imports to allow for both use in type hints and instantiation
|
|
14
|
-
PymatgenLattice = PymatgenLattice
|
|
15
|
-
PymatgenStructure = PymatgenStructure
|
|
16
|
-
PymatgenSlab = PymatgenSlab
|
|
17
|
-
PymatgenInterface = PymatgenInterface
|
|
18
|
-
ASEAtoms = ASEAtoms
|
|
19
|
-
label_pymatgen_slab_termination = label_termination
|
|
6
|
+
|
|
7
|
+
from ..third_party import ASEAtoms, PymatgenInterface, PymatgenStructure
|
|
20
8
|
|
|
21
9
|
INTERFACE_LABELS_MAP = {"substrate": 0, "film": 1}
|
|
22
10
|
|
|
@@ -1,12 +1,17 @@
|
|
|
1
|
-
from typing import List, Union
|
|
1
|
+
from typing import Callable, List, Optional, Union
|
|
2
2
|
|
|
3
|
+
import numpy as np
|
|
3
4
|
from mat3ra.made.material import Material
|
|
4
|
-
from pymatgen.analysis.structure_analyzer import SpacegroupAnalyzer
|
|
5
|
-
from pymatgen.core.structure import Structure
|
|
6
5
|
|
|
7
|
-
from .analyze import
|
|
6
|
+
from .analyze import get_atom_indices_with_condition_on_coordinates, get_atom_indices_within_radius_pbc
|
|
8
7
|
from .convert import decorator_convert_material_args_kwargs_to_structure
|
|
9
|
-
from .
|
|
8
|
+
from .third_party import PymatgenSpacegroupAnalyzer, PymatgenStructure
|
|
9
|
+
from .utils import (
|
|
10
|
+
is_coordinate_in_box,
|
|
11
|
+
is_coordinate_in_cylinder,
|
|
12
|
+
is_coordinate_within_layer,
|
|
13
|
+
translate_to_bottom_pymatgen_structure,
|
|
14
|
+
)
|
|
10
15
|
|
|
11
16
|
|
|
12
17
|
def filter_by_label(material: Material, label: Union[int, str]) -> Material:
|
|
@@ -30,7 +35,7 @@ def filter_by_label(material: Material, label: Union[int, str]) -> Material:
|
|
|
30
35
|
|
|
31
36
|
|
|
32
37
|
@decorator_convert_material_args_kwargs_to_structure
|
|
33
|
-
def translate_to_bottom(structure:
|
|
38
|
+
def translate_to_bottom(structure: PymatgenStructure, use_conventional_cell: bool = True):
|
|
34
39
|
"""
|
|
35
40
|
Translate atoms to the bottom of the cell (vacuum on top) to allow for the correct consecutive interface generation.
|
|
36
41
|
If use_conventional_cell is passed, conventional cell is used.
|
|
@@ -42,20 +47,20 @@ def translate_to_bottom(structure: Structure, use_conventional_cell: bool = True
|
|
|
42
47
|
Structure: The normalized pymatgen Structure object.
|
|
43
48
|
"""
|
|
44
49
|
if use_conventional_cell:
|
|
45
|
-
structure =
|
|
50
|
+
structure = PymatgenSpacegroupAnalyzer(structure).get_conventional_standard_structure()
|
|
46
51
|
structure = translate_to_bottom_pymatgen_structure(structure)
|
|
47
52
|
return structure
|
|
48
53
|
|
|
49
54
|
|
|
50
55
|
@decorator_convert_material_args_kwargs_to_structure
|
|
51
|
-
def wrap_to_unit_cell(structure:
|
|
56
|
+
def wrap_to_unit_cell(structure: PymatgenStructure):
|
|
52
57
|
"""
|
|
53
58
|
Wrap atoms to the cell
|
|
54
59
|
|
|
55
60
|
Args:
|
|
56
|
-
structure (
|
|
61
|
+
structure (PymatgenStructure): The pymatgen PymatgenStructure object to normalize.
|
|
57
62
|
Returns:
|
|
58
|
-
|
|
63
|
+
PymatgenStructure: The wrapped pymatgen PymatgenStructure object.
|
|
59
64
|
"""
|
|
60
65
|
structure.make_supercell((1, 1, 1), to_unit_cell=True)
|
|
61
66
|
return structure
|
|
@@ -82,30 +87,73 @@ def filter_material_by_ids(material: Material, ids: List[int], invert: bool = Fa
|
|
|
82
87
|
return new_material
|
|
83
88
|
|
|
84
89
|
|
|
90
|
+
def filter_by_condition_on_coordinates(
|
|
91
|
+
material: Material,
|
|
92
|
+
condition: Callable[[List[float]], bool],
|
|
93
|
+
use_cartesian_coordinates: bool = False,
|
|
94
|
+
invert_selection: bool = False,
|
|
95
|
+
) -> Material:
|
|
96
|
+
"""
|
|
97
|
+
Filter atoms based on a condition on their coordinates.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
material (Material): The material object to filter.
|
|
101
|
+
condition (Callable): The condition on coordinate function.
|
|
102
|
+
use_cartesian_coordinates (bool): Whether to use cartesian coordinates.
|
|
103
|
+
invert_selection (bool): Whether to invert the selection.
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
Material: The filtered material object.
|
|
107
|
+
"""
|
|
108
|
+
new_material = material.clone()
|
|
109
|
+
ids = get_atom_indices_with_condition_on_coordinates(
|
|
110
|
+
material,
|
|
111
|
+
condition,
|
|
112
|
+
use_cartesian_coordinates=use_cartesian_coordinates,
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
new_material = filter_material_by_ids(new_material, ids, invert=invert_selection)
|
|
116
|
+
return new_material
|
|
117
|
+
|
|
118
|
+
|
|
85
119
|
def filter_by_layers(
|
|
86
|
-
material: Material,
|
|
120
|
+
material: Material,
|
|
121
|
+
center_coordinate: List[float] = [0, 0, 0],
|
|
122
|
+
central_atom_id: Optional[int] = None,
|
|
123
|
+
layer_thickness: float = 1.0,
|
|
124
|
+
invert_selection: bool = False,
|
|
87
125
|
) -> Material:
|
|
88
126
|
"""
|
|
89
127
|
Filter out atoms within a specified layer thickness of a central atom along c-vector direction.
|
|
90
128
|
|
|
91
129
|
Args:
|
|
92
130
|
material (Material): The material object to filter.
|
|
131
|
+
center_coordinate (List[float]): Index of the central atom.
|
|
93
132
|
central_atom_id (int): Index of the central atom.
|
|
94
133
|
layer_thickness (float): Thickness of the layer in angstroms.
|
|
95
|
-
|
|
134
|
+
invert_selection (bool): Whether to invert the selection.
|
|
96
135
|
|
|
97
136
|
Returns:
|
|
98
137
|
Material: The filtered material object.
|
|
99
138
|
"""
|
|
100
|
-
|
|
101
|
-
material
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
139
|
+
if central_atom_id is not None:
|
|
140
|
+
center_coordinate = material.basis.coordinates.get_element_value_by_index(central_atom_id)
|
|
141
|
+
vectors = material.lattice.vectors
|
|
142
|
+
direction_vector = np.array(vectors[2])
|
|
143
|
+
|
|
144
|
+
def condition(coordinate):
|
|
145
|
+
return is_coordinate_within_layer(coordinate, center_coordinate, direction_vector, layer_thickness)
|
|
106
146
|
|
|
147
|
+
return filter_by_condition_on_coordinates(material, condition, invert_selection=invert_selection)
|
|
107
148
|
|
|
108
|
-
|
|
149
|
+
|
|
150
|
+
def filter_by_sphere(
|
|
151
|
+
material: Material,
|
|
152
|
+
center_coordinate: List[float] = [0, 0, 0],
|
|
153
|
+
central_atom_id: Optional[int] = None,
|
|
154
|
+
radius: float = 1,
|
|
155
|
+
invert: bool = False,
|
|
156
|
+
) -> Material:
|
|
109
157
|
"""
|
|
110
158
|
Filter out atoms within a specified radius of a central atom considering periodic boundary conditions.
|
|
111
159
|
|
|
@@ -121,6 +169,124 @@ def filter_by_sphere(material: Material, central_atom_id: int, radius: float, in
|
|
|
121
169
|
ids = get_atom_indices_within_radius_pbc(
|
|
122
170
|
material=material,
|
|
123
171
|
atom_index=central_atom_id,
|
|
172
|
+
position=center_coordinate,
|
|
124
173
|
radius=radius,
|
|
125
174
|
)
|
|
126
175
|
return filter_material_by_ids(material, ids, invert=invert)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def filter_by_circle_projection(
|
|
179
|
+
material: Material,
|
|
180
|
+
x: float = 0.5,
|
|
181
|
+
y: float = 0.5,
|
|
182
|
+
r: float = 0.25,
|
|
183
|
+
use_cartesian_coordinates: bool = False,
|
|
184
|
+
invert_selection: bool = False,
|
|
185
|
+
) -> Material:
|
|
186
|
+
"""
|
|
187
|
+
Get material with atoms that are within or outside an XY circle projection.
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
material (Material): The material object to filter.
|
|
191
|
+
x (float): The x-coordinate of the circle center.
|
|
192
|
+
y (float): The y-coordinate of the circle center.
|
|
193
|
+
r (float): The radius of the circle.
|
|
194
|
+
use_cartesian_coordinates (bool): Whether to use cartesian coordinates
|
|
195
|
+
invert_selection (bool): Whether to invert the selection.
|
|
196
|
+
|
|
197
|
+
Returns:
|
|
198
|
+
Material: The filtered material object.
|
|
199
|
+
"""
|
|
200
|
+
|
|
201
|
+
def condition(coordinate):
|
|
202
|
+
return is_coordinate_in_cylinder(coordinate, [x, y, 0], r, min_z=0, max_z=1)
|
|
203
|
+
|
|
204
|
+
return filter_by_condition_on_coordinates(
|
|
205
|
+
material, condition, use_cartesian_coordinates=use_cartesian_coordinates, invert_selection=invert_selection
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def filter_by_cylinder(
|
|
210
|
+
material: Material,
|
|
211
|
+
center_position: List[float] = [0.5, 0.5],
|
|
212
|
+
min_z: float = 0,
|
|
213
|
+
max_z: float = 1,
|
|
214
|
+
radius: float = 0.25,
|
|
215
|
+
use_cartesian_coordinates: bool = False,
|
|
216
|
+
invert_selection: bool = False,
|
|
217
|
+
) -> Material:
|
|
218
|
+
"""
|
|
219
|
+
Get material with atoms that are within or outside a cylinder.
|
|
220
|
+
|
|
221
|
+
Args:
|
|
222
|
+
material (Material): The material object to filter.
|
|
223
|
+
center_position (List[float]): The coordinates of the center position.
|
|
224
|
+
radius (float): The radius of the cylinder.
|
|
225
|
+
min_z (float): Lower limit of z-coordinate.
|
|
226
|
+
max_z (float): Upper limit of z-coordinate.
|
|
227
|
+
use_cartesian_coordinates (bool): Whether to use cartesian coordinates
|
|
228
|
+
invert_selection (bool): Whether to invert the selection.
|
|
229
|
+
|
|
230
|
+
Returns:
|
|
231
|
+
Material: The filtered material object.
|
|
232
|
+
"""
|
|
233
|
+
|
|
234
|
+
def condition(coordinate):
|
|
235
|
+
return is_coordinate_in_cylinder(coordinate, center_position, radius, min_z, max_z)
|
|
236
|
+
|
|
237
|
+
return filter_by_condition_on_coordinates(
|
|
238
|
+
material, condition, use_cartesian_coordinates=use_cartesian_coordinates, invert_selection=invert_selection
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def filter_by_rectangle_projection(
|
|
243
|
+
material: Material,
|
|
244
|
+
x_min: float = 0.0,
|
|
245
|
+
y_min: float = 0.0,
|
|
246
|
+
x_max: float = 1.0,
|
|
247
|
+
y_max: float = 1.0,
|
|
248
|
+
use_cartesian_coordinates: bool = False,
|
|
249
|
+
invert_selection: bool = False,
|
|
250
|
+
) -> Material:
|
|
251
|
+
"""
|
|
252
|
+
Get material with atoms that are within or outside an XY rectangle projection.
|
|
253
|
+
|
|
254
|
+
Args:
|
|
255
|
+
|
|
256
|
+
material (Material): The material object to filter.
|
|
257
|
+
x_min (float): The minimum x-coordinate of the rectangle.
|
|
258
|
+
y_min (float): The minimum y-coordinate of the rectangle.
|
|
259
|
+
x_max (float): The maximum x-coordinate of the rectangle.
|
|
260
|
+
y_max (float): The maximum y-coordinate of the rectangle.
|
|
261
|
+
use_cartesian_coordinates (bool): Whether to use cartesian coordinates
|
|
262
|
+
invert_selection (bool): Whether to invert the selection.
|
|
263
|
+
|
|
264
|
+
Returns:
|
|
265
|
+
Material: The filtered material object.
|
|
266
|
+
"""
|
|
267
|
+
|
|
268
|
+
def condition(coordinate):
|
|
269
|
+
return is_coordinate_in_box(coordinate, [x_min, y_min, 0], [x_max, y_max, 1])
|
|
270
|
+
|
|
271
|
+
return filter_by_condition_on_coordinates(
|
|
272
|
+
material, condition, use_cartesian_coordinates=use_cartesian_coordinates, invert_selection=invert_selection
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def filter_by_box(
|
|
277
|
+
material: Material,
|
|
278
|
+
min_coordinate: List[float] = [0.0, 0.0, 0.0],
|
|
279
|
+
max_coordinate: List[float] = [1.0, 1.0, 1.0],
|
|
280
|
+
use_cartesian_coordinates: bool = False,
|
|
281
|
+
invert_selection: bool = False,
|
|
282
|
+
) -> Material:
|
|
283
|
+
"""
|
|
284
|
+
Get material with atoms that are within or outside an XYZ box.
|
|
285
|
+
"""
|
|
286
|
+
|
|
287
|
+
def condition(coordinate):
|
|
288
|
+
return is_coordinate_in_box(coordinate, min_coordinate, max_coordinate)
|
|
289
|
+
|
|
290
|
+
return filter_by_condition_on_coordinates(
|
|
291
|
+
material, condition, use_cartesian_coordinates=use_cartesian_coordinates, invert_selection=invert_selection
|
|
292
|
+
)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
from ase import Atoms as ASEAtoms
|
|
2
|
+
from ase.build.supercells import make_supercell as ase_make_supercell
|
|
3
|
+
from ase.calculators.calculator import Calculator as ASECalculator
|
|
4
|
+
from ase.calculators.emt import EMT as ASECalculatorEMT
|
|
5
|
+
from pymatgen.analysis.defects.core import Interstitial as PymatgenInterstitial
|
|
6
|
+
from pymatgen.analysis.defects.core import Substitution as PymatgenSubstitution
|
|
7
|
+
from pymatgen.analysis.defects.core import Vacancy as PymatgenVacancy
|
|
8
|
+
from pymatgen.core import IStructure as PymatgenIStructure
|
|
9
|
+
from pymatgen.core import PeriodicSite as PymatgenPeriodicSite
|
|
10
|
+
from pymatgen.core.interface import Interface as PymatgenInterface
|
|
11
|
+
from pymatgen.core.interface import label_termination as label_pymatgen_slab_termination
|
|
12
|
+
from pymatgen.core.structure import Lattice as PymatgenLattice
|
|
13
|
+
from pymatgen.core.structure import Structure as PymatgenStructure
|
|
14
|
+
from pymatgen.core.surface import Slab as PymatgenSlab
|
|
15
|
+
from pymatgen.core.surface import SlabGenerator as PymatgenSlabGenerator
|
|
16
|
+
from pymatgen.io.ase import AseAtomsAdaptor as PymatgenAseAtomsAdaptor
|
|
17
|
+
from pymatgen.io.vasp.inputs import Poscar as PymatgenPoscar
|
|
18
|
+
from pymatgen.symmetry.analyzer import SpacegroupAnalyzer as PymatgenSpacegroupAnalyzer
|
|
19
|
+
|
|
20
|
+
# Re-exported imports to allow for both use in type hints and instantiation
|
|
21
|
+
|
|
22
|
+
__all__ = [
|
|
23
|
+
"ASEAtoms",
|
|
24
|
+
"ASECalculator",
|
|
25
|
+
"ASECalculatorEMT",
|
|
26
|
+
"PymatgenLattice",
|
|
27
|
+
"PymatgenStructure",
|
|
28
|
+
"PymatgenIStructure",
|
|
29
|
+
"PymatgenSlab",
|
|
30
|
+
"PymatgenSlabGenerator",
|
|
31
|
+
"PymatgenInterface",
|
|
32
|
+
"PymatgenPeriodicSite",
|
|
33
|
+
"PymatgenSpacegroupAnalyzer",
|
|
34
|
+
"PymatgenVacancy",
|
|
35
|
+
"PymatgenSubstitution",
|
|
36
|
+
"PymatgenInterstitial",
|
|
37
|
+
"label_pymatgen_slab_termination",
|
|
38
|
+
"ase_make_supercell",
|
|
39
|
+
"PymatgenAseAtomsAdaptor",
|
|
40
|
+
"PymatgenPoscar",
|
|
41
|
+
]
|
|
@@ -4,18 +4,19 @@ from typing import Callable, List
|
|
|
4
4
|
import numpy as np
|
|
5
5
|
from mat3ra.made.basis import Basis
|
|
6
6
|
from mat3ra.utils.matrix import convert_2x2_to_3x3
|
|
7
|
-
|
|
7
|
+
|
|
8
|
+
from .third_party import PymatgenStructure
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
# TODO: convert to accept ASE Atoms object
|
|
11
|
-
def translate_to_bottom_pymatgen_structure(structure:
|
|
12
|
+
def translate_to_bottom_pymatgen_structure(structure: PymatgenStructure):
|
|
12
13
|
"""
|
|
13
14
|
Translate the structure to the bottom of the cell.
|
|
14
15
|
Args:
|
|
15
|
-
structure (
|
|
16
|
+
structure (PymatgenStructure): The pymatgen Structure object to translate.
|
|
16
17
|
|
|
17
18
|
Returns:
|
|
18
|
-
|
|
19
|
+
PymatgenStructure: The translated pymatgen Structure object.
|
|
19
20
|
"""
|
|
20
21
|
min_c = min(site.c for site in structure)
|
|
21
22
|
translation_vector = [0, 0, -min_c]
|
|
@@ -95,3 +96,69 @@ def get_norm(vector: List[float]) -> float:
|
|
|
95
96
|
float: The norm of the vector.
|
|
96
97
|
"""
|
|
97
98
|
return float(np.linalg.norm(vector))
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
# Condition functions:
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def is_coordinate_in_cylinder(
|
|
105
|
+
coordinate: List[float], center_position: List[float], radius: float = 0.25, min_z: float = 0, max_z: float = 1
|
|
106
|
+
) -> bool:
|
|
107
|
+
"""
|
|
108
|
+
Check if a point is inside a cylinder.
|
|
109
|
+
Args:
|
|
110
|
+
coordinate (List[float]): The coordinate to check.
|
|
111
|
+
center_position (List[float]): The coordinates of the center position.
|
|
112
|
+
min_z (float): Lower limit of z-coordinate.
|
|
113
|
+
max_z (float): Upper limit of z-coordinate.
|
|
114
|
+
radius (float): The radius of the cylinder.
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
bool: True if the point is inside the cylinder, False otherwise.
|
|
118
|
+
"""
|
|
119
|
+
return (coordinate[0] - center_position[0]) ** 2 + (coordinate[1] - center_position[1]) ** 2 <= radius**2 and (
|
|
120
|
+
min_z <= coordinate[2] <= max_z
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def is_coordinate_in_box(
|
|
125
|
+
coordinate: List[float], min_coordinate: List[float] = [0, 0, 0], max_coordinate: List[float] = [1, 1, 1]
|
|
126
|
+
) -> bool:
|
|
127
|
+
"""
|
|
128
|
+
Check if a point is inside a box.
|
|
129
|
+
Args:
|
|
130
|
+
coordinate (List[float]): The coordinate to check.
|
|
131
|
+
min_coordinate (List[float]): The minimum coordinate of the box.
|
|
132
|
+
max_coordinate (List[float]): The maximum coordinate of the box.
|
|
133
|
+
Returns:
|
|
134
|
+
bool: True if the point is inside the box, False otherwise.
|
|
135
|
+
"""
|
|
136
|
+
x_min, y_min, z_min = min_coordinate
|
|
137
|
+
x_max, y_max, z_max = max_coordinate
|
|
138
|
+
return x_min <= coordinate[0] <= x_max and y_min <= coordinate[1] <= y_max and z_min <= coordinate[2] <= z_max
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def is_coordinate_within_layer(
|
|
142
|
+
coordinate: List[float], center_position: List[float], direction_vector: List[float], layer_thickness: float
|
|
143
|
+
) -> bool:
|
|
144
|
+
"""
|
|
145
|
+
Checks if a point's projection along a specified direction vector
|
|
146
|
+
is within a certain layer thickness centered around a given position.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
coordinate (List[float]): The coordinate to check.
|
|
150
|
+
center_position (List[float]): The coordinates of the center position.
|
|
151
|
+
direction_vector (List[float]): The direction vector along which the layer thickness is defined.
|
|
152
|
+
layer_thickness (float): The thickness of the layer along the direction vector.
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
bool: True if the point is within the layer thickness, False otherwise.
|
|
156
|
+
"""
|
|
157
|
+
direction_norm = np.array(direction_vector) / np.linalg.norm(direction_vector)
|
|
158
|
+
central_projection = np.dot(center_position, direction_norm)
|
|
159
|
+
layer_thickness_frac = layer_thickness / np.linalg.norm(direction_vector)
|
|
160
|
+
|
|
161
|
+
lower_bound = central_projection - layer_thickness_frac / 2
|
|
162
|
+
upper_bound = central_projection + layer_thickness_frac / 2
|
|
163
|
+
|
|
164
|
+
return lower_bound <= np.dot(coordinate, direction_norm) <= upper_bound
|
|
@@ -7,8 +7,8 @@ from mat3ra.utils import assertion as assertion_utils
|
|
|
7
7
|
|
|
8
8
|
ase_ni = bulk("Ni", "fcc", a=3.52, cubic=True)
|
|
9
9
|
material = Material(from_ase(ase_ni))
|
|
10
|
-
section = filter_by_layers(material, 0, 1.0)
|
|
11
|
-
cavity = filter_by_layers(material, 0, 1.0,
|
|
10
|
+
section = filter_by_layers(material, central_atom_id=0, layer_thickness=1.0)
|
|
11
|
+
cavity = filter_by_layers(material, central_atom_id=0, layer_thickness=1.0, invert_selection=True)
|
|
12
12
|
|
|
13
13
|
# Change 0th element
|
|
14
14
|
section.basis.elements.values[0] = "Ge"
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
from ase.build import bulk
|
|
2
2
|
from mat3ra.made.material import Material
|
|
3
3
|
from mat3ra.made.tools.convert import from_ase
|
|
4
|
-
from mat3ra.made.tools.modify import
|
|
4
|
+
from mat3ra.made.tools.modify import (
|
|
5
|
+
filter_by_circle_projection,
|
|
6
|
+
filter_by_label,
|
|
7
|
+
filter_by_layers,
|
|
8
|
+
filter_by_rectangle_projection,
|
|
9
|
+
filter_by_sphere,
|
|
10
|
+
)
|
|
5
11
|
from mat3ra.utils import assertion as assertion_utils
|
|
6
12
|
|
|
7
13
|
from .fixtures import SI_CONVENTIONAL_CELL
|
|
@@ -46,15 +52,15 @@ expected_basis_layers_cavity = {
|
|
|
46
52
|
|
|
47
53
|
|
|
48
54
|
expected_basis_sphere_cluster = {
|
|
49
|
-
"elements": [{"id":
|
|
50
|
-
"coordinates": [{"id":
|
|
55
|
+
"elements": [{"id": 2, "value": "Si"}],
|
|
56
|
+
"coordinates": [{"id": 2, "value": [0.5, 0.5, 0.5]}],
|
|
51
57
|
**COMMON_PART,
|
|
52
58
|
}
|
|
53
59
|
|
|
54
60
|
expected_basis_sphere_cavity = {
|
|
55
61
|
"elements": [
|
|
62
|
+
{"id": 0, "value": "Si"},
|
|
56
63
|
{"id": 1, "value": "Si"},
|
|
57
|
-
{"id": 2, "value": "Si"},
|
|
58
64
|
{"id": 3, "value": "Si"},
|
|
59
65
|
{"id": 4, "value": "Si"},
|
|
60
66
|
{"id": 5, "value": "Si"},
|
|
@@ -62,8 +68,8 @@ expected_basis_sphere_cavity = {
|
|
|
62
68
|
{"id": 7, "value": "Si"},
|
|
63
69
|
],
|
|
64
70
|
"coordinates": [
|
|
71
|
+
{"id": 0, "value": [0.5, 0.0, 0.0]},
|
|
65
72
|
{"id": 1, "value": [0.25, 0.25, 0.75]},
|
|
66
|
-
{"id": 2, "value": [0.5, 0.5, 0.5]},
|
|
67
73
|
{"id": 3, "value": [0.25, 0.75, 0.25]},
|
|
68
74
|
{"id": 4, "value": [0.0, 0.0, 0.5]},
|
|
69
75
|
{"id": 5, "value": [0.75, 0.25, 0.25]},
|
|
@@ -73,6 +79,9 @@ expected_basis_sphere_cavity = {
|
|
|
73
79
|
**COMMON_PART,
|
|
74
80
|
}
|
|
75
81
|
|
|
82
|
+
CRYSTAL_RADIUS = 0.25 # in crystal coordinates
|
|
83
|
+
CRYSTAL_CENTER_3D = [0.5, 0.5, 0.5] # in crystal coordinates
|
|
84
|
+
|
|
76
85
|
|
|
77
86
|
def test_filter_by_label():
|
|
78
87
|
substrate = bulk("Si", cubic=True)
|
|
@@ -89,15 +98,31 @@ def test_filter_by_label():
|
|
|
89
98
|
|
|
90
99
|
def test_filter_by_layers():
|
|
91
100
|
material = Material(SI_CONVENTIONAL_CELL)
|
|
92
|
-
section = filter_by_layers(material, 0, 3.0)
|
|
93
|
-
cavity = filter_by_layers(material, 0, 3.0,
|
|
101
|
+
section = filter_by_layers(material=material, central_atom_id=0, layer_thickness=3.0)
|
|
102
|
+
cavity = filter_by_layers(material=material, central_atom_id=0, layer_thickness=3.0, invert_selection=True)
|
|
94
103
|
assertion_utils.assert_deep_almost_equal(expected_basis_layers_section, section.basis.to_json())
|
|
95
104
|
assertion_utils.assert_deep_almost_equal(expected_basis_layers_cavity, cavity.basis.to_json())
|
|
96
105
|
|
|
97
106
|
|
|
98
107
|
def test_filter_by_sphere():
|
|
99
108
|
material = Material(SI_CONVENTIONAL_CELL)
|
|
100
|
-
cluster = filter_by_sphere(material,
|
|
101
|
-
cavity = filter_by_sphere(material,
|
|
109
|
+
cluster = filter_by_sphere(material, center_coordinate=CRYSTAL_CENTER_3D, radius=CRYSTAL_RADIUS)
|
|
110
|
+
cavity = filter_by_sphere(material, center_coordinate=CRYSTAL_CENTER_3D, radius=CRYSTAL_RADIUS, invert=True)
|
|
102
111
|
assertion_utils.assert_deep_almost_equal(expected_basis_sphere_cluster, cluster.basis.to_json())
|
|
103
112
|
assertion_utils.assert_deep_almost_equal(expected_basis_sphere_cavity, cavity.basis.to_json())
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def test_filter_by_circle_projection():
|
|
116
|
+
material = Material(SI_CONVENTIONAL_CELL)
|
|
117
|
+
# Small cylinder in the middle of the cell containing the central atom will be removed -- the same as with sphere
|
|
118
|
+
section = filter_by_circle_projection(material, 0.5, 0.5, CRYSTAL_RADIUS)
|
|
119
|
+
cavity = filter_by_circle_projection(material, 0.5, 0.5, CRYSTAL_RADIUS, invert_selection=True)
|
|
120
|
+
assertion_utils.assert_deep_almost_equal(expected_basis_sphere_cluster, section.basis.to_json())
|
|
121
|
+
assertion_utils.assert_deep_almost_equal(expected_basis_sphere_cavity, cavity.basis.to_json())
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def test_filter_by_rectangle_projection():
|
|
125
|
+
material = Material(SI_CONVENTIONAL_CELL)
|
|
126
|
+
# Default will contain all the atoms
|
|
127
|
+
section = filter_by_rectangle_projection(material)
|
|
128
|
+
assertion_utils.assert_deep_almost_equal(material.basis.to_json(), section.basis.to_json())
|