Metadata-Version: 2.4
Name: spectroprint
Version: 0.1.0
Summary: Signal fingerprinting for unknown physical signals — physics atlas + spectral matching
Author: Franco Fitte
License: MIT
Keywords: atlas,fingerprinting,physics,signal-processing,spectroscopy
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Scientific/Engineering :: Chemistry
Classifier: Topic :: Scientific/Engineering :: Physics
Requires-Python: >=3.10
Requires-Dist: numpy>=1.24
Requires-Dist: scipy>=1.10
Provides-Extra: dev
Requires-Dist: matplotlib; extra == 'dev'
Requires-Dist: pytest; extra == 'dev'
Description-Content-Type: text/markdown

# spectroprint

**Signal fingerprinting for unknown physical signals.**

Given a measured 1D signal, `spectroprint` compares its spectral-entropic fingerprint against an atlas of known physical functions and element emission spectra, and returns a ranked similarity landscape — not a single answer, but the full distribution of compatible hypotheses.

```python
from spectroprint import PhysicsAtlas

atlas = PhysicsAtlas(instrument="uv_vis")
report = atlas.match(my_signal)
print(report)
```

```
spectroprint — similarity landscape
instrument : uv_vis   |   signal length : 512
──────────────────────────────────────────────────────────────

  ┌ Cluster 1  (97.4% – 100.0%)
  │  1. Chirp                          100.0%
  │  2. Discrete curvature              97.4%
  └

  ┌ Cluster 2  (77.8% – 89.3%)
  │  3. Airy function                   89.3%
  │  4. Damped sine                     88.8%
  │  5. Fano resonance                  84.2%
  └

  ──────────────────────────────────────────────────────────
  39 entries compared against atlas.
```

---

## The idea

Most signal analysis tools ask *"what is this signal?"* and return a single answer.

`spectroprint` asks a different question: **what is this signal compatible with?**

The library does not decide. It presents the full similarity landscape so the physicist can interpret it. Close matches are flagged as ambiguous. A large gap between clusters signals a confident separation.

### How it works

Every signal has a **shape fingerprint** — a fixed-length vector of spectral and statistical features that is completely invariant to amplitude:

| Feature | What it captures |
|---|---|
| Spectral centroid | Where the energy is concentrated |
| Spectral bandwidth | How spread out the spectrum is |
| Spectral flatness | Tonal vs noise-like character |
| Shannon entropy | Complexity / disorder |
| Kurtosis | Tail weight / impulsivity |
| Skewness | Asymmetry |
| Zero-crossing rate | Oscillation density |
| Spectral rolloff | Where 85% of energy falls |
| Hurst exponent | Long-range memory / persistence |

This fingerprint is compared against every entry in the **physics atlas** — 44 mathematical families and 21 element emission spectra — using cosine similarity on z-scored features. The result is a full ranked distribution, grouped into clusters by proximity.

---

## Installation

```bash
pip install spectroprint
```

**Requirements:** Python >= 3.10, NumPy, SciPy.

---

## Quick start

```python
import numpy as np
from spectroprint import PhysicsAtlas

atlas = PhysicsAtlas(instrument="uv_vis")

t = np.linspace(-6, 6, 512)
signal = np.exp(-0.3 * np.abs(t)) * np.sin(3 * np.pi * t / 4)

report = atlas.match(signal)
print(report)

# Top match
print(report.top.label)           # "Damped sine"
print(report.top.similarity)      # 91.3
print(report.top.interpretation)  # "Damped oscillator..."

# Iterate clusters
for cluster in report.clusters:
    print(cluster)

# Raw fingerprint
fp = atlas.fingerprint(signal)
# {'spectral_centroid': 0.12, 'shannon_entropy': 4.8, ...}
```

---

## Instrument presets

```python
atlas = PhysicsAtlas(instrument="uv_vis")      # 200-780 nm  (default)
atlas = PhysicsAtlas(instrument="visible")     # 380-780 nm
atlas = PhysicsAtlas(instrument="uv")          # 200-400 nm
atlas = PhysicsAtlas(instrument="nir")         # 780-2500 nm
atlas = PhysicsAtlas(instrument="vis_nir")     # 380-1100 nm
atlas = PhysicsAtlas(instrument="plasma_vis")  # 380-780 nm  (ICP-OES, arc/spark)
atlas = PhysicsAtlas(instrument="solar")       # 300-1000 nm (Fraunhofer lines)
atlas = PhysicsAtlas(instrument="full")        # 200-2500 nm (simulation)

# Custom range (advanced)
atlas = PhysicsAtlas.custom(wl_min=450, wl_max=700)
```

---

## The atlas — 65 entries

### Mathematical families (44)

| Category | Entries |
|---|---|
| **Resonance** | Gaussian, Lorentzian, Voigt, Double Gaussian, Double Lorentzian, Fano resonance, Damped sine, Sech2 soliton, Gaussian derivative, Gaussian x sine |
| **Phase transitions** | Sigmoid, Tanh critical, Landau order parameter, Cusp catastrophe, Critical power law, Finite-size scaling |
| **Complex systems** | Stretched exponential, Power law with cutoff, SOC proxy (1/f^1.5), Percolation threshold, Scale-free power law |
| **Information** | KL divergence proxy, Mutual information decay, Markov autocorrelation |
| **Dynamics** | AR(1) process, Response function, Memory kernel, Hysteresis proxy, Exponential decay, Chirp |
| **Geometry** | Discrete curvature, Geodesic proxy, Distance to manifold, Airy function |
| **Signals** | Sine, Cosine, Sinc, Step, Square, Sawtooth, Triangle |
| **Noise** | White noise, Pink noise (1/f), Brown noise (1/f2) |

### Element emission spectra (21)

H, He, Li, Na, K, Ca, Mg, Fe, Cu, Zn, Hg, Ne, Ar, Sr, Ba, Cr, Mn, Ni, Pb, Cd, plus Hg calibration lamp.

Lines from the NIST Atomic Spectra Database, modeled as Lorentzian profiles. Each match preserves both layers of information:

```
Lorentzian | Sodium (Na)
[element] Sodium D doublet. Flame tests (yellow), street lamps, solar Fraunhofer lines.
```

---

## Reading the output

### Clusters

Matches are grouped by proximity. A tight cluster means those hypotheses are indistinguishable by fingerprint alone — additional physical reasoning is needed to separate them.

### Ambiguity flag

When the gap between the top two matches is below the threshold (default 5%), the report is flagged:

```
Ambiguous top cluster (gap < 5%). Multiple hypotheses are equally compatible.
```

This is intentional. The library does not force a single answer when the evidence is insufficient.

### Amplitude invariance

The fingerprint is amplitude-normalized. A Gaussian at 1 mW and at 1 kW produce identical fingerprints. What matters is the shape, not the intensity.

---

## Atlas validation

```python
report = atlas.validate(separation_threshold=5.0)
print(report)
# Shows all atlas pairs that are too close to distinguish reliably
```

---

## Design philosophy

- **Hypotheses, not conclusions.** The output is a ranked similarity distribution, not a classification.
- **Shape over intensity.** Amplitude is never part of the fingerprint.
- **Ambiguity is shown, not hidden.** When evidence is insufficient, the report says so.
- **Two layers of meaning.** Every match carries the mathematical form *and* the physical interpretation.
- **Instrument-aware.** Elements with no lines in your measurement range are excluded automatically.

---

## Roadmap

- **v0.2** — Composition grammar: `Gaussian * Lorentzian`, `Exponential + Sine`, physically motivated combinations as new atlas entries
- **v0.3** — Larger atlas (200+ entries), user-extensible families
- **v0.4** — Visualization: similarity heatmap, fingerprint radar chart
- **v0.5** — Optional ML-based embedding for higher-dimensional feature discrimination

---

## Author

**Franco Fitte**

---

## License

MIT
