Skip to main content

dsfb_semiconductor/
lib.rs

1//! `dsfb-semiconductor` — deterministic DSFB kernel and SECOM benchmark companion.
2//!
3//! # Non-Intrusion Guarantees
4//!
5//! DSFB is a **read-only supervisory system** operating on residual streams.
6//!
7//! | Guarantee | Enforcement |
8//! |-----------|-------------|
9//! | No mutation of upstream data | Observer API accepts only `&[f64]` (shared ref) |
10//! | No control-path influence | No write path into any upstream data structure |
11//! | Deterministic outputs under identical inputs | Pure function composition over fixed params |
12//! | Removable without system impact | Advisory outputs only; zero coupling |
13//!
14//! # Feature flags
15//!
16//! | Feature | Default | Effect |
17//! |---------|---------|--------|
18//! | `std`   | yes     | Enables CLI, I/O, plotting, networking, and all dataset adapters. |
19//! | *(none)* | —      | Kernel-only build: sign, grammar, syntax, semantics, policy, process_context, units. Suitable for bare-metal / RTOS / FPGA deployments. |
20//!
21//! # `no_std` kernel surface
22//!
23//! When compiled with `--no-default-features`, the following modules are
24//! available and require only `alloc`:
25//!
26//! - [`process_context`] — recipe-step admissibility LUT and maintenance hysteresis
27//! - [`units`] — type-safe physical quantity newtypes
28//! - [`signs`] — residual sign computation (drift, slew)
29//! - [`sign`] — streaming sign point construction
30//! - [`grammar`] — three-state admissibility FSM with hysteresis
31//! - [`grammar::layer`] — six-state streaming grammar
32//! - [`syntax`] — motif classifier
33//! - [`policy`] — decision ranking
34//! - [`semantics`] — heuristics bank lookup
35//! - [`config`] — pipeline configuration
36//! - [`nominal`] — healthy-window model
37//! - [`residual`] — residual set construction
38//! - [`input`] — residual and alarm stream types
39
40#![cfg_attr(not(feature = "std"), no_std)]
41
42// When std is disabled, pull in alloc for Vec, String, BTreeMap, format!, etc.
43#[cfg(not(feature = "std"))]
44#[macro_use]
45extern crate alloc;
46
47#[cfg(not(feature = "std"))]
48use alloc::vec::Vec;
49
50// ── Minimal observer API ───────────────────────────────────────────────────────────────
51
52/// A structured episode produced by the DSFB observer layer.
53///
54/// Advisory only. No upstream state is modified.
55#[derive(Debug, Clone, PartialEq)]
56pub struct Episode {
57    /// Sample index within the input slice.
58    pub index: usize,
59    /// Squared residual norm (`|x - nominal|²`), avoiding `sqrt` for kernel compatibility.
60    pub residual_norm_sq: f64,
61    /// Rolling drift estimate (mean first-difference of absolute residuals over last 5 samples).
62    pub drift: f64,
63    /// Grammar state: `"Admissible"`, `"Boundary"`, or `"Violation"`.
64    pub grammar: &'static str,
65    /// Advisory decision: `"Silent"`, `"Review"`, or `"Escalate"`.
66    pub decision: &'static str,
67}
68
69/// A list of [`Episode`] values returned by [`observe`].
70pub type Episodes = Vec<Episode>;
71
72/// Read-only observation of a raw residual slice.
73///
74/// Accepts a **shared reference only** — no write-back, no upstream coupling,
75/// no side effects. Deterministic: identical inputs produce identical outputs.
76///
77/// NaN or non-finite samples are treated as imputed (missing data) and always
78/// return `grammar = "Admissible"`, `decision = "Silent"`.
79///
80/// # Key guarantees
81///
82/// - No mutable access to any upstream structure.
83/// - Deterministic: identical ordered inputs → identical episode sequence.
84/// - No side effects of any kind.
85///
86/// # Example
87///
88/// ```
89/// let residuals: &[f64] = &[0.1, 0.2, 0.5, 1.2, 2.1];
90/// let episodes = dsfb_semiconductor::observe(residuals);
91/// for e in &episodes {
92///     // advisory only — no write-back, no coupling
93///     println!("index={} grammar={} decision={}", e.index, e.grammar, e.decision);
94/// }
95/// ```
96pub fn observe(residuals: &[f64]) -> Episodes {
97    if residuals.is_empty() {
98        return Episodes::new();
99    }
100
101    const DRIFT_WINDOW: usize = 5;
102
103    // Nominal estimate from first 20 % of samples (at least 1, at most all).
104    let nominal_len = (residuals.len() / 5).max(1).min(residuals.len());
105    let mut nominal_sum = 0.0f64;
106    let mut nominal_count = 0usize;
107    for x in &residuals[..nominal_len] {
108        if x.is_finite() {
109            nominal_sum += x;
110            nominal_count += 1;
111        }
112    }
113    let nominal_mean = if nominal_count == 0 {
114        0.0
115    } else {
116        nominal_sum / nominal_count as f64
117    };
118
119    // Threshold: rho^2 = (3*std)^2 = 9 * var.  Avoids sqrt for no_std compat.
120    let mut var_sum = 0.0f64;
121    for x in &residuals[..nominal_len] {
122        if x.is_finite() {
123            let d = x - nominal_mean;
124            var_sum += d * d;
125        }
126    }
127    let var = var_sum / nominal_count.max(1) as f64;
128    let rho_sq = (9.0 * var).max(1e-18);
129    let boundary_rho_sq = 0.25 * rho_sq; // (0.5 * rho)^2
130
131    let mut episodes = Episodes::with_capacity(residuals.len());
132
133    for i in 0..residuals.len() {
134        let x = residuals[i];
135
136        // Imputed (NaN / inf) samples never trigger violation.
137        if !x.is_finite() {
138            episodes.push(Episode {
139                index: i,
140                residual_norm_sq: 0.0,
141                drift: 0.0,
142                grammar: "Admissible",
143                decision: "Silent",
144            });
145            continue;
146        }
147
148        let r = x - nominal_mean;
149        let r_sq = r * r;
150
151        // Rolling drift: mean first-difference of |residual| over last DRIFT_WINDOW samples.
152        let drift = if i == 0 {
153            0.0
154        } else {
155            let start = i.saturating_sub(DRIFT_WINDOW);
156            let count = i - start;
157            let mut d_sum = 0.0f64;
158            for j in start..i {
159                let a = if residuals[j].is_finite() {
160                    (residuals[j] - nominal_mean).abs()
161                } else {
162                    0.0
163                };
164                let b = if residuals[j + 1].is_finite() {
165                    (residuals[j + 1] - nominal_mean).abs()
166                } else {
167                    0.0
168                };
169                d_sum += b - a;
170            }
171            d_sum / count as f64
172        };
173
174        let grammar = if r_sq > rho_sq {
175            "Violation"
176        } else if r_sq > boundary_rho_sq && drift > 0.0 {
177            "Boundary"
178        } else {
179            "Admissible"
180        };
181
182        let decision = match grammar {
183            "Violation" => "Escalate",
184            "Boundary" => "Review",
185            _ => "Silent",
186        };
187
188        episodes.push(Episode {
189            index: i,
190            residual_norm_sq: r_sq,
191            drift,
192            grammar,
193            decision,
194        });
195    }
196
197    episodes
198}
199
200// ── Kernel modules (always compiled) ───────────────────────────────────────────────────
201pub mod config;
202pub mod grammar;
203pub mod input;
204pub mod nominal;
205pub mod policy;
206pub mod process_context;
207pub mod residual;
208pub mod semantics;
209pub mod sign;
210pub mod signs;
211pub mod syntax;
212pub mod units;
213
214// ── std-only modules ────────────────────────────────────────────────────────────
215#[cfg(feature = "std")]
216pub mod baselines;
217#[cfg(feature = "std")]
218pub mod calibration;
219#[cfg(feature = "std")]
220pub mod cli;
221#[cfg(feature = "std")]
222pub mod cohort;
223#[cfg(feature = "std")]
224pub mod dataset;
225#[cfg(feature = "std")]
226pub mod error;
227#[cfg(feature = "std")]
228pub mod failure_driven;
229#[cfg(feature = "std")]
230pub mod heuristics;
231#[cfg(feature = "std")]
232pub mod interface;
233#[cfg(feature = "std")]
234pub mod metrics;
235#[cfg(feature = "std")]
236pub mod missingness;
237#[cfg(feature = "std")]
238pub mod multivariate_observer;
239#[cfg(feature = "std")]
240pub mod non_intrusive;
241#[cfg(feature = "std")]
242pub mod output_paths;
243#[cfg(feature = "std")]
244pub mod phm2018_loader;
245#[cfg(feature = "std")]
246pub mod pipeline;
247#[cfg(feature = "std")]
248pub mod plots;
249#[cfg(feature = "std")]
250pub mod precursor;
251#[cfg(feature = "std")]
252pub mod preprocessing;
253#[cfg(feature = "std")]
254pub mod report;
255#[cfg(feature = "std")]
256pub mod secom_addendum;
257#[cfg(feature = "std")]
258pub mod semiotics;
259#[cfg(feature = "std")]
260pub mod signature;
261#[cfg(feature = "std")]
262pub mod traceability;
263#[cfg(feature = "std")]
264pub mod unified_value_figure;