use dualis::prelude::*;
fn units_are_types() {
let area: Area = Length::mm(10.0) * Length::mm(10.0);
let absorbed: Power = Irradiance::mw_per_cm2(50.0) * area * 0.02;
let capacity: HeatCapacity = Mass::g(2.0) * SpecificHeat::j_per_kg_k(858.0);
let rise: Temperature = (absorbed * Time::s(1.0)) / capacity;
println!("1. units");
println!(" 50 mW/cm^2 on a 1 cm^2 surface absorbing 2% warms 2 g of glass");
println!(" by {:.3} mK in a second", rise.to_si() * 1e3);
}
struct Heater {
watts: f64,
reserve: f64,
}
impl Domain for Heater {
fn name(&self) -> &'static str {
"heater"
}
fn step(&mut self, _t: Time, dt: Time, bus: &mut Exchange) -> Result<(), Violation> {
let joules = (self.watts * dt.to_si()).min(self.reserve);
self.reserve -= joules;
bus.publish(HEAT, joules);
Ok(())
}
fn ledger(&self) -> Ledger {
Ledger::new().with(quantity::ENERGY, self.reserve)
}
}
struct Slab {
stored: f64,
lossy: bool,
}
impl Domain for Slab {
fn name(&self) -> &'static str {
"slab"
}
fn step(&mut self, _t: Time, _dt: Time, bus: &mut Exchange) -> Result<(), Violation> {
let arrived = bus.take(HEAT);
self.stored += if self.lossy { 0.9 * arrived } else { arrived };
Ok(())
}
fn ledger(&self) -> Ledger {
Ledger::new().with(quantity::ENERGY, self.stored)
}
}
fn run(lossy: bool) -> Result<f64, Violation> {
let mut sim = Simulation::new(Schedule::Staggered)
.with(Heater {
watts: 100.0,
reserve: 500.0,
})
.with(Slab { stored: 0.0, lossy });
for _ in 0..100 {
sim.advance(Time::ms(50.0))?;
}
Ok(sim
.ledger()
.get(quantity::ENERGY)
.expect("both domains report energy"))
}
fn junction_to_case() {
let mut motor = ThermalNetwork::new("motor");
let winding = motor.node(
"winding",
Substance::copper(),
Volume::from_si(18e-6),
Length::mm(2.0),
Temperature::celsius(25.0),
);
let case = motor.node_losing_to(
"case",
Substance::aluminium_6061(),
Volume::from_si(220e-6),
Length::mm(4.0),
Temperature::celsius(25.0),
Environment::still_air(Temperature::celsius(25.0), Area::from_si(0.042)),
);
motor
.link(winding, case, Conductance::w_per_k(0.9))
.expect("two distinct nodes and a positive conductance");
motor.absorbing(winding).expect("winding is a node of this");
let mut sim = Simulation::new(Schedule::Staggered)
.with(Heater {
watts: 6.0,
reserve: 6_000.0,
})
.with(motor);
for _ in 0..900 {
sim.advance(Time::s(1.0)).expect("the books close");
}
let motor = sim
.domain_as::<ThermalNetwork>("motor")
.expect("it is still there");
let (hot, cold) = (
motor.node_named("winding").unwrap(),
motor.node_named("case").unwrap(),
);
let drop = motor.temperature(hot).to_si() - motor.temperature(cold).to_si();
println!(
" winding {:.1} C, case {:.1} C — a drop of {drop:.1} K across the joint",
motor.temperature(hot).to_si() - 273.15,
motor.temperature(cold).to_si() - 273.15,
);
println!(" a LumpedMass would have reported the case's number for both");
assert!(drop > 5.5 && drop < 6.67, "drop {drop:.3} K");
assert!(
(drop * 10.0).round() == 62.0,
"AGENTS.md says 6.2 K; this run gives {drop:.3} K. Update both or neither."
);
}
fn main() {
units_are_types();
println!("\n2. a correct pair of domains");
let total = run(false).expect("the books close");
assert!((total - 500.0).abs() < 1e-9, "total {total}");
println!(" 500.0 J started in the heater's tank");
println!(" {total:.1} J is still accounted for after five seconds");
println!("\n3. the same pair, with the slab booking 90% of what it takes");
match run(true) {
Ok(total) => panic!("the audit should have refused this: {total}"),
Err(v) => {
println!(" advance() returned Err, and this is the whole message:");
println!("\n {v}\n");
assert_eq!(v.quantity, quantity::ENERGY);
println!(" v.quantity = {:?}", v.quantity);
println!(" v.site = {:?}", v.site);
println!(" v.before = {:.4} J", v.before);
println!(" v.after = {:.4} J", v.after);
}
}
println!("\n4. several bodies and the drop between them");
junction_to_case();
println!("\nRead next: AGENTS.md for the API surface, examples/ for physics that is");
println!("checked in public, CONTRIBUTING.md if you are adding to it.");
}