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
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
//! 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; updated 2026-07-21 for Issue #455)
//!
//! **Integers / structural sites only**, EXCEPT `LN_3_FACTORIAL` which is a float
//! normalizer. Originally excluded from centralization to avoid changing computed
//! values, but Issue #455 requires unification to fix a ~3e-13 typo drift between
//! two definitions. The float is now included here as the authoritative source.
//!
//! The two float normalizers that lived elsewhere in the codebase were:
//! - `interbar_math/tier3.rs:65`: `1.791_759_469_228_055` (correct)
//! - `intrabar/features.rs:103`: `1.791_759_469_228_327` (typo: last three digits differ)
//!
//! Centralizing only the integer/structural embedding (before this fix) was
//! 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;
/// Natural logarithm of 3! = ln(6)
///
/// Exact value: 1.791759469228055 (IEEE-754 f64 round-to-nearest of f64::ln(6.0))
/// Equivalent: std::f64::consts::LN_2 + std::f64::consts::LN_3
///
/// Used for Shannon entropy normalization in permutation entropy (Bandt-Pompe, M=3):
/// max_entropy = ln(3!) = ln(6), so normalized entropy = H / LN_3_FACTORIAL ∈ [0, 1]
///
/// Used by:
/// - Inter-bar: `lookback_permutation_entropy` (via interbar_math/tier3.rs)
/// - Intra-bar: `intra_permutation_entropy` (via intrabar/features.rs)
///
/// Issue #455: Unified from two definitions with ~3e-13 typo drift:
/// - `interbar_math/tier3.rs:65`: `1.791_759_469_228_055` (correct)
/// - `intrabar/features.rs:103`: `1.791_759_469_228_327` (typo)
pub const LN_3_FACTORIAL: f64 = 1.791_759_469_228_055;
// 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!;