# iqdb-quantize v0.2.0 — Scalar Quantization (SQ8) + the `Quantizer` Trait
**The first scheme lands.** v0.2.0 turns the scaffold into a working quantizer: scalar quantization (SQ8, 4× compression) behind the `Quantizer` trait every scheme will implement, with per-dimension affine calibration, asymmetric distance through `iqdb-distance`, and typed-error input validation. This is the foundation the product and binary schemes are built onto.
## What is iqdb-quantize?
The memory-efficiency layer of the iQDB vector database. It compresses `f32` vectors into compact codes that preserve search quality, and computes distance directly against the compressed form. Quantization is lossy by design, so the contract is two rules: **train on representative data**, and **search quantized, then rerank with full `f32`**.
## What's new in 0.2.0
### The `Quantizer` trait
Every scheme in the crate implements one trait. Each method is fallible and returns `iqdb_types::Result`, so bad input becomes a typed `IqdbError` instead of a panic.
```rust
use iqdb_quantize::{Quantizer, ScalarQuantizer};
use iqdb_types::DistanceMetric;
let training = [
vec![0.10_f32, 0.20, 0.30],
vec![0.15, 0.18, 0.32],
vec![0.12, 0.22, 0.28],
];
let refs: Vec<&[f32]> = training.iter().map(Vec::as_slice).collect();
let mut sq = ScalarQuantizer::new();
sq.train(&refs).expect("non-empty, consistent dims, finite values");
let code = sq.quantize(&[0.11_f32, 0.21, 0.29]).expect("dim matches training");
let d = sq
.distance(&[0.10_f32, 0.20, 0.30], &code, DistanceMetric::Cosine)
.expect("dim matches");
assert!(d.is_finite());
```
### `ScalarQuantizer` (SQ8)
Per-dimension affine calibration: each dimension stores its trained `min` and a `scale = (max - min) / 255`. Encoding clamps into `[min, max]`, scales onto `[0, 255]`, and rounds; decoding reverses it. A zero-range dimension (`max == min`) collapses to a `scale = 0` lane — every byte is `0` and `dequantize` returns `min`, so there is no division by zero. Asymmetric distance keeps the query in `f32`, dequantizes the candidate, and routes through `iqdb_distance::compute` for **every** `DistanceMetric`.
### `Sq8Code` — owned and immutable
Codes are produced exclusively by `ScalarQuantizer::quantize`; there are no public mutators, so a code's contents always match the calibrated quantizer that made it.
```rust
use iqdb_quantize::{Quantizer, ScalarQuantizer};
let mut sq = ScalarQuantizer::new();
sq.train(&[&[0.0_f32, 1.0, 2.0][..]]).expect("ok");
let code = sq.quantize(&[0.5_f32, 0.5, 0.5]).expect("ok");
assert_eq!(code.len(), 3); // one u8 per dimension
assert_eq!(code.as_bytes().len(), 3); // borrow the raw bytes
```
### Typed-error validation — never a panic
Empty or non-finite vectors surface as `IqdbError::InvalidVector`; dimension drift as `IqdbError::DimensionMismatch`; an empty training set or a hot call before `train` as `IqdbError::InvalidConfig`.
### `VERSION`
`iqdb_quantize::VERSION` exposes the crate's compile-time `CARGO_PKG_VERSION` for diagnostics and version-skew checks across the family.
## Breaking changes
**Pre-1.0 API churn.** Relative to the v0.1.0 scaffold (no domain logic), everything here is an addition. The crate now depends on `iqdb-types` 1.0 (the shared `DistanceMetric` / `IqdbError` / `Result` vocabulary) and `iqdb-distance` 1.0 (the f32 distance kernels the SQ8 asymmetric path delegates to).
## Verification
SQ8 is covered by property tests for round-trip error bounds, distance finiteness, and metric-aware non-negativity, plus edge cases (empty / single-vector training, zero-range dimensions, dimension mismatch, quantize/distance before train, NaN / infinite inputs, out-of-range clamp). The same gates run across the CI matrix on stable and the 1.87 MSRV:
```bash
cargo fmt --all -- --check
cargo clippy --all-targets --all-features -- -D warnings
cargo test --all-features
RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --all-features
```
MSRV: Rust 1.87.
## What's next
- **v0.3.0 — product quantization.** k-means codebooks per subvector and asymmetric distance computation (ADC) by table lookup — the primitive IVF-PQ is built on.
## Installation
```toml
[dependencies]
iqdb-quantize = "0.2"
```
## Documentation
- [README](https://github.com/jamesgober/iqdb-quantize/blob/main/README.md)
- [API reference](https://github.com/jamesgober/iqdb-quantize/blob/main/docs/API.md)
- [ROADMAP](https://github.com/jamesgober/iqdb-quantize/blob/main/dev/ROADMAP.md)
- [Standards (REPS)](https://github.com/jamesgober/iqdb-quantize/blob/main/REPS.md)
- [CHANGELOG](https://github.com/jamesgober/iqdb-quantize/blob/main/CHANGELOG.md)
---
**Full diff:** [`v0.1.0...v0.2.0`](https://github.com/jamesgober/iqdb-quantize/compare/v0.1.0...v0.2.0).
**Changelog:** [`CHANGELOG.md`](https://github.com/jamesgober/iqdb-quantize/blob/main/CHANGELOG.md).