ALICE-Analytics
Probabilistic data structures and streaming statistics with stated error
bounds: HyperLogLog cardinality, DDSketch quantiles with a relative-error
guarantee, Count-Min frequencies and heavy hitters, streaming moments /
covariance / regression, windowed aggregates and anomaly detectors. The core is no_std with fixed-size, stack-allocated
sketches. With the law feature it also summarises how a set of points deviates
from a fitted alice_zip::law::SignalLaw.
It is not a time-series database, a metrics backend or a dashboard: it keeps summaries in memory and leaves storage and transport to the caller. Sketches answer approximate queries; when exact quantiles or exact distinct counts are needed, keep the raw values.
License: Apache-2.0 (0.3.0 and earlier: MIT OR Apache-2.0)
Contents
- Installation
- Example
- Residual distribution of a law
- Features
- Modules
- Error bounds
- Determinism
- Minimum supported Rust version
- Building and testing
- Related crates
- License
Installation
# with the residual summary of alice-zip laws
Example
use ;
// quantiles with a relative-error guarantee of 1 %
let mut latency = new;
for ms in 1..=1000
let p99 = latency.quantile;
assert!;
// distinct count in 16 KiB of registers
let mut users = new;
for id in 0..10_000_u64
let n = users.cardinality;
assert!;
Residual distribution of a law
law::residual_summary(&law, &points) (feature law) streams the residuals
y − f(x) of points about an alice_zip::law::SignalLaw into a
DDSketch2048 and returns a ResidualSummary:
| Field | Meaning |
|---|---|
count |
points summarised |
out_of_range |
points whose x is outside the law's valid range (or not finite); the law is not evaluated there, nothing is extrapolated |
non_finite |
points in range whose y − f(x) is NaN or infinite; counted, not summarised |
distribution |
None when nothing was summarised; otherwise mean, min, max, max_abs (exact) and p50, p90, p99 (sketched) |
relative_accuracy |
α: each reported quantile q̂ of a residual q satisfies |q̂ − q| ≤ α·|q| |
accurate_range |
residual magnitudes for which that bound holds; zero is always exact |
outside_accuracy |
non-zero residuals outside accurate_range (0 means the bound holds for every quantile) |
The quantile of rank ⌈q·n⌉ is reported, the same order statistic the
sketch defines. Residuals are divided by a power-of-two scale before they are
inserted (by default the largest power of two not above the law's own residual
RMS), which moves accurate_range to the size of the residuals without
rounding. law::ResidualSketch is the streaming form: push one point at a
time, read quantile(q) for any q, choose α and the scale.
use residual_summary;
let s = residual_summary;
println!;
if let Some = s.distribution
A complete program is in examples/residual_summary.rs
(cargo run --example residual_summary --features law).
Features
| Feature | Default | Description |
|---|---|---|
std |
yes | standard-library float functions, entropy-seeded privacy constructors, JSON / Prometheus export |
law |
no | law module: residual summary of an alice_zip::law::SignalLaw (adds alice-zip without its std feature, and alloc) |
simd |
no | no effect; kept so existing feature lists still build |
Without std the privacy mechanisms take an explicit seed, and sqrt /
ceil / floor / round / mul_add come from libm; IEEE 754 requires all
five to be correctly rounded, so they return the same bits as the inherent
methods. The transcendentals go through alice-det-math in both builds, so a
no_std build and a std build agree bit for bit (see
Determinism). CI builds the library for
thumbv7em-none-eabihf with and without law.
Modules
| Module | Contents |
|---|---|
sketch |
HyperLogLog10/12/14/16, DDSketch128 … DDSketch2048, CountMinSketch1024x5 / 2048x7 / 4096x5, HeavyHitters5/10/20, FnvHasher, Mergeable |
stats |
StreamingStats (mean / variance / skewness / kurtosis), CovarianceMatrix, quantile_sorted, percentile_rank, iqr |
window |
TumblingWindow, SlidingWindow, HierarchicalRollup |
streaming_ops |
SimpleMovingAverage, ExponentialMovingAverage, ChangeRate, LinearRegression, LinearRegressionFull |
anomaly |
StreamingMedian, MadDetector, ZScoreDetector, EwmaDetector, CompositeDetector |
privacy |
deprecated, not differentially private (predictable noise source; its clock-seeded constructors are removed in 0.6.0, the module stays deprecated): use alice_crypto::dp |
pipeline |
MetricPipeline, MetricRegistry, MetricSnapshot, RingBuffer |
export |
JSON and Prometheus text for MetricSnapshot (std) |
law |
residual_summary, ResidualSketch, ResidualSummary (feature law) |
Error bounds
| Structure | Bound | Checked in |
|---|---|---|
DDSketch |
|q̂ − q| ≤ α·|q| for magnitudes inside accurate_range(), negative values included |
tests/analytic_oracle.rs |
HyperLogLog |
standard error 1.04/√m (m registers) |
tests/analytic_oracle.rs |
CountMinSketch |
estimate ≥ true count, overestimate ≤ ε·N, ε = e/w |
tests/analytic_oracle.rs |
law::ResidualSketch |
the DDSketch bound on residuals, exact mean / min / max |
tests/law_residual.rs |
The expected values in these tests are closed forms or two-pass references written in the test files, not outputs of the functions under test.
A non-finite sample (NaN, ±inf) is not a magnitude: DDSketch::insert
counts it in non_finite() and leaves count, sum, min, max and every
bin alone, so the ranks behind a quantile are the ranks of the values that
reached a bin. This is the same classification the law module applies to a
non-finite residual. A finite magnitude outside accurate_range() is
counted and filed in the nearest edge bin: it keeps its rank, but not the α
bound.
Determinism
The same inputs produce the same bits on every supported target. This matters because sketches are merged across machines, telemetry is replayed, and an audit may re-derive a quantile from the raw events: a last-ulp difference between two hosts turns an estimate into two estimates.
| Tier | What | Why it is bit-exact |
|---|---|---|
| IEEE basic operations | + − × ÷, sqrt, mul_add, ceil / floor / round |
IEEE 754 requires correct rounding, so every target agrees. mul_add is a fused multiply-add with a single rounding; a target without an FMA instruction uses the correctly rounded software fma |
| Transcendentals | ln, exp, powf |
Routed through alice-det-math, whose kernels are built from the operations above in a fixed evaluation order. The platform libm is not used: its last ulp differs between macOS, glibc, MSVC and wasm |
| Integer powers | γⁿ for the DDSketch bin layout |
Binary exponentiation with a fixed association order, rather than powi, whose multiplication tree is unspecified |
| Pseudo-random | XorShift64 and everything seeded from it |
Integer state; a seeded constructor replays exactly. The entropy-seeded constructors (std only) are by design not reproducible |
Enforcement is mechanical, in two layers:
clippy.tomllists the inherentf32/f64transcendentals underdisallowed-methods, and CI runscargo clippy --all-targets --all-features -- -D warnings, so reintroducingx.ln()is a compile error rather than a silent divergence. This is the layer that catches a regression on the machine that writes it.tests/determinism_golden.rsserialises the outputs of seven scenarios (one per module with float arithmetic) bit for bit and compares a SHA-256 against a recorded constant. Each scenario also asserts that it serialised a non-zero number of bytes, so a scenario that stops exercising its module fails instead of passing on the hash of an empty buffer. CI runs the file on macOSaarch64, Linuxx86_64, Linuxaarch64and Windowsx86_64.
Naming the arithmetic
Bit-exactness across machines is only half of what a stored estimate needs.
The other half is being able to say which arithmetic produced it, because a
quantile computed under one implementation of ln and one computed under
another are two numbers, however close they look.
alice_analytics::SEMANTICS_ID is that name: the 32-byte identifier
alice-det-math publishes for its own numeric behaviour, re-exported here.
Record it next to any estimate that is stored, transmitted or merged; two
results are comparable as numbers only if the identifier agrees. It is pinned
as hex by golden_semantics_id, so an arithmetic change cannot reach a release
unnoticed.
With the law feature, law::ResidualSummary::law_id goes one step further
and names both halves at once: it is the summarised law's own identifier taken
under SEMANTICS_ID, so it changes when f(x) changes and when the
arithmetic changes. It covers what f(x) reads (the domain and the
coefficients) and not how the law was obtained, so two laws fitted from
different measurements that evaluate identically share one identifier.
Determinism is not correctness. A golden hash pins whatever the code does today, including a mistake; the error bounds above are checked separately against closed forms. The two are independent and both are required.
Outside the guarantee: a target that does not honour IEEE 754 for the
basic operations (32-bit x86 built for x87 without SSE2), any build with
fast-math style flags, and the entropy-seeded privacy constructors. Results
across a version change of this crate or of alice-det-math are also not
pinned — only results across platforms at a given version. What SEMANTICS_ID
adds is not a promise that the arithmetic never changes, but the ability to
tell that it did.
Minimum supported Rust version
rust-version = "1.87" (measured: 1.86 does not build the library). CI checks
the library on 1.87 with default and with all features; the development
toolchain is pinned in rust-toolchain.toml.
Building and testing
Related crates
alice-det-math— the cross-platform bit-exactln/exp/powfevery float path here goes throughalice-zip—law::SignalLaw: a fitted law with its valid range, residual statistics, provenance and oracle cases, whose residual distribution thelawfeature summarises
ALICE-Law integration
Generated by ALICE-Law scripts/gen-law-readme-block.py from ALICE-Law 6a0a5587c152 and this repository's alice-law-conformance.toml; do not edit it by hand.
| Release train | alice-analytics 0.5.0 (published), law dependency: yes |
| Arithmetic | alice-det-math 0.5.0 in the train, arithmetic identifier 135c407d4249c1c200e4d1be79a12e848ce5c355fc3a918da965849e697ef000; this crate requires alice-det-math = "0.5.1" |
| End to end check | covered (alice-analytics = "0.5"), digest 966a78784bb4e3341ac6627d519db896f398176b71c2402b188294db6c8e3728, the same on aarch64 and x86_64 |
Conformance (G1-G14)
| gate | status | note |
|---|---|---|
| G1 no-libm-direct | pass | every method the G1 list marks banned is in clippy.toml for f32 and f64, the pending powi is banned, every feature and the no_std target are linted with -D … |
| G2 ieee-max-min | known unmet | no Law helper for max/min and no default-deny manifest of raw_* exceptions exist. The library calls f64::max / f64::min / clamp directly at src/anomaly.rs:799, … |
| G3 no-outer-fneg-of-fma | known unmet | no checker for an outer negation of an fma result exists in this repository. mul_add is used in src/stats.rs, src/privacy.rs, src/anomaly.rs and … |
| G4 debug-release-arch-goldens | known unmet | the cross-platform golden file is tests/determinism_golden.rs (golden_sketch, golden_stats, golden_window, golden_anomaly, golden_privacy, … |
| G5 release-profile-test-leg | known unmet | no CI step runs the test suite under the release profile: ci.yml:40,43,50,52 are cargo test without --release, and the aarch64 legs (macos-latest, … |
| G6 semantics-id | known unmet | src/lib.rs:80 is pub use alice_det_math::SEMANTICS_ID;: the crate re-exports its arithmetic crate's identifier and folds none of its own pins. Its own … |
| G7 handshake-carries-id | unverified | audit: the crate has no decode / read / from_bytes / open entry point, no extern "C" or no_mangle item, no pyo3 or wasm-bindgen export, no dyn Any or … |
| G8 persisted-semantics-id | unverified | undecided: the crate writes no file or database row, but src/export.rs:24 snapshot_to_json, :58 snapshots_to_json, :86 snapshot_to_prometheus and :146 … |
| G9 persisted-meaning-change-is-major | unverified | depends on G8 (the persisted identifier whose history G9 audits), which is undecided. For the in-process identifier the history is: SEMANTICS_ID first appears … |
| G10 no-run-varying-artifacts | pass | the clock-seeded constructors of the deprecated privacy module (XorShift64::from_entropy, impl Default for XorShift64, LaplaceNoise::new, Rappor::new, … |
| G11 gate-self-tests | known unmet | the Law gates that are implemented here have no self-test: the G1 clippy lint has no canary that injects a banned call and confirms the lint still fires (a … |
| G12 public-repo-text-rules | pass | the CI job fetches ALICE-Law's own scripts/public_text_check.py at the revision named in .github/public-text-pin and refuses it unless its SHA-256 is the one … |
| G13 no-unspecified-order | unverified | audit: no HashMap or HashSet in src (the only one in the repository is tests/analytic_oracle.rs:434, in a test), no rayon, no thread spawn and no par_iter in … |
| G14 float-environment-invariants | known unmet | the build configuration sets no fast-math-style flag and no target-cpu=native: there is no .cargo directory and no build.rs, no RUSTFLAGS in any workflow, no … |
Features and the guarantee
| feature | guarantee | note |
|---|---|---|
law |
not declared | |
simd |
not declared | |
std |
not declared |
License
- Apache License, Version 2.0, from 0.4.0
- Redistributions, in source or binary form, carry LICENSE-APACHE and NOTICE (Apache-2.0 §4); the notice need not appear in an application's user interface
- Earlier versions keep the licence they were released under:
alice-analyticsup to 0.3.0 on crates.io is MIT OR Apache-2.0 - The "ALICE" names are covered by TRADEMARK_NOTICE, whatever the licence