@mat3ra/made 2024.6.29-0 → 2024.7.2-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,4 +1,4 @@
|
|
|
1
|
-
from typing import List, Optional
|
|
1
|
+
from typing import Callable, List, Optional
|
|
2
2
|
|
|
3
3
|
import numpy as np
|
|
4
4
|
|
|
@@ -198,3 +198,34 @@ def get_atom_indices_within_radius_pbc(
|
|
|
198
198
|
|
|
199
199
|
selected_indices = [site.index for site in sites_within_radius]
|
|
200
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
|
|
@@ -1,11 +1,18 @@
|
|
|
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
5
|
|
|
5
|
-
from .analyze import
|
|
6
|
+
from .analyze import get_atom_indices_with_condition_on_coordinates, get_atom_indices_within_radius_pbc
|
|
6
7
|
from .convert import decorator_convert_material_args_kwargs_to_structure
|
|
7
8
|
from .third_party import PymatgenSpacegroupAnalyzer, PymatgenStructure
|
|
8
|
-
from .utils import
|
|
9
|
+
from .utils import (
|
|
10
|
+
is_coordinate_in_box,
|
|
11
|
+
is_coordinate_in_cylinder,
|
|
12
|
+
is_coordinate_in_triangular_prism,
|
|
13
|
+
is_coordinate_within_layer,
|
|
14
|
+
translate_to_bottom_pymatgen_structure,
|
|
15
|
+
)
|
|
9
16
|
|
|
10
17
|
|
|
11
18
|
def filter_by_label(material: Material, label: Union[int, str]) -> Material:
|
|
@@ -81,30 +88,73 @@ def filter_material_by_ids(material: Material, ids: List[int], invert: bool = Fa
|
|
|
81
88
|
return new_material
|
|
82
89
|
|
|
83
90
|
|
|
91
|
+
def filter_by_condition_on_coordinates(
|
|
92
|
+
material: Material,
|
|
93
|
+
condition: Callable[[List[float]], bool],
|
|
94
|
+
use_cartesian_coordinates: bool = False,
|
|
95
|
+
invert_selection: bool = False,
|
|
96
|
+
) -> Material:
|
|
97
|
+
"""
|
|
98
|
+
Filter atoms based on a condition on their coordinates.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
material (Material): The material object to filter.
|
|
102
|
+
condition (Callable): The condition on coordinate function.
|
|
103
|
+
use_cartesian_coordinates (bool): Whether to use cartesian coordinates.
|
|
104
|
+
invert_selection (bool): Whether to invert the selection.
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
Material: The filtered material object.
|
|
108
|
+
"""
|
|
109
|
+
new_material = material.clone()
|
|
110
|
+
ids = get_atom_indices_with_condition_on_coordinates(
|
|
111
|
+
material,
|
|
112
|
+
condition,
|
|
113
|
+
use_cartesian_coordinates=use_cartesian_coordinates,
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
new_material = filter_material_by_ids(new_material, ids, invert=invert_selection)
|
|
117
|
+
return new_material
|
|
118
|
+
|
|
119
|
+
|
|
84
120
|
def filter_by_layers(
|
|
85
|
-
material: Material,
|
|
121
|
+
material: Material,
|
|
122
|
+
center_coordinate: List[float] = [0, 0, 0],
|
|
123
|
+
central_atom_id: Optional[int] = None,
|
|
124
|
+
layer_thickness: float = 1.0,
|
|
125
|
+
invert_selection: bool = False,
|
|
86
126
|
) -> Material:
|
|
87
127
|
"""
|
|
88
128
|
Filter out atoms within a specified layer thickness of a central atom along c-vector direction.
|
|
89
129
|
|
|
90
130
|
Args:
|
|
91
131
|
material (Material): The material object to filter.
|
|
132
|
+
center_coordinate (List[float]): Index of the central atom.
|
|
92
133
|
central_atom_id (int): Index of the central atom.
|
|
93
134
|
layer_thickness (float): Thickness of the layer in angstroms.
|
|
94
|
-
|
|
135
|
+
invert_selection (bool): Whether to invert the selection.
|
|
95
136
|
|
|
96
137
|
Returns:
|
|
97
138
|
Material: The filtered material object.
|
|
98
139
|
"""
|
|
99
|
-
|
|
100
|
-
material
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
140
|
+
if central_atom_id is not None:
|
|
141
|
+
center_coordinate = material.basis.coordinates.get_element_value_by_index(central_atom_id)
|
|
142
|
+
vectors = material.lattice.vectors
|
|
143
|
+
direction_vector = np.array(vectors[2])
|
|
144
|
+
|
|
145
|
+
def condition(coordinate):
|
|
146
|
+
return is_coordinate_within_layer(coordinate, center_coordinate, direction_vector, layer_thickness)
|
|
147
|
+
|
|
148
|
+
return filter_by_condition_on_coordinates(material, condition, invert_selection=invert_selection)
|
|
105
149
|
|
|
106
150
|
|
|
107
|
-
def filter_by_sphere(
|
|
151
|
+
def filter_by_sphere(
|
|
152
|
+
material: Material,
|
|
153
|
+
center_coordinate: List[float] = [0, 0, 0],
|
|
154
|
+
central_atom_id: Optional[int] = None,
|
|
155
|
+
radius: float = 1,
|
|
156
|
+
invert: bool = False,
|
|
157
|
+
) -> Material:
|
|
108
158
|
"""
|
|
109
159
|
Filter out atoms within a specified radius of a central atom considering periodic boundary conditions.
|
|
110
160
|
|
|
@@ -120,6 +170,156 @@ def filter_by_sphere(material: Material, central_atom_id: int, radius: float, in
|
|
|
120
170
|
ids = get_atom_indices_within_radius_pbc(
|
|
121
171
|
material=material,
|
|
122
172
|
atom_index=central_atom_id,
|
|
173
|
+
position=center_coordinate,
|
|
123
174
|
radius=radius,
|
|
124
175
|
)
|
|
125
176
|
return filter_material_by_ids(material, ids, invert=invert)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def filter_by_circle_projection(
|
|
180
|
+
material: Material,
|
|
181
|
+
x: float = 0.5,
|
|
182
|
+
y: float = 0.5,
|
|
183
|
+
r: float = 0.25,
|
|
184
|
+
use_cartesian_coordinates: bool = False,
|
|
185
|
+
invert_selection: bool = False,
|
|
186
|
+
) -> Material:
|
|
187
|
+
"""
|
|
188
|
+
Get material with atoms that are within or outside an XY circle projection.
|
|
189
|
+
|
|
190
|
+
Args:
|
|
191
|
+
material (Material): The material object to filter.
|
|
192
|
+
x (float): The x-coordinate of the circle center.
|
|
193
|
+
y (float): The y-coordinate of the circle center.
|
|
194
|
+
r (float): The radius of the circle.
|
|
195
|
+
use_cartesian_coordinates (bool): Whether to use cartesian coordinates
|
|
196
|
+
invert_selection (bool): Whether to invert the selection.
|
|
197
|
+
|
|
198
|
+
Returns:
|
|
199
|
+
Material: The filtered material object.
|
|
200
|
+
"""
|
|
201
|
+
|
|
202
|
+
def condition(coordinate):
|
|
203
|
+
return is_coordinate_in_cylinder(coordinate, [x, y, 0], r, min_z=0, max_z=1)
|
|
204
|
+
|
|
205
|
+
return filter_by_condition_on_coordinates(
|
|
206
|
+
material, condition, use_cartesian_coordinates=use_cartesian_coordinates, invert_selection=invert_selection
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def filter_by_cylinder(
|
|
211
|
+
material: Material,
|
|
212
|
+
center_position: List[float] = [0.5, 0.5],
|
|
213
|
+
min_z: float = 0,
|
|
214
|
+
max_z: float = 1,
|
|
215
|
+
radius: float = 0.25,
|
|
216
|
+
use_cartesian_coordinates: bool = False,
|
|
217
|
+
invert_selection: bool = False,
|
|
218
|
+
) -> Material:
|
|
219
|
+
"""
|
|
220
|
+
Get material with atoms that are within or outside a cylinder.
|
|
221
|
+
|
|
222
|
+
Args:
|
|
223
|
+
material (Material): The material object to filter.
|
|
224
|
+
center_position (List[float]): The coordinates of the center position.
|
|
225
|
+
radius (float): The radius of the cylinder.
|
|
226
|
+
min_z (float): Lower limit of z-coordinate.
|
|
227
|
+
max_z (float): Upper limit of z-coordinate.
|
|
228
|
+
use_cartesian_coordinates (bool): Whether to use cartesian coordinates
|
|
229
|
+
invert_selection (bool): Whether to invert the selection.
|
|
230
|
+
|
|
231
|
+
Returns:
|
|
232
|
+
Material: The filtered material object.
|
|
233
|
+
"""
|
|
234
|
+
|
|
235
|
+
def condition(coordinate):
|
|
236
|
+
return is_coordinate_in_cylinder(coordinate, center_position, radius, min_z, max_z)
|
|
237
|
+
|
|
238
|
+
return filter_by_condition_on_coordinates(
|
|
239
|
+
material, condition, use_cartesian_coordinates=use_cartesian_coordinates, invert_selection=invert_selection
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def filter_by_rectangle_projection(
|
|
244
|
+
material: Material,
|
|
245
|
+
min_coordinate: List[float] = [0, 0],
|
|
246
|
+
max_coordinate: List[float] = [1, 1],
|
|
247
|
+
use_cartesian_coordinates: bool = False,
|
|
248
|
+
invert_selection: bool = False,
|
|
249
|
+
) -> Material:
|
|
250
|
+
"""
|
|
251
|
+
Get material with atoms that are within or outside an XY rectangle projection.
|
|
252
|
+
|
|
253
|
+
Args:
|
|
254
|
+
material (Material): The material object to filter.
|
|
255
|
+
min_coordinate (List[float]): The minimum coordinate of the rectangle.
|
|
256
|
+
max_coordinate (List[float]): The maximum coordinate of the rectangle.
|
|
257
|
+
use_cartesian_coordinates (bool): Whether to use cartesian coordinates
|
|
258
|
+
invert_selection (bool): Whether to invert the selection.
|
|
259
|
+
|
|
260
|
+
Returns:
|
|
261
|
+
Material: The filtered material object.
|
|
262
|
+
"""
|
|
263
|
+
min_coordinate = min_coordinate[:2] + [0]
|
|
264
|
+
max_coordinate = max_coordinate[:2] + [1]
|
|
265
|
+
|
|
266
|
+
def condition(coordinate):
|
|
267
|
+
return is_coordinate_in_box(coordinate, min_coordinate, max_coordinate)
|
|
268
|
+
|
|
269
|
+
return filter_by_condition_on_coordinates(
|
|
270
|
+
material, condition, use_cartesian_coordinates=use_cartesian_coordinates, invert_selection=invert_selection
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def filter_by_box(
|
|
275
|
+
material: Material,
|
|
276
|
+
min_coordinate: List[float] = [0.0, 0.0, 0.0],
|
|
277
|
+
max_coordinate: List[float] = [1.0, 1.0, 1.0],
|
|
278
|
+
use_cartesian_coordinates: bool = False,
|
|
279
|
+
invert_selection: bool = False,
|
|
280
|
+
) -> Material:
|
|
281
|
+
"""
|
|
282
|
+
Get material with atoms that are within or outside an XYZ box.
|
|
283
|
+
"""
|
|
284
|
+
|
|
285
|
+
def condition(coordinate):
|
|
286
|
+
return is_coordinate_in_box(coordinate, min_coordinate, max_coordinate)
|
|
287
|
+
|
|
288
|
+
return filter_by_condition_on_coordinates(
|
|
289
|
+
material, condition, use_cartesian_coordinates=use_cartesian_coordinates, invert_selection=invert_selection
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def filter_by_triangle_projection(
|
|
294
|
+
material: Material,
|
|
295
|
+
coordinate_1: List[float] = [0, 0],
|
|
296
|
+
coordinate_2: List[float] = [0, 1],
|
|
297
|
+
coordinate_3: List[float] = [1, 0],
|
|
298
|
+
min_z: float = 0,
|
|
299
|
+
max_z: float = 1,
|
|
300
|
+
use_cartesian_coordinates: bool = False,
|
|
301
|
+
invert_selection: bool = False,
|
|
302
|
+
) -> Material:
|
|
303
|
+
"""
|
|
304
|
+
Get material with atoms that are within or outside a prism formed by triangle projection.
|
|
305
|
+
|
|
306
|
+
Args:
|
|
307
|
+
material (Material): The material object to filter.
|
|
308
|
+
coordinate_1 (List[float]): The coordinate of the first vertex.
|
|
309
|
+
coordinate_2 (List[float]): The coordinate of the second vertex.
|
|
310
|
+
coordinate_3 (List[float]): The coordinate of the third vertex.
|
|
311
|
+
min_z (float): Lower limit of z-coordinate.
|
|
312
|
+
max_z (float): Upper limit of z-coordinate.
|
|
313
|
+
use_cartesian_coordinates (bool): Whether to use cartesian coordinates
|
|
314
|
+
invert_selection (bool): Whether to invert the selection.
|
|
315
|
+
|
|
316
|
+
Returns:
|
|
317
|
+
Material: The filtered material object.
|
|
318
|
+
"""
|
|
319
|
+
|
|
320
|
+
def condition(coordinate):
|
|
321
|
+
return is_coordinate_in_triangular_prism(coordinate, coordinate_1, coordinate_2, coordinate_3, min_z, max_z)
|
|
322
|
+
|
|
323
|
+
return filter_by_condition_on_coordinates(
|
|
324
|
+
material, condition, use_cartesian_coordinates=use_cartesian_coordinates, invert_selection=invert_selection
|
|
325
|
+
)
|
|
@@ -96,3 +96,120 @@ def get_norm(vector: List[float]) -> float:
|
|
|
96
96
|
float: The norm of the vector.
|
|
97
97
|
"""
|
|
98
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 coordinate 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 coordinate 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 coordinate 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 coordinate 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 coordinate'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 coordinate 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
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def is_coordinate_in_triangular_prism(
|
|
168
|
+
coordinate: List[float],
|
|
169
|
+
coordinate_1: List[float],
|
|
170
|
+
coordinate_2: List[float],
|
|
171
|
+
coordinate_3: List[float],
|
|
172
|
+
min_z: float = 0,
|
|
173
|
+
max_z: float = 1,
|
|
174
|
+
) -> bool:
|
|
175
|
+
"""
|
|
176
|
+
Check if a coordinate is inside a triangular prism.
|
|
177
|
+
Args:
|
|
178
|
+
coordinate (List[float]): The coordinate to check.
|
|
179
|
+
coordinate_1 (List[float]): The first coordinate of the triangle.
|
|
180
|
+
coordinate_2 (List[float]): The second coordinate of the triangle.
|
|
181
|
+
coordinate_3 (List[float]): The third coordinate of the triangle.
|
|
182
|
+
min_z (float): Lower limit of z-coordinate.
|
|
183
|
+
max_z (float): Upper limit of z-coordinate.
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
bool: True if the coordinate is inside the triangular prism, False otherwise.
|
|
187
|
+
"""
|
|
188
|
+
# convert to 3D coordinates at the origin XY plane
|
|
189
|
+
coordinate_1.extend([0] * (3 - len(coordinate_1)))
|
|
190
|
+
coordinate_2.extend([0] * (3 - len(coordinate_2)))
|
|
191
|
+
coordinate_3.extend([0] * (3 - len(coordinate_3)))
|
|
192
|
+
|
|
193
|
+
coordinate = np.array(coordinate)
|
|
194
|
+
v1 = np.array(coordinate_1)
|
|
195
|
+
v2 = np.array(coordinate_2)
|
|
196
|
+
v3 = np.array(coordinate_3)
|
|
197
|
+
|
|
198
|
+
v2_v1 = v2 - v1
|
|
199
|
+
v3_v1 = v3 - v1
|
|
200
|
+
coordinate_v1 = coordinate - v1
|
|
201
|
+
|
|
202
|
+
# Compute dot products for the barycentric coordinates
|
|
203
|
+
d00 = np.dot(v2_v1, v2_v1)
|
|
204
|
+
d01 = np.dot(v2_v1, v3_v1)
|
|
205
|
+
d11 = np.dot(v3_v1, v3_v1)
|
|
206
|
+
d20 = np.dot(coordinate_v1, v2_v1)
|
|
207
|
+
d21 = np.dot(coordinate_v1, v3_v1)
|
|
208
|
+
|
|
209
|
+
# Calculate barycentric coordinates
|
|
210
|
+
denom = d00 * d11 - d01 * d01
|
|
211
|
+
v = (d11 * d20 - d01 * d21) / denom
|
|
212
|
+
w = (d00 * d21 - d01 * d20) / denom
|
|
213
|
+
u = 1.0 - v - w
|
|
214
|
+
|
|
215
|
+
return (u >= 0) and (v >= 0) and (w >= 0) and (u + v + w <= 1) and (min_z <= coordinate[2] <= max_z)
|
|
@@ -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,14 @@
|
|
|
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
|
+
filter_by_triangle_projection,
|
|
11
|
+
)
|
|
5
12
|
from mat3ra.utils import assertion as assertion_utils
|
|
6
13
|
|
|
7
14
|
from .fixtures import SI_CONVENTIONAL_CELL
|
|
@@ -46,15 +53,15 @@ expected_basis_layers_cavity = {
|
|
|
46
53
|
|
|
47
54
|
|
|
48
55
|
expected_basis_sphere_cluster = {
|
|
49
|
-
"elements": [{"id":
|
|
50
|
-
"coordinates": [{"id":
|
|
56
|
+
"elements": [{"id": 2, "value": "Si"}],
|
|
57
|
+
"coordinates": [{"id": 2, "value": [0.5, 0.5, 0.5]}],
|
|
51
58
|
**COMMON_PART,
|
|
52
59
|
}
|
|
53
60
|
|
|
54
61
|
expected_basis_sphere_cavity = {
|
|
55
62
|
"elements": [
|
|
63
|
+
{"id": 0, "value": "Si"},
|
|
56
64
|
{"id": 1, "value": "Si"},
|
|
57
|
-
{"id": 2, "value": "Si"},
|
|
58
65
|
{"id": 3, "value": "Si"},
|
|
59
66
|
{"id": 4, "value": "Si"},
|
|
60
67
|
{"id": 5, "value": "Si"},
|
|
@@ -62,8 +69,8 @@ expected_basis_sphere_cavity = {
|
|
|
62
69
|
{"id": 7, "value": "Si"},
|
|
63
70
|
],
|
|
64
71
|
"coordinates": [
|
|
72
|
+
{"id": 0, "value": [0.5, 0.0, 0.0]},
|
|
65
73
|
{"id": 1, "value": [0.25, 0.25, 0.75]},
|
|
66
|
-
{"id": 2, "value": [0.5, 0.5, 0.5]},
|
|
67
74
|
{"id": 3, "value": [0.25, 0.75, 0.25]},
|
|
68
75
|
{"id": 4, "value": [0.0, 0.0, 0.5]},
|
|
69
76
|
{"id": 5, "value": [0.75, 0.25, 0.25]},
|
|
@@ -73,6 +80,9 @@ expected_basis_sphere_cavity = {
|
|
|
73
80
|
**COMMON_PART,
|
|
74
81
|
}
|
|
75
82
|
|
|
83
|
+
CRYSTAL_RADIUS = 0.25 # in crystal coordinates
|
|
84
|
+
CRYSTAL_CENTER_3D = [0.5, 0.5, 0.5] # in crystal coordinates
|
|
85
|
+
|
|
76
86
|
|
|
77
87
|
def test_filter_by_label():
|
|
78
88
|
substrate = bulk("Si", cubic=True)
|
|
@@ -89,15 +99,40 @@ def test_filter_by_label():
|
|
|
89
99
|
|
|
90
100
|
def test_filter_by_layers():
|
|
91
101
|
material = Material(SI_CONVENTIONAL_CELL)
|
|
92
|
-
section = filter_by_layers(material, 0, 3.0)
|
|
93
|
-
cavity = filter_by_layers(material, 0, 3.0,
|
|
102
|
+
section = filter_by_layers(material=material, central_atom_id=0, layer_thickness=3.0)
|
|
103
|
+
cavity = filter_by_layers(material=material, central_atom_id=0, layer_thickness=3.0, invert_selection=True)
|
|
94
104
|
assertion_utils.assert_deep_almost_equal(expected_basis_layers_section, section.basis.to_json())
|
|
95
105
|
assertion_utils.assert_deep_almost_equal(expected_basis_layers_cavity, cavity.basis.to_json())
|
|
96
106
|
|
|
97
107
|
|
|
98
108
|
def test_filter_by_sphere():
|
|
99
109
|
material = Material(SI_CONVENTIONAL_CELL)
|
|
100
|
-
cluster = filter_by_sphere(material,
|
|
101
|
-
cavity = filter_by_sphere(material,
|
|
110
|
+
cluster = filter_by_sphere(material, center_coordinate=CRYSTAL_CENTER_3D, radius=CRYSTAL_RADIUS)
|
|
111
|
+
cavity = filter_by_sphere(material, center_coordinate=CRYSTAL_CENTER_3D, radius=CRYSTAL_RADIUS, invert=True)
|
|
102
112
|
assertion_utils.assert_deep_almost_equal(expected_basis_sphere_cluster, cluster.basis.to_json())
|
|
103
113
|
assertion_utils.assert_deep_almost_equal(expected_basis_sphere_cavity, cavity.basis.to_json())
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def test_filter_by_circle_projection():
|
|
117
|
+
material = Material(SI_CONVENTIONAL_CELL)
|
|
118
|
+
# Small cylinder in the middle of the cell containing the central atom will be removed -- the same as with sphere
|
|
119
|
+
section = filter_by_circle_projection(material, 0.5, 0.5, CRYSTAL_RADIUS)
|
|
120
|
+
cavity = filter_by_circle_projection(material, 0.5, 0.5, CRYSTAL_RADIUS, invert_selection=True)
|
|
121
|
+
assertion_utils.assert_deep_almost_equal(expected_basis_sphere_cluster, section.basis.to_json())
|
|
122
|
+
assertion_utils.assert_deep_almost_equal(expected_basis_sphere_cavity, cavity.basis.to_json())
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def test_filter_by_rectangle_projection():
|
|
126
|
+
material = Material(SI_CONVENTIONAL_CELL)
|
|
127
|
+
# Default will contain all the atoms
|
|
128
|
+
section = filter_by_rectangle_projection(material)
|
|
129
|
+
assertion_utils.assert_deep_almost_equal(material.basis.to_json(), section.basis.to_json())
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def test_filter_by_triangle_projection():
|
|
133
|
+
# Small prism in the middle of the cell containing the central atom will be removed -- the same as with sphere
|
|
134
|
+
material = Material(SI_CONVENTIONAL_CELL)
|
|
135
|
+
section = filter_by_triangle_projection(material, [0.4, 0.4], [0.4, 0.5], [0.5, 0.5])
|
|
136
|
+
cavity = filter_by_triangle_projection(material, [0.4, 0.4], [0.4, 0.5], [0.5, 0.5], invert_selection=True)
|
|
137
|
+
assertion_utils.assert_deep_almost_equal(expected_basis_sphere_cluster, section.basis.to_json())
|
|
138
|
+
assertion_utils.assert_deep_almost_equal(expected_basis_sphere_cavity, cavity.basis.to_json())
|