iqdb-quantize 1.0.0

Vector quantization (scalar, product, binary) for memory-efficient vector search - part of the iQDB family.
Documentation
# 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).