Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 5 additions & 7 deletions crates/rumoca-solver-rk45/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ mod no_state;
mod reset;
mod trace;

use no_state::NoStateSession;
use no_state::{NoStateSession, simulate_no_state};
use reset::Rk45ResetSnapshot;
use trace::{
record_derivative_eval_trace, record_root_eval_trace, reset_rk_eval_trace,
Expand All @@ -38,9 +38,6 @@ const ALGEBRAIC_REFRESH_TOL: f64 = 1.0e-10;

#[derive(Debug, thiserror::Error)]
pub enum SimError {
#[error("empty system: no state equations to simulate")]
EmptySystem,

#[error("rk45 backend does not support solver mode {requested:?}")]
UnsupportedSolverMode { requested: SimSolverMode },

Expand Down Expand Up @@ -484,6 +481,10 @@ pub fn simulate(model: &solve::SolveModel, opts: &SimOptions) -> Result<SimResul
requested => return Err(SimError::UnsupportedSolverMode { requested }),
}

if model.state_scalar_count() == 0 {
return simulate_no_state(model, opts);
}

validate_explicit_solve_model(model)?;
let model = SolveRuntime::new(model)?;
let sample_dt = default_output_dt(opts);
Expand Down Expand Up @@ -532,9 +533,6 @@ pub fn simulate(model: &solve::SolveModel, opts: &SimOptions) -> Result<SimResul

fn validate_explicit_solve_model(model: &solve::SolveModel) -> Result<(), SimError> {
let layout = &model.problem.solve_layout;
if layout.state_scalar_count == 0 {
return Err(SimError::EmptySystem);
}
if model.initial_y.len() != model.solver_scalar_count() {
return Err(SimError::SolveIr(format!(
"initial vector length {} does not match solver layout {}",
Expand Down
44 changes: 43 additions & 1 deletion crates/rumoca-solver-rk45/src/no_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,53 @@ use rumoca_solver::{
timeline::{event_left_limit_time, sample_time_match_with_tol},
};

use crate::{SessionState, SimError};
use crate::{SessionState, SimError, checked_vec_with_capacity, default_output_dt};

const NO_STATE_EVENT_UPDATE_MAX_ITERS: usize = 256;
const ROOT_BISECTION_ITERS: usize = 64;

pub(crate) fn simulate_no_state(
model: &solve::SolveModel,
opts: &SimOptions,
) -> Result<rumoca_solver::SimResult, SimError> {
let sample_dt = default_output_dt(opts);
let sample_times =
rumoca_solver::timeline::build_output_times(opts.t_start, opts.t_end, sample_dt);
let mut session = NoStateSession::new(model, opts.clone())?;
let mut times = checked_vec_with_capacity(sample_times.len(), "RK45 no-state output times")?;
let mut data =
checked_vec_with_capacity(model.visible_names.len(), "RK45 no-state output series")?;
for _ in &model.visible_names {
data.push(checked_vec_with_capacity(
sample_times.len(),
"RK45 no-state output samples",
)?);
}

for sample_time in sample_times {
session.advance_to(sample_time)?;
let values = session.runtime.runtime.visible_values(
&session.runtime.current_y,
&session.runtime.params,
session.runtime.current_t,
)?;
for (series, value) in data.iter_mut().zip(values) {
series.push(value);
}
times.push(session.runtime.current_t);
if session.runtime.termination.is_some() {
break;
}
}

Ok(rumoca_solver::build_sim_result_from_solve_model(
model,
times,
data,
session.runtime.termination,
))
}

pub(crate) struct NoStateSession {
model: solve::SolveModel,
opts: SimOptions,
Expand Down
20 changes: 20 additions & 0 deletions crates/rumoca-solver-rk45/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -882,6 +882,26 @@ fn rk45_session_runs_no_state_discrete_controller() {
assert_eq!(session.get("y").expect("read y"), Some(7.5));
}

#[test]
fn rk45_batch_runs_no_state_discrete_controller() {
let model = no_state_input_accumulator_model();
let result = simulate(
&model,
&SimOptions {
t_end: 0.05,
dt: Some(0.01),
solver_mode: SimSolverMode::RkLike,
..Default::default()
},
)
.expect("no-state batch simulation should succeed");

assert_eq!(result.times.len(), 6);
assert_eq!(result.names, ["y"]);
assert_eq!(result.data, [vec![0.0; 6]]);
assert_eq!(result.n_states, 0);
}

#[test]
fn rk45_session_uses_adaptive_event_integration_for_stiff_contact() {
let model = stiff_contact_model();
Expand Down
Loading