structuretoolkit 0.0.41__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.41 → structuretoolkit-0.0.43}/PKG-INFO +11 -9
- {structuretoolkit-0.0.41 → structuretoolkit-0.0.43}/pyproject.toml +11 -8
- {structuretoolkit-0.0.41 → structuretoolkit-0.0.43}/src/structuretoolkit/_version.py +2 -2
- {structuretoolkit-0.0.41 → structuretoolkit-0.0.43}/src/structuretoolkit/analyse/symmetry.py +22 -7
- {structuretoolkit-0.0.41 → structuretoolkit-0.0.43}/src/structuretoolkit/build/__init__.py +6 -0
- structuretoolkit-0.0.43/src/structuretoolkit/build/geometry.py +141 -0
- structuretoolkit-0.0.43/src/structuretoolkit/build/materialsproject.py +138 -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.41 → structuretoolkit-0.0.43}/src/structuretoolkit/common/pymatgen.py +1 -1
- structuretoolkit-0.0.41/.gitignore +0 -10
- structuretoolkit-0.0.41/src/structuretoolkit/build/sqs.py +0 -236
- {structuretoolkit-0.0.41 → structuretoolkit-0.0.43}/LICENSE +0 -0
- {structuretoolkit-0.0.41 → structuretoolkit-0.0.43}/README.md +0 -0
- {structuretoolkit-0.0.41 → structuretoolkit-0.0.43}/src/structuretoolkit/__init__.py +0 -0
- {structuretoolkit-0.0.41 → structuretoolkit-0.0.43}/src/structuretoolkit/analyse/__init__.py +0 -0
- {structuretoolkit-0.0.41 → structuretoolkit-0.0.43}/src/structuretoolkit/analyse/distance.py +0 -0
- {structuretoolkit-0.0.41 → structuretoolkit-0.0.43}/src/structuretoolkit/analyse/dscribe.py +0 -0
- {structuretoolkit-0.0.41 → structuretoolkit-0.0.43}/src/structuretoolkit/analyse/neighbors.py +0 -0
- {structuretoolkit-0.0.41 → structuretoolkit-0.0.43}/src/structuretoolkit/analyse/phonopy.py +0 -0
- {structuretoolkit-0.0.41 → structuretoolkit-0.0.43}/src/structuretoolkit/analyse/pyscal.py +0 -0
- {structuretoolkit-0.0.41 → structuretoolkit-0.0.43}/src/structuretoolkit/analyse/snap.py +0 -0
- {structuretoolkit-0.0.41 → structuretoolkit-0.0.43}/src/structuretoolkit/analyse/spatial.py +0 -0
- {structuretoolkit-0.0.41 → structuretoolkit-0.0.43}/src/structuretoolkit/analyse/strain.py +0 -0
- {structuretoolkit-0.0.41 → structuretoolkit-0.0.43}/src/structuretoolkit/build/aimsgb.py +0 -0
- {structuretoolkit-0.0.41 → structuretoolkit-0.0.43}/src/structuretoolkit/build/compound.py +0 -0
- {structuretoolkit-0.0.41 → structuretoolkit-0.0.43}/src/structuretoolkit/build/mesh.py +0 -0
- {structuretoolkit-0.0.41 → structuretoolkit-0.0.43}/src/structuretoolkit/build/surface.py +0 -0
- {structuretoolkit-0.0.41 → structuretoolkit-0.0.43}/src/structuretoolkit/common/__init__.py +0 -0
- {structuretoolkit-0.0.41 → structuretoolkit-0.0.43}/src/structuretoolkit/common/error.py +0 -0
- {structuretoolkit-0.0.41 → structuretoolkit-0.0.43}/src/structuretoolkit/common/helper.py +0 -0
- {structuretoolkit-0.0.41 → structuretoolkit-0.0.43}/src/structuretoolkit/common/phonopy.py +0 -0
- {structuretoolkit-0.0.41 → structuretoolkit-0.0.43}/src/structuretoolkit/common/pyscal.py +0 -0
- {structuretoolkit-0.0.41 → 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,35 +41,37 @@ 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
|
-
Requires-Dist: scipy<=1.17.
|
|
51
|
+
Requires-Dist: scipy<=1.17.1,>=1.15.0
|
|
53
52
|
Provides-Extra: clusters
|
|
54
53
|
Requires-Dist: scikit-learn==1.8.0; extra == 'clusters'
|
|
55
54
|
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'
|
|
61
|
+
Provides-Extra: mp-api
|
|
62
|
+
Requires-Dist: mp-api==0.45.15; extra == 'mp-api'
|
|
63
|
+
Requires-Dist: pymatgen==2026.3.23; extra == 'mp-api'
|
|
62
64
|
Provides-Extra: nglview
|
|
63
65
|
Requires-Dist: nglview<=4.0.1,>=2.7.7; extra == 'nglview'
|
|
64
66
|
Provides-Extra: phonopy
|
|
65
|
-
Requires-Dist: phonopy<=3.
|
|
67
|
+
Requires-Dist: phonopy<=3.5.1,>=2.21.2; extra == 'phonopy'
|
|
66
68
|
Requires-Dist: spglib==2.7.0; extra == 'phonopy'
|
|
67
69
|
Provides-Extra: plotly
|
|
68
|
-
Requires-Dist: plotly<=6.6.0,>=
|
|
70
|
+
Requires-Dist: plotly<=6.6.0,>=6.0.0; extra == 'plotly'
|
|
69
71
|
Provides-Extra: pyscal
|
|
70
72
|
Requires-Dist: pyscal3<=3.3.2,>=3.2.5; extra == 'pyscal'
|
|
71
73
|
Provides-Extra: surface
|
|
72
|
-
Requires-Dist: pymatgen
|
|
74
|
+
Requires-Dist: pymatgen==2026.3.23; extra == 'surface'
|
|
73
75
|
Requires-Dist: spglib==2.7.0; extra == 'surface'
|
|
74
76
|
Provides-Extra: symmetry
|
|
75
77
|
Requires-Dist: spglib==2.7.0; extra == 'symmetry'
|
|
@@ -18,15 +18,14 @@ 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
|
-
"scipy>=1.15.0,<=1.17.
|
|
28
|
+
"scipy>=1.15.0,<=1.17.1",
|
|
30
29
|
]
|
|
31
30
|
dynamic = ["version"]
|
|
32
31
|
|
|
@@ -39,22 +38,26 @@ 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
|
]
|
|
57
|
+
mp-api = [
|
|
58
|
+
"mp-api==0.45.15",
|
|
59
|
+
"pymatgen==2026.3.23",
|
|
60
|
+
]
|
|
58
61
|
|
|
59
62
|
[tool.ruff]
|
|
60
63
|
exclude = [".ci_support", "tests", "setup.py", "_version.py"]
|
|
@@ -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.41 → 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(
|
|
@@ -2,6 +2,10 @@ from structuretoolkit.build.aimsgb import get_grainboundary_info, grainboundary
|
|
|
2
2
|
from structuretoolkit.build.compound import B2, C14, C15, C36, D03
|
|
3
3
|
from structuretoolkit.build.mesh import create_mesh
|
|
4
4
|
from structuretoolkit.build.sqs import sqs_structures
|
|
5
|
+
from structuretoolkit.build.materialsproject import (
|
|
6
|
+
search as materialsproject_search,
|
|
7
|
+
by_id as materialsproject_by_id,
|
|
8
|
+
)
|
|
5
9
|
from structuretoolkit.build.surface import (
|
|
6
10
|
get_high_index_surface_info,
|
|
7
11
|
high_index_surface,
|
|
@@ -19,4 +23,6 @@ __all__ = [
|
|
|
19
23
|
"sqs_structures",
|
|
20
24
|
"get_high_index_surface_info",
|
|
21
25
|
"high_index_surface",
|
|
26
|
+
"materialsproject_search",
|
|
27
|
+
"materialsproject_by_id",
|
|
22
28
|
]
|
|
@@ -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,138 @@
|
|
|
1
|
+
from typing import Any, Iterable
|
|
2
|
+
from collections.abc import Generator
|
|
3
|
+
from ase.atoms import Atoms
|
|
4
|
+
from structuretoolkit.common.pymatgen import pymatgen_to_ase
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def search(
|
|
8
|
+
chemsys: str | list[str], fields: Iterable[str] = (), api_key=None, **kwargs
|
|
9
|
+
) -> Generator[dict[str, Any], None, None]:
|
|
10
|
+
"""
|
|
11
|
+
Search the database for all structures matching the given query.
|
|
12
|
+
|
|
13
|
+
Note that `chemsys` takes distinct values for unaries, binaries and so! A query with `chemsys=["Fe", "O"]` will
|
|
14
|
+
return iron and oxygen structures but not iron oxide. Similarly `chemsys=["Fe-O"]` will
|
|
15
|
+
not return unary structures.
|
|
16
|
+
|
|
17
|
+
All keyword arguments for filtering from the original API are supported. See the
|
|
18
|
+
`original docs <https://docs.materialsproject.org/downloading-data/using-the-api>`_ for them.
|
|
19
|
+
|
|
20
|
+
Search for all iron structures:
|
|
21
|
+
|
|
22
|
+
>>> irons = structuretoolkit.build.materialsproject.search("Fe")
|
|
23
|
+
>>> len(list(irons))
|
|
24
|
+
10
|
|
25
|
+
|
|
26
|
+
Search for all structures with Al, Li that are on the T=0 convex hull:
|
|
27
|
+
|
|
28
|
+
>>> alli = structuretoolkit.build.materialsproject.search(['Al', 'Li', 'Al-Li'], is_stable=True)
|
|
29
|
+
>>> len(list(alli))
|
|
30
|
+
6
|
|
31
|
+
|
|
32
|
+
Usage is only possible with an API key obtained from the Materials Project. To do this, create an account with
|
|
33
|
+
them, login and access `this webpage <https://next-gen.materialsproject.org/api#api-key>`.
|
|
34
|
+
|
|
35
|
+
Once you have a key, either pass it as the `api_key` parameter or export an
|
|
36
|
+
environment variable, called `MP_API_KEY`, in your shell setup.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
chemsys (str, list of str): confine search to given elements; either an element symbol or multiple element
|
|
40
|
+
symbols separated by dashes; if a list of strings is given return structures matching either of them
|
|
41
|
+
fields (iterable of str): pass as `fields` to :meth:`mp_api.MPRester.summary.search` to request additional
|
|
42
|
+
database entries beyond the structure
|
|
43
|
+
api_key (str, optional): if your API key is not exported in the environment flag MP_API_KEY, pass it here
|
|
44
|
+
**kwargs: passed verbatim to :meth:`mp_api.MPRester.summary.search` to further filter the results
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
list of dict: one dictionary for each search results with at least keys
|
|
48
|
+
'material_id': database key of the hit
|
|
49
|
+
'structure': ASE atoms object
|
|
50
|
+
plus any requested via `fields`.
|
|
51
|
+
"""
|
|
52
|
+
from mp_api.client import MPRester
|
|
53
|
+
|
|
54
|
+
rest_kwargs = {
|
|
55
|
+
"use_document_model": False, # returns results as dictionaries
|
|
56
|
+
"include_user_agent": True, # send some additional software version info to MP
|
|
57
|
+
}
|
|
58
|
+
if api_key is not None:
|
|
59
|
+
rest_kwargs["api_key"] = api_key
|
|
60
|
+
with MPRester(**rest_kwargs) as mpr:
|
|
61
|
+
results = mpr.summary.search(
|
|
62
|
+
chemsys=chemsys,
|
|
63
|
+
**kwargs,
|
|
64
|
+
fields=list(fields) + ["structure", "material_id"],
|
|
65
|
+
)
|
|
66
|
+
for r in results:
|
|
67
|
+
if "structure" in r:
|
|
68
|
+
r["structure"] = pymatgen_to_ase(r["structure"])
|
|
69
|
+
yield r
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def by_id(
|
|
73
|
+
material_id: str | int,
|
|
74
|
+
final: bool = True,
|
|
75
|
+
conventional_unit_cell: bool = False,
|
|
76
|
+
api_key=None,
|
|
77
|
+
) -> Atoms | list[Atoms]:
|
|
78
|
+
"""
|
|
79
|
+
Retrieve a structure by material id.
|
|
80
|
+
|
|
81
|
+
This is how you would ask for the iron ground state:
|
|
82
|
+
|
|
83
|
+
>>> structuretoolkit.build.materialsproject.by_id('mp-13')
|
|
84
|
+
Fe: [0. 0. 0.]
|
|
85
|
+
tags:
|
|
86
|
+
spin: [(0: 2.214)]
|
|
87
|
+
pbc: [ True True True]
|
|
88
|
+
cell:
|
|
89
|
+
Cell([[2.318956, 0.000185, -0.819712], [-1.159251, 2.008215, -0.819524], [2.5e-05, 0.000273, 2.459206]])
|
|
90
|
+
|
|
91
|
+
Usage is only possible with an API key obtained from the Materials Project. To do this, create an account with
|
|
92
|
+
them, login and access `this webpage <https://next-gen.materialsproject.org/api#api-key>`.
|
|
93
|
+
|
|
94
|
+
Once you have a key, either pass it as the `api_key` parameter or export an
|
|
95
|
+
environment variable, called `MP_API_KEY`, in your shell setup.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
material_id (str): the id assigned to a structure by the materials project
|
|
99
|
+
api_key (str, optional): if your API key is not exported in the environment flag MP_API_KEY, pass it here
|
|
100
|
+
final (bool, optional): if set to False, returns the list of initial structures,
|
|
101
|
+
else returns the final structure. (Default is True)
|
|
102
|
+
conventional_unit_cell (bool, optional): if set to True, returns the standard conventional unit cell.
|
|
103
|
+
(Default is False)
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
:class:`~.Atoms`: requested final structure if final is True
|
|
107
|
+
list of :class:~.Atoms`: a list of initial (pre-relaxation) structures if final is False
|
|
108
|
+
|
|
109
|
+
Raises:
|
|
110
|
+
ValueError: material id does not exist
|
|
111
|
+
"""
|
|
112
|
+
from mp_api.client import MPRester
|
|
113
|
+
|
|
114
|
+
rest_kwargs = {
|
|
115
|
+
"include_user_agent": True, # send some additional software version info to MP
|
|
116
|
+
}
|
|
117
|
+
if api_key is not None:
|
|
118
|
+
rest_kwargs["api_key"] = api_key
|
|
119
|
+
with MPRester(**rest_kwargs) as mpr:
|
|
120
|
+
if final:
|
|
121
|
+
return pymatgen_to_ase(
|
|
122
|
+
mpr.get_structure_by_material_id(
|
|
123
|
+
material_id=material_id,
|
|
124
|
+
final=final,
|
|
125
|
+
conventional_unit_cell=conventional_unit_cell,
|
|
126
|
+
)
|
|
127
|
+
)
|
|
128
|
+
else:
|
|
129
|
+
return [
|
|
130
|
+
pymatgen_to_ase(mpr_structure)
|
|
131
|
+
for mpr_structure in (
|
|
132
|
+
mpr.get_structure_by_material_id(
|
|
133
|
+
material_id=material_id,
|
|
134
|
+
final=final,
|
|
135
|
+
conventional_unit_cell=conventional_unit_cell,
|
|
136
|
+
)
|
|
137
|
+
)
|
|
138
|
+
]
|