rinse-descriptor 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.
- rinse_descriptor-0.1.0/.github/workflows/ci.yml +69 -0
- rinse_descriptor-0.1.0/.github/workflows/publish.yml +27 -0
- rinse_descriptor-0.1.0/.gitignore +80 -0
- rinse_descriptor-0.1.0/.pre-commit-config.yaml +35 -0
- rinse_descriptor-0.1.0/.python-version +1 -0
- rinse_descriptor-0.1.0/PKG-INFO +200 -0
- rinse_descriptor-0.1.0/README.md +180 -0
- rinse_descriptor-0.1.0/benchmarks/bench_descriptor.py +40 -0
- rinse_descriptor-0.1.0/demo.py +795 -0
- rinse_descriptor-0.1.0/main.py +6 -0
- rinse_descriptor-0.1.0/pyproject.toml +58 -0
- rinse_descriptor-0.1.0/python/rinse_descriptor/__init__.py +210 -0
- rinse_descriptor-0.1.0/python/rinse_descriptor/_crystal.py +246 -0
- rinse_descriptor-0.1.0/python/rinse_descriptor/_descriptor.py +380 -0
- rinse_descriptor-0.1.0/python/rinse_descriptor/_radial_basis.py +232 -0
- rinse_descriptor-0.1.0/python/rinse_descriptor/_structure_factors.py +353 -0
- rinse_descriptor-0.1.0/tests/fixtures/mpox.cif +331 -0
- rinse_descriptor-0.1.0/tests/fixtures/nacl.cif +452 -0
- rinse_descriptor-0.1.0/tests/fixtures/si.cif +258 -0
- rinse_descriptor-0.1.0/tests/fixtures/ylid.cif +110 -0
- rinse_descriptor-0.1.0/tests/test_descriptor.py +463 -0
- rinse_descriptor-0.1.0/uv.lock +1787 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
# --------------------------------------------------------------------------
|
|
11
|
+
# Python – lint, type-check, test
|
|
12
|
+
# --------------------------------------------------------------------------
|
|
13
|
+
python:
|
|
14
|
+
name: Python ${{ matrix.python-version }} / ${{ matrix.os }}
|
|
15
|
+
runs-on: ${{ matrix.os }}
|
|
16
|
+
strategy:
|
|
17
|
+
fail-fast: false
|
|
18
|
+
matrix:
|
|
19
|
+
os: [ubuntu-latest, macos-latest]
|
|
20
|
+
python-version: ["3.11", "3.12"]
|
|
21
|
+
|
|
22
|
+
steps:
|
|
23
|
+
- uses: actions/checkout@v4
|
|
24
|
+
|
|
25
|
+
- name: Install uv
|
|
26
|
+
uses: astral-sh/setup-uv@v5
|
|
27
|
+
with:
|
|
28
|
+
version: "latest"
|
|
29
|
+
|
|
30
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
31
|
+
run: uv python install ${{ matrix.python-version }}
|
|
32
|
+
|
|
33
|
+
- name: Install project (dev dependencies)
|
|
34
|
+
run: uv sync --group dev
|
|
35
|
+
|
|
36
|
+
- name: ruff lint
|
|
37
|
+
run: uv run ruff check python/ tests/
|
|
38
|
+
|
|
39
|
+
- name: ruff format check
|
|
40
|
+
run: uv run ruff format --check python/ tests/
|
|
41
|
+
|
|
42
|
+
- name: mypy
|
|
43
|
+
run: uv run mypy python/rinse_descriptor/
|
|
44
|
+
|
|
45
|
+
- name: pytest
|
|
46
|
+
run: uv run pytest tests/ -v --tb=short
|
|
47
|
+
|
|
48
|
+
# --------------------------------------------------------------------------
|
|
49
|
+
# Build sdist + wheel (on push to main)
|
|
50
|
+
# --------------------------------------------------------------------------
|
|
51
|
+
build:
|
|
52
|
+
name: Build distribution
|
|
53
|
+
runs-on: ubuntu-latest
|
|
54
|
+
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
|
|
55
|
+
needs: [python]
|
|
56
|
+
steps:
|
|
57
|
+
- uses: actions/checkout@v4
|
|
58
|
+
|
|
59
|
+
- name: Install uv
|
|
60
|
+
uses: astral-sh/setup-uv@v5
|
|
61
|
+
|
|
62
|
+
- name: Build sdist and wheel
|
|
63
|
+
run: uv build
|
|
64
|
+
|
|
65
|
+
- name: Upload artifacts
|
|
66
|
+
uses: actions/upload-artifact@v4
|
|
67
|
+
with:
|
|
68
|
+
name: dist
|
|
69
|
+
path: dist/
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
name: "Publish"
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
# Publish on any tag starting with a `v`, e.g., v0.1.0
|
|
7
|
+
- v*
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
run:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
environment:
|
|
13
|
+
name: pypi
|
|
14
|
+
permissions:
|
|
15
|
+
id-token: write
|
|
16
|
+
contents: read
|
|
17
|
+
steps:
|
|
18
|
+
- name: Checkout
|
|
19
|
+
uses: actions/checkout@v6
|
|
20
|
+
- name: Install uv
|
|
21
|
+
uses: astral-sh/setup-uv@v7
|
|
22
|
+
- name: Install Python 3.13
|
|
23
|
+
run: uv python install 3.13
|
|
24
|
+
- name: Build
|
|
25
|
+
run: uv build
|
|
26
|
+
- name: Publish
|
|
27
|
+
run: uv publish
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
share/python-wheels/
|
|
24
|
+
*.egg-info/
|
|
25
|
+
.installed.cfg
|
|
26
|
+
*.egg
|
|
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
|
+
.pytest_cache/
|
|
45
|
+
cache/
|
|
46
|
+
.nosetests.xml
|
|
47
|
+
coverage.xml
|
|
48
|
+
*.cover
|
|
49
|
+
*.py,cover
|
|
50
|
+
|
|
51
|
+
# Jupyter Notebook
|
|
52
|
+
.ipynb_checkpoints
|
|
53
|
+
*.ipynb
|
|
54
|
+
|
|
55
|
+
# Marimo Notebook
|
|
56
|
+
__marimo__/
|
|
57
|
+
|
|
58
|
+
# Environments
|
|
59
|
+
.env
|
|
60
|
+
.venv
|
|
61
|
+
env/
|
|
62
|
+
venv/
|
|
63
|
+
ENV/
|
|
64
|
+
env.bak/
|
|
65
|
+
venv.bak/
|
|
66
|
+
|
|
67
|
+
# mypy
|
|
68
|
+
.mypy_cache/
|
|
69
|
+
.dmypy.json
|
|
70
|
+
dmypy.json
|
|
71
|
+
|
|
72
|
+
# Pyre type checker
|
|
73
|
+
.pyre/
|
|
74
|
+
|
|
75
|
+
# sqlite database
|
|
76
|
+
*.db
|
|
77
|
+
*.db-journal
|
|
78
|
+
|
|
79
|
+
crystal_pickles/
|
|
80
|
+
umap_hdbscan_results.csv
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
repos:
|
|
2
|
+
- repo: local
|
|
3
|
+
hooks:
|
|
4
|
+
- id: ruff-fix
|
|
5
|
+
name: ruff check --fix (staged files)
|
|
6
|
+
entry: uv run ruff check --fix
|
|
7
|
+
language: system
|
|
8
|
+
types_or: [python, pyi]
|
|
9
|
+
files: ^(python/|tests/)
|
|
10
|
+
stages: [pre-commit]
|
|
11
|
+
- id: ruff-format
|
|
12
|
+
name: ruff format (staged files)
|
|
13
|
+
entry: uv run ruff format
|
|
14
|
+
language: system
|
|
15
|
+
types_or: [python, pyi]
|
|
16
|
+
files: ^(python/|tests/)
|
|
17
|
+
stages: [pre-commit]
|
|
18
|
+
- id: ruff-check-all
|
|
19
|
+
name: ruff check (full)
|
|
20
|
+
entry: uv run ruff check python/ tests/
|
|
21
|
+
language: system
|
|
22
|
+
pass_filenames: false
|
|
23
|
+
stages: [pre-push]
|
|
24
|
+
- id: mypy
|
|
25
|
+
name: mypy (package)
|
|
26
|
+
entry: uv run mypy python/rinse_descriptor/
|
|
27
|
+
language: system
|
|
28
|
+
pass_filenames: false
|
|
29
|
+
stages: [pre-push]
|
|
30
|
+
- id: pytest
|
|
31
|
+
name: pytest (suite)
|
|
32
|
+
entry: sh -c 'PYTHONPATH=python uv run pytest tests/ -v --tb=short'
|
|
33
|
+
language: system
|
|
34
|
+
pass_filenames: false
|
|
35
|
+
stages: [pre-push]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.14
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: rinse-descriptor
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Reciprocal-space INvariant Spectral Embedding (RINSE) descriptors for crystalline materials
|
|
5
|
+
License: MIT
|
|
6
|
+
Keywords: crystallography,descriptors,machine learning,materials science
|
|
7
|
+
Classifier: Development Status :: 3 - Alpha
|
|
8
|
+
Classifier: Intended Audience :: Science/Research
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Topic :: Scientific/Engineering :: Chemistry
|
|
12
|
+
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
13
|
+
Requires-Python: >=3.11
|
|
14
|
+
Requires-Dist: ase>=3.23.0
|
|
15
|
+
Requires-Dist: gemmi>=0.6.7
|
|
16
|
+
Requires-Dist: numpy>=1.26
|
|
17
|
+
Requires-Dist: pandas>=3.0.3
|
|
18
|
+
Requires-Dist: scipy>=1.12
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
# RINSE – Reciprocal-space INvariant Spectral Embedding
|
|
22
|
+
|
|
23
|
+
RINSE computes rotationally invariant descriptors of crystalline materials
|
|
24
|
+
by projecting the intensity-weighted reciprocal lattice onto a combined radial
|
|
25
|
+
and angular basis (analogous to SOAP, but working entirely in reciprocal space).
|
|
26
|
+
|
|
27
|
+
## Descriptor formulation
|
|
28
|
+
|
|
29
|
+
For a crystal, all reflections $\mathbf{G}_{\mathrm{hkl}}$ within a resolution cutoff
|
|
30
|
+
(default $\sin{\theta}/\lambda \leq 0.6 Å^{-1}$ , i.e. $\mathbf{G} \leq 0.6 Å^{-1}$) are enumerated.
|
|
31
|
+
Each reflection is assigned an intensity $\mathrm{I}(\mathbf{G}) = \lvert\mathrm{F}(\mathbf{G})\rvert^{2}$ from the
|
|
32
|
+
structure factor calculated via Gemmi (direct summation, IT92 X-ray form
|
|
33
|
+
factors by default).
|
|
34
|
+
|
|
35
|
+
The expansion coefficients are:
|
|
36
|
+
|
|
37
|
+
$$
|
|
38
|
+
A_{nlm} = Σ_\mathbf{G} I(\mathbf{G}) · R_n(\mathbf{G}) · Y_l^m(\hat{\mathbf{G}})
|
|
39
|
+
$$
|
|
40
|
+
|
|
41
|
+
and the rotationally invariant power spectrum is:
|
|
42
|
+
|
|
43
|
+
$$
|
|
44
|
+
p_{nl} = Σ_m \lvert A_{nlm}\rvert^{2}
|
|
45
|
+
$$
|
|
46
|
+
|
|
47
|
+
Because the intensity field is centrosymmetric if anomalous dispersion is not considered, only even *l*
|
|
48
|
+
contributes. Default parameters give a **8 × 16 = 128-element** descriptor:
|
|
49
|
+
|
|
50
|
+
| Axis | Values | Count |
|
|
51
|
+
|------|--------|-------|
|
|
52
|
+
| Radial (*n*) | 0, 1, …, 7 | 8 |
|
|
53
|
+
| Angular (*l*) | 0, 2, 4, …, 30 (even only) | 16 |
|
|
54
|
+
|
|
55
|
+
## Installation
|
|
56
|
+
|
|
57
|
+
### From source (requires `uv`)
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
# Clone
|
|
61
|
+
git clone https://github.com/DuMOCC-Group/rinse-descriptor.git
|
|
62
|
+
cd rinse-descriptor
|
|
63
|
+
|
|
64
|
+
# Install uv (if not already available)
|
|
65
|
+
curl -Ls https://astral.sh/uv/install.sh | sh
|
|
66
|
+
|
|
67
|
+
# Install all dependencies
|
|
68
|
+
uv sync
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### From PyPI (once published)
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
pip install rinse-descriptor
|
|
75
|
+
# or
|
|
76
|
+
uv add rinse-descriptor
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Quick start
|
|
80
|
+
|
|
81
|
+
### From an ASE Atoms object
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
from ase.build import bulk
|
|
85
|
+
from rinse_descriptor import descriptor, descriptor_many, RinseParams
|
|
86
|
+
|
|
87
|
+
# Single structure → (8, 16) matrix
|
|
88
|
+
atoms = bulk("NaCl", "rocksalt", a=5.64)
|
|
89
|
+
x = descriptor(atoms)
|
|
90
|
+
print(x.shape) # (8, 16)
|
|
91
|
+
|
|
92
|
+
# Flatten to 1-D feature vector
|
|
93
|
+
x_vec = descriptor(atoms, flatten=True)
|
|
94
|
+
print(x_vec.shape) # (128,)
|
|
95
|
+
|
|
96
|
+
# Batch of structures → (N, 8, 16)
|
|
97
|
+
structures = [bulk("Si", "diamond", a=5.43), bulk("Cu", "fcc", a=3.62)]
|
|
98
|
+
X = descriptor_many(structures)
|
|
99
|
+
print(X.shape) # (2, 8, 16)
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### From a CIF file
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
from rinse_descriptor import descriptor
|
|
106
|
+
|
|
107
|
+
x = descriptor("mystructure.cif")
|
|
108
|
+
print(x.shape) # (8, 16)
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Custom parameters
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
from rinse_descriptor import RinseParams, descriptor
|
|
115
|
+
|
|
116
|
+
params = RinseParams(
|
|
117
|
+
n_max=8, # radial basis order (n = 0 … 7)
|
|
118
|
+
l_max=16, # angular levels (gives l = 0,2,...,30)
|
|
119
|
+
sin_theta_over_lambda_max=0.6, # resolution cutoff in Å⁻¹
|
|
120
|
+
radial_basis="chebyshev", # or "bessel" / "smooth_shells_cw" / "smooth_shells_nl"
|
|
121
|
+
)
|
|
122
|
+
x = descriptor(atoms, params=params)
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Form factors and structure factor type
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
from rinse_descriptor import descriptor
|
|
129
|
+
|
|
130
|
+
# Electron scattering factors, intensities
|
|
131
|
+
x = descriptor(atoms, form_factor_type="electron", structure_factor_type="F2")
|
|
132
|
+
|
|
133
|
+
# Neutron scattering lengths, amplitudes
|
|
134
|
+
x = descriptor(atoms, form_factor_type="neutron", structure_factor_type="F")
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Available `form_factor_type` values: `"xray"` (default), `"electron"`, `"neutron"`, `"unity"`.
|
|
138
|
+
|
|
139
|
+
Available `structure_factor_type` values: `"F2"` (default), `"F"`.
|
|
140
|
+
|
|
141
|
+
## Development
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
# Run tests
|
|
145
|
+
uv run pytest tests/ -v
|
|
146
|
+
|
|
147
|
+
# Run benchmarks
|
|
148
|
+
uv run pytest benchmarks/ --benchmark-only -v
|
|
149
|
+
|
|
150
|
+
# Lint / format
|
|
151
|
+
uv run ruff check python/ tests/
|
|
152
|
+
uv run ruff format python/ tests/
|
|
153
|
+
uv run mypy python/rinse_descriptor/
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Pre-commit hooks (recommended)
|
|
157
|
+
|
|
158
|
+
This repository includes a `.pre-commit-config.yaml` that runs:
|
|
159
|
+
|
|
160
|
+
- On commit: `ruff check --fix` and `ruff format` for staged Python files.
|
|
161
|
+
- On push: full `ruff check`, `mypy`, and `pytest`.
|
|
162
|
+
|
|
163
|
+
Install and run once:
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
uv sync --group dev
|
|
167
|
+
uv run pre-commit install --hook-type pre-commit --hook-type pre-push
|
|
168
|
+
uv run pre-commit run --all-files
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Project structure
|
|
172
|
+
|
|
173
|
+
```
|
|
174
|
+
rinse-descriptor/
|
|
175
|
+
├── python/rinse_descriptor/ # Python package
|
|
176
|
+
│ ├── __init__.py # Public API: descriptor(), descriptor_many()
|
|
177
|
+
│ ├── _crystal.py # Crystal dataclass (ASE/Gemmi-independent)
|
|
178
|
+
│ ├── _structure_factors.py # Gemmi structure factor calculation
|
|
179
|
+
│ ├── _radial_basis.py # Chebyshev / Bessel / smooth-shell radial bases
|
|
180
|
+
│ └── _descriptor.py # Power spectrum computation
|
|
181
|
+
├── tests/ # pytest test suite
|
|
182
|
+
├── benchmarks/ # pytest-benchmark suite
|
|
183
|
+
├── .github/workflows/ci.yml
|
|
184
|
+
└── pyproject.toml
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## Future: `rinse_descriptor.diffraction` submodule
|
|
188
|
+
|
|
189
|
+
The package is designed to support a future `rinse_descriptor.diffraction` submodule that will provide:
|
|
190
|
+
|
|
191
|
+
- Indexed diffraction patterns (hkl, d-spacing, intensity)
|
|
192
|
+
- Unindexed powder diffraction patterns (2θ or *d*-spacing profiles)
|
|
193
|
+
- Reciprocal-space descriptors beyond the power spectrum (e.g. bispectrum)
|
|
194
|
+
|
|
195
|
+
The `Crystal`, `ReflectionList`, and `RinseParams` types are designed to be
|
|
196
|
+
shared between the core descriptor and the diffraction submodule.
|
|
197
|
+
|
|
198
|
+
## License
|
|
199
|
+
|
|
200
|
+
MIT
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# RINSE – Reciprocal-space INvariant Spectral Embedding
|
|
2
|
+
|
|
3
|
+
RINSE computes rotationally invariant descriptors of crystalline materials
|
|
4
|
+
by projecting the intensity-weighted reciprocal lattice onto a combined radial
|
|
5
|
+
and angular basis (analogous to SOAP, but working entirely in reciprocal space).
|
|
6
|
+
|
|
7
|
+
## Descriptor formulation
|
|
8
|
+
|
|
9
|
+
For a crystal, all reflections $\mathbf{G}_{\mathrm{hkl}}$ within a resolution cutoff
|
|
10
|
+
(default $\sin{\theta}/\lambda \leq 0.6 Å^{-1}$ , i.e. $\mathbf{G} \leq 0.6 Å^{-1}$) are enumerated.
|
|
11
|
+
Each reflection is assigned an intensity $\mathrm{I}(\mathbf{G}) = \lvert\mathrm{F}(\mathbf{G})\rvert^{2}$ from the
|
|
12
|
+
structure factor calculated via Gemmi (direct summation, IT92 X-ray form
|
|
13
|
+
factors by default).
|
|
14
|
+
|
|
15
|
+
The expansion coefficients are:
|
|
16
|
+
|
|
17
|
+
$$
|
|
18
|
+
A_{nlm} = Σ_\mathbf{G} I(\mathbf{G}) · R_n(\mathbf{G}) · Y_l^m(\hat{\mathbf{G}})
|
|
19
|
+
$$
|
|
20
|
+
|
|
21
|
+
and the rotationally invariant power spectrum is:
|
|
22
|
+
|
|
23
|
+
$$
|
|
24
|
+
p_{nl} = Σ_m \lvert A_{nlm}\rvert^{2}
|
|
25
|
+
$$
|
|
26
|
+
|
|
27
|
+
Because the intensity field is centrosymmetric if anomalous dispersion is not considered, only even *l*
|
|
28
|
+
contributes. Default parameters give a **8 × 16 = 128-element** descriptor:
|
|
29
|
+
|
|
30
|
+
| Axis | Values | Count |
|
|
31
|
+
|------|--------|-------|
|
|
32
|
+
| Radial (*n*) | 0, 1, …, 7 | 8 |
|
|
33
|
+
| Angular (*l*) | 0, 2, 4, …, 30 (even only) | 16 |
|
|
34
|
+
|
|
35
|
+
## Installation
|
|
36
|
+
|
|
37
|
+
### From source (requires `uv`)
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
# Clone
|
|
41
|
+
git clone https://github.com/DuMOCC-Group/rinse-descriptor.git
|
|
42
|
+
cd rinse-descriptor
|
|
43
|
+
|
|
44
|
+
# Install uv (if not already available)
|
|
45
|
+
curl -Ls https://astral.sh/uv/install.sh | sh
|
|
46
|
+
|
|
47
|
+
# Install all dependencies
|
|
48
|
+
uv sync
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### From PyPI (once published)
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
pip install rinse-descriptor
|
|
55
|
+
# or
|
|
56
|
+
uv add rinse-descriptor
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Quick start
|
|
60
|
+
|
|
61
|
+
### From an ASE Atoms object
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
from ase.build import bulk
|
|
65
|
+
from rinse_descriptor import descriptor, descriptor_many, RinseParams
|
|
66
|
+
|
|
67
|
+
# Single structure → (8, 16) matrix
|
|
68
|
+
atoms = bulk("NaCl", "rocksalt", a=5.64)
|
|
69
|
+
x = descriptor(atoms)
|
|
70
|
+
print(x.shape) # (8, 16)
|
|
71
|
+
|
|
72
|
+
# Flatten to 1-D feature vector
|
|
73
|
+
x_vec = descriptor(atoms, flatten=True)
|
|
74
|
+
print(x_vec.shape) # (128,)
|
|
75
|
+
|
|
76
|
+
# Batch of structures → (N, 8, 16)
|
|
77
|
+
structures = [bulk("Si", "diamond", a=5.43), bulk("Cu", "fcc", a=3.62)]
|
|
78
|
+
X = descriptor_many(structures)
|
|
79
|
+
print(X.shape) # (2, 8, 16)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### From a CIF file
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
from rinse_descriptor import descriptor
|
|
86
|
+
|
|
87
|
+
x = descriptor("mystructure.cif")
|
|
88
|
+
print(x.shape) # (8, 16)
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Custom parameters
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
from rinse_descriptor import RinseParams, descriptor
|
|
95
|
+
|
|
96
|
+
params = RinseParams(
|
|
97
|
+
n_max=8, # radial basis order (n = 0 … 7)
|
|
98
|
+
l_max=16, # angular levels (gives l = 0,2,...,30)
|
|
99
|
+
sin_theta_over_lambda_max=0.6, # resolution cutoff in Å⁻¹
|
|
100
|
+
radial_basis="chebyshev", # or "bessel" / "smooth_shells_cw" / "smooth_shells_nl"
|
|
101
|
+
)
|
|
102
|
+
x = descriptor(atoms, params=params)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Form factors and structure factor type
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
from rinse_descriptor import descriptor
|
|
109
|
+
|
|
110
|
+
# Electron scattering factors, intensities
|
|
111
|
+
x = descriptor(atoms, form_factor_type="electron", structure_factor_type="F2")
|
|
112
|
+
|
|
113
|
+
# Neutron scattering lengths, amplitudes
|
|
114
|
+
x = descriptor(atoms, form_factor_type="neutron", structure_factor_type="F")
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Available `form_factor_type` values: `"xray"` (default), `"electron"`, `"neutron"`, `"unity"`.
|
|
118
|
+
|
|
119
|
+
Available `structure_factor_type` values: `"F2"` (default), `"F"`.
|
|
120
|
+
|
|
121
|
+
## Development
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
# Run tests
|
|
125
|
+
uv run pytest tests/ -v
|
|
126
|
+
|
|
127
|
+
# Run benchmarks
|
|
128
|
+
uv run pytest benchmarks/ --benchmark-only -v
|
|
129
|
+
|
|
130
|
+
# Lint / format
|
|
131
|
+
uv run ruff check python/ tests/
|
|
132
|
+
uv run ruff format python/ tests/
|
|
133
|
+
uv run mypy python/rinse_descriptor/
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Pre-commit hooks (recommended)
|
|
137
|
+
|
|
138
|
+
This repository includes a `.pre-commit-config.yaml` that runs:
|
|
139
|
+
|
|
140
|
+
- On commit: `ruff check --fix` and `ruff format` for staged Python files.
|
|
141
|
+
- On push: full `ruff check`, `mypy`, and `pytest`.
|
|
142
|
+
|
|
143
|
+
Install and run once:
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
uv sync --group dev
|
|
147
|
+
uv run pre-commit install --hook-type pre-commit --hook-type pre-push
|
|
148
|
+
uv run pre-commit run --all-files
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Project structure
|
|
152
|
+
|
|
153
|
+
```
|
|
154
|
+
rinse-descriptor/
|
|
155
|
+
├── python/rinse_descriptor/ # Python package
|
|
156
|
+
│ ├── __init__.py # Public API: descriptor(), descriptor_many()
|
|
157
|
+
│ ├── _crystal.py # Crystal dataclass (ASE/Gemmi-independent)
|
|
158
|
+
│ ├── _structure_factors.py # Gemmi structure factor calculation
|
|
159
|
+
│ ├── _radial_basis.py # Chebyshev / Bessel / smooth-shell radial bases
|
|
160
|
+
│ └── _descriptor.py # Power spectrum computation
|
|
161
|
+
├── tests/ # pytest test suite
|
|
162
|
+
├── benchmarks/ # pytest-benchmark suite
|
|
163
|
+
├── .github/workflows/ci.yml
|
|
164
|
+
└── pyproject.toml
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## Future: `rinse_descriptor.diffraction` submodule
|
|
168
|
+
|
|
169
|
+
The package is designed to support a future `rinse_descriptor.diffraction` submodule that will provide:
|
|
170
|
+
|
|
171
|
+
- Indexed diffraction patterns (hkl, d-spacing, intensity)
|
|
172
|
+
- Unindexed powder diffraction patterns (2θ or *d*-spacing profiles)
|
|
173
|
+
- Reciprocal-space descriptors beyond the power spectrum (e.g. bispectrum)
|
|
174
|
+
|
|
175
|
+
The `Crystal`, `ReflectionList`, and `RinseParams` types are designed to be
|
|
176
|
+
shared between the core descriptor and the diffraction submodule.
|
|
177
|
+
|
|
178
|
+
## License
|
|
179
|
+
|
|
180
|
+
MIT
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""Benchmarks for the RINSE descriptor (pytest-benchmark)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
from ase.build import bulk, make_supercell
|
|
7
|
+
from rinse_descriptor import RinseParams, descriptor
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@pytest.fixture(scope="module")
|
|
11
|
+
def nacl_unit():
|
|
12
|
+
return bulk("NaCl", "rocksalt", a=5.6402)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@pytest.fixture(scope="module")
|
|
16
|
+
def nacl_supercell():
|
|
17
|
+
atoms = bulk("NaCl", "rocksalt", a=5.6402)
|
|
18
|
+
return make_supercell(atoms, [[2, 0, 0], [0, 2, 0], [0, 0, 2]])
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@pytest.fixture(scope="module")
|
|
22
|
+
def params_default():
|
|
23
|
+
return RinseParams()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@pytest.fixture(scope="module")
|
|
27
|
+
def params_small():
|
|
28
|
+
return RinseParams(n_max=8, l_max=8, sin_theta_over_lambda_max=1.0)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def test_benchmark_nacl_unit(benchmark, nacl_unit, params_default):
|
|
32
|
+
benchmark(descriptor, nacl_unit, params=params_default)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_benchmark_nacl_supercell(benchmark, nacl_supercell, params_default):
|
|
36
|
+
benchmark(descriptor, nacl_supercell, params=params_default)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def test_benchmark_small_params(benchmark, nacl_unit, params_small):
|
|
40
|
+
benchmark(descriptor, nacl_unit, params=params_small)
|