matid 2.0.0.dev2__tar.gz → 2.1.0__tar.gz
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.
- {matid-2.0.0.dev2 → matid-2.1.0}/MANIFEST.in +1 -0
- {matid-2.0.0.dev2/matid.egg-info → matid-2.1.0}/PKG-INFO +12 -8
- {matid-2.0.0.dev2 → matid-2.1.0}/README.md +2 -2
- {matid-2.0.0.dev2 → matid-2.1.0}/matid/classification/classifications.py +3 -21
- {matid-2.0.0.dev2 → matid-2.1.0}/matid/classification/classifier.py +4 -14
- {matid-2.0.0.dev2 → matid-2.1.0}/matid/clustering/sbc.py +164 -162
- {matid-2.0.0.dev2 → matid-2.1.0}/matid/core/distances.py +0 -2
- matid-2.1.0/matid/core/linkedunits.py +178 -0
- {matid-2.0.0.dev2 → matid-2.1.0}/matid/core/periodicfinder.py +101 -109
- {matid-2.0.0.dev2 → matid-2.1.0}/matid/data/element_data.py +1 -0
- matid-2.1.0/matid/ext/celllist.h +106 -0
- matid-2.1.0/matid/ext/geometry.h +89 -0
- {matid-2.0.0.dev2 → matid-2.1.0}/matid/geometry/geometry.py +53 -184
- {matid-2.0.0.dev2 → matid-2.1.0}/matid/symmetry/symmetryanalyzer.py +3 -3
- {matid-2.0.0.dev2 → matid-2.1.0}/matid/utils/segfault_protect.py +1 -0
- {matid-2.0.0.dev2 → matid-2.1.0}/matid/utils/symmetry_info_generation/generate_symmetry_info.py +1 -0
- {matid-2.0.0.dev2 → matid-2.1.0}/matid/utils/symmetry_info_generation/query_continuous_translations.py +1 -0
- {matid-2.0.0.dev2 → matid-2.1.0}/matid/utils/symmetry_info_generation/query_conventional_transform_info.py +1 -0
- {matid-2.0.0.dev2 → matid-2.1.0}/matid/utils/symmetry_info_generation/query_normalizers.py +1 -0
- {matid-2.0.0.dev2 → matid-2.1.0}/matid/utils/symmetry_info_generation/query_space_group_info.py +1 -0
- {matid-2.0.0.dev2 → matid-2.1.0}/matid/utils/symmetry_info_generation/query_wyckoff_sets.py +2 -2
- {matid-2.0.0.dev2 → matid-2.1.0/matid.egg-info}/PKG-INFO +12 -8
- {matid-2.0.0.dev2 → matid-2.1.0}/matid.egg-info/SOURCES.txt +2 -0
- {matid-2.0.0.dev2 → matid-2.1.0}/matid.egg-info/requires.txt +3 -2
- matid-2.1.0/pyproject.toml +93 -0
- matid-2.0.0.dev2/matid/core/linkedunits.py +0 -573
- matid-2.0.0.dev2/pyproject.toml +0 -48
- {matid-2.0.0.dev2 → matid-2.1.0}/LICENSE +0 -0
- {matid-2.0.0.dev2 → matid-2.1.0}/matid/__init__.py +0 -0
- {matid-2.0.0.dev2 → matid-2.1.0}/matid/classification/__init__.py +0 -0
- {matid-2.0.0.dev2 → matid-2.1.0}/matid/clustering/__init__.py +0 -0
- {matid-2.0.0.dev2 → matid-2.1.0}/matid/clustering/cluster.py +0 -0
- {matid-2.0.0.dev2 → matid-2.1.0}/matid/core/__init__.py +0 -0
- {matid-2.0.0.dev2 → matid-2.1.0}/matid/core/lattice.py +0 -0
- {matid-2.0.0.dev2 → matid-2.1.0}/matid/core/system.py +0 -0
- {matid-2.0.0.dev2 → matid-2.1.0}/matid/data/__init__.py +0 -0
- {matid-2.0.0.dev2 → matid-2.1.0}/matid/data/alphabet_data.py +0 -0
- {matid-2.0.0.dev2 → matid-2.1.0}/matid/data/constants.py +0 -0
- {matid-2.0.0.dev2 → matid-2.1.0}/matid/data/symmetry_data.py +0 -0
- {matid-2.0.0.dev2 → matid-2.1.0}/matid/ext/celllist.cpp +0 -0
- {matid-2.0.0.dev2 → matid-2.1.0}/matid/ext/ext.cpp +0 -0
- {matid-2.0.0.dev2 → matid-2.1.0}/matid/ext/geometry.cpp +0 -0
- {matid-2.0.0.dev2 → matid-2.1.0}/matid/geometry/__init__.py +0 -0
- {matid-2.0.0.dev2 → matid-2.1.0}/matid/symmetry/__init__.py +0 -0
- {matid-2.0.0.dev2 → matid-2.1.0}/matid/symmetry/wyckoffset.py +0 -0
- {matid-2.0.0.dev2 → matid-2.1.0}/matid/utils/__init__.py +0 -0
- {matid-2.0.0.dev2 → matid-2.1.0}/matid/utils/exceptions.py +0 -0
- {matid-2.0.0.dev2 → matid-2.1.0}/matid/utils/surfacegenerator.py +0 -0
- {matid-2.0.0.dev2 → matid-2.1.0}/matid.egg-info/dependency_links.txt +0 -0
- {matid-2.0.0.dev2 → matid-2.1.0}/matid.egg-info/top_level.txt +0 -0
- {matid-2.0.0.dev2 → matid-2.1.0}/requirements.txt +0 -0
- {matid-2.0.0.dev2 → matid-2.1.0}/setup.cfg +0 -0
- {matid-2.0.0.dev2 → matid-2.1.0}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: matid
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.1.0
|
|
4
4
|
Summary: MatID is a Python package for identifying and analyzing atomistic systems based on their structure.
|
|
5
5
|
Author: Lauri Himanen
|
|
6
6
|
License: Apache License
|
|
@@ -214,13 +214,16 @@ Classifier: Topic :: Scientific/Engineering :: Physics
|
|
|
214
214
|
Classifier: License :: OSI Approved :: Apache Software License
|
|
215
215
|
Classifier: Programming Language :: Python
|
|
216
216
|
Classifier: Programming Language :: Python :: 3
|
|
217
|
-
Classifier: Programming Language :: Python :: 3.7
|
|
218
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
219
217
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
220
|
-
|
|
218
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
219
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
220
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
221
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
222
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
223
|
+
Requires-Python: >=3.8
|
|
221
224
|
Description-Content-Type: text/markdown
|
|
222
225
|
License-File: LICENSE
|
|
223
|
-
Requires-Dist: numpy
|
|
226
|
+
Requires-Dist: numpy<2.0.0
|
|
224
227
|
Requires-Dist: ase
|
|
225
228
|
Requires-Dist: scipy
|
|
226
229
|
Requires-Dist: spglib>=1.15.0
|
|
@@ -229,14 +232,15 @@ Requires-Dist: networkx>=2.4
|
|
|
229
232
|
Provides-Extra: dev
|
|
230
233
|
Requires-Dist: coverage; extra == "dev"
|
|
231
234
|
Requires-Dist: pytest; extra == "dev"
|
|
232
|
-
Requires-Dist:
|
|
235
|
+
Requires-Dist: ruff; extra == "dev"
|
|
233
236
|
Requires-Dist: pydoc-markdown; extra == "dev"
|
|
237
|
+
Requires-Dist: genbadge[coverage]; extra == "dev"
|
|
234
238
|
|
|
235
239
|
<img src="https://raw.githubusercontent.com/nomad-coe/matid/main/docs/static/img/logo.png" width="300">
|
|
236
240
|
|
|
237
241
|

|
|
238
|
-
|
|
239
|
-
[
|
|
243
|
+
[](https://github.com/astral-sh/ruff)
|
|
240
244
|
|
|
241
245
|
MatID is a Python package for identifying and analyzing atomistic systems based
|
|
242
246
|
on their structure.
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
<img src="https://raw.githubusercontent.com/nomad-coe/matid/main/docs/static/img/logo.png" width="300">
|
|
2
2
|
|
|
3
3
|

|
|
4
|
-
|
|
5
|
-
[
|
|
5
|
+
[](https://github.com/astral-sh/ruff)
|
|
6
6
|
|
|
7
7
|
MatID is a Python package for identifying and analyzing atomistic systems based
|
|
8
8
|
on their structure.
|
|
@@ -50,27 +50,9 @@ class Class2DWithCell(Class2D):
|
|
|
50
50
|
|
|
51
51
|
@property
|
|
52
52
|
def outliers(self):
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
def interstitials(self):
|
|
57
|
-
return self.region.get_interstitials()
|
|
58
|
-
|
|
59
|
-
@property
|
|
60
|
-
def adsorbates(self):
|
|
61
|
-
return self.region.get_adsorbates()
|
|
62
|
-
|
|
63
|
-
@property
|
|
64
|
-
def substitutions(self):
|
|
65
|
-
return self.region.get_substitutions()
|
|
66
|
-
|
|
67
|
-
@property
|
|
68
|
-
def vacancies(self):
|
|
69
|
-
return self.region.get_vacancies()
|
|
70
|
-
|
|
71
|
-
@property
|
|
72
|
-
def unknowns(self):
|
|
73
|
-
return self.region.get_unknowns()
|
|
53
|
+
all = set(list(range(len(self.atoms))))
|
|
54
|
+
region = self.region.get_basis_indices()
|
|
55
|
+
return list(all - region)
|
|
74
56
|
|
|
75
57
|
@property
|
|
76
58
|
def prototype_cell(self):
|
|
@@ -127,7 +127,7 @@ class Classifier:
|
|
|
127
127
|
self.min_coverage = min_coverage
|
|
128
128
|
|
|
129
129
|
# Check seed position
|
|
130
|
-
if
|
|
130
|
+
if isinstance(seed_position, str):
|
|
131
131
|
if seed_position == "cm":
|
|
132
132
|
pass
|
|
133
133
|
else:
|
|
@@ -251,7 +251,7 @@ class Classifier:
|
|
|
251
251
|
if len(elems) == 0:
|
|
252
252
|
break
|
|
253
253
|
else:
|
|
254
|
-
if
|
|
254
|
+
if isinstance(self.seed_position, int):
|
|
255
255
|
seed_indices = [self.seed_position]
|
|
256
256
|
elif isinstance(self.seed_position, (tuple, list, np.ndarray)):
|
|
257
257
|
seed_indices = self.seed_position
|
|
@@ -267,26 +267,18 @@ class Classifier:
|
|
|
267
267
|
n_region_conn = np.sum(region_conn)
|
|
268
268
|
region_is_periodic = n_region_conn == 2
|
|
269
269
|
|
|
270
|
-
# This might be unnecessary because the connectivity of the
|
|
271
|
-
# unit cell is already checked.
|
|
272
|
-
clusters = best_region.get_clusters()
|
|
273
|
-
basis_indices = set(list(best_region.get_basis_indices()))
|
|
274
|
-
split = True
|
|
275
|
-
for cluster in clusters:
|
|
276
|
-
if basis_indices.issubset(cluster):
|
|
277
|
-
split = False
|
|
278
|
-
|
|
279
270
|
# Check that the found region covers enough of the entire
|
|
280
271
|
# system. If not, then the region alone cannot be used to
|
|
281
272
|
# classify the entire structure. This happens e.g. when one
|
|
282
273
|
# 2D sheet is found from a 2D heterostructure, or a local
|
|
283
274
|
# pattern is found inside a structure.
|
|
275
|
+
basis_indices = set(list(best_region.get_basis_indices()))
|
|
284
276
|
n_atoms = len(system)
|
|
285
277
|
n_basis_atoms = len(basis_indices)
|
|
286
278
|
coverage = n_basis_atoms / n_atoms
|
|
287
279
|
covered = coverage >= self.min_coverage
|
|
288
280
|
|
|
289
|
-
if
|
|
281
|
+
if covered and region_is_periodic:
|
|
290
282
|
if best_region.is_2d:
|
|
291
283
|
classification = Material2D(input_system, best_region)
|
|
292
284
|
else:
|
|
@@ -329,14 +321,12 @@ class Classifier:
|
|
|
329
321
|
cell_size_tol=self.cell_size_tol,
|
|
330
322
|
max_2d_cell_height=self.max_2d_cell_height,
|
|
331
323
|
max_2d_single_cell_size=self.max_2d_single_cell_size,
|
|
332
|
-
chem_similarity_threshold=self.chem_similarity_threshold,
|
|
333
324
|
)
|
|
334
325
|
region = periodicfinder.get_region(
|
|
335
326
|
system,
|
|
336
327
|
index,
|
|
337
328
|
size,
|
|
338
329
|
tol,
|
|
339
|
-
self.abs_delaunay_threshold,
|
|
340
330
|
self.bond_threshold,
|
|
341
331
|
distances=distances,
|
|
342
332
|
)
|
|
@@ -19,13 +19,164 @@ class SBC:
|
|
|
19
19
|
will be returned as isolated clusters.
|
|
20
20
|
"""
|
|
21
21
|
|
|
22
|
-
def
|
|
22
|
+
def get_clusters(
|
|
23
|
+
self,
|
|
24
|
+
system,
|
|
25
|
+
angle_tol=20,
|
|
26
|
+
max_cell_size=6,
|
|
27
|
+
pos_tol=0.7,
|
|
28
|
+
merge_threshold=0.5,
|
|
29
|
+
merge_radius=1,
|
|
30
|
+
bond_threshold=0.65,
|
|
31
|
+
overlap_threshold=-0.1,
|
|
32
|
+
radii="covalent",
|
|
33
|
+
seed=7,
|
|
34
|
+
):
|
|
23
35
|
"""
|
|
36
|
+
Used to detect and return structurally separate clusters within the
|
|
37
|
+
given system.
|
|
38
|
+
|
|
24
39
|
Args:
|
|
40
|
+
system (ase.Atoms): The structure to partition.
|
|
41
|
+
angle_tol (float): angle_tol parameter for PeriodicFinder
|
|
42
|
+
max_cell_size (float): max_cell_size parameter for PeriodicFinder.get_region
|
|
43
|
+
pos_tol (float): pos_tol parameter for PeriodicFinder.get_region
|
|
44
|
+
merge_threshold (float): A threshold for merging two clusters
|
|
45
|
+
together. Give as a fraction of shared atoms. Value of 1 would
|
|
46
|
+
mean that clusters are never merged, value of 0 means that they
|
|
47
|
+
are merged always when at least one atom is shared.
|
|
48
|
+
merge_radius (float): Radius for finding nearby atoms when deciding
|
|
49
|
+
which cluster is closest. The atomic radii are subtracted from
|
|
50
|
+
distances. Given in angstroms.
|
|
51
|
+
bond_threshold(float): Used to control the connectivity threshold
|
|
52
|
+
for defining a chemical connection between atoms. Controls e.g.
|
|
53
|
+
what types of unit cells are accepted and how outliers are
|
|
54
|
+
removed from clusters.
|
|
55
|
+
overlap_threshold(float): Used to exclude non-physical cells by
|
|
56
|
+
checking overlap of atoms. Overlap between two atoms is
|
|
57
|
+
calculated by subtracting atomic radii from the distance between
|
|
58
|
+
the atoms.
|
|
59
|
+
radii(str|np.ndarray): The radii to use for atoms. Use either a preset
|
|
60
|
+
or a custom list of atomic radii for each atom. The available presets are:
|
|
61
|
+
|
|
62
|
+
- covalent: Covalent radii from DOI:10.1039/B801115J
|
|
63
|
+
- vdw: van Der Waals radii from DOI:10.1039/C3DT50599E
|
|
64
|
+
- vdw_covalent: preferably van Der Waals radii, covalent if vdw
|
|
65
|
+
not defined.
|
|
25
66
|
seed(int): The seed that is used for random number generation.
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
A list of Clusters.
|
|
26
70
|
"""
|
|
71
|
+
# Create a random number gen with custom seed
|
|
27
72
|
self.rng = np.random.default_rng(seed)
|
|
28
73
|
|
|
74
|
+
# Copy the system to avoid mutating the original
|
|
75
|
+
system_copy = system.copy()
|
|
76
|
+
|
|
77
|
+
# Here we ensure that the system has three valid basis vectors which
|
|
78
|
+
# when forming a unit cell contain all of the atoms in the system. This
|
|
79
|
+
# is to ensure that the code that requires scaled positions works
|
|
80
|
+
# correctly.
|
|
81
|
+
pbc = system_copy.get_pbc()
|
|
82
|
+
basis = system_copy.get_cell()
|
|
83
|
+
requires_completion = False
|
|
84
|
+
for i in range(3):
|
|
85
|
+
if not basis[i, :].any():
|
|
86
|
+
if not pbc[i]:
|
|
87
|
+
requires_completion = True
|
|
88
|
+
else:
|
|
89
|
+
raise ValueError(
|
|
90
|
+
"Cannot process system with zero-volume cell and periodic boundaries."
|
|
91
|
+
)
|
|
92
|
+
if requires_completion:
|
|
93
|
+
system_copy.set_cell(ase.geometry.complete_cell(basis))
|
|
94
|
+
if not all(pbc):
|
|
95
|
+
scaled_positions = system_copy.get_scaled_positions()
|
|
96
|
+
new_cell = system_copy.get_cell()
|
|
97
|
+
scale_cell = False
|
|
98
|
+
for i in range(3):
|
|
99
|
+
if not pbc[i]:
|
|
100
|
+
i_pos = scaled_positions[:, i]
|
|
101
|
+
max_pos = i_pos.max()
|
|
102
|
+
min_pos = i_pos.min()
|
|
103
|
+
if max_pos > 1 or min_pos < 0:
|
|
104
|
+
scale_cell = True
|
|
105
|
+
new_cell[i, :] *= (max_pos - min_pos) + 1
|
|
106
|
+
if scale_cell:
|
|
107
|
+
system_copy.set_cell(new_cell)
|
|
108
|
+
system_copy.center()
|
|
109
|
+
|
|
110
|
+
# Positions are wrapped
|
|
111
|
+
system_copy.wrap()
|
|
112
|
+
|
|
113
|
+
atomic_numbers = system.get_atomic_numbers()
|
|
114
|
+
radii = matid.geometry.get_radii(radii, atomic_numbers)
|
|
115
|
+
|
|
116
|
+
# Calculate the distances here once if they have not been provided.
|
|
117
|
+
distances = matid.geometry.get_distances(system_copy, radii)
|
|
118
|
+
|
|
119
|
+
# Iteratively search for new clusters until whole system is covered
|
|
120
|
+
periodic_finder = PeriodicFinder(angle_tol=angle_tol)
|
|
121
|
+
indices = set(list(range(len(system_copy))))
|
|
122
|
+
clusters = []
|
|
123
|
+
while len(indices) != 0:
|
|
124
|
+
i_seed = self.rng.choice(list(indices), 1)[0]
|
|
125
|
+
i_grain, mask = periodic_finder.get_region(
|
|
126
|
+
system_copy,
|
|
127
|
+
seed_index=i_seed,
|
|
128
|
+
max_cell_size=max_cell_size,
|
|
129
|
+
pos_tol=pos_tol,
|
|
130
|
+
bond_threshold=bond_threshold,
|
|
131
|
+
overlap_threshold=overlap_threshold,
|
|
132
|
+
distances=distances,
|
|
133
|
+
return_mask=True,
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
# All neighbours that the periodic finder has tested are removed
|
|
137
|
+
# from the search. This significantly helps with the scaling of the
|
|
138
|
+
# clustering.
|
|
139
|
+
tested_indices = set(np.arange(len(mask))[mask])
|
|
140
|
+
indices -= tested_indices
|
|
141
|
+
|
|
142
|
+
# If a grain is found, it is added as a single cluster and removed
|
|
143
|
+
# from the search
|
|
144
|
+
if i_grain is not None:
|
|
145
|
+
i_indices = {i_seed}
|
|
146
|
+
i_indices.update(i_grain.get_basis_indices())
|
|
147
|
+
i_species = set(atomic_numbers[list(i_indices)])
|
|
148
|
+
clusters.append(
|
|
149
|
+
Cluster(
|
|
150
|
+
i_indices,
|
|
151
|
+
i_species,
|
|
152
|
+
i_grain,
|
|
153
|
+
system=system_copy,
|
|
154
|
+
distances=distances,
|
|
155
|
+
radii=radii,
|
|
156
|
+
bond_threshold=bond_threshold,
|
|
157
|
+
)
|
|
158
|
+
)
|
|
159
|
+
indices -= i_indices
|
|
160
|
+
|
|
161
|
+
# Check overlaps of the regions. For large overlaps the grains are
|
|
162
|
+
# merged (the real region was probably cut into pieces by unfortunate
|
|
163
|
+
# selection of the seed atom)
|
|
164
|
+
clusters = self._merge_clusters(
|
|
165
|
+
system_copy, clusters, merge_threshold, distances, bond_threshold
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
# Any remaining overlaps are resolved by assigning atoms to the
|
|
169
|
+
# "nearest" cluster
|
|
170
|
+
clusters = self._localize_clusters(
|
|
171
|
+
system_copy, clusters, merge_radius, distances
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
# Any atoms that are not chemically connected to the region will be
|
|
175
|
+
# excluded.
|
|
176
|
+
clusters = self._clean_clusters(clusters, bond_threshold)
|
|
177
|
+
|
|
178
|
+
return clusters
|
|
179
|
+
|
|
29
180
|
def _merge_clusters(
|
|
30
181
|
self, system, clusters, merge_threshold, distances, bond_threshold
|
|
31
182
|
):
|
|
@@ -172,167 +323,18 @@ class SBC:
|
|
|
172
323
|
Returns:
|
|
173
324
|
List of Clusters where any outlier atoms have been removed.
|
|
174
325
|
"""
|
|
326
|
+
clusters_cleaned = []
|
|
175
327
|
for cluster in clusters:
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
328
|
+
# If the cluster cleaning fails, the cluster is not reported
|
|
329
|
+
try:
|
|
330
|
+
dbscan_clusters = matid.geometry.get_clusters(
|
|
331
|
+
cluster._get_distance_matrix_radii_mic(),
|
|
332
|
+
bond_threshold,
|
|
333
|
+
min_samples=1,
|
|
334
|
+
)
|
|
335
|
+
except Exception:
|
|
336
|
+
continue
|
|
181
337
|
largest_indices = max(dbscan_clusters, key=lambda x: len(x))
|
|
182
338
|
cluster.indices = np.array(cluster.indices)[largest_indices].tolist()
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
def get_clusters(
|
|
186
|
-
self,
|
|
187
|
-
system,
|
|
188
|
-
angle_tol=20,
|
|
189
|
-
max_cell_size=6,
|
|
190
|
-
pos_tol=0.7,
|
|
191
|
-
merge_threshold=0.5,
|
|
192
|
-
merge_radius=1,
|
|
193
|
-
bond_threshold=0.65,
|
|
194
|
-
overlap_threshold=-0.1,
|
|
195
|
-
radii="covalent",
|
|
196
|
-
):
|
|
197
|
-
"""
|
|
198
|
-
Used to detect and return structurally separate clusters within the
|
|
199
|
-
given system.
|
|
200
|
-
|
|
201
|
-
Args:
|
|
202
|
-
system (ase.Atoms): The structure to partition.
|
|
203
|
-
angle_tol (float): angle_tol parameter for PeriodicFinder
|
|
204
|
-
max_cell_size (float): max_cell_size parameter for PeriodicFinder.get_region
|
|
205
|
-
pos_tol (float): pos_tol parameter for PeriodicFinder.get_region
|
|
206
|
-
merge_threshold (float): A threshold for merging two clusters
|
|
207
|
-
together. Give as a fraction of shared atoms. Value of 1 would
|
|
208
|
-
mean that clusters are never merged, value of 0 means that they
|
|
209
|
-
are merged always when at least one atom is shared.
|
|
210
|
-
merge_radius (float): Radius for finding nearby atoms when deciding
|
|
211
|
-
which cluster is closest. The atomic radii are subtracted from
|
|
212
|
-
distances. Given in angstroms.
|
|
213
|
-
bond_threshold(float): Used to control the connectivity threshold
|
|
214
|
-
for defining a chemical connection between atoms. Controls e.g.
|
|
215
|
-
what types of unit cells are accepted and how outliers are
|
|
216
|
-
removed from clusters.
|
|
217
|
-
overlap_threshold(float): Used to exclude non-physical cells by
|
|
218
|
-
checking overlap of atoms. Overlap between two atoms is
|
|
219
|
-
calculated by subtracting atomic radii from the distance between
|
|
220
|
-
the atoms.
|
|
221
|
-
radii(str|np.ndarray): The radii to use for atoms. Use either a preset
|
|
222
|
-
or a custom list of atomic radii for each atom. The available presets are:
|
|
223
|
-
|
|
224
|
-
- covalent: Covalent radii from DOI:10.1039/B801115J
|
|
225
|
-
- vdw: van Der Waals radii from DOI:10.1039/C3DT50599E
|
|
226
|
-
- vdw_covalent: preferably van Der Waals radii, covalent if vdw
|
|
227
|
-
not defined.
|
|
228
|
-
|
|
229
|
-
Returns:
|
|
230
|
-
A list of Clusters.
|
|
231
|
-
"""
|
|
232
|
-
# Copy the system to avoid mutating the original
|
|
233
|
-
system_copy = system.copy()
|
|
234
|
-
|
|
235
|
-
# Here we ensure that the system has three valid basis vectors which
|
|
236
|
-
# when forming a unit cell contain all of the atoms in the system. This
|
|
237
|
-
# is to ensure that the code that requires scaled positions works
|
|
238
|
-
# correctly.
|
|
239
|
-
pbc = system_copy.get_pbc()
|
|
240
|
-
basis = system_copy.get_cell()
|
|
241
|
-
requires_completion = False
|
|
242
|
-
for i in range(3):
|
|
243
|
-
if not basis[i, :].any():
|
|
244
|
-
if not pbc[i]:
|
|
245
|
-
requires_completion = True
|
|
246
|
-
else:
|
|
247
|
-
raise ValueError(
|
|
248
|
-
"Cannot process system with zero-volume cell and periodic boundaries."
|
|
249
|
-
)
|
|
250
|
-
if requires_completion:
|
|
251
|
-
system_copy.set_cell(ase.geometry.complete_cell(basis))
|
|
252
|
-
if not all(pbc):
|
|
253
|
-
scaled_positions = system_copy.get_scaled_positions()
|
|
254
|
-
new_cell = system_copy.get_cell()
|
|
255
|
-
scale_cell = False
|
|
256
|
-
for i in range(3):
|
|
257
|
-
if not pbc[i]:
|
|
258
|
-
i_pos = scaled_positions[:, i]
|
|
259
|
-
max_pos = i_pos.max()
|
|
260
|
-
min_pos = i_pos.min()
|
|
261
|
-
if max_pos > 1 or min_pos < 0:
|
|
262
|
-
scale_cell = True
|
|
263
|
-
new_cell[i, :] *= (max_pos - min_pos) + 1
|
|
264
|
-
if scale_cell:
|
|
265
|
-
system_copy.set_cell(new_cell)
|
|
266
|
-
system_copy.center()
|
|
267
|
-
|
|
268
|
-
# Positions are wrapped
|
|
269
|
-
system_copy.wrap()
|
|
270
|
-
|
|
271
|
-
atomic_numbers = system.get_atomic_numbers()
|
|
272
|
-
radii = matid.geometry.get_radii(radii, atomic_numbers)
|
|
273
|
-
|
|
274
|
-
# Calculate the distances here once if they have not been provided.
|
|
275
|
-
distances = matid.geometry.get_distances(system_copy, radii)
|
|
276
|
-
|
|
277
|
-
# Iteratively search for new clusters until whole system is covered
|
|
278
|
-
periodic_finder = PeriodicFinder(
|
|
279
|
-
angle_tol=angle_tol, chem_similarity_threshold=0
|
|
280
|
-
)
|
|
281
|
-
indices = set(list(range(len(system_copy))))
|
|
282
|
-
clusters = []
|
|
283
|
-
while len(indices) != 0:
|
|
284
|
-
i_seed = self.rng.choice(list(indices), 1)[0]
|
|
285
|
-
i_grain, mask = periodic_finder.get_region(
|
|
286
|
-
system_copy,
|
|
287
|
-
seed_index=i_seed,
|
|
288
|
-
max_cell_size=max_cell_size,
|
|
289
|
-
pos_tol=pos_tol,
|
|
290
|
-
bond_threshold=bond_threshold,
|
|
291
|
-
overlap_threshold=overlap_threshold,
|
|
292
|
-
distances=distances,
|
|
293
|
-
return_mask=True,
|
|
294
|
-
)
|
|
295
|
-
|
|
296
|
-
# All neighbours that the periodic finder has tested are removed
|
|
297
|
-
# from the search. This significantly helps with the scaling of the
|
|
298
|
-
# clustering.
|
|
299
|
-
tested_indices = set(np.arange(len(mask))[mask])
|
|
300
|
-
indices -= tested_indices
|
|
301
|
-
|
|
302
|
-
# If a grain is found, it is added as a single cluster and removed
|
|
303
|
-
# from the search
|
|
304
|
-
if i_grain is not None:
|
|
305
|
-
i_indices = {i_seed}
|
|
306
|
-
i_indices.update(i_grain.get_basis_indices())
|
|
307
|
-
i_species = set(atomic_numbers[list(i_indices)])
|
|
308
|
-
clusters.append(
|
|
309
|
-
Cluster(
|
|
310
|
-
i_indices,
|
|
311
|
-
i_species,
|
|
312
|
-
i_grain,
|
|
313
|
-
system=system_copy,
|
|
314
|
-
distances=distances,
|
|
315
|
-
radii=radii,
|
|
316
|
-
bond_threshold=bond_threshold,
|
|
317
|
-
)
|
|
318
|
-
)
|
|
319
|
-
indices -= i_indices
|
|
320
|
-
|
|
321
|
-
# Check overlaps of the regions. For large overlaps the grains are
|
|
322
|
-
# merged (the real region was probably cut into pieces by unfortunate
|
|
323
|
-
# selection of the seed atom)
|
|
324
|
-
clusters = self._merge_clusters(
|
|
325
|
-
system_copy, clusters, merge_threshold, distances, bond_threshold
|
|
326
|
-
)
|
|
327
|
-
|
|
328
|
-
# Any remaining overlaps are resolved by assigning atoms to the
|
|
329
|
-
# "nearest" cluster
|
|
330
|
-
clusters = self._localize_clusters(
|
|
331
|
-
system_copy, clusters, merge_radius, distances
|
|
332
|
-
)
|
|
333
|
-
|
|
334
|
-
# Any atoms that are not chemically connected to the region will be
|
|
335
|
-
# excluded.
|
|
336
|
-
clusters = self._clean_clusters(clusters, bond_threshold)
|
|
337
|
-
|
|
338
|
-
return clusters
|
|
339
|
+
clusters_cleaned.append(cluster)
|
|
340
|
+
return clusters_cleaned
|
|
@@ -7,14 +7,12 @@ class Distances:
|
|
|
7
7
|
self,
|
|
8
8
|
disp_tensor_mic,
|
|
9
9
|
disp_factors,
|
|
10
|
-
disp_tensor_finite,
|
|
11
10
|
dist_matrix_mic,
|
|
12
11
|
dist_matrix_radii_mic,
|
|
13
12
|
cell_list=None,
|
|
14
13
|
):
|
|
15
14
|
self.disp_tensor_mic = disp_tensor_mic
|
|
16
15
|
self.disp_factors = disp_factors
|
|
17
|
-
self.disp_tensor_finite = disp_tensor_finite
|
|
18
16
|
self.dist_matrix_mic = dist_matrix_mic
|
|
19
17
|
self.dist_matrix_radii_mic = dist_matrix_radii_mic
|
|
20
18
|
self.cell_list = cell_list
|