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.
Files changed (53) hide show
  1. {matid-2.0.0.dev2 → matid-2.1.0}/MANIFEST.in +1 -0
  2. {matid-2.0.0.dev2/matid.egg-info → matid-2.1.0}/PKG-INFO +12 -8
  3. {matid-2.0.0.dev2 → matid-2.1.0}/README.md +2 -2
  4. {matid-2.0.0.dev2 → matid-2.1.0}/matid/classification/classifications.py +3 -21
  5. {matid-2.0.0.dev2 → matid-2.1.0}/matid/classification/classifier.py +4 -14
  6. {matid-2.0.0.dev2 → matid-2.1.0}/matid/clustering/sbc.py +164 -162
  7. {matid-2.0.0.dev2 → matid-2.1.0}/matid/core/distances.py +0 -2
  8. matid-2.1.0/matid/core/linkedunits.py +178 -0
  9. {matid-2.0.0.dev2 → matid-2.1.0}/matid/core/periodicfinder.py +101 -109
  10. {matid-2.0.0.dev2 → matid-2.1.0}/matid/data/element_data.py +1 -0
  11. matid-2.1.0/matid/ext/celllist.h +106 -0
  12. matid-2.1.0/matid/ext/geometry.h +89 -0
  13. {matid-2.0.0.dev2 → matid-2.1.0}/matid/geometry/geometry.py +53 -184
  14. {matid-2.0.0.dev2 → matid-2.1.0}/matid/symmetry/symmetryanalyzer.py +3 -3
  15. {matid-2.0.0.dev2 → matid-2.1.0}/matid/utils/segfault_protect.py +1 -0
  16. {matid-2.0.0.dev2 → matid-2.1.0}/matid/utils/symmetry_info_generation/generate_symmetry_info.py +1 -0
  17. {matid-2.0.0.dev2 → matid-2.1.0}/matid/utils/symmetry_info_generation/query_continuous_translations.py +1 -0
  18. {matid-2.0.0.dev2 → matid-2.1.0}/matid/utils/symmetry_info_generation/query_conventional_transform_info.py +1 -0
  19. {matid-2.0.0.dev2 → matid-2.1.0}/matid/utils/symmetry_info_generation/query_normalizers.py +1 -0
  20. {matid-2.0.0.dev2 → matid-2.1.0}/matid/utils/symmetry_info_generation/query_space_group_info.py +1 -0
  21. {matid-2.0.0.dev2 → matid-2.1.0}/matid/utils/symmetry_info_generation/query_wyckoff_sets.py +2 -2
  22. {matid-2.0.0.dev2 → matid-2.1.0/matid.egg-info}/PKG-INFO +12 -8
  23. {matid-2.0.0.dev2 → matid-2.1.0}/matid.egg-info/SOURCES.txt +2 -0
  24. {matid-2.0.0.dev2 → matid-2.1.0}/matid.egg-info/requires.txt +3 -2
  25. matid-2.1.0/pyproject.toml +93 -0
  26. matid-2.0.0.dev2/matid/core/linkedunits.py +0 -573
  27. matid-2.0.0.dev2/pyproject.toml +0 -48
  28. {matid-2.0.0.dev2 → matid-2.1.0}/LICENSE +0 -0
  29. {matid-2.0.0.dev2 → matid-2.1.0}/matid/__init__.py +0 -0
  30. {matid-2.0.0.dev2 → matid-2.1.0}/matid/classification/__init__.py +0 -0
  31. {matid-2.0.0.dev2 → matid-2.1.0}/matid/clustering/__init__.py +0 -0
  32. {matid-2.0.0.dev2 → matid-2.1.0}/matid/clustering/cluster.py +0 -0
  33. {matid-2.0.0.dev2 → matid-2.1.0}/matid/core/__init__.py +0 -0
  34. {matid-2.0.0.dev2 → matid-2.1.0}/matid/core/lattice.py +0 -0
  35. {matid-2.0.0.dev2 → matid-2.1.0}/matid/core/system.py +0 -0
  36. {matid-2.0.0.dev2 → matid-2.1.0}/matid/data/__init__.py +0 -0
  37. {matid-2.0.0.dev2 → matid-2.1.0}/matid/data/alphabet_data.py +0 -0
  38. {matid-2.0.0.dev2 → matid-2.1.0}/matid/data/constants.py +0 -0
  39. {matid-2.0.0.dev2 → matid-2.1.0}/matid/data/symmetry_data.py +0 -0
  40. {matid-2.0.0.dev2 → matid-2.1.0}/matid/ext/celllist.cpp +0 -0
  41. {matid-2.0.0.dev2 → matid-2.1.0}/matid/ext/ext.cpp +0 -0
  42. {matid-2.0.0.dev2 → matid-2.1.0}/matid/ext/geometry.cpp +0 -0
  43. {matid-2.0.0.dev2 → matid-2.1.0}/matid/geometry/__init__.py +0 -0
  44. {matid-2.0.0.dev2 → matid-2.1.0}/matid/symmetry/__init__.py +0 -0
  45. {matid-2.0.0.dev2 → matid-2.1.0}/matid/symmetry/wyckoffset.py +0 -0
  46. {matid-2.0.0.dev2 → matid-2.1.0}/matid/utils/__init__.py +0 -0
  47. {matid-2.0.0.dev2 → matid-2.1.0}/matid/utils/exceptions.py +0 -0
  48. {matid-2.0.0.dev2 → matid-2.1.0}/matid/utils/surfacegenerator.py +0 -0
  49. {matid-2.0.0.dev2 → matid-2.1.0}/matid.egg-info/dependency_links.txt +0 -0
  50. {matid-2.0.0.dev2 → matid-2.1.0}/matid.egg-info/top_level.txt +0 -0
  51. {matid-2.0.0.dev2 → matid-2.1.0}/requirements.txt +0 -0
  52. {matid-2.0.0.dev2 → matid-2.1.0}/setup.cfg +0 -0
  53. {matid-2.0.0.dev2 → matid-2.1.0}/setup.py +0 -0
@@ -1,3 +1,4 @@
1
1
  include LICENSE
2
2
  include requirements.txt
3
+ recursive-include matid/ext *.h
3
4
  recursive-exclude tests *
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: matid
3
- Version: 2.0.0.dev2
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
- Requires-Python: >=3.7
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: black; extra == "dev"
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
  ![Build status](https://github.com/nomad-coe/matid/actions/workflows/test.yml/badge.svg)
238
- [![Coverage Status](https://coveralls.io/repos/github/nomad-coe/matid/badge.svg?branch=main)](https://coveralls.io/github/nomad-coe/matid?branch=main)
239
- [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
242
+ ![Coverage Status](./reports/coverage/coverage-badge.svg)
243
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](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
  ![Build status](https://github.com/nomad-coe/matid/actions/workflows/test.yml/badge.svg)
4
- [![Coverage Status](https://coveralls.io/repos/github/nomad-coe/matid/badge.svg?branch=main)](https://coveralls.io/github/nomad-coe/matid?branch=main)
5
- [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
4
+ ![Coverage Status](./reports/coverage/coverage-badge.svg)
5
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](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
- return self.region.get_outliers()
54
-
55
- @property
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 type(seed_position) == str:
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 type(self.seed_position) == int:
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 not split and covered and region_is_periodic:
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 __init__(self, seed=7):
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
- dbscan_clusters = matid.geometry.get_clusters(
177
- cluster._get_distance_matrix_radii_mic(),
178
- bond_threshold,
179
- min_samples=1,
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
- return clusters
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