Skip to main content

RINSE – Reciprocal-space INvariant Spectral Embedding

RINSE computes rotationally invariant descriptors of crystalline materials by projecting the intensity-weighted reciprocal lattice onto a combined radial and angular basis (analogous to SOAP, but working entirely in reciprocal space).

Descriptor formulation

For a crystal, all reflections $\mathbf{G}_{\mathrm{hkl}}$ within a resolution cutoff (default $\sin{\theta}/\lambda \leq 0.35 Å^{-1}$, i.e. $|\mathbf{G}| \leq 0.70 Å^{-1}$) are enumerated. Each reflection is assigned an intensity $\mathrm{I}(\mathbf{G}) = \lvert\mathrm{F}(\mathbf{G})\rvert^{2}$ from the structure factor calculated via cctbx direct summation with X-ray form factors by default. Isotropic and anisotropic displacement parameters are read from the CIF by default. The resolution-dependent intensity envelope is removed at the power-spectrum level by monopole (l = 0) normalisation, which divides each radial level by its spherically-averaged scattering power.

The expansion coefficients are:

$$ A_{nlm} = Σ_\mathbf{G} I(\mathbf{G}) · R_n(|\mathbf{G}|) · Y_l^m(\hat{\mathbf{G}}) $$

and the rotationally invariant power spectrum is:

$$ p_{nl} = Σ_m \lvert A_{nlm}\rvert^{2} $$

Because the intensity field is centrosymmetric if anomalous dispersion is not considered, only even l contributes. By default, RINSE also drops the monopole (l = 0) and quadrupole (l = 2) terms. Default parameters give an 16 × 8 = 128-element descriptor:

Axis Values Count
Radial (n) 0, 1, …, 15 16
Angular (l) 4, 6, 8, …, 18 (even only) 8

Installation

From source (requires uv)

# Clone
git clone https://github.com/DuMOCC-Group/rinse-descriptor.git
cd rinse-descriptor

# Install uv (if not already available)
curl -Ls https://astral.sh/uv/install.sh | sh

# Install all dependencies
uv sync

# Run the demo.py notebook
uv run marimo edit demo.py

Optional CSD API Installation for PCA analysis

To recompute the PCA components used for the dimensionality reduction in the locality-sensitive hash (see Locality-sensitive hashing below), install the CSD Python API and run the scripts in the tools folder.

# Install CSD Python API
uv pip install --extra-index-url https://pip.ccdc.cam.ac.uk/ csd-python-api

# Generate descriptors for CSD structures
uv run tools/compute_csd_hashes.py 

# Perform PCA analysis and update components in python/rinse_descriptor/data folder
uv run tools/compute_pca.py 

From PyPI

pip install rinse-descriptor
# or
uv add rinse-descriptor

Quick start

From a CIF file

from rinse_descriptor import RinseParams, descriptor, descriptor_many

# Single structure → 1-D feature vector
x = descriptor("mystructure.cif")
print(x.shape)  # (128,)

# Return the 2-D power-spectrum matrix instead
params = RinseParams(flatten=False)
x_mat = descriptor("mystructure.cif", params=params)
print(x_mat.shape)  # (16, 8)

# Batch of structures → (N, 128)
structures = ["structure_1.cif", "structure_2.cif"]
X = descriptor_many(structures)
print(X.shape)  # (2, 128)

From a loaded cctbx structure

from rinse_descriptor import descriptor, load_cif

xrs = load_cif("mystructure.cif")
x = descriptor(xrs)
print(x.shape)  # (128,)

Custom parameters

from rinse_descriptor import RinseParams, descriptor

params = RinseParams(
    n_max=16,                       # radial shells (n = 0 … 15)
    l_max=20,                       # angular levels (gives l = 4,6,...,18 by default)
    radial_scale=0.3,               # per-shell scale in Å⁻¹ (resolution cutoff is derived)
    radial_basis="cv_gaussian",  # or "lin_gaussian"
    monopole_normalisation=False,    # optional: disable the default monopole (ℓ=0) envelope removal
)
x = descriptor("mystructure.cif", params=params)

Form factors

from rinse_descriptor import descriptor

# Electron scattering factors; descriptor weights are still intensities I = |F|²
x = descriptor("mystructure.cif", form_factor_type="electron")

# Monopole (l=0) normalisation removes the resolution envelope by default.
x_norm = descriptor("mystructure.cif")

Available form_factor_type values: "xray" (default), "electron", "neutron".

Locality-sensitive hashing

RINSE provides a deterministic locality-sensitive hash that converts descriptor vectors into short, pronounceable strings (proquints) for quick similarity comparisons and indexing.

Basic usage

from rinse_descriptor import descriptor, descriptor_hash

x = descriptor("mystructure.cif")
hash_str = descriptor_hash(x)
print(hash_str)  # e.g., "lusab"

# Generate longer hashes with more words (each word = 16 bits)
hash_str = descriptor_hash(x, n_words=5)
print(hash_str)  # e.g., "lusab-babad-gutih-tugad-mudof"

Algorithm

The hash is computed using PCA-based SimHash:

  1. Project the descriptor vector onto the first n principal components (PCA model precomputed from the CSD and bundled with the package)
  2. Binarize each projection coefficient by its sign: bits[i] = (projection[i] > 0)
  3. Encode each 16-bit chunk as a five-character proquint word (CVCVC pattern using consonants for 4-bit values and vowels for 2-bit values)

By default, n_words=1 produces a single 5-character word encoding 16 hash bits. Structurally similar crystals produce similar hash bits and thus identical or nearby proquint strings.

Properties

  • Deterministic: The same descriptor always produces the same hash
  • Locality-sensitive: Similar descriptors map to similar hash bits
  • Pronounceable: Uses the proquint encoding (babab, zuzuz, etc.) for human-readability
  • PCA-based: Uses learned principal components from the Cambridge Structural Database rather than random projections

Decode hash to bits

from rinse_descriptor import descriptor_hash, hash_to_bits
import numpy as np

hash_str = descriptor_hash(x, n_words=3)
bits = hash_to_bits(hash_str)
print(bits.shape)  # (48,) — 3 words × 16 bits/word
print(bits.dtype)  # bool

Custom PCA model

To use your own PCA model (e.g., trained on a different dataset):

hash_str = descriptor_hash(x, n_words=5, pca_file="my_pca_components.json")

The JSON file must contain "components" (2-D array) and "mean" (1-D array) keys.

Development

# Run tests
uv run pytest tests/ -v

# Run benchmarks
uv run pytest benchmarks/ --benchmark-only -v

# Lint / format
uv run ruff check python/ tests/
uv run ruff format python/ tests/
uv run mypy python/rinse_descriptor/

This repository includes a .pre-commit-config.yaml that runs:

  • On commit: ruff check --fix and ruff format for staged Python files.
  • On push: full ruff check, mypy, and pytest.

Install and run once:

uv sync --group dev
uv run pre-commit install --hook-type pre-commit --hook-type pre-push
uv run pre-commit run --all-files

Project structure

rinse-descriptor/
├── python/rinse_descriptor/  # Python package
│   ├── __init__.py        # Public API: descriptor(), descriptor_many(), descriptor_hash(), hash_to_bits()
│   ├── _crystal.py        # CIF loading into cctbx xray.structure objects
│   ├── _structure_factors.py  # cctbx structure factor calculation
│   ├── _radial_basis.py   # smooth-shell radial bases (index-anchored)
│   ├── _descriptor.py     # Power spectrum computation
│   ├── _hash.py           # Locality-sensitive hashing via PCA-based SimHash
│   └── data/
│       └── pca_components.json  # Precomputed PCA model for hashing
├── tests/                 # pytest test suite
├── benchmarks/            # pytest-benchmark suite
├── .github/workflows/ci.yml
└── pyproject.toml

Future: rinse_descriptor.diffraction submodule

The package is designed to support a future rinse_descriptor.diffraction submodule that will provide:

  • Indexed diffraction patterns (hkl, d-spacing, intensity)
  • Unindexed powder diffraction patterns (2θ or d-spacing profiles)
  • Reciprocal-space descriptors beyond the power spectrum (e.g. bispectrum)

The ReflectionList and RinseParams types are designed to be shared between the core descriptor and the diffraction submodule.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

rinse_descriptor-3.0.0.tar.gz (646.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

rinse_descriptor-3.0.0-py3-none-any.whl (198.8 kB view details)

Uploaded Python 3

File details

Details for the file rinse_descriptor-3.0.0.tar.gz.

File metadata

  • Download URL: rinse_descriptor-3.0.0.tar.gz
  • Upload date:
  • Size: 646.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.12.14 {"installer":{"name":"uv","version":"0.12.14","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for rinse_descriptor-3.0.0.tar.gz
Algorithm Hash digest
SHA256 a027c25837b1e431f39056c050bb421ac4136f7b04d956571d4d1274bab9b350
MD5 bac57db771695b7ac8e41cbdbd65b1fa
BLAKE2b-256 1f90139994d7277bb63c6f4922a7b03d134716c2c8239ede0eab573071195e0d

See more details on using hashes here.

File details

Details for the file rinse_descriptor-3.0.0-py3-none-any.whl.

File metadata

  • Download URL: rinse_descriptor-3.0.0-py3-none-any.whl
  • Upload date:
  • Size: 198.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.12.14 {"installer":{"name":"uv","version":"0.12.14","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for rinse_descriptor-3.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 36ded9eef03a3cfb4b3c43d4bc14f05e658d3b86ca424149baf7cac2fd1f5970
MD5 fef073b86cdb3be8ffffea328db9978e
BLAKE2b-256 f350b27e8cbd5327facada01c8261b651e644e77605dd2be91902676d1ad509e

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

3.0.0 This release

2 files

2.0.0

2 files

1.2.0

2 files

1.1.1

2 files

1.1.0

2 files

1.0.7

2 files

1.0.6

2 files

1.0.5

2 files

1.0.4

2 files

1.0.3

2 files

1.0.2

2 files

1.0.1

2 files

1.0.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page