Skip to main content

LumpedMass

Struct LumpedMass 

Source
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

Source

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.

Source

pub fn temperature(&self) -> Temperature

Absolute temperature of the whole body.

Source

pub fn rise(&self) -> Temperature

Rise above ambient.

Source

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.

Source

pub fn absorbed_energy(&self) -> Energy

Heat taken from the bus over the run.

Source

pub fn lost_energy(&self) -> Energy

Heat given up to the environment over the run.

Source

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.

Source

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.6

Why the temperature it is at now, and not ambient. The radiative conductance grows as , 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.

Source

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

Source§

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 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

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)

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>

One temperature, which is the whole of what a lumped model claims to know.

Source§

fn name(&self) -> &str

What this domain is called. Used to look it up and to name it in a violation. Read more
Source§

fn kind(&self) -> Kind

Whether it has state to roll forward. Defaults to Kind::Evolving.
Source§

fn step( &mut self, _t: Time, dt: Time, bus: &mut Exchange, ) -> Result<(), Violation>

Advance by dt from t, reading inputs from bus and publishing outputs to it. A quasi-static domain ignores dt. Read more
Source§

fn restore(&mut self)

Restore the last Domain::checkpoint.
Source§

fn supports_restore(&self) -> bool

Whether Domain::checkpoint and Domain::restore actually do something. Read more
Source§

fn as_any(&self) -> Option<&dyn Any>

This domain as Any, so a caller can get the concrete type back out of a Simulation — see Simulation::domain_as. Read more
Source§

fn as_any_mut(&mut self) -> Option<&mut dyn Any>

The same, mutably, so a caller can write to a domain between steps. Read more
Source§

fn residual(&self) -> f64

How far this domain still is from agreeing with its neighbours, for Schedule::Iterative. Zero means converged.
Source§

fn books_balance(&self) -> bool

Whether this domain’s books are exact: its ledger changes by precisely what it takes from the bus minus what it publishes, every step. Read more
Source§

fn as_field(&self) -> Option<&dyn ScalarField>

This domain as a ScalarField, if it has one to show. Read more
Source§

fn as_bodies(&self) -> Option<&dyn Bodies>

This domain as a countable set of bodies, if that is what it is. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.