opendeviationbar-core 13.76.0

Core open deviation bar construction algorithm with temporal integrity guarantees
Documentation
//! Bandt–Pompe ordinal-pattern embedding constants (structural, integer-only).
//!
//! These centralize the embedding `(m = 3, τ = 1)` that is shared across the
//! Bandt–Pompe family of features — permutation entropy, weighted permutation
//! entropy, Fisher information, and SVD entropy — and is **already shipped in
//! production** via the hardcoded `ordinal_pattern_index_m3` path and the
//! ClickHouse columns `lookback_permutation_entropy` / `intra_permutation_entropy`.
//!
//! # Scope (locked — operator, 2026-06-04)
//!
//! **Integers / structural sites only.** The two `ln(3!)` float normalizers that
//! live elsewhere in the codebase are intentionally **not** centralized here:
//! touching any float would change computed values. Centralizing only the
//! integer/structural embedding is provably value-neutral —
//! `3usize` → [`ORDINAL_M`], `[u16; 6]` → `[u16; ORDINAL_M_FACTORIAL]`, and
//! `embed_dim == 3` → `embed_dim == ORDINAL_M` are the same integers, hence the
//! same output (proven bit-exact by the golden-snapshot gate).
//!
//! `m = 3, τ = 1` are compile-time constants with **no runtime / env / pydantic
//! knob**: a project-wide constant that can be tuned is not a constant. See
//! `docs/adr/2026-06-03-bandt-pompe-embedding-constant.md`.

/// Embedding dimension `m` for the Bandt–Pompe ordinal-pattern construction.
///
/// The production permutation-entropy path is specialized for `m = 3`
/// (`ordinal_pattern_index_m3`); this is the structural source of truth for that
/// dimension across the Bandt–Pompe feature family.
///
/// This is a **structural label, not a retargeting knob.** The shipped kernels
/// (`tier3::ordinal_pattern_index_m3`, the SIMD triplet unroll, the `n - 2`
/// triplet count, the `features.rs` `embed_dim == ORDINAL_M` decision tree and
/// its `MAX_ENTROPY_M3` normalizer) are *hardcoded* for `m = 3`. Changing this
/// value would NOT retarget those kernels — it would silently compute m=3
/// patterns under a mismatched buffer/denominator. The `ORDINAL_M == 3` invariant
/// below converts any such edit into a build failure.
pub const ORDINAL_M: usize = 3;

/// Embedding delay `τ` (gap, in samples, between embedded coordinates).
///
/// `τ = 1` uses consecutive samples.
///
/// NOTE: this constant is currently **unused in the production Rust path** — the
/// shipped permutation-entropy code (`ordinal_pattern_index_m3`) is specialized for
/// `m = 3, τ = 1` and reads three *consecutive* samples, so `τ = 1` is implicit and
/// there is no `τ` site to substitute. It is kept for completeness and as the
/// authoritative mirror of the Python eval SSoT (`entropy_candidates.ORDINAL_TAU`),
/// pinning the embedding's full `(m, τ)` definition in one place. (Surfaced by the
/// 2026-06-05 adversarial review; intentionally retained, not removed.)
pub const ORDINAL_TAU: usize = 1;

/// `m!` — the number of distinct ordinal patterns for embedding dimension
/// [`ORDINAL_M`] (`3! = 6`).
///
/// Used to size fixed-length ordinal-pattern count buffers, e.g.
/// `[u16; ORDINAL_M_FACTORIAL]`.
pub const ORDINAL_M_FACTORIAL: usize = 6;

// Compile-time invariant: ORDINAL_M_FACTORIAL must equal ORDINAL_M! exactly.
// Integer-only check — guards the structural relationship without touching any
// float. If ORDINAL_M ever changes, this fails the build until the buffer-sizing
// constant is updated to match.
const _: () = {
    let mut factorial = 1usize;
    let mut i = 2usize;
    while i <= ORDINAL_M {
        factorial *= i;
        i += 1;
    }
    assert!(
        factorial == ORDINAL_M_FACTORIAL,
        "ORDINAL_M_FACTORIAL must equal ORDINAL_M! (m factorial)"
    );
};

// Compile-time invariant: the shipped permutation-entropy kernels are HARDCODED
// for m = 3 (ordinal_pattern_index_m3, the SIMD triplet unroll, `n - 2`, the
// `embed_dim == ORDINAL_M` decision tree + MAX_ENTROPY_M3 normalizer). ORDINAL_M
// is the structural label for that dimension, not a knob that retargets them.
// Pinning it here turns an accidental retarget into a build error instead of
// silently-wrong output under a mismatched buffer/denominator.
const _: () = assert!(
    ORDINAL_M == 3,
    "production permutation-entropy kernels are hardcoded for m = 3; \
     retargeting m requires rewriting ordinal_pattern_index_m3 + the m=3 decision tree, \
     not just changing ORDINAL_M"
);

#[cfg(test)]
mod tests {
    use super::*;

    /// Pins the embedding constants to their ratified values. A hermetic, CI-runnable
    /// regression tripwire (needs no fixtures / `test-utils`): complements — does not
    /// replace — the golden-snapshot bit-exact gate. If any value changes, this trips
    /// here long before the 863K-trade golden is consulted.
    #[test]
    fn const_values_pinned() {
        assert_eq!(ORDINAL_M, 3, "Bandt–Pompe embedding dimension m");
        assert_eq!(ORDINAL_TAU, 1, "Bandt–Pompe embedding delay τ");
        assert_eq!(
            ORDINAL_M_FACTORIAL, 6,
            "m! (ordinal-pattern count / buffer size)"
        );
    }
}