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.
@@ -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)