use super::*;
#[cfg(feature = "solve")]
pub(crate) fn reject_nonfinite_span(t0: f64, t_end: f64) -> Result<(), SimulateError> {
if t0.is_finite() && t_end.is_finite() {
return Ok(());
}
Err(SimulateError::InvalidTimeSpan {
start: t0,
end: t_end,
})
}
#[cfg(feature = "solve")]
pub(crate) fn nonadvancing_trajectory(
t0: f64,
t_end: f64,
initial_state: &[f64],
opts: &SolveOptions,
) -> Option<RawTrajectory> {
let grid_ends_at_start = match &opts.saveat {
Some(t_eval) => t_eval.iter().all(|&t| t <= t0),
None => false,
};
if t_end > t0 && !grid_ends_at_start {
return None;
}
let mut times: Vec<f64> = Vec::new();
let mut state_rows: Vec<Vec<f64>> = vec![Vec::new(); initial_state.len()];
if let Some(cb) = &opts.progress {
let p = Progress {
t0,
t: t0,
t_end,
step: 0,
maxiters: opts.maxiters,
u: initial_state,
};
if matches!(cb(&p), Flow::Cancel) {
return Some((times, state_rows, ReturnCode::Terminated));
}
}
let natural = [t0];
let grid: &[f64] = opts.saveat.as_deref().unwrap_or(&natural);
for &t in grid {
times.push(t);
for (i, &v) in initial_state.iter().enumerate() {
state_rows[i].push(v);
}
}
Some((times, state_rows, ReturnCode::Success))
}
#[cfg(feature = "solve")]
#[derive(Debug, Clone, Default)]
pub(crate) struct SolveStats {
pub n_rhs_calls: usize,
pub n_jacobian_calls: usize,
pub n_accepted_steps: usize,
pub n_rejected_steps: usize,
}
#[cfg(feature = "solve")]
impl SolveStats {
pub(crate) fn from_solver<'a, S, Eqn>(
solver: &S,
n_accepted_steps: usize,
n_rejected_steps: usize,
) -> Self
where
S: OdeSolverMethod<'a, Eqn>,
Eqn: diffsol::OdeEquations<T = f64, V = diffsol::FaerVec<f64>> + 'a,
{
let op = solver.problem().eqn.rhs().statistics();
Self {
n_rhs_calls: op.number_of_calls,
n_jacobian_calls: op.number_of_matrix_evals,
n_accepted_steps,
n_rejected_steps,
}
}
}
#[cfg(feature = "solve")]
impl std::ops::AddAssign for SolveStats {
fn add_assign(&mut self, rhs: Self) {
self.n_rhs_calls += rhs.n_rhs_calls;
self.n_jacobian_calls += rhs.n_jacobian_calls;
self.n_accepted_steps += rhs.n_accepted_steps;
self.n_rejected_steps += rhs.n_rejected_steps;
}
}
#[cfg(feature = "solve")]
pub(crate) struct SequentialFaer(());
#[cfg(feature = "solve")]
static SEQUENTIAL_FAER: std::sync::Mutex<(usize, Option<faer::Par>)> =
std::sync::Mutex::new((0, None));
#[cfg(feature = "solve")]
impl SequentialFaer {
pub(crate) fn enter() -> Self {
let mut held = SEQUENTIAL_FAER.lock().unwrap_or_else(|e| e.into_inner());
if held.0 == 0 {
held.1 = Some(faer::get_global_parallelism());
faer::set_global_parallelism(faer::Par::Seq);
}
held.0 += 1;
SequentialFaer(())
}
}
#[cfg(feature = "solve")]
impl Drop for SequentialFaer {
fn drop(&mut self) {
let mut held = SEQUENTIAL_FAER.lock().unwrap_or_else(|e| e.into_inner());
held.0 -= 1;
if held.0 == 0
&& let Some(par) = held.1.take()
{
faer::set_global_parallelism(par);
}
}
}
#[cfg(feature = "solve")]
pub(crate) fn run_solver<'a, S, Eqn>(
solver: &mut S,
t_end: f64,
opts: &SolveOptions,
) -> Result<RawTrajectory, SimulateError>
where
S: OdeSolverMethod<'a, Eqn>,
Eqn: diffsol::OdeEquations<T = f64, V = diffsol::FaerVec<f64>>,
Eqn: 'a,
{
use diffsol::OdeSolverStopReason;
let t0 = solver.state().t;
let n_states = solver.state().y.as_slice().len();
let initial_state: Vec<f64> = solver.state().y.as_slice().to_vec();
let mut times: Vec<f64> = Vec::new();
let mut state_rows: Vec<Vec<f64>> = vec![Vec::new(); n_states];
let push_state = |times: &mut Vec<f64>, state_rows: &mut [Vec<f64>], t: f64, y: &[f64]| {
times.push(t);
for (i, &v) in y.iter().enumerate() {
state_rows[i].push(v);
}
};
let report = |step: usize, t: f64, u: &[f64]| -> Flow {
let Some(cb) = &opts.progress else {
return Flow::Continue;
};
let p = Progress {
t0,
t,
t_end,
step,
maxiters: opts.maxiters,
u,
};
cb(&p)
};
if let Some(traj) = nonadvancing_trajectory(t0, t_end, &initial_state, opts) {
return Ok(traj);
}
let mut retcode = ReturnCode::Success;
if matches!(report(0, t0, &initial_state), Flow::Cancel) {
return Ok((times, state_rows, ReturnCode::Terminated));
}
solver
.set_stop_time(t_end)
.map_err(|e| SimulateError::DiffsolError {
details: e.to_string(),
})?;
let mut step_count: usize = 0;
if let Some(t_eval) = &opts.saveat {
let mut next_idx: usize = 0;
while next_idx < t_eval.len() && t_eval[next_idx] <= t0 {
push_state(
&mut times,
&mut state_rows,
t_eval[next_idx],
&initial_state,
);
next_idx += 1;
}
let mut t_prev = t0;
loop {
if next_idx >= t_eval.len() {
break;
}
if opts.maxiters.is_some_and(|cap| step_count >= cap) {
retcode = ReturnCode::MaxIters;
break;
}
let stop = solver.step().map_err(|e| SimulateError::DiffsolError {
details: e.to_string(),
})?;
step_count += 1;
let t_curr = solver.state().t;
let y_now = solver.state().y.as_slice().to_vec();
if !y_now.iter().all(|v| v.is_finite()) {
retcode = ReturnCode::Unstable;
break;
}
if matches!(report(step_count, t_curr, &y_now), Flow::Cancel) {
retcode = ReturnCode::Terminated;
break;
}
while next_idx < t_eval.len() && t_eval[next_idx] <= t_curr {
let t = t_eval[next_idx];
let y = solver
.interpolate(t)
.map_err(|e| SimulateError::DiffsolError {
details: e.to_string(),
})?;
let y_s = y.as_slice();
push_state(&mut times, &mut state_rows, t, y_s);
next_idx += 1;
}
t_prev = t_curr;
if matches!(stop, OdeSolverStopReason::TstopReached) {
break;
}
}
let _ = t_prev;
} else {
push_state(&mut times, &mut state_rows, t0, &initial_state);
loop {
if opts.maxiters.is_some_and(|cap| step_count >= cap) {
retcode = ReturnCode::MaxIters;
break;
}
let stop = solver.step().map_err(|e| SimulateError::DiffsolError {
details: e.to_string(),
})?;
step_count += 1;
let t_curr = solver.state().t;
let y_owned: Vec<f64> = solver.state().y.as_slice().to_vec();
if !y_owned.iter().all(|v| v.is_finite()) {
retcode = ReturnCode::Unstable;
break;
}
push_state(&mut times, &mut state_rows, t_curr, &y_owned);
if matches!(report(step_count, t_curr, &y_owned), Flow::Cancel) {
retcode = ReturnCode::Terminated;
break;
}
if matches!(stop, OdeSolverStopReason::TstopReached) {
break;
}
}
}
Ok((times, state_rows, retcode))
}
fn refuse_coupled_subsystem_event(file: &EsmFile) -> Result<(), CompileError> {
match crate::compile_error::first_event_in_file(file) {
Some((construct, name)) => Err(crate::compile_error::event_refusal(
construct,
crate::compile_error::ARRAY_EVALUATOR,
name.as_deref(),
)),
None => Ok(()),
}
}
pub(crate) fn whole_document_is_one_model(file: &EsmFile) -> bool {
let one_model = file.models.as_ref().map_or(0, |m| m.len()) == 1;
let has_reactions = file
.reaction_systems
.as_ref()
.is_some_and(|systems| systems.iter().any(|(_, rs)| !rs.reactions.is_empty()));
one_model && !has_reactions
}
pub(crate) fn is_array_file(file: &EsmFile) -> bool {
crate::simulate_array::file_has_array_ops(file)
|| crate::simulate_array::file_has_spatial_model(file)
}
pub(crate) fn build_array_compiled(
file: &EsmFile,
runtime_array_parameters: bool,
) -> Result<crate::simulate_array::ArrayCompiled, SimulateError> {
let env = crate::precision_infer::env_of_file(file).map_err(SimulateError::Compile)?;
let _precision_guard = env.enter();
let annotated = crate::precision_infer::annotated(file).map_err(SimulateError::Compile)?;
let file = annotated.as_ref().unwrap_or(file);
if !whole_document_is_one_model(file) {
refuse_coupled_subsystem_event(file)?;
let flat = flatten(file).map_err(CompileError::from)?;
Ok(crate::simulate_array::ArrayCompiled::from_flattened_with(
&flat,
runtime_array_parameters,
)?)
} else {
Ok(crate::simulate_array::ArrayCompiled::from_file_with(
file,
runtime_array_parameters,
)?)
}
}
pub fn compile_array(file: EsmFile) -> Result<crate::simulate_array::ArrayCompiled, SimulateError> {
if !is_array_file(&file) {
return Err(SimulateError::Compile(
CompileError::InterpreterBuildError {
details: "compile_array requires an array/spatial model (this file has none); \
use esm_problem for pure-scalar files"
.to_string(),
},
));
}
let env = crate::precision_infer::env_of_file(&file).map_err(SimulateError::Compile)?;
let _precision_guard = env.enter();
let file = match crate::precision_infer::annotated(&file).map_err(SimulateError::Compile)? {
Some(annotated) => annotated,
None => file,
};
if !whole_document_is_one_model(&file) {
refuse_coupled_subsystem_event(&file)?;
let flat = flatten(&file).map_err(CompileError::from)?;
drop(file);
Ok(crate::simulate_array::ArrayCompiled::from_flattened(&flat)?)
} else {
Ok(crate::simulate_array::ArrayCompiled::from_file_owned(file)?)
}
}