use crate::conservation::ConservationMonitor;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SolverQuality {
Excellent,
Good,
Marginal,
Poor,
Critical,
}
impl SolverQuality {
pub fn assess(error: f64, tolerance: f64) -> Self {
let ratio = error / tolerance;
if ratio < 0.01 {
Self::Excellent
} else if ratio < 0.1 {
Self::Good
} else if ratio < 1.0 {
Self::Marginal
} else if ratio < 10.0 {
Self::Poor
} else {
Self::Critical
}
}
pub fn assess_from_monitor(monitor: &ConservationMonitor, tolerance: f64) -> Self {
let error = monitor.max_relative_error();
Self::assess(error, tolerance)
}
pub fn is_acceptable(&self) -> bool {
matches!(self, Self::Excellent | Self::Good | Self::Marginal)
}
pub fn needs_intervention(&self) -> bool {
matches!(self, Self::Poor | Self::Critical)
}
}
#[derive(Debug, Clone)]
pub struct DegradationStrategy {
pub quality: SolverQuality,
pub dt_factor: f64,
pub solver_suggestion: SolverSuggestion,
pub should_pause: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SolverSuggestion {
KeepCurrent,
SwitchToRk4,
SwitchToRk2,
SwitchToSymplectic,
}
impl DegradationStrategy {
pub fn for_quality(quality: SolverQuality) -> Self {
match quality {
SolverQuality::Excellent => Self {
quality,
dt_factor: 1.5, solver_suggestion: SolverSuggestion::SwitchToRk2,
should_pause: false,
},
SolverQuality::Good => Self {
quality,
dt_factor: 1.0, solver_suggestion: SolverSuggestion::KeepCurrent,
should_pause: false,
},
SolverQuality::Marginal => Self {
quality,
dt_factor: 0.5, solver_suggestion: SolverSuggestion::KeepCurrent,
should_pause: false,
},
SolverQuality::Poor => Self {
quality,
dt_factor: 0.1, solver_suggestion: SolverSuggestion::SwitchToRk4,
should_pause: false,
},
SolverQuality::Critical => Self {
quality,
dt_factor: 0.05, solver_suggestion: SolverSuggestion::SwitchToSymplectic,
should_pause: true,
},
}
}
pub fn apply_dt_adjustment(&self, current_dt: f64) -> f64 {
current_dt * self.dt_factor
}
}
#[derive(Debug, Clone)]
pub struct AutoSwitchController {
pub tolerance: f64,
pub switch_threshold: usize,
poor_count: usize,
excellent_count: usize,
}
impl AutoSwitchController {
pub fn new(tolerance: f64, switch_threshold: usize) -> Self {
Self {
tolerance,
switch_threshold,
poor_count: 0,
excellent_count: 0,
}
}
pub fn update(&mut self, quality: SolverQuality) -> DegradationStrategy {
match quality {
SolverQuality::Poor | SolverQuality::Critical => {
self.poor_count += 1;
self.excellent_count = 0;
}
SolverQuality::Excellent => {
self.excellent_count += 1;
self.poor_count = 0;
}
_ => {
self.poor_count = 0;
self.excellent_count = 0;
}
}
let mut strategy = DegradationStrategy::for_quality(quality);
if self.poor_count >= self.switch_threshold {
strategy.solver_suggestion = SolverSuggestion::SwitchToRk4;
} else if self.excellent_count >= self.switch_threshold {
strategy.solver_suggestion = SolverSuggestion::SwitchToRk2;
}
strategy
}
pub fn reset(&mut self) {
self.poor_count = 0;
self.excellent_count = 0;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_quality_assessment() {
assert_eq!(SolverQuality::assess(0.005, 1.0), SolverQuality::Excellent);
assert_eq!(SolverQuality::assess(0.05, 1.0), SolverQuality::Good);
assert_eq!(SolverQuality::assess(0.5, 1.0), SolverQuality::Marginal);
assert_eq!(SolverQuality::assess(5.0, 1.0), SolverQuality::Poor);
assert_eq!(SolverQuality::assess(50.0, 1.0), SolverQuality::Critical);
}
#[test]
fn test_degradation_strategy() {
let excellent = DegradationStrategy::for_quality(SolverQuality::Excellent);
assert!(excellent.dt_factor > 1.0);
assert!(!excellent.should_pause);
let critical = DegradationStrategy::for_quality(SolverQuality::Critical);
assert!(critical.dt_factor < 0.1);
assert!(critical.should_pause);
}
#[test]
fn test_auto_switch_controller() {
let mut controller = AutoSwitchController::new(1e-4, 3);
for _ in 0..2 {
let strategy = controller.update(SolverQuality::Poor);
assert_eq!(strategy.solver_suggestion, SolverSuggestion::SwitchToRk4);
}
let strategy = controller.update(SolverQuality::Poor);
assert_eq!(strategy.solver_suggestion, SolverSuggestion::SwitchToRk4);
controller.update(SolverQuality::Good);
for _ in 0..3 {
controller.update(SolverQuality::Excellent);
}
let strategy = controller.update(SolverQuality::Excellent);
assert_eq!(strategy.solver_suggestion, SolverSuggestion::SwitchToRk2);
}
}