#[derive(Debug, Clone)]
pub enum JidokaViolation {
NaN { count: usize },
Inf { count: usize },
ZeroVariance { mean: f32 },
ShapeMismatch {
expected: Vec<usize>,
actual: Vec<usize>,
},
ChecksumFailed { expected: u32, actual: u32 },
}
impl fmt::Display for JidokaViolation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::NaN { count } => write!(f, "NaN values detected: {count}"),
Self::Inf { count } => write!(f, "Infinite values detected: {count}"),
Self::ZeroVariance { mean } => write!(f, "Zero variance with mean={mean}"),
Self::ShapeMismatch { expected, actual } => {
write!(f, "Shape mismatch: expected {expected:?}, got {actual:?}")
}
Self::ChecksumFailed { expected, actual } => {
write!(
f,
"Checksum failed: expected {expected:#x}, got {actual:#x}"
)
}
}
}
}
#[derive(Debug, Clone)]
pub struct AnomalyDetector {
mean: Vec<f32>,
inv_cov: Vec<Vec<f32>>,
shrinkage: f32,
n_samples: usize,
threshold: f32,
}
impl AnomalyDetector {
#[must_use]
pub fn fit(training_data: &[TensorFeatures], shrinkage: f32, threshold: f32) -> Option<Self> {
if training_data.len() < 13 {
return None;
}
let n = training_data.len();
let p = 12;
let mean = compute_feature_mean(training_data, p, n);
let mut cov = compute_covariance(training_data, &mean, p, n);
apply_ledoit_wolf_shrinkage(&mut cov, p, shrinkage);
let inv_cov = invert_matrix(&cov)?;
Some(Self {
mean,
inv_cov,
shrinkage,
n_samples: n,
threshold,
})
}
#[must_use]
pub fn mahalanobis_distance_sq(&self, features: &TensorFeatures) -> f32 {
let x = features.to_vec();
let p = x.len();
let mut diff = vec![0.0f32; p];
for i in 0..p {
diff[i] = x[i] - self.mean[i];
}
let mut result = 0.0f32;
for i in 0..p {
for j in 0..p {
result += diff[i] * self.inv_cov[i][j] * diff[j];
}
}
result
}
#[must_use]
pub fn is_anomaly(&self, features: &TensorFeatures) -> bool {
self.mahalanobis_distance_sq(features) > self.threshold
}
#[must_use]
pub fn anomaly_score(&self, features: &TensorFeatures) -> f32 {
self.mahalanobis_distance_sq(features) / self.threshold
}
#[must_use]
pub fn shrinkage(&self) -> f32 {
self.shrinkage
}
#[must_use]
pub fn n_samples(&self) -> usize {
self.n_samples
}
#[must_use]
pub fn threshold(&self) -> f32 {
self.threshold
}
}
fn compute_feature_mean(training_data: &[TensorFeatures], p: usize, n: usize) -> Vec<f32> {
let mut mean = vec![0.0f32; p];
for features in training_data {
let vec = features.to_vec();
for (i, &v) in vec.iter().enumerate() {
mean[i] += v;
}
}
for m in &mut mean {
*m /= n as f32;
}
mean
}
fn compute_covariance(
training_data: &[TensorFeatures],
mean: &[f32],
p: usize,
n: usize,
) -> Vec<Vec<f32>> {
let mut cov = vec![vec![0.0f32; p]; p];
for features in training_data {
let vec = features.to_vec();
for i in 0..p {
for j in 0..p {
cov[i][j] += (vec[i] - mean[i]) * (vec[j] - mean[j]);
}
}
}
for row in &mut cov {
for c in row {
*c /= (n - 1) as f32;
}
}
cov
}
fn apply_ledoit_wolf_shrinkage(cov: &mut [Vec<f32>], p: usize, shrinkage: f32) {
let trace: f32 = (0..p).map(|i| cov[i][i]).sum();
let shrink_target = trace / p as f32;
for i in 0..p {
for j in 0..p {
cov[i][j] *= 1.0 - shrinkage;
if i == j {
cov[i][j] += shrinkage * shrink_target;
}
}
}
}
fn invert_matrix(matrix: &[Vec<f32>]) -> Option<Vec<Vec<f32>>> {
let n = matrix.len();
if n == 0 || matrix[0].len() != n {
return None;
}
let mut aug = build_augmented_matrix(matrix, n);
for i in 0..n {
partial_pivot(&mut aug, i);
let pivot = aug[i][i];
if pivot.abs() < 1e-10 {
return None; }
scale_row(&mut aug[i], pivot);
eliminate_column(&mut aug, i, n);
}
Some(extract_inverse(&aug, n))
}
fn build_augmented_matrix(matrix: &[Vec<f32>], n: usize) -> Vec<Vec<f32>> {
let mut aug = vec![vec![0.0f32; 2 * n]; n];
for i in 0..n {
for j in 0..n {
aug[i][j] = matrix[i][j];
}
aug[i][n + i] = 1.0;
}
aug
}
fn partial_pivot(aug: &mut [Vec<f32>], col: usize) {
let mut max_row = col;
for k in (col + 1)..aug.len() {
if aug[k][col].abs() > aug[max_row][col].abs() {
max_row = k;
}
}
aug.swap(col, max_row);
}
fn scale_row(row: &mut [f32], pivot: f32) {
for val in row.iter_mut() {
*val /= pivot;
}
}
fn eliminate_column(aug: &mut [Vec<f32>], col: usize, n: usize) {
for k in 0..n {
if k == col {
continue;
}
let factor = aug[k][col];
for j in 0..(2 * n) {
aug[k][j] -= factor * aug[col][j];
}
}
}
fn extract_inverse(aug: &[Vec<f32>], n: usize) -> Vec<Vec<f32>> {
let mut inverse = vec![vec![0.0f32; n]; n];
for i in 0..n {
for j in 0..n {
inverse[i][j] = aug[i][n + j];
}
}
inverse
}
#[derive(Debug, Clone, Copy)]
pub struct WilsonScore {
pub proportion: f32,
pub lower: f32,
pub upper: f32,
pub n: usize,
pub confidence: f32,
}
impl WilsonScore {
#[must_use]
pub fn calculate(successes: usize, total: usize, confidence: f32) -> Self {
if total == 0 {
return Self {
proportion: 0.0,
lower: 0.0,
upper: 0.0,
n: 0,
confidence,
};
}
let n = total as f32;
let p = successes as f32 / n;
let z = match confidence {
c if c >= 0.99 => 2.576,
c if c >= 0.95 => 1.96,
c if c >= 0.90 => 1.645,
_ => 1.96,
};
let z2 = z * z;
let denominator = 1.0 + z2 / n;
let center = p + z2 / (2.0 * n);
let margin = z * (p * (1.0 - p) / n + z2 / (4.0 * n * n)).sqrt();
let lower = ((center - margin) / denominator).max(0.0);
let upper = ((center + margin) / denominator).min(1.0);
Self {
proportion: p,
lower,
upper,
n: total,
confidence,
}
}
#[must_use]
pub fn andon_level(&self, target: f32) -> AndonLevel {
if self.proportion >= target {
AndonLevel::Green
} else if self.proportion >= 0.5 * target {
AndonLevel::Yellow
} else {
AndonLevel::Red
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AndonLevel {
Green,
Yellow,
Red,
}
impl fmt::Display for AndonLevel {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Green => write!(f, "GREEN"),
Self::Yellow => write!(f, "YELLOW"),
Self::Red => write!(f, "RED"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum FixAction {
SwapDimensions,
Requantize { block_size: usize },
RecomputeChecksum,
PadAlignment { alignment: usize },
SkipTensor,
FallbackF32,
Custom { description: String },
}
#[derive(Debug, Clone)]
pub struct ErrorPattern {
pub id: String,
pub keywords: Vec<String>,
pub fix: FixAction,
pub applications: usize,
pub successes: usize,
pub source: PatternSource,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PatternSource {
Bootstrap,
Corpus,
Llm,
Manual,
}