use super::diagnostics::GlobalDiagnostics;
use std::cell::Cell;
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum ExitReason {
TimeLimitReached,
SteadyState,
EnergyDrift,
MassLoss,
CasimirDrift,
CflViolation,
WallClockLimit,
#[serde(alias = "CausticFormed")]
FirstCausticFormed,
#[serde(alias = "VirialStabilized")]
VirialRelaxed,
#[serde(alias = "UserStop")]
UserDefined,
}
impl std::fmt::Display for ExitReason {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::TimeLimitReached => write!(f, "Time limit reached"),
Self::SteadyState => write!(f, "Steady state reached"),
Self::EnergyDrift => write!(f, "Energy drift exceeded"),
Self::MassLoss => write!(f, "Mass loss exceeded"),
Self::CasimirDrift => write!(f, "Casimir drift exceeded"),
Self::CflViolation => write!(f, "CFL violation"),
Self::WallClockLimit => write!(f, "Wall clock limit reached"),
Self::FirstCausticFormed => write!(f, "Caustic formed"),
Self::VirialRelaxed => write!(f, "Virial ratio stabilized"),
Self::UserDefined => write!(f, "User stop"),
}
}
}
pub trait ExitCondition {
fn check(&self, diag: &GlobalDiagnostics, initial: &GlobalDiagnostics) -> Option<ExitReason>;
}
pub struct TimeLimitCondition {
pub t_final: f64,
}
impl ExitCondition for TimeLimitCondition {
fn check(&self, diag: &GlobalDiagnostics, _initial: &GlobalDiagnostics) -> Option<ExitReason> {
if diag.time >= self.t_final {
Some(ExitReason::TimeLimitReached)
} else {
None
}
}
}
fn exceeds_relative_drift(current: f64, initial: f64, tolerance: f64) -> bool {
let ref_val = initial.abs();
ref_val > 1e-30 && (current - initial).abs() / ref_val > tolerance
}
pub struct EnergyDriftCondition {
pub tolerance: f64,
}
impl ExitCondition for EnergyDriftCondition {
fn check(&self, diag: &GlobalDiagnostics, initial: &GlobalDiagnostics) -> Option<ExitReason> {
exceeds_relative_drift(diag.total_energy, initial.total_energy, self.tolerance)
.then_some(ExitReason::EnergyDrift)
}
}
pub struct MassLossCondition {
pub threshold: f64,
}
impl ExitCondition for MassLossCondition {
fn check(&self, diag: &GlobalDiagnostics, initial: &GlobalDiagnostics) -> Option<ExitReason> {
let m0 = initial.mass_in_box.abs();
if m0 > 1e-30 && diag.mass_in_box / m0 < self.threshold {
Some(ExitReason::MassLoss)
} else {
None
}
}
}
pub struct CasimirDriftCondition {
pub tolerance: f64,
}
impl ExitCondition for CasimirDriftCondition {
fn check(&self, diag: &GlobalDiagnostics, initial: &GlobalDiagnostics) -> Option<ExitReason> {
exceeds_relative_drift(diag.casimir_c2, initial.casimir_c2, self.tolerance)
.then_some(ExitReason::CasimirDrift)
}
}
pub struct WallClockCondition {
pub limit_secs: f64,
pub start: std::time::Instant,
}
impl WallClockCondition {
pub fn new(limit_secs: f64) -> Self {
Self {
limit_secs,
start: std::time::Instant::now(),
}
}
}
impl ExitCondition for WallClockCondition {
fn check(&self, _diag: &GlobalDiagnostics, _initial: &GlobalDiagnostics) -> Option<ExitReason> {
if self.start.elapsed().as_secs_f64() > self.limit_secs {
Some(ExitReason::WallClockLimit)
} else {
None
}
}
}
pub struct SteadyStateCondition {
pub threshold: f64,
prev_entropy: Cell<Option<f64>>,
}
impl SteadyStateCondition {
pub fn new(threshold: f64) -> Self {
Self {
threshold,
prev_entropy: Cell::new(None),
}
}
}
impl ExitCondition for SteadyStateCondition {
fn check(&self, diag: &GlobalDiagnostics, _initial: &GlobalDiagnostics) -> Option<ExitReason> {
let current = diag.entropy;
if let Some(prev) = self.prev_entropy.get() {
let dt = diag.time; let rate = if dt > 1e-30 {
(current - prev).abs() / dt
} else {
f64::MAX
};
self.prev_entropy.set(Some(current));
if rate < self.threshold {
return Some(ExitReason::SteadyState);
}
} else {
self.prev_entropy.set(Some(current));
}
None
}
}
pub struct CflViolationCondition {
pub dt_min: f64,
}
impl ExitCondition for CflViolationCondition {
fn check(&self, _diag: &GlobalDiagnostics, _initial: &GlobalDiagnostics) -> Option<ExitReason> {
None
}
}
pub struct CausticFormationCondition;
impl ExitCondition for CausticFormationCondition {
fn check(&self, _diag: &GlobalDiagnostics, _initial: &GlobalDiagnostics) -> Option<ExitReason> {
None
}
}
pub struct VirialRelaxedCondition {
pub tolerance: f64,
}
impl ExitCondition for VirialRelaxedCondition {
fn check(&self, diag: &GlobalDiagnostics, _initial: &GlobalDiagnostics) -> Option<ExitReason> {
if (diag.virial_ratio - 1.0).abs() < self.tolerance {
Some(ExitReason::VirialRelaxed)
} else {
None
}
}
}