structuretoolkit 0.0.42__tar.gz → 0.0.43__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 (36) hide show
  1. structuretoolkit-0.0.43/.gitignore +217 -0
  2. {structuretoolkit-0.0.42 → structuretoolkit-0.0.43}/PKG-INFO +8 -9
  3. {structuretoolkit-0.0.42 → structuretoolkit-0.0.43}/pyproject.toml +7 -8
  4. {structuretoolkit-0.0.42 → structuretoolkit-0.0.43}/src/structuretoolkit/_version.py +2 -2
  5. {structuretoolkit-0.0.42 → structuretoolkit-0.0.43}/src/structuretoolkit/analyse/symmetry.py +22 -7
  6. structuretoolkit-0.0.43/src/structuretoolkit/build/geometry.py +141 -0
  7. structuretoolkit-0.0.43/src/structuretoolkit/build/sqs/__init__.py +33 -0
  8. structuretoolkit-0.0.43/src/structuretoolkit/build/sqs/_interface.py +310 -0
  9. structuretoolkit-0.0.43/src/structuretoolkit/build/sqs/_types.py +195 -0
  10. {structuretoolkit-0.0.42 → structuretoolkit-0.0.43}/src/structuretoolkit/common/pymatgen.py +1 -1
  11. structuretoolkit-0.0.42/.gitignore +0 -10
  12. structuretoolkit-0.0.42/src/structuretoolkit/build/sqs.py +0 -236
  13. {structuretoolkit-0.0.42 → structuretoolkit-0.0.43}/LICENSE +0 -0
  14. {structuretoolkit-0.0.42 → structuretoolkit-0.0.43}/README.md +0 -0
  15. {structuretoolkit-0.0.42 → structuretoolkit-0.0.43}/src/structuretoolkit/__init__.py +0 -0
  16. {structuretoolkit-0.0.42 → structuretoolkit-0.0.43}/src/structuretoolkit/analyse/__init__.py +0 -0
  17. {structuretoolkit-0.0.42 → structuretoolkit-0.0.43}/src/structuretoolkit/analyse/distance.py +0 -0
  18. {structuretoolkit-0.0.42 → structuretoolkit-0.0.43}/src/structuretoolkit/analyse/dscribe.py +0 -0
  19. {structuretoolkit-0.0.42 → structuretoolkit-0.0.43}/src/structuretoolkit/analyse/neighbors.py +0 -0
  20. {structuretoolkit-0.0.42 → structuretoolkit-0.0.43}/src/structuretoolkit/analyse/phonopy.py +0 -0
  21. {structuretoolkit-0.0.42 → structuretoolkit-0.0.43}/src/structuretoolkit/analyse/pyscal.py +0 -0
  22. {structuretoolkit-0.0.42 → structuretoolkit-0.0.43}/src/structuretoolkit/analyse/snap.py +0 -0
  23. {structuretoolkit-0.0.42 → structuretoolkit-0.0.43}/src/structuretoolkit/analyse/spatial.py +0 -0
  24. {structuretoolkit-0.0.42 → structuretoolkit-0.0.43}/src/structuretoolkit/analyse/strain.py +0 -0
  25. {structuretoolkit-0.0.42 → structuretoolkit-0.0.43}/src/structuretoolkit/build/__init__.py +0 -0
  26. {structuretoolkit-0.0.42 → structuretoolkit-0.0.43}/src/structuretoolkit/build/aimsgb.py +0 -0
  27. {structuretoolkit-0.0.42 → structuretoolkit-0.0.43}/src/structuretoolkit/build/compound.py +0 -0
  28. {structuretoolkit-0.0.42 → structuretoolkit-0.0.43}/src/structuretoolkit/build/materialsproject.py +0 -0
  29. {structuretoolkit-0.0.42 → structuretoolkit-0.0.43}/src/structuretoolkit/build/mesh.py +0 -0
  30. {structuretoolkit-0.0.42 → structuretoolkit-0.0.43}/src/structuretoolkit/build/surface.py +0 -0
  31. {structuretoolkit-0.0.42 → structuretoolkit-0.0.43}/src/structuretoolkit/common/__init__.py +0 -0
  32. {structuretoolkit-0.0.42 → structuretoolkit-0.0.43}/src/structuretoolkit/common/error.py +0 -0
  33. {structuretoolkit-0.0.42 → structuretoolkit-0.0.43}/src/structuretoolkit/common/helper.py +0 -0
  34. {structuretoolkit-0.0.42 → structuretoolkit-0.0.43}/src/structuretoolkit/common/phonopy.py +0 -0
  35. {structuretoolkit-0.0.42 → structuretoolkit-0.0.43}/src/structuretoolkit/common/pyscal.py +0 -0
  36. {structuretoolkit-0.0.42 → structuretoolkit-0.0.43}/src/structuretoolkit/visualize.py +0 -0
@@ -0,0 +1,217 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[codz]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ develop-eggs/
12
+ dist/
13
+ downloads/
14
+ eggs/
15
+ .eggs/
16
+ lib/
17
+ lib64/
18
+ parts/
19
+ sdist/
20
+ var/
21
+ wheels/
22
+ share/python-wheels/
23
+ *.egg-info/
24
+ .installed.cfg
25
+ *.egg
26
+ MANIFEST
27
+
28
+ # PyInstaller
29
+ # Usually these files are written by a python script from a template
30
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
31
+ *.manifest
32
+ *.spec
33
+
34
+ # Installer logs
35
+ pip-log.txt
36
+ pip-delete-this-directory.txt
37
+
38
+ # Unit test / coverage reports
39
+ htmlcov/
40
+ .tox/
41
+ .nox/
42
+ .coverage
43
+ .coverage.*
44
+ .cache
45
+ nosetests.xml
46
+ coverage.xml
47
+ *.cover
48
+ *.py.cover
49
+ .hypothesis/
50
+ .pytest_cache/
51
+ cover/
52
+
53
+ # Translations
54
+ *.mo
55
+ *.pot
56
+
57
+ # Django stuff:
58
+ *.log
59
+ local_settings.py
60
+ db.sqlite3
61
+ db.sqlite3-journal
62
+
63
+ # Flask stuff:
64
+ instance/
65
+ .webassets-cache
66
+
67
+ # Scrapy stuff:
68
+ .scrapy
69
+
70
+ # Sphinx documentation
71
+ docs/_build/
72
+
73
+ # PyBuilder
74
+ .pybuilder/
75
+ target/
76
+
77
+ # Jupyter Notebook
78
+ .ipynb_checkpoints
79
+
80
+ # IPython
81
+ profile_default/
82
+ ipython_config.py
83
+
84
+ # pyenv
85
+ # For a library or package, you might want to ignore these files since the code is
86
+ # intended to run in multiple environments; otherwise, check them in:
87
+ # .python-version
88
+
89
+ # pipenv
90
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
91
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
92
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
93
+ # install all needed dependencies.
94
+ # Pipfile.lock
95
+
96
+ # UV
97
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
98
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
99
+ # commonly ignored for libraries.
100
+ # uv.lock
101
+
102
+ # poetry
103
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
104
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
105
+ # commonly ignored for libraries.
106
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
107
+ # poetry.lock
108
+ # poetry.toml
109
+
110
+ # pdm
111
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
112
+ # pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
113
+ # https://pdm-project.org/en/latest/usage/project/#working-with-version-control
114
+ # pdm.lock
115
+ # pdm.toml
116
+ .pdm-python
117
+ .pdm-build/
118
+
119
+ # pixi
120
+ # Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
121
+ # pixi.lock
122
+ # Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
123
+ # in the .venv directory. It is recommended not to include this directory in version control.
124
+ .pixi
125
+
126
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
127
+ __pypackages__/
128
+
129
+ # Celery stuff
130
+ celerybeat-schedule
131
+ celerybeat.pid
132
+
133
+ # Redis
134
+ *.rdb
135
+ *.aof
136
+ *.pid
137
+
138
+ # RabbitMQ
139
+ mnesia/
140
+ rabbitmq/
141
+ rabbitmq-data/
142
+
143
+ # ActiveMQ
144
+ activemq-data/
145
+
146
+ # SageMath parsed files
147
+ *.sage.py
148
+
149
+ # Environments
150
+ .env
151
+ .envrc
152
+ .venv
153
+ env/
154
+ venv/
155
+ ENV/
156
+ env.bak/
157
+ venv.bak/
158
+
159
+ # Spyder project settings
160
+ .spyderproject
161
+ .spyproject
162
+
163
+ # Rope project settings
164
+ .ropeproject
165
+
166
+ # mkdocs documentation
167
+ /site
168
+
169
+ # mypy
170
+ .mypy_cache/
171
+ .dmypy.json
172
+ dmypy.json
173
+
174
+ # Pyre type checker
175
+ .pyre/
176
+
177
+ # pytype static type analyzer
178
+ .pytype/
179
+
180
+ # Cython debug symbols
181
+ cython_debug/
182
+
183
+ # PyCharm
184
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
185
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
186
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
187
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
188
+ # .idea/
189
+
190
+ # Abstra
191
+ # Abstra is an AI-powered process automation framework.
192
+ # Ignore directories containing user credentials, local state, and settings.
193
+ # Learn more at https://abstra.io/docs
194
+ .abstra/
195
+
196
+ # Visual Studio Code
197
+ # Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
198
+ # that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
199
+ # and can be added to the global gitignore or merged into this file. However, if you prefer,
200
+ # you could uncomment the following to ignore the entire vscode folder
201
+ # .vscode/
202
+ # Temporary file for partial code execution
203
+ tempCodeRunnerFile.py
204
+
205
+ # Ruff stuff:
206
+ .ruff_cache/
207
+
208
+ # PyPI configuration file
209
+ .pypirc
210
+
211
+ # Marimo
212
+ marimo/_static/
213
+ marimo/_lsp/
214
+ __marimo__/
215
+
216
+ # Streamlit
217
+ .streamlit/secrets.toml
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: structuretoolkit
3
- Version: 0.0.42
3
+ Version: 0.0.43
4
4
  Summary: build, analyse and visualise atomistic structures for materials science
5
5
  Project-URL: Homepage, https://github.com/pyiron/structuretoolkit
6
6
  Project-URL: Documentation, https://github.com/pyiron/structuretoolkit
@@ -41,13 +41,12 @@ Classifier: Development Status :: 5 - Production/Stable
41
41
  Classifier: Intended Audience :: Science/Research
42
42
  Classifier: License :: OSI Approved :: BSD License
43
43
  Classifier: Operating System :: OS Independent
44
- Classifier: Programming Language :: Python :: 3.10
45
44
  Classifier: Programming Language :: Python :: 3.11
46
45
  Classifier: Programming Language :: Python :: 3.12
47
46
  Classifier: Programming Language :: Python :: 3.13
48
47
  Classifier: Topic :: Scientific/Engineering :: Physics
49
48
  Requires-Python: <3.15,>=3.10
50
- Requires-Dist: ase<=3.28.0,>=3.20.1
49
+ Requires-Dist: ase<=3.28.0,>=3.24.0
51
50
  Requires-Dist: numpy<=2.4.3,>=1.23.5
52
51
  Requires-Dist: scipy<=1.17.1,>=1.15.0
53
52
  Provides-Extra: clusters
@@ -56,23 +55,23 @@ Provides-Extra: dscribe
56
55
  Requires-Dist: dscribe==2.1.2; extra == 'dscribe'
57
56
  Provides-Extra: grainboundary
58
57
  Requires-Dist: aimsgb<=1.1.1,>=1.0.2; extra == 'grainboundary'
59
- Requires-Dist: pymatgen<=2026.3.23,>=2022.2.1; extra == 'grainboundary'
58
+ Requires-Dist: pymatgen==2026.3.23; extra == 'grainboundary'
60
59
  Provides-Extra: matplotlib
61
- Requires-Dist: matplotlib==3.10.8; extra == 'matplotlib'
60
+ Requires-Dist: matplotlib==3.10.9; extra == 'matplotlib'
62
61
  Provides-Extra: mp-api
63
62
  Requires-Dist: mp-api==0.45.15; extra == 'mp-api'
64
- Requires-Dist: pymatgen<=2026.3.23,>=2022.2.1; extra == 'mp-api'
63
+ Requires-Dist: pymatgen==2026.3.23; extra == 'mp-api'
65
64
  Provides-Extra: nglview
66
65
  Requires-Dist: nglview<=4.0.1,>=2.7.7; extra == 'nglview'
67
66
  Provides-Extra: phonopy
68
- Requires-Dist: phonopy<=3.5.0,>=2.16.2; extra == 'phonopy'
67
+ Requires-Dist: phonopy<=3.5.1,>=2.21.2; extra == 'phonopy'
69
68
  Requires-Dist: spglib==2.7.0; extra == 'phonopy'
70
69
  Provides-Extra: plotly
71
- Requires-Dist: plotly<=6.6.0,>=4.14.3; extra == 'plotly'
70
+ Requires-Dist: plotly<=6.6.0,>=6.0.0; extra == 'plotly'
72
71
  Provides-Extra: pyscal
73
72
  Requires-Dist: pyscal3<=3.3.2,>=3.2.5; extra == 'pyscal'
74
73
  Provides-Extra: surface
75
- Requires-Dist: pymatgen<=2026.3.23,>=2022.2.1; extra == 'surface'
74
+ Requires-Dist: pymatgen==2026.3.23; extra == 'surface'
76
75
  Requires-Dist: spglib==2.7.0; extra == 'surface'
77
76
  Provides-Extra: symmetry
78
77
  Requires-Dist: spglib==2.7.0; extra == 'symmetry'
@@ -18,13 +18,12 @@ classifiers = [
18
18
  "License :: OSI Approved :: BSD License",
19
19
  "Intended Audience :: Science/Research",
20
20
  "Operating System :: OS Independent",
21
- "Programming Language :: Python :: 3.10",
22
21
  "Programming Language :: Python :: 3.11",
23
22
  "Programming Language :: Python :: 3.12",
24
23
  "Programming Language :: Python :: 3.13",
25
24
  ]
26
25
  dependencies = [
27
- "ase>=3.20.1,<=3.28.0",
26
+ "ase>=3.24.0,<=3.28.0",
28
27
  "numpy>=1.23.5,<=2.4.3",
29
28
  "scipy>=1.15.0,<=1.17.1",
30
29
  ]
@@ -39,25 +38,25 @@ Repository = "https://github.com/pyiron/structuretoolkit"
39
38
  dscribe = ["dscribe==2.1.2"]
40
39
  grainboundary = [
41
40
  "aimsgb>=1.0.2,<=1.1.1",
42
- "pymatgen>=2022.2.1,<=2026.3.23",
41
+ "pymatgen==2026.3.23",
43
42
  ]
44
43
  pyscal = ["pyscal3>=3.2.5,<=3.3.2"]
45
44
  nglview = ["nglview>=2.7.7,<=4.0.1"]
46
- matplotlib = ["matplotlib==3.10.8"]
47
- plotly = ["plotly>=4.14.3,<=6.6.0"]
45
+ matplotlib = ["matplotlib==3.10.9"]
46
+ plotly = ["plotly>=6.0.0,<=6.6.0"]
48
47
  clusters = ["scikit-learn==1.8.0"]
49
48
  symmetry = ["spglib==2.7.0"]
50
49
  surface = [
51
50
  "spglib==2.7.0",
52
- "pymatgen>=2022.2.1,<=2026.3.23",
51
+ "pymatgen==2026.3.23",
53
52
  ]
54
53
  phonopy = [
55
- "phonopy>=2.16.2,<=3.5.0",
54
+ "phonopy>=2.21.2,<=3.5.1",
56
55
  "spglib==2.7.0",
57
56
  ]
58
57
  mp-api = [
59
58
  "mp-api==0.45.15",
60
- "pymatgen>=2022.2.1,<=2026.3.23",
59
+ "pymatgen==2026.3.23",
61
60
  ]
62
61
 
63
62
  [tool.ruff]
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '0.0.42'
32
- __version_tuple__ = version_tuple = (0, 0, 42)
31
+ __version__ = version = '0.0.43'
32
+ __version_tuple__ = version_tuple = (0, 0, 43)
33
33
 
34
34
  __commit_id__ = commit_id = None
@@ -4,6 +4,7 @@
4
4
  import ast
5
5
  import dataclasses
6
6
  import string
7
+ import warnings
7
8
 
8
9
  import numpy as np
9
10
  import spglib
@@ -400,26 +401,40 @@ class Symmetry(dict):
400
401
  >>> symmetry = Symmetry(structure)
401
402
  >>> len(symmetry.get_primitive_cell()) == len(basis)
402
403
  True
404
+
405
+ .. warning::
406
+ Custom arrays defined in the base structures
407
+ :attr:`ase.atoms.Atoms.arrays` and other state (.info, .calc, etc.) are not copied to the new structure!
403
408
  """
409
+ if not all(self._structure.pbc):
410
+ raise ValueError("Can only symmetrize periodic structures.")
404
411
  ret = spglib.standardize_cell(
405
412
  self._get_spglib_cell(use_elements=use_elements, use_magmoms=use_magmoms),
406
413
  to_primitive=not standardize,
407
414
  )
408
415
  if ret is None:
409
416
  raise SymmetryError(spglib.error.get_error_message())
410
- cell, positions, indices = ret
411
- positions = (cell.T @ positions.T).T
412
- new_structure = self._structure.copy()
413
- new_structure.cell = cell
414
- new_structure = new_structure[: len(indices)]
417
+ cell, scaled_positions, indices = ret
415
418
  indices_dict = {
416
419
  v: k
417
420
  for k, v in structuretoolkit.common.helper.get_species_indices_dict(
418
421
  structure=self._structure
419
422
  ).items()
420
423
  }
421
- new_structure.symbols = [indices_dict[i] for i in indices]
422
- new_structure.positions = positions
424
+ symbols = [indices_dict[i] for i in indices]
425
+ new_structure = type(self._structure)(
426
+ symbols=symbols,
427
+ scaled_positions=scaled_positions,
428
+ cell=cell,
429
+ pbc=[True, True, True],
430
+ )
431
+ keys = set(self._structure.arrays) - {"numbers", "positions"}
432
+ if len(keys) > 0:
433
+ warnings.warn(
434
+ f"Custom arrays {keys} do not carry over to new structure!",
435
+ stacklevel=2,
436
+ )
437
+
423
438
  return new_structure
424
439
 
425
440
  def get_ir_reciprocal_mesh(
@@ -0,0 +1,141 @@
1
+ """Utilities that operate purely geometric aspects of structures."""
2
+
3
+ import numpy as np
4
+ from ase.atoms import Atoms
5
+
6
+ from structuretoolkit.analyse import get_neighbors
7
+
8
+
9
+ def repulse(
10
+ structure: Atoms,
11
+ min_dist: float = 1.5,
12
+ step_size: float = 0.2,
13
+ axis: int | None = None,
14
+ iterations: int = 100,
15
+ inplace: bool = False,
16
+ ) -> Atoms:
17
+ """Iteratively displace atoms apart until all interatomic distances exceed a minimum threshold.
18
+
19
+ For each pair of atoms closer than ``min_dist``, the atom is displaced away from its nearest
20
+ neighbour by up to ``step_size`` along the direction of the interatomic vector. The loop
21
+ repeats until all nearest-neighbour distances satisfy the minimum criterion or the iteration
22
+ limit is reached.
23
+
24
+ Args:
25
+ structure (:class:`ase.Atoms`):
26
+ Structure to modify.
27
+ min_dist (float):
28
+ Minimum interatomic distance (in Å) to enforce between every pair of atoms.
29
+ Defaults to 1.5.
30
+ step_size (float):
31
+ Maximum displacement (in Å) applied to a single atom per iteration.
32
+ Smaller values give smoother convergence but require more iterations.
33
+ Defaults to 0.2.
34
+ axis (int or None):
35
+ Cartesian axis index (0, 1, or 2) along which displacements are restricted.
36
+ When *None* (default) displacements are applied in all three directions.
37
+ iterations (int):
38
+ Maximum number of displacement steps before raising a :class:`RuntimeError`.
39
+ Defaults to 100.
40
+ inplace (bool):
41
+ If *True*, the positions of ``structure`` are modified directly.
42
+ If *False* (default), a copy is made and the original is left unchanged.
43
+
44
+ Returns:
45
+ :class:`ase.Atoms`: The structure with adjusted atomic positions. This is the
46
+ same object as ``structure`` when ``inplace=True``, or a new copy otherwise.
47
+
48
+ Raises:
49
+ RuntimeError: If the minimum distance criterion is not satisfied within
50
+ ``iterations`` steps.
51
+ """
52
+ if not inplace:
53
+ structure = structure.copy()
54
+ if axis is None:
55
+ axis = slice(None)
56
+ for _ in range(iterations):
57
+ neigh = get_neighbors(structure, num_neighbors=1)
58
+ dd = neigh.distances[:, 0]
59
+ if dd.min() >= min_dist:
60
+ break
61
+
62
+ I = dd < min_dist
63
+
64
+ dd_I = dd[I]
65
+ vv = neigh.vecs[I, 0, :].copy()
66
+ # Avoid division by zero for coincident atoms (distance == 0).
67
+ # Assign opposite fallback directions based on atom-index ordering so
68
+ # the two coincident atoms separate rather than move together.
69
+ atom_indices = np.where(I)[0]
70
+ zero_mask = dd_I == 0
71
+ if np.any(zero_mask):
72
+ neighbor_indices = neigh.indices[atom_indices[zero_mask], 0]
73
+ sign = np.where(atom_indices[zero_mask] < neighbor_indices, 1.0, -1.0)
74
+ vv[zero_mask] = sign[:, None] * np.array([1.0, 0.0, 0.0])
75
+ safe_dd = np.where(dd_I > 0, dd_I, 1.0)
76
+ vv /= safe_dd[:, None]
77
+
78
+ disp = np.clip(min_dist - dd[I], 0, step_size)
79
+
80
+ displacement = disp[:, None] * vv # (N_close, 3)
81
+ structure.positions[I, axis] -= displacement[:, axis]
82
+
83
+ else:
84
+ raise RuntimeError(f"repulse did not converge within {iterations} iterations")
85
+
86
+ return structure
87
+
88
+
89
+ def merge(
90
+ structure: "ase.Atoms", cutoff: float = 1.8, iterations: int = 10
91
+ ) -> "ase.Atoms":
92
+ """Merge pairs of atoms that are closer than ``cutoff`` by collapsing each
93
+ pair to their midpoint and deleting one of the two atoms.
94
+
95
+ The operation is applied repeatedly (up to ``iterations`` times) to handle
96
+ cases where a merge creates new close contacts.
97
+
98
+ .. note::
99
+ The structure is modified **in place**. Pass a copy if you need the
100
+ original to remain unchanged.
101
+
102
+ Args:
103
+ structure (:class:`ase.Atoms`):
104
+ Structure to modify.
105
+ cutoff (float):
106
+ Distance threshold in Ångström below which two atoms are
107
+ considered clashing and will be merged. Defaults to ``1.8``.
108
+ iterations (int):
109
+ Maximum number of recursive merge passes. Defaults to ``10``.
110
+
111
+ Returns:
112
+ :class:`ase.Atoms`: The modified structure with clashing atom pairs
113
+ replaced by single atoms at their midpoints.
114
+ """
115
+ neigh = get_neighbors(structure, 1)
116
+ clashing = np.argwhere(neigh.distances[:, 0] < cutoff).ravel()
117
+ if len(clashing) == 0:
118
+ return structure
119
+
120
+ moving = []
121
+ deleting = []
122
+
123
+ for c in clashing:
124
+ if c in deleting:
125
+ continue
126
+
127
+ moving.append(c)
128
+ deleting.append(neigh.indices[c, 0])
129
+
130
+ structure.positions[moving] += neigh.vecs[moving, 0] / 2
131
+ del structure[deleting]
132
+
133
+ if iterations > 0:
134
+ return merge(structure, cutoff=cutoff, iterations=iterations - 1)
135
+ return structure
136
+
137
+
138
+ __all__ = [
139
+ "merge",
140
+ "repulse",
141
+ ]
@@ -0,0 +1,33 @@
1
+ from ._types import (
2
+ SqsResultSplit,
3
+ SqsResult,
4
+ SqsResultInteract,
5
+ SublatticeMode,
6
+ IterationMode,
7
+ Site,
8
+ Element,
9
+ ShellRadii,
10
+ ShellWeights,
11
+ Shell,
12
+ Composition,
13
+ Prec,
14
+ SroParameter,
15
+ )
16
+ from ._interface import sqs_structures
17
+
18
+ __all__ = [
19
+ "Composition",
20
+ "Element",
21
+ "IterationMode",
22
+ "Prec",
23
+ "Shell",
24
+ "ShellRadii",
25
+ "ShellWeights",
26
+ "Site",
27
+ "SroParameter",
28
+ "SqsResult",
29
+ "SqsResultInteract",
30
+ "SqsResultSplit",
31
+ "SublatticeMode",
32
+ "sqs_structures",
33
+ ]