///|
/// A deterministic simulation run plan.
pub(all) struct RunPlan {
steps : Int
sample_every : Int
record_initial : Bool
stop_on_unstable : Bool
} derive(Debug)
///|
/// Build a conservative run plan.
pub fn RunPlan::new(
steps~ : Int,
sample_every? : Int = 1,
record_initial? : Bool = true,
stop_on_unstable? : Bool = false,
) -> RunPlan {
{
steps: steps.max(0),
sample_every: sample_every.max(1),
record_initial,
stop_on_unstable,
}
}
///|
/// Summary returned by a planned run.
pub(all) struct RunSummary {
steps : Int
samples : Int
final_mass : Double
final_max_speed : Double
converged : Bool
stopped_early : Bool
} derive(Debug)
///|
/// Execute a run plan and record deterministic observation count.
pub fn Simulation::run_plan(self : Simulation, plan : RunPlan) -> RunSummary {
let initial_mass = self.mass()
let mut samples = if plan.record_initial { 1 } else { 0 }
let mut stopped_early = false
for step in 1..<=plan.steps {
self.step()
if step % plan.sample_every == 0 {
samples += 1
}
if plan.stop_on_unstable && !self.health().pass {
stopped_early = true
break
}
}
let actual_steps = if stopped_early { self.step_count } else { plan.steps }
let final_mass = self.mass()
{
steps: actual_steps,
samples,
final_mass,
final_max_speed: self.stability().max_speed,
converged: abs_double(final_mass - initial_mass) < 0.000001,
stopped_early,
}
}
///|
/// Return the absolute mass change represented by a run summary.
pub fn RunSummary::mass_change(
self : RunSummary,
initial_mass~ : Double,
) -> Double {
abs_double(self.final_mass - initial_mass)
}
///|
/// Serialize a run summary as CSV.
pub fn RunSummary::to_csv(self : RunSummary) -> String {
"\{self.steps},\{self.samples},\{self.final_mass},\{self.final_max_speed},\{self.converged},\{self.stopped_early}\n"
}
///|
/// Plan a geometrically increasing sequence of sample intervals.
pub fn schedule_intervals(total_steps~ : Int, levels~ : Int) -> Array[Int] {
let result = Array::new()
let count = levels.max(0)
for level in 0.. Bool {
summary.steps == plan.steps && !summary.stopped_early
}
///|
/// Compare two run summaries by final mass and speed.
pub fn run_summary_distance(left : RunSummary, right : RunSummary) -> Double {
abs_double(left.final_mass - right.final_mass) +
abs_double(left.final_max_speed - right.final_max_speed)
}