pub struct LumpedMass { /* private fields */ }Expand description
A body at one temperature.
The lumped approximation: valid when heat spreads through the body faster than it
escapes, which is the small-Biot-number condition hL/k << 1. For a 10 mm piece
of N-BK7 in still air that number is about 0.03, so it holds comfortably; for the
same piece in flowing water it does not, and Bar1D is the honest choice.
LumpedMass::biot_number says which situation you are in.
Implementations§
Source§impl LumpedMass
impl LumpedMass
Sourcepub fn new(
name: impl Into<String>,
substance: Substance,
volume: Volume,
thickness: Length,
initial: Temperature,
environment: Environment,
) -> LumpedMass
pub fn new( name: impl Into<String>, substance: Substance, volume: Volume, thickness: Length, initial: Temperature, environment: Environment, ) -> LumpedMass
A body of one substance at one temperature, losing heat to its surroundings.
thickness is the characteristic length for the Biot number, usually volume over
surface area. It does not enter the dynamics — only
LumpedMass::biot_number, which is how you find out whether the lumped
approximation was honest here.
Sourcepub fn temperature(&self) -> Temperature
pub fn temperature(&self) -> Temperature
Absolute temperature of the whole body.
Sourcepub fn rise(&self) -> Temperature
pub fn rise(&self) -> Temperature
Rise above ambient.
Sourcepub fn heat_capacity(&self) -> HeatCapacity
pub fn heat_capacity(&self) -> HeatCapacity
mc_p for this body. Infinite if the substance has no specific heat recorded,
which makes it refuse to warm rather than warm by a made-up amount.
Sourcepub fn absorbed_energy(&self) -> Energy
pub fn absorbed_energy(&self) -> Energy
Heat taken from the bus over the run.
Sourcepub fn lost_energy(&self) -> Energy
pub fn lost_energy(&self) -> Energy
Heat given up to the environment over the run.
Sourcepub fn biot_number(&self) -> f64
pub fn biot_number(&self) -> f64
hL/k — whether the lumped approximation is honest here. Under about 0.1 it
is; well past that, the body has an internal gradient this domain cannot see.
Sourcepub fn time_constant(&self) -> Time
pub fn time_constant(&self) -> Time
Time constant C/(hA) — how long it takes to get most of the way to
equilibrium, and the step this domain must not much exceed.
How long it takes to settle, linearised at the temperature it is at now.
C / (hA + 4εσA·T³). Both loss paths, because the crate’s own
Environment::loss_from says why: at room temperature a black surface radiates about
6 W·m⁻²·K⁻¹, the same order as still-air convection, so leaving it out roughly halves
the conductance and doubles this number.
It used to leave it out, and reported the same time constant for a polished surface and a blackbody one. Measured on a 1.12 kg box in still air under 21 W, against the time to reach 63% of its settled rise:
emissivity was at rest once hot measured
0.05 53.0 50.8 48.3 49.2 min
0.09 53.0 49.2 45.4 46.6
0.50 53.0 37.1 30.1 32.2
0.90 53.0 29.9 23.8 25.6Why the temperature it is at now, and not ambient. The radiative conductance grows
as T³, so a body running hot settles faster than the same body at rest. This is not a
constant of the body; it is a property of its current state, and a function returning
one number has to say which one. The two right-hand columns above are the same call on
the same body cold and settled — and the measured figure lies between them, because a
large-signal time constant is an average over the trajectory and the small-signal one
bounds it at each end.
LumpedMass::max_stable_dt re-reads this every step, so a scheduler tightens as the
body warms: 179 s to 143 s over that last row, against 318 s before radiation was
counted at all.
Infinite when nothing carries heat away — no convection and no emissivity — which is a body that never settles rather than one that settles instantly.
Sourcepub fn equilibrium_rise(&self, absorbed: Power) -> Temperature
pub fn equilibrium_rise(&self, absorbed: Power) -> Temperature
Steady-state rise for a constant absorbed power, with radiation.
Solves P = hA·ΔT + εσA((Tₐ+ΔT)⁴ − Tₐ⁴) rather than P/(hA). The answer a designer
actually wants: not how it gets there, but where it ends up — and the radiative term is
not a correction to it. On a 1.12 kg box in still air under 21 W, P/(hA) says 99.2 K
whatever the surface is, against a measured 92.9 K at ε = 0.05 and 47.5 K at ε = 1.0.
It was over by 2.09× at the top of that range, and the error is always in the
comfortable direction.
One positive root, because the right-hand side is strictly increasing in ΔT above
−Tₐ. Newton from the convective guess, which is an overestimate and therefore
approaches from the side where the derivative is largest — three or four steps.
Infinite only when nothing carries heat away at all.
Trait Implementations§
Source§impl Domain for LumpedMass
impl Domain for LumpedMass
Source§fn max_stable_dt(&self, _now: Time) -> Time
fn max_stable_dt(&self, _now: Time) -> Time
A tenth of the time constant. Explicit Euler on dT/dt = -(T-Ta)/τ is stable
up to 2τ and accurate nowhere near it, so the limit reported is the
accuracy one — a scheduler that honours it gets a curve rather than a
staircase.
Source§fn ledger(&self) -> Ledger
fn ledger(&self) -> Ledger
Energy this body accounts for: what is stored above ambient, plus what has already left to the environment.
Not minus what it absorbed. stored + lost is what it absorbed, so
subtracting that as well would cancel the entry against itself and leave the
publisher’s debt unmatched — the audit catches that immediately, which is how
this convention got settled.
Source§fn checkpoint(&mut self)
fn checkpoint(&mut self)
Everything the ledger reads, not only the temperature.
ledger() is stored + lost, and stored follows the temperature while lost is a
running total. Saving one and not the other means a sweep that gets rewound leaves its
losses behind: under Schedule::Iterative the books then grow by one sweep of shed heat
per iteration and the audit sees energy created out of nothing. Measured at 1567.6 J
becoming 1600.9 J over forty advances of three sweeps each.
It went unnoticed because nothing in this workspace has a residual, so iterate always
converged on its first sweep and the restore branch never ran —
crates/pantometry/tests/iterative_restore.rs supplies the domain that makes it run.
Source§fn readings(&self) -> Vec<Reading>
fn readings(&self) -> Vec<Reading>
One temperature, which is the whole of what a lumped model claims to know.
Source§fn name(&self) -> &str
fn name(&self) -> &str
Source§fn kind(&self) -> Kind
fn kind(&self) -> Kind
Kind::Evolving.Source§fn step(
&mut self,
_t: Time,
dt: Time,
bus: &mut Exchange,
) -> Result<(), Violation>
fn step( &mut self, _t: Time, dt: Time, bus: &mut Exchange, ) -> Result<(), Violation>
dt from t, reading inputs from bus and publishing outputs
to it. A quasi-static domain ignores dt. Read moreSource§fn restore(&mut self)
fn restore(&mut self)
Domain::checkpoint.Source§fn supports_restore(&self) -> bool
fn supports_restore(&self) -> bool
Source§fn as_any(&self) -> Option<&dyn Any>
fn as_any(&self) -> Option<&dyn Any>
Any, so a caller can get the concrete type back out of a
Simulation — see Simulation::domain_as. Read moreSource§fn as_any_mut(&mut self) -> Option<&mut dyn Any>
fn as_any_mut(&mut self) -> Option<&mut dyn Any>
Source§fn residual(&self) -> f64
fn residual(&self) -> f64
Schedule::Iterative. Zero means converged.Source§fn books_balance(&self) -> bool
fn books_balance(&self) -> bool
Source§fn as_field(&self) -> Option<&dyn ScalarField>
fn as_field(&self) -> Option<&dyn ScalarField>
ScalarField, if it has one to show. Read more