use crate::core::config::DsfbConfig;
use crate::core::envelope::{AdmissibilityEnvelope, EnvelopeStatus};
use crate::core::residual::ResidualSign;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GrammarState {
Admissible,
Boundary,
Violation,
}
impl GrammarState {
#[must_use]
pub const fn severity(self) -> u8 {
match self {
Self::Admissible => 0,
Self::Boundary => 1,
Self::Violation => 2,
}
}
#[must_use]
pub const fn label(self) -> &'static str {
match self {
Self::Admissible => "Admissible",
Self::Boundary => "Boundary",
Self::Violation => "Violation",
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct GrammarEngine {
state: GrammarState,
drift_persistence_count: u32,
slew_persistence_count: u32,
first_boundary_cycle: Option<u32>,
first_violation_cycle: Option<u32>,
}
impl GrammarEngine {
#[must_use]
pub const fn new() -> Self {
Self {
state: GrammarState::Admissible,
drift_persistence_count: 0,
slew_persistence_count: 0,
first_boundary_cycle: None,
first_violation_cycle: None,
}
}
#[must_use]
pub const fn state(&self) -> GrammarState {
self.state
}
#[must_use]
pub const fn first_boundary_cycle(&self) -> Option<u32> {
self.first_boundary_cycle
}
#[must_use]
pub const fn first_violation_cycle(&self) -> Option<u32> {
self.first_violation_cycle
}
pub fn advance(
&mut self,
sign: &ResidualSign,
envelope: &AdmissibilityEnvelope,
config: &DsfbConfig,
) {
let env_status = envelope.classify_position(sign.residual);
let drift_outward = sign.drift.abs() > config.drift_floor;
let slew_positive = sign.slew.abs() > config.slew_floor;
match self.state {
GrammarState::Admissible => {
if env_status == EnvelopeStatus::Exceeded {
self.transition_to(GrammarState::Violation, sign.cycle);
} else if env_status == EnvelopeStatus::Approaching {
self.transition_to(GrammarState::Boundary, sign.cycle);
} else if drift_outward {
self.drift_persistence_count += 1;
if self.drift_persistence_count >= config.persistence_threshold as u32 {
self.transition_to(GrammarState::Boundary, sign.cycle);
}
} else {
self.drift_persistence_count = 0;
}
}
GrammarState::Boundary => {
if env_status == EnvelopeStatus::Exceeded {
self.transition_to(GrammarState::Violation, sign.cycle);
} else if slew_positive {
self.slew_persistence_count += 1;
if self.slew_persistence_count >= config.slew_persistence_threshold as u32 {
self.transition_to(GrammarState::Violation, sign.cycle);
}
} else if env_status == EnvelopeStatus::Interior && !drift_outward {
self.drift_persistence_count += 1;
if self.drift_persistence_count >= config.persistence_threshold as u32 {
self.state = GrammarState::Admissible;
self.drift_persistence_count = 0;
self.slew_persistence_count = 0;
}
} else {
self.drift_persistence_count = 0;
if !slew_positive {
self.slew_persistence_count = 0;
}
}
}
GrammarState::Violation => {
}
}
}
fn transition_to(&mut self, new_state: GrammarState, cycle: u32) {
if new_state == GrammarState::Boundary && self.first_boundary_cycle.is_none() {
self.first_boundary_cycle = Some(cycle);
}
if new_state == GrammarState::Violation && self.first_violation_cycle.is_none() {
self.first_violation_cycle = Some(cycle);
}
self.state = new_state;
self.drift_persistence_count = 0;
self.slew_persistence_count = 0;
}
}
pub const MAX_CHANNELS: usize = 21;
#[derive(Debug, Clone, Copy)]
pub struct MultiChannelGrammar {
pub channel_states: [GrammarState; MAX_CHANNELS],
pub active_channels: usize,
pub aggregate_state: GrammarState,
pub channels_alarming: usize,
}
#[must_use]
pub fn aggregate_grammar(
states: &[GrammarState],
vote_fraction: f64,
) -> GrammarState {
if states.is_empty() {
return GrammarState::Admissible;
}
let n = states.len();
let mut violation_count = 0u32;
let mut boundary_count = 0u32;
let mut i = 0;
while i < n {
match states[i] {
GrammarState::Violation => violation_count += 1,
GrammarState::Boundary => boundary_count += 1,
GrammarState::Admissible => {}
}
i += 1;
}
let threshold = (n as f64 * vote_fraction) as u32;
let threshold = if threshold == 0 { 1 } else { threshold };
if violation_count >= threshold {
GrammarState::Violation
} else if (violation_count + boundary_count) >= threshold {
GrammarState::Boundary
} else {
GrammarState::Admissible
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_initial_state() {
let engine = GrammarEngine::new();
assert_eq!(engine.state(), GrammarState::Admissible);
}
#[test]
fn test_aggregate_all_admissible() {
let states = [GrammarState::Admissible; 5];
assert_eq!(aggregate_grammar(&states, 0.3), GrammarState::Admissible);
}
#[test]
fn test_aggregate_majority_boundary() {
let states = [
GrammarState::Admissible,
GrammarState::Boundary,
GrammarState::Boundary,
GrammarState::Admissible,
GrammarState::Admissible,
];
assert_eq!(aggregate_grammar(&states, 0.3), GrammarState::Boundary);
}
}