pub mod backward_euler;
pub mod bdf2;
pub mod crank_nicolson;
pub mod dopri45;
pub mod euler;
pub mod imex;
pub mod implicit;
pub mod rk4;
pub mod step_control;
pub use backward_euler::BackwardEulerSolver;
pub use bdf2::BDF2Solver;
pub use crank_nicolson::CrankNicolsonSolver;
pub use dopri45::DoPri45Solver;
pub use euler::ForwardEulerSolver;
pub use imex::{OperatorSplittingSolver, SplitOperator, SplittingScheme};
pub use rk4::RK4Solver;
pub use step_control::StepSizeController;
use std::collections::HashMap;
use crate::context::compute::ComputeContext;
use crate::context::error::OxiflowError;
use crate::context::value::ContextValue;
use crate::context::ContextCalculator;
use crate::solver::chain::build_calculator_chain;
use crate::solver::config::StepControl;
use crate::solver::scenario::{Domain, Scenario};
use crate::solver::{SimulationResult, Solver, SolverConfiguration};
pub(crate) fn apply_boundary_conditions(
domain: &Domain,
state: &mut ContextValue,
ctx: &ComputeContext,
) -> Result<(), OxiflowError> {
if domain.boundary_conditions.is_empty() {
return Ok(());
}
let field = state.as_scalar_field_mut()?;
for bc in &domain.boundary_conditions {
bc.apply(field, ctx, domain.mesh.as_ref())?;
}
Ok(())
}
pub(crate) fn evaluate_derivative(
domain: &Domain,
chain: &[&dyn ContextCalculator],
state: &mut ContextValue,
t: f64,
dt: f64,
) -> Result<ContextValue, OxiflowError> {
let mut ctx = ComputeContext::new(t, dt);
for calc in chain {
let value = calc
.compute(state, &ctx)
.map_err(|e| OxiflowError::ComputationFailed {
variable: calc.provides(),
source: Box::new(e),
})?;
ctx.insert(calc.provides(), value);
}
apply_boundary_conditions(domain, state, &ctx)?;
domain.model.compute_physics(state, &ctx)
}
pub(crate) fn check_finite(state: &ContextValue, t: f64) -> Result<(), OxiflowError> {
if let Ok(field) = state.as_scalar_field() {
if field.iter().any(|v| !v.is_finite()) {
return Err(OxiflowError::SolverDivergence {
time: t,
reason: "non-finite value detected in state vector".into(),
});
}
}
Ok(())
}
pub trait SteppableSolver: Solver {
fn history_depth(&self) -> usize {
0
}
fn step(
&self,
domain: &Domain,
chain: &[&dyn ContextCalculator],
state: &mut ContextValue,
history: &[ContextValue],
t: f64,
dt: f64,
) -> Result<ContextValue, OxiflowError>;
fn solve_fixed_step(
&self,
scenario: &Scenario,
config: &SolverConfiguration,
) -> Result<SimulationResult, OxiflowError> {
scenario.validate()?;
let domain = scenario.single_domain()?;
let dt = match &config.time.step_control {
StepControl::Fixed { dt } => *dt,
_ => {
return Err(OxiflowError::InvalidDomain(
"this solver only supports StepControl::Fixed (adaptive step control \
not supported by this method)"
.into(),
))
}
};
let t_end = config.time.t_end;
let t_start = scenario.t_start;
if dt <= 0.0 {
return Err(OxiflowError::InvalidDomain(
"dt must be strictly positive".into(),
));
}
if t_end <= t_start {
return Err(OxiflowError::InvalidDomain(
"t_end must be greater than t_start".into(),
));
}
let requirements = scenario.context_requirements();
let chain = build_calculator_chain(&requirements, &config.calculators)?;
let mut u = domain.model.initial_state(domain.mesh.as_ref());
let mut history: Vec<ContextValue> = Vec::new();
let n_steps = ((t_end - t_start) / dt).round() as usize;
let save_every = config.time.save_every.unwrap_or(1);
let capacity = n_steps / save_every + 1;
let mut states: Vec<ContextValue> = Vec::with_capacity(capacity);
let mut times: Vec<f64> = Vec::with_capacity(capacity);
states.push(u.clone());
times.push(t_start);
let depth = self.history_depth();
for step in 0..n_steps {
let t = t_start + (step as f64) * dt;
let t_next = t_start + ((step + 1) as f64) * dt;
let next = self.step(domain, &chain, &mut u, &history, t, dt)?;
if depth > 0 {
let prev = std::mem::replace(&mut u, next);
history.insert(0, prev);
history.truncate(depth);
} else {
u = next;
}
if let Err(e) = check_finite(&u, t_next) {
self.on_divergence(&crate::solver::snapshot::SimulationSnapshot {
time_config: config.time.clone(),
integrator: config.integrator.clone(),
state: u.clone(),
t: t_next,
n_steps: step + 1,
error: Some(e.to_string()),
partial: Some(SimulationResult {
states: states.clone(),
times: times.clone(),
n_steps: step + 1,
metadata: HashMap::new(),
}),
});
return Err(e);
}
if (step + 1) % save_every == 0 {
states.push(u.clone());
times.push(t_next);
}
}
Ok(SimulationResult {
states,
times,
n_steps,
metadata: HashMap::new(),
})
}
}