use dualis::prelude::*;
struct Converging {
x: f64,
estimate: f64,
last_change: f64,
saved: Option<f64>,
}
impl Converging {
fn new() -> Converging {
Converging {
x: 0.0,
estimate: 0.0,
last_change: f64::INFINITY,
saved: None,
}
}
}
impl Domain for Converging {
fn name(&self) -> &str {
"converging"
}
fn step(&mut self, _t: Time, _dt: Time, _bus: &mut Exchange) -> Result<(), Violation> {
let next = 0.5 * (self.estimate + 1.0);
self.last_change = (next - self.estimate).abs();
self.estimate = next;
self.x = next;
Ok(())
}
fn residual(&self) -> f64 {
self.last_change
}
fn checkpoint(&mut self) {
self.saved = Some(self.x);
self.estimate = 0.0;
self.last_change = f64::INFINITY;
}
fn restore(&mut self) {
if let Some(x) = self.saved {
self.x = x;
}
}
fn supports_restore(&self) -> bool {
true
}
fn as_any(&self) -> Option<&dyn std::any::Any> {
Some(self)
}
}
fn plate() -> LumpedMass {
LumpedMass::new(
"plate",
Substance::aluminium_6061(),
Volume::from_si(60e-3 * 60e-3 * 3e-3),
Length::mm(1.5),
Temperature::celsius(80.0),
Environment::still_air(
Temperature::celsius(20.0),
Area::from_si(2.0 * 60e-3 * 60e-3),
),
)
}
#[test]
fn an_iterative_coupling_actually_iterates_and_actually_restores() {
let mut sim = Simulation::new(Schedule::Iterative {
max_iter: 8,
tol: 0.2,
})
.with(Converging::new());
let report = sim.advance(Time::s(1.0)).expect("it converges");
assert_eq!(
report.iterations, 3,
"0.5, 0.25, 0.125 against a tolerance of 0.2 is three sweeps"
);
let mut stubborn = Simulation::new(Schedule::Iterative {
max_iter: 2,
tol: 1e-9,
})
.with(Converging::new());
let violation = stubborn
.advance(Time::s(1.0))
.expect_err("two sweeps cannot reach 1e-9");
assert_eq!(violation.quantity, "coupling residual");
assert_eq!(
stubborn.time().to_si(),
0.0,
"a refused step keeps the clock"
);
}
#[test]
fn a_rewound_lumped_mass_does_not_invent_the_heat_it_shed() {
let mut sim = Simulation::new(Schedule::Iterative {
max_iter: 8,
tol: 0.2,
})
.conservation_tolerance(1e-9)
.with(Converging::new())
.with(plate());
let start = sim
.domain_as::<LumpedMass>("plate")
.expect("the plate is there")
.lost_energy()
.to_si();
assert_eq!(start, 0.0);
for _ in 0..40 {
let report = sim
.advance(Time::s(5.0))
.expect("a rewound sweep must not create energy");
assert_eq!(report.iterations, 3);
}
let shed = sim
.domain_as::<LumpedMass>("plate")
.expect("the plate is there")
.lost_energy()
.to_si();
assert!(
shed > 100.0,
"the plate should have shed real joules, got {shed:.3} J"
);
}