1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
//! 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 _: = ;
// 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!;