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.
- structuretoolkit-0.0.43/.gitignore +217 -0
- {structuretoolkit-0.0.42 → structuretoolkit-0.0.43}/PKG-INFO +8 -9
- {structuretoolkit-0.0.42 → structuretoolkit-0.0.43}/pyproject.toml +7 -8
- {structuretoolkit-0.0.42 → structuretoolkit-0.0.43}/src/structuretoolkit/_version.py +2 -2
- {structuretoolkit-0.0.42 → structuretoolkit-0.0.43}/src/structuretoolkit/analyse/symmetry.py +22 -7
- structuretoolkit-0.0.43/src/structuretoolkit/build/geometry.py +141 -0
- structuretoolkit-0.0.43/src/structuretoolkit/build/sqs/__init__.py +33 -0
- structuretoolkit-0.0.43/src/structuretoolkit/build/sqs/_interface.py +310 -0
- structuretoolkit-0.0.43/src/structuretoolkit/build/sqs/_types.py +195 -0
- {structuretoolkit-0.0.42 → structuretoolkit-0.0.43}/src/structuretoolkit/common/pymatgen.py +1 -1
- structuretoolkit-0.0.42/.gitignore +0 -10
- structuretoolkit-0.0.42/src/structuretoolkit/build/sqs.py +0 -236
- {structuretoolkit-0.0.42 → structuretoolkit-0.0.43}/LICENSE +0 -0
- {structuretoolkit-0.0.42 → structuretoolkit-0.0.43}/README.md +0 -0
- {structuretoolkit-0.0.42 → structuretoolkit-0.0.43}/src/structuretoolkit/__init__.py +0 -0
- {structuretoolkit-0.0.42 → structuretoolkit-0.0.43}/src/structuretoolkit/analyse/__init__.py +0 -0
- {structuretoolkit-0.0.42 → structuretoolkit-0.0.43}/src/structuretoolkit/analyse/distance.py +0 -0
- {structuretoolkit-0.0.42 → structuretoolkit-0.0.43}/src/structuretoolkit/analyse/dscribe.py +0 -0
- {structuretoolkit-0.0.42 → structuretoolkit-0.0.43}/src/structuretoolkit/analyse/neighbors.py +0 -0
- {structuretoolkit-0.0.42 → structuretoolkit-0.0.43}/src/structuretoolkit/analyse/phonopy.py +0 -0
- {structuretoolkit-0.0.42 → structuretoolkit-0.0.43}/src/structuretoolkit/analyse/pyscal.py +0 -0
- {structuretoolkit-0.0.42 → structuretoolkit-0.0.43}/src/structuretoolkit/analyse/snap.py +0 -0
- {structuretoolkit-0.0.42 → structuretoolkit-0.0.43}/src/structuretoolkit/analyse/spatial.py +0 -0
- {structuretoolkit-0.0.42 → structuretoolkit-0.0.43}/src/structuretoolkit/analyse/strain.py +0 -0
- {structuretoolkit-0.0.42 → structuretoolkit-0.0.43}/src/structuretoolkit/build/__init__.py +0 -0
- {structuretoolkit-0.0.42 → structuretoolkit-0.0.43}/src/structuretoolkit/build/aimsgb.py +0 -0
- {structuretoolkit-0.0.42 → structuretoolkit-0.0.43}/src/structuretoolkit/build/compound.py +0 -0
- {structuretoolkit-0.0.42 → structuretoolkit-0.0.43}/src/structuretoolkit/build/materialsproject.py +0 -0
- {structuretoolkit-0.0.42 → structuretoolkit-0.0.43}/src/structuretoolkit/build/mesh.py +0 -0
- {structuretoolkit-0.0.42 → structuretoolkit-0.0.43}/src/structuretoolkit/build/surface.py +0 -0
- {structuretoolkit-0.0.42 → structuretoolkit-0.0.43}/src/structuretoolkit/common/__init__.py +0 -0
- {structuretoolkit-0.0.42 → structuretoolkit-0.0.43}/src/structuretoolkit/common/error.py +0 -0
- {structuretoolkit-0.0.42 → structuretoolkit-0.0.43}/src/structuretoolkit/common/helper.py +0 -0
- {structuretoolkit-0.0.42 → structuretoolkit-0.0.43}/src/structuretoolkit/common/phonopy.py +0 -0
- {structuretoolkit-0.0.42 → structuretoolkit-0.0.43}/src/structuretoolkit/common/pyscal.py +0 -0
- {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.
|
|
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.
|
|
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
|
|
58
|
+
Requires-Dist: pymatgen==2026.3.23; extra == 'grainboundary'
|
|
60
59
|
Provides-Extra: matplotlib
|
|
61
|
-
Requires-Dist: matplotlib==3.10.
|
|
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
|
|
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.
|
|
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,>=
|
|
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
|
|
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.
|
|
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
|
|
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.
|
|
47
|
-
plotly = ["plotly>=
|
|
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
|
|
51
|
+
"pymatgen==2026.3.23",
|
|
53
52
|
]
|
|
54
53
|
phonopy = [
|
|
55
|
-
"phonopy>=2.
|
|
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
|
|
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.
|
|
32
|
-
__version_tuple__ = version_tuple = (0, 0,
|
|
31
|
+
__version__ = version = '0.0.43'
|
|
32
|
+
__version_tuple__ = version_tuple = (0, 0, 43)
|
|
33
33
|
|
|
34
34
|
__commit_id__ = commit_id = None
|
{structuretoolkit-0.0.42 → structuretoolkit-0.0.43}/src/structuretoolkit/analyse/symmetry.py
RENAMED
|
@@ -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,
|
|
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
|
-
|
|
422
|
-
new_structure
|
|
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
|
+
]
|