AtomVoxelizer 0.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.
- atomvoxelizer-0.1.0/.readthedocs.yaml +17 -0
- atomvoxelizer-0.1.0/MANIFEST.in +8 -0
- atomvoxelizer-0.1.0/PKG-INFO +174 -0
- atomvoxelizer-0.1.0/README.md +121 -0
- atomvoxelizer-0.1.0/benchmarks/benchmark_backends.py +149 -0
- atomvoxelizer-0.1.0/benchmarks/benchmark_structures.py +41 -0
- atomvoxelizer-0.1.0/docs/source/analysis.rst +113 -0
- atomvoxelizer-0.1.0/docs/source/api.rst +17 -0
- atomvoxelizer-0.1.0/docs/source/backends.rst +64 -0
- atomvoxelizer-0.1.0/docs/source/conf.py +24 -0
- atomvoxelizer-0.1.0/docs/source/index.rst +14 -0
- atomvoxelizer-0.1.0/docs/source/publishing.rst +53 -0
- atomvoxelizer-0.1.0/docs/source/usage.rst +43 -0
- atomvoxelizer-0.1.0/examples/BEA.cif +68 -0
- atomvoxelizer-0.1.0/examples/MWW.cif +79 -0
- atomvoxelizer-0.1.0/examples/zeolite_analysis.py +116 -0
- atomvoxelizer-0.1.0/examples/zeolite_voxel.py +230 -0
- atomvoxelizer-0.1.0/pyproject.toml +81 -0
- atomvoxelizer-0.1.0/setup.cfg +4 -0
- atomvoxelizer-0.1.0/src/AtomVoxelizer.egg-info/PKG-INFO +174 -0
- atomvoxelizer-0.1.0/src/AtomVoxelizer.egg-info/SOURCES.txt +33 -0
- atomvoxelizer-0.1.0/src/AtomVoxelizer.egg-info/dependency_links.txt +1 -0
- atomvoxelizer-0.1.0/src/AtomVoxelizer.egg-info/requires.txt +40 -0
- atomvoxelizer-0.1.0/src/AtomVoxelizer.egg-info/top_level.txt +1 -0
- atomvoxelizer-0.1.0/src/atomvoxelizer/__init__.py +30 -0
- atomvoxelizer-0.1.0/src/atomvoxelizer/analysis.py +207 -0
- atomvoxelizer-0.1.0/src/atomvoxelizer/cupy_backend.py +88 -0
- atomvoxelizer-0.1.0/src/atomvoxelizer/numba_backend.py +135 -0
- atomvoxelizer-0.1.0/src/atomvoxelizer/taichi_backend.py +157 -0
- atomvoxelizer-0.1.0/src/atomvoxelizer/voxelgrid.py +308 -0
- atomvoxelizer-0.1.0/tests/test_analysis.py +74 -0
- atomvoxelizer-0.1.0/tests/test_backends.py +60 -0
- atomvoxelizer-0.1.0/tests/test_imports.py +13 -0
- atomvoxelizer-0.1.0/tests/test_voxelgrid_numpy.py +96 -0
- atomvoxelizer-0.1.0/tests/test_zeolite_analysis_example.py +39 -0
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: AtomVoxelizer
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Periodic atom-centered voxel grids for atomistic structures.
|
|
5
|
+
Author: AtomVoxelizer contributors
|
|
6
|
+
Project-URL: Homepage, https://gitlab.com/tgmaxson/atomvoxelizer
|
|
7
|
+
Project-URL: Documentation, https://atomvoxelizer.readthedocs.io/
|
|
8
|
+
Project-URL: Repository, https://gitlab.com/tgmaxson/atomvoxelizer
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Intended Audience :: Science/Research
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Topic :: Scientific/Engineering :: Chemistry
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
20
|
+
Requires-Python: >=3.9
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
Requires-Dist: matplotlib
|
|
23
|
+
Requires-Dist: numpy
|
|
24
|
+
Provides-Extra: numba
|
|
25
|
+
Requires-Dist: numba; extra == "numba"
|
|
26
|
+
Provides-Extra: cupy
|
|
27
|
+
Requires-Dist: cupy; extra == "cupy"
|
|
28
|
+
Requires-Dist: numba; extra == "cupy"
|
|
29
|
+
Provides-Extra: taichi
|
|
30
|
+
Requires-Dist: taichi; extra == "taichi"
|
|
31
|
+
Provides-Extra: analysis
|
|
32
|
+
Requires-Dist: scikit-image; extra == "analysis"
|
|
33
|
+
Provides-Extra: examples
|
|
34
|
+
Requires-Dist: ase; extra == "examples"
|
|
35
|
+
Requires-Dist: requests; extra == "examples"
|
|
36
|
+
Provides-Extra: docs
|
|
37
|
+
Requires-Dist: sphinx; extra == "docs"
|
|
38
|
+
Provides-Extra: dev
|
|
39
|
+
Requires-Dist: ase; extra == "dev"
|
|
40
|
+
Requires-Dist: numba; extra == "dev"
|
|
41
|
+
Requires-Dist: pytest; extra == "dev"
|
|
42
|
+
Requires-Dist: requests; extra == "dev"
|
|
43
|
+
Requires-Dist: scikit-image; extra == "dev"
|
|
44
|
+
Requires-Dist: sphinx; extra == "dev"
|
|
45
|
+
Requires-Dist: taichi; extra == "dev"
|
|
46
|
+
Provides-Extra: bench
|
|
47
|
+
Requires-Dist: ase; extra == "bench"
|
|
48
|
+
Requires-Dist: numba; extra == "bench"
|
|
49
|
+
Requires-Dist: taichi; extra == "bench"
|
|
50
|
+
Provides-Extra: publish
|
|
51
|
+
Requires-Dist: build; extra == "publish"
|
|
52
|
+
Requires-Dist: twine; extra == "publish"
|
|
53
|
+
|
|
54
|
+
# AtomVoxelizer
|
|
55
|
+
|
|
56
|
+
AtomVoxelizer builds periodic atom-centered voxel grids for atomistic structures.
|
|
57
|
+
The core `VoxelGrid` class stores a 3D NumPy grid over a periodic cell and provides
|
|
58
|
+
helpers for adding, setting, scaling, sampling, and plotting spherical regions.
|
|
59
|
+
|
|
60
|
+
## Installation
|
|
61
|
+
|
|
62
|
+
Install from this repository:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
pip install .
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Install optional acceleration backends with extras:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
pip install ".[numba]"
|
|
72
|
+
pip install ".[taichi]"
|
|
73
|
+
pip install ".[cupy]"
|
|
74
|
+
pip install ".[analysis]"
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
`VoxelGrid` is always the NumPy backend. Optional acceleration backends are
|
|
78
|
+
explicit: `VoxelGridNumba`, `VoxelGridTaichi`, and `VoxelGridCuPy`.
|
|
79
|
+
`VoxelGridAnalysis` provides connected-volume and marching-cubes surface-area
|
|
80
|
+
analysis when the `analysis` extra is installed.
|
|
81
|
+
|
|
82
|
+
For development, examples, tests, and documentation:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
pip install -e ".[dev,examples]"
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Basic Usage
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
import numpy as np
|
|
92
|
+
|
|
93
|
+
from atomvoxelizer import VoxelGrid
|
|
94
|
+
|
|
95
|
+
cell = np.eye(3) * 10.0
|
|
96
|
+
grid = VoxelGrid(cell=cell, resolution=0.25)
|
|
97
|
+
|
|
98
|
+
grid.add_sphere(center=np.array([5.0, 5.0, 5.0]), radius=1.0, value=1.0)
|
|
99
|
+
grid.set_sphere(center=np.array([2.0, 2.0, 2.0]), radius=0.5, value=-1.0)
|
|
100
|
+
grid.clamp_grid(min_val=-1.0, max_val=1.0)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Zeolite Example
|
|
104
|
+
|
|
105
|
+
The zeolite example and CIF files live in `examples/`.
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
pip install -e ".[examples]"
|
|
109
|
+
python examples/zeolite_voxel.py BEA
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
The script reads a framework CIF, builds voxel grids at several resolutions, plots
|
|
113
|
+
middle XZ slices, benchmarks supercell scaling, and opens a 3D scatter plot.
|
|
114
|
+
|
|
115
|
+
The analysis example estimates pore volume and internal surface area:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
pip install -e ".[examples,analysis]"
|
|
119
|
+
python examples/zeolite_analysis.py BEA --resolution 0.25
|
|
120
|
+
python examples/zeolite_analysis.py BEA --convergence 1.0 0.75 0.5 --plot bea_convergence.png
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Tests and Benchmarks
|
|
124
|
+
|
|
125
|
+
Run the correctness tests with:
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
pytest
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Run the backend benchmark with:
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
python benchmarks/benchmark_backends.py --backends numpy numba taichi cupy
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Run the built-in structure benchmarks for a zeolite and a roughly 1000 atom Wulff
|
|
138
|
+
construction with:
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
python benchmarks/benchmark_structures.py
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Backends whose optional dependencies are not installed are reported as missing.
|
|
145
|
+
|
|
146
|
+
## Documentation
|
|
147
|
+
|
|
148
|
+
Documentation is scaffolded with Sphinx for Read the Docs.
|
|
149
|
+
|
|
150
|
+
Build it locally with:
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
pip install -e ".[docs]"
|
|
154
|
+
sphinx-build -b html docs/source docs/build/html
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Read the Docs can use `.readthedocs.yaml` directly.
|
|
158
|
+
|
|
159
|
+
## Publishing
|
|
160
|
+
|
|
161
|
+
Build and check PyPI artifacts with:
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
pip install -e ".[publish]"
|
|
165
|
+
python -m build
|
|
166
|
+
twine check dist/*
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Upload to TestPyPI first, then PyPI:
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
twine upload --repository testpypi dist/*
|
|
173
|
+
twine upload dist/*
|
|
174
|
+
```
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# AtomVoxelizer
|
|
2
|
+
|
|
3
|
+
AtomVoxelizer builds periodic atom-centered voxel grids for atomistic structures.
|
|
4
|
+
The core `VoxelGrid` class stores a 3D NumPy grid over a periodic cell and provides
|
|
5
|
+
helpers for adding, setting, scaling, sampling, and plotting spherical regions.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
Install from this repository:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pip install .
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Install optional acceleration backends with extras:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pip install ".[numba]"
|
|
19
|
+
pip install ".[taichi]"
|
|
20
|
+
pip install ".[cupy]"
|
|
21
|
+
pip install ".[analysis]"
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
`VoxelGrid` is always the NumPy backend. Optional acceleration backends are
|
|
25
|
+
explicit: `VoxelGridNumba`, `VoxelGridTaichi`, and `VoxelGridCuPy`.
|
|
26
|
+
`VoxelGridAnalysis` provides connected-volume and marching-cubes surface-area
|
|
27
|
+
analysis when the `analysis` extra is installed.
|
|
28
|
+
|
|
29
|
+
For development, examples, tests, and documentation:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install -e ".[dev,examples]"
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Basic Usage
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
import numpy as np
|
|
39
|
+
|
|
40
|
+
from atomvoxelizer import VoxelGrid
|
|
41
|
+
|
|
42
|
+
cell = np.eye(3) * 10.0
|
|
43
|
+
grid = VoxelGrid(cell=cell, resolution=0.25)
|
|
44
|
+
|
|
45
|
+
grid.add_sphere(center=np.array([5.0, 5.0, 5.0]), radius=1.0, value=1.0)
|
|
46
|
+
grid.set_sphere(center=np.array([2.0, 2.0, 2.0]), radius=0.5, value=-1.0)
|
|
47
|
+
grid.clamp_grid(min_val=-1.0, max_val=1.0)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Zeolite Example
|
|
51
|
+
|
|
52
|
+
The zeolite example and CIF files live in `examples/`.
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
pip install -e ".[examples]"
|
|
56
|
+
python examples/zeolite_voxel.py BEA
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
The script reads a framework CIF, builds voxel grids at several resolutions, plots
|
|
60
|
+
middle XZ slices, benchmarks supercell scaling, and opens a 3D scatter plot.
|
|
61
|
+
|
|
62
|
+
The analysis example estimates pore volume and internal surface area:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
pip install -e ".[examples,analysis]"
|
|
66
|
+
python examples/zeolite_analysis.py BEA --resolution 0.25
|
|
67
|
+
python examples/zeolite_analysis.py BEA --convergence 1.0 0.75 0.5 --plot bea_convergence.png
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Tests and Benchmarks
|
|
71
|
+
|
|
72
|
+
Run the correctness tests with:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
pytest
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Run the backend benchmark with:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
python benchmarks/benchmark_backends.py --backends numpy numba taichi cupy
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Run the built-in structure benchmarks for a zeolite and a roughly 1000 atom Wulff
|
|
85
|
+
construction with:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
python benchmarks/benchmark_structures.py
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Backends whose optional dependencies are not installed are reported as missing.
|
|
92
|
+
|
|
93
|
+
## Documentation
|
|
94
|
+
|
|
95
|
+
Documentation is scaffolded with Sphinx for Read the Docs.
|
|
96
|
+
|
|
97
|
+
Build it locally with:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
pip install -e ".[docs]"
|
|
101
|
+
sphinx-build -b html docs/source docs/build/html
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Read the Docs can use `.readthedocs.yaml` directly.
|
|
105
|
+
|
|
106
|
+
## Publishing
|
|
107
|
+
|
|
108
|
+
Build and check PyPI artifacts with:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
pip install -e ".[publish]"
|
|
112
|
+
python -m build
|
|
113
|
+
twine check dist/*
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Upload to TestPyPI first, then PyPI:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
twine upload --repository testpypi dist/*
|
|
120
|
+
twine upload dist/*
|
|
121
|
+
```
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import importlib
|
|
5
|
+
import time
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
from ase.cluster import wulff_construction
|
|
10
|
+
from ase.data import covalent_radii
|
|
11
|
+
from ase.io import read
|
|
12
|
+
|
|
13
|
+
from atomvoxelizer import VoxelGrid
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
ROOT = Path(__file__).resolve().parents[1]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def load_backend(name):
|
|
20
|
+
if name == "numpy":
|
|
21
|
+
return VoxelGrid
|
|
22
|
+
if name == "numba":
|
|
23
|
+
module = importlib.import_module("atomvoxelizer.numba_backend")
|
|
24
|
+
return module.VoxelGridNumba
|
|
25
|
+
if name == "taichi":
|
|
26
|
+
module = importlib.import_module("atomvoxelizer.taichi_backend")
|
|
27
|
+
return module.VoxelGridTaichi
|
|
28
|
+
if name == "cupy":
|
|
29
|
+
module = importlib.import_module("atomvoxelizer.cupy_backend")
|
|
30
|
+
return module.VoxelGridCuPy
|
|
31
|
+
raise ValueError(f"Unknown backend: {name}")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def make_synthetic_workload(cell_length, atom_count, seed):
|
|
35
|
+
rng = np.random.default_rng(seed)
|
|
36
|
+
centers = rng.random((atom_count, 3)) * cell_length
|
|
37
|
+
radii = rng.choice(np.array([0.55, 0.75, 1.0], dtype=np.float64), size=atom_count)
|
|
38
|
+
return np.eye(3) * cell_length, centers, radii
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def make_zeolite_workload(framework, radius_scale):
|
|
42
|
+
atoms = read(ROOT / "examples" / f"{framework.upper()}.cif")
|
|
43
|
+
centers = atoms.get_positions()
|
|
44
|
+
radii = np.array([covalent_radii[atom.number] * radius_scale for atom in atoms], dtype=np.float64)
|
|
45
|
+
return atoms.cell.array, centers, radii
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def make_wulff_workload(size, radius_scale, padding):
|
|
49
|
+
atoms = wulff_construction(
|
|
50
|
+
"Pt",
|
|
51
|
+
surfaces=[(1, 0, 0), (1, 1, 0), (1, 1, 1)],
|
|
52
|
+
energies=[1.0, 1.08, 0.92],
|
|
53
|
+
size=size,
|
|
54
|
+
structure="fcc",
|
|
55
|
+
latticeconstant=3.92,
|
|
56
|
+
rounding="closest",
|
|
57
|
+
)
|
|
58
|
+
positions = atoms.get_positions()
|
|
59
|
+
positions -= positions.min(axis=0)
|
|
60
|
+
positions += padding
|
|
61
|
+
lengths = positions.max(axis=0) + padding
|
|
62
|
+
radii = np.array([covalent_radii[atom.number] * radius_scale for atom in atoms], dtype=np.float64)
|
|
63
|
+
return np.diag(lengths), positions, radii
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def make_workload(args):
|
|
67
|
+
if args.workload == "synthetic":
|
|
68
|
+
return make_synthetic_workload(args.cell_length, args.atoms, args.seed)
|
|
69
|
+
if args.workload == "zeolite":
|
|
70
|
+
return make_zeolite_workload(args.framework, args.radius_scale)
|
|
71
|
+
if args.workload == "wulff":
|
|
72
|
+
return make_wulff_workload(args.wulff_size, args.radius_scale, args.padding)
|
|
73
|
+
raise ValueError(f"Unknown workload: {args.workload}")
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def run_once(cls, cell, resolution, centers, radii):
|
|
77
|
+
grid = cls(cell=cell, resolution=resolution)
|
|
78
|
+
grid.add_spheres(centers, radii, value=1.0)
|
|
79
|
+
grid.set_spheres(centers, radii * 0.5, value=-1.0)
|
|
80
|
+
grid.clamp_grid(-1.0, 1.0)
|
|
81
|
+
synchronize = getattr(grid, "synchronize", None)
|
|
82
|
+
if synchronize is not None:
|
|
83
|
+
synchronize()
|
|
84
|
+
return grid
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def time_backend(cls, cell, resolution, centers, radii, repeats):
|
|
88
|
+
run_once(cls, cell, resolution, centers[: min(2, len(centers))], radii[: min(2, len(radii))])
|
|
89
|
+
times = []
|
|
90
|
+
grid = None
|
|
91
|
+
for _ in range(repeats):
|
|
92
|
+
start = time.perf_counter()
|
|
93
|
+
grid = run_once(cls, cell, resolution, centers, radii)
|
|
94
|
+
times.append(time.perf_counter() - start)
|
|
95
|
+
return np.array(times), grid
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def consistency_summary(values, reference):
|
|
99
|
+
occupied = int(np.count_nonzero(values))
|
|
100
|
+
total = float(values.sum())
|
|
101
|
+
if reference is None:
|
|
102
|
+
return occupied, total, 0.0
|
|
103
|
+
return occupied, total, float(np.max(np.abs(values - reference)))
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def main():
|
|
107
|
+
parser = argparse.ArgumentParser(description="Benchmark AtomVoxelizer backends.")
|
|
108
|
+
parser.add_argument("--backends", nargs="+", default=["numpy", "numba", "taichi", "cupy"])
|
|
109
|
+
parser.add_argument("--workload", choices=["synthetic", "zeolite", "wulff"], default="zeolite")
|
|
110
|
+
parser.add_argument("--resolution", type=float, default=0.25)
|
|
111
|
+
parser.add_argument("--repeats", type=int, default=3)
|
|
112
|
+
parser.add_argument("--radius-scale", type=float, default=1.0)
|
|
113
|
+
parser.add_argument("--framework", default="BEA")
|
|
114
|
+
parser.add_argument("--wulff-size", type=int, default=1000)
|
|
115
|
+
parser.add_argument("--padding", type=float, default=5.0)
|
|
116
|
+
parser.add_argument("--cell-length", type=float, default=32.0)
|
|
117
|
+
parser.add_argument("--atoms", type=int, default=512)
|
|
118
|
+
parser.add_argument("--seed", type=int, default=123)
|
|
119
|
+
args = parser.parse_args()
|
|
120
|
+
|
|
121
|
+
cell, centers, radii = make_workload(args)
|
|
122
|
+
reference = None
|
|
123
|
+
|
|
124
|
+
print(
|
|
125
|
+
"workload,backend,backend_name,atoms,gpts,best_s,mean_s,std_s,"
|
|
126
|
+
"occupied_voxels,value_sum,max_abs_diff"
|
|
127
|
+
)
|
|
128
|
+
for backend in args.backends:
|
|
129
|
+
try:
|
|
130
|
+
cls = load_backend(backend)
|
|
131
|
+
times, grid = time_backend(cls, cell, args.resolution, centers, radii, args.repeats)
|
|
132
|
+
except ImportError as exc:
|
|
133
|
+
print(f"{args.workload},{backend},missing,{len(centers)},(),nan,nan,nan,0,nan,nan # {exc}")
|
|
134
|
+
continue
|
|
135
|
+
|
|
136
|
+
values = grid.to_numpy()
|
|
137
|
+
if reference is None:
|
|
138
|
+
reference = values
|
|
139
|
+
occupied, total, max_abs_diff = consistency_summary(values, reference)
|
|
140
|
+
print(
|
|
141
|
+
f"{args.workload},{backend},{grid.backend_name},{len(centers)},"
|
|
142
|
+
f"{tuple(int(x) for x in grid.gpts)},{times.min():.6f},{times.mean():.6f},"
|
|
143
|
+
f"{times.std():.6f},{occupied},{total:.6f},{max_abs_diff:.6g}"
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
if __name__ == "__main__":
|
|
148
|
+
main()
|
|
149
|
+
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import subprocess
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def main():
|
|
8
|
+
commands = [
|
|
9
|
+
[
|
|
10
|
+
sys.executable,
|
|
11
|
+
"benchmarks/benchmark_backends.py",
|
|
12
|
+
"--workload",
|
|
13
|
+
"zeolite",
|
|
14
|
+
"--framework",
|
|
15
|
+
"BEA",
|
|
16
|
+
"--resolution",
|
|
17
|
+
"0.25",
|
|
18
|
+
"--repeats",
|
|
19
|
+
"3",
|
|
20
|
+
],
|
|
21
|
+
[
|
|
22
|
+
sys.executable,
|
|
23
|
+
"benchmarks/benchmark_backends.py",
|
|
24
|
+
"--workload",
|
|
25
|
+
"wulff",
|
|
26
|
+
"--wulff-size",
|
|
27
|
+
"1000",
|
|
28
|
+
"--resolution",
|
|
29
|
+
"0.35",
|
|
30
|
+
"--repeats",
|
|
31
|
+
"3",
|
|
32
|
+
],
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
for command in commands:
|
|
36
|
+
subprocess.run(command, check=True)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
if __name__ == "__main__":
|
|
40
|
+
main()
|
|
41
|
+
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
Analysis
|
|
2
|
+
========
|
|
3
|
+
|
|
4
|
+
``VoxelGridAnalysis`` provides post-processing helpers for voxel grids. The
|
|
5
|
+
analysis dependency is optional because marching cubes and connected-component
|
|
6
|
+
labeling use scikit-image:
|
|
7
|
+
|
|
8
|
+
.. code-block:: bash
|
|
9
|
+
|
|
10
|
+
pip install ".[analysis]"
|
|
11
|
+
|
|
12
|
+
Connected Volumes
|
|
13
|
+
-----------------
|
|
14
|
+
|
|
15
|
+
The analysis class can select voxel regions by value, label connected
|
|
16
|
+
components, and convert voxel counts to physical volumes using the determinant
|
|
17
|
+
of the periodic cell:
|
|
18
|
+
|
|
19
|
+
.. code-block:: python
|
|
20
|
+
|
|
21
|
+
from atomvoxelizer import VoxelGridAnalysis
|
|
22
|
+
|
|
23
|
+
analysis = VoxelGridAnalysis(grid)
|
|
24
|
+
regions = analysis.analyze_regions(threshold=0.5)
|
|
25
|
+
|
|
26
|
+
for region in regions:
|
|
27
|
+
print(region.voxel_count, region.volume, region.surface_area)
|
|
28
|
+
|
|
29
|
+
``volume`` is reported in cubic Angstrom when the input cell is in Angstrom.
|
|
30
|
+
``surface_area`` is estimated by applying marching cubes to the selected voxel
|
|
31
|
+
mask and transforming mesh vertices into real-space coordinates. By default,
|
|
32
|
+
connected-component labeling and surface-area estimation apply periodic boundary
|
|
33
|
+
conditions. Periodic connected components are merged across opposite cell faces;
|
|
34
|
+
periodic surface areas are measured from a tiled mask and counted only for the
|
|
35
|
+
central periodic image.
|
|
36
|
+
|
|
37
|
+
Zeolite Pore Volume And Surface Area
|
|
38
|
+
------------------------------------
|
|
39
|
+
|
|
40
|
+
For zeolites, a common workflow is:
|
|
41
|
+
|
|
42
|
+
1. Build an occupied framework mask from atomic cores.
|
|
43
|
+
2. Analyze the inverse mask as the pore space.
|
|
44
|
+
3. Sum connected-region volumes to estimate accessible pore volume.
|
|
45
|
+
4. Sum marching-cubes surface areas to estimate internal surface area.
|
|
46
|
+
|
|
47
|
+
Example:
|
|
48
|
+
|
|
49
|
+
.. code-block:: python
|
|
50
|
+
|
|
51
|
+
from atomvoxelizer import VoxelGridAnalysis
|
|
52
|
+
|
|
53
|
+
analysis = VoxelGridAnalysis(voxel_grid)
|
|
54
|
+
pore_regions = analysis.analyze_regions(max_value=0.0)
|
|
55
|
+
pore_volume_a3 = sum(region.volume for region in pore_regions)
|
|
56
|
+
pore_area_a2 = sum(region.surface_area for region in pore_regions)
|
|
57
|
+
|
|
58
|
+
mass_amu = sum(atoms.get_masses())
|
|
59
|
+
pore_volume_cm3_g = analysis.volume_angstrom3_to_cm3_per_g(pore_volume_a3, mass_amu)
|
|
60
|
+
internal_area_m2_g = analysis.area_angstrom2_to_m2_per_g(pore_area_a2, mass_amu)
|
|
61
|
+
|
|
62
|
+
The same workflow is provided as ``examples/zeolite_analysis.py``:
|
|
63
|
+
|
|
64
|
+
.. code-block:: bash
|
|
65
|
+
|
|
66
|
+
python examples/zeolite_analysis.py BEA --resolution 0.25
|
|
67
|
+
|
|
68
|
+
The example can also run a resolution-convergence study and save a plot:
|
|
69
|
+
|
|
70
|
+
.. code-block:: bash
|
|
71
|
+
|
|
72
|
+
python examples/zeolite_analysis.py BEA --convergence 1.0 0.75 0.5 0.35 --plot bea_convergence.png
|
|
73
|
+
|
|
74
|
+
Experimental Comparison
|
|
75
|
+
-----------------------
|
|
76
|
+
|
|
77
|
+
Experimental BET surface area and pore volume are usually reported as
|
|
78
|
+
``m^2/g`` and ``cm^3/g``. Direct comparison to a geometric voxel model requires
|
|
79
|
+
normalizing by the mass represented by the simulated unit cell or supercell and
|
|
80
|
+
matching the experimental assumptions: framework composition, extra-framework
|
|
81
|
+
cations, adsorbate probe size, activation state, defects, and whether the
|
|
82
|
+
reported pore volume is micropore, mesopore, or total pore volume.
|
|
83
|
+
|
|
84
|
+
AtomVoxelizer provides unit-conversion helpers once you know the mass represented
|
|
85
|
+
by the simulated structure:
|
|
86
|
+
|
|
87
|
+
.. code-block:: python
|
|
88
|
+
|
|
89
|
+
mass_amu = sum(atoms.get_masses())
|
|
90
|
+
pore_volume_cm3_g = analysis.volume_angstrom3_to_cm3_per_g(pore_volume_a3, mass_amu)
|
|
91
|
+
area_m2_g = analysis.area_angstrom2_to_m2_per_g(pore_area_a2, mass_amu)
|
|
92
|
+
|
|
93
|
+
The comparison table should be filled with values from the specific material
|
|
94
|
+
and synthesis route being modeled:
|
|
95
|
+
|
|
96
|
+
.. list-table::
|
|
97
|
+
:header-rows: 1
|
|
98
|
+
|
|
99
|
+
* - Framework
|
|
100
|
+
- Experimental BET surface area
|
|
101
|
+
- Experimental pore volume
|
|
102
|
+
- Notes
|
|
103
|
+
* - BEA
|
|
104
|
+
- source-specific
|
|
105
|
+
- source-specific
|
|
106
|
+
- Zeolite beta values depend strongly on Si/Al ratio and activation.
|
|
107
|
+
* - MWW/MCM-22
|
|
108
|
+
- source-specific
|
|
109
|
+
- source-specific
|
|
110
|
+
- MCM-22 reports often distinguish micropore and external surface area.
|
|
111
|
+
|
|
112
|
+
Use the voxel result as a geometric internal-surface estimate, not as a direct
|
|
113
|
+
replacement for adsorbate-specific BET analysis.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
API Reference
|
|
2
|
+
=============
|
|
3
|
+
|
|
4
|
+
.. autoclass:: atomvoxelizer.VoxelGrid
|
|
5
|
+
:members:
|
|
6
|
+
:undoc-members:
|
|
7
|
+
:show-inheritance:
|
|
8
|
+
|
|
9
|
+
.. autoclass:: atomvoxelizer.VoxelGridNumPy
|
|
10
|
+
:members:
|
|
11
|
+
:undoc-members:
|
|
12
|
+
:show-inheritance:
|
|
13
|
+
|
|
14
|
+
.. autoclass:: atomvoxelizer.VoxelGridAnalysis
|
|
15
|
+
:members:
|
|
16
|
+
:undoc-members:
|
|
17
|
+
:show-inheritance:
|