#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(not(feature = "std"))]
#[macro_use]
extern crate alloc;
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
#[derive(Debug, Clone, PartialEq)]
pub struct Episode {
pub index: usize,
pub residual_norm_sq: f64,
pub drift: f64,
pub grammar: &'static str,
pub decision: &'static str,
}
pub type Episodes = Vec<Episode>;
pub fn observe(residuals: &[f64]) -> Episodes {
if residuals.is_empty() {
return Episodes::new();
}
const DRIFT_WINDOW: usize = 5;
let nominal_len = (residuals.len() / 5).max(1).min(residuals.len());
let mut nominal_sum = 0.0f64;
let mut nominal_count = 0usize;
for x in &residuals[..nominal_len] {
if x.is_finite() {
nominal_sum += x;
nominal_count += 1;
}
}
let nominal_mean = if nominal_count == 0 {
0.0
} else {
nominal_sum / nominal_count as f64
};
let mut var_sum = 0.0f64;
for x in &residuals[..nominal_len] {
if x.is_finite() {
let d = x - nominal_mean;
var_sum += d * d;
}
}
let var = var_sum / nominal_count.max(1) as f64;
let rho_sq = (9.0 * var).max(1e-18);
let boundary_rho_sq = 0.25 * rho_sq;
let mut episodes = Episodes::with_capacity(residuals.len());
for i in 0..residuals.len() {
let x = residuals[i];
if !x.is_finite() {
episodes.push(Episode {
index: i,
residual_norm_sq: 0.0,
drift: 0.0,
grammar: "Admissible",
decision: "Silent",
});
continue;
}
let r = x - nominal_mean;
let r_sq = r * r;
let drift = if i == 0 {
0.0
} else {
let start = i.saturating_sub(DRIFT_WINDOW);
let count = i - start;
let mut d_sum = 0.0f64;
for j in start..i {
let a = if residuals[j].is_finite() {
(residuals[j] - nominal_mean).abs()
} else {
0.0
};
let b = if residuals[j + 1].is_finite() {
(residuals[j + 1] - nominal_mean).abs()
} else {
0.0
};
d_sum += b - a;
}
d_sum / count as f64
};
let grammar = if r_sq > rho_sq {
"Violation"
} else if r_sq > boundary_rho_sq && drift > 0.0 {
"Boundary"
} else {
"Admissible"
};
let decision = match grammar {
"Violation" => "Escalate",
"Boundary" => "Review",
_ => "Silent",
};
episodes.push(Episode {
index: i,
residual_norm_sq: r_sq,
drift,
grammar,
decision,
});
}
episodes
}
pub mod config;
pub mod grammar;
pub mod input;
pub mod nominal;
pub mod policy;
pub mod process_context;
pub mod residual;
pub mod semantics;
pub mod sign;
pub mod signs;
pub mod syntax;
pub mod units;
#[cfg(feature = "std")]
pub mod baselines;
#[cfg(feature = "std")]
pub mod calibration;
#[cfg(feature = "std")]
pub mod cli;
#[cfg(feature = "std")]
pub mod cohort;
#[cfg(feature = "std")]
pub mod dataset;
#[cfg(feature = "std")]
pub mod error;
#[cfg(feature = "std")]
pub mod failure_driven;
#[cfg(feature = "std")]
pub mod heuristics;
#[cfg(feature = "std")]
pub mod interface;
#[cfg(feature = "std")]
pub mod metrics;
#[cfg(feature = "std")]
pub mod missingness;
#[cfg(feature = "std")]
pub mod multivariate_observer;
#[cfg(feature = "std")]
pub mod non_intrusive;
#[cfg(feature = "std")]
pub mod output_paths;
#[cfg(feature = "std")]
pub mod phm2018_loader;
#[cfg(feature = "std")]
pub mod pipeline;
#[cfg(feature = "std")]
pub mod plots;
#[cfg(feature = "std")]
pub mod precursor;
#[cfg(feature = "std")]
pub mod preprocessing;
#[cfg(feature = "std")]
pub mod report;
#[cfg(feature = "std")]
pub mod secom_addendum;
#[cfg(feature = "std")]
pub mod semiotics;
#[cfg(feature = "std")]
pub mod signature;
#[cfg(feature = "std")]
pub mod traceability;
#[cfg(feature = "std")]
pub mod unified_value_figure;