pub struct Winding { /* private fields */ }Expand description
A length of conductor carrying current, dissipating I²R onto the heat channel.
The resistance is computed from the conductor’s geometry and its temperature rather than stated, so it is a model rather than a number: get the length or the cross-section wrong and the dissipation is wrong in a way a closed form can catch.
Implementations§
Source§impl Winding
impl Winding
Sourcepub fn of_copper(
name: impl Into<String>,
length: Length,
cross_section_m2: f64,
at: Temperature,
) -> Winding
pub fn of_copper( name: impl Into<String>, length: Length, cross_section_m2: f64, at: Temperature, ) -> Winding
A winding of copper: length of wire with a given cross-section, at a temperature.
R = ρ(T)·L/A. The cross-section is in square metres — 0.35 mm² of magnet wire is
0.35e-6, which is roughly AWG 22.
Sourcepub fn of_resistance(
name: impl Into<String>,
at_20c: Resistance,
alpha: f64,
at: Temperature,
) -> Winding
pub fn of_resistance( name: impl Into<String>, at_20c: Resistance, alpha: f64, at: Temperature, ) -> Winding
A winding of stated resistance at 20 °C, for a conductor this crate has no geometry for.
alpha is the temperature coefficient per kelvin; pass 0.0 for a resistor whose
coefficient you do not want to model, which is the honest choice for a wirewound part
whose datasheet quotes a tolerance band rather than a number.
Sourcepub fn driven_at(self, current: Current) -> Winding
pub fn driven_at(self, current: Current) -> Winding
Drive it at a constant current. Dissipation is I²R and rises as it warms.
Sourcepub fn driven_from(self, voltage: Voltage) -> Winding
pub fn driven_from(self, voltage: Voltage) -> Winding
Drive it from a constant voltage. Dissipation is V²/R and falls as it warms.
The opposite sign of feedback from driven_at, and the difference
decides whether a runaway is possible at all: a constant-current winding can run away, a
constant-voltage one cannot.
Sourcepub fn with_reserve(self, joules: f64) -> Winding
pub fn with_reserve(self, joules: f64) -> Winding
Joules it may dissipate before it goes quiet.
Not optional in practice. Without it the reserve is infinite, and step refuses —
see the comment there for why the refusal is the domain’s job rather than the audit’s.
Saying where the energy comes from is the point.
Sourcepub fn at_temperature(&mut self, t: Temperature)
pub fn at_temperature(&mut self, t: Temperature)
Tell it what temperature it is now.
The manual half of a coupling this crate cannot close by itself. A domain cannot read another’s temperature inside the step loop — see the module documentation — so a caller wanting the electro-thermal feedback reads the thermal domain’s temperature between steps and passes it here.
Sourcepub fn resistance(&self) -> Resistance
pub fn resistance(&self) -> Resistance
Resistance at its current temperature: R₂₀(1 + α(T − 20 °C)).
Sourcepub fn dissipation(&self) -> Power
pub fn dissipation(&self) -> Power
Power it is dissipating right now.
Sourcepub fn resistance_at(&self, at: Temperature) -> Resistance
pub fn resistance_at(&self, at: Temperature) -> Resistance
Resistance at an arbitrary temperature, without changing the winding.
Sourcepub fn dissipation_at(&self, at: Temperature) -> Power
pub fn dissipation_at(&self, at: Temperature) -> Power
What it would dissipate at a temperature, without changing the winding.
A pure function, and that is the point. Everything else here is a Domain method,
which means it can only be reached by stepping — and the electro-thermal feedback needs
P(T) evaluated at a temperature the electrical domain has no way to learn, since
Exchange carries amounts and not state.
As a plain function it is composable by whoever does hold both sides. A caller with a
thermal network and a winding can write coil.dissipation_at(net.temperature(node))
between frames — which is what pantometry-world’s scene 13 does — and any future in-loop
coupling needs this same function rather than a different one. So it is correct under
every answer to that design question, which is why it exists before the question is
settled.
Sourcepub fn runaway_current(&self, path: Conductance) -> Option<Current>
pub fn runaway_current(&self, path: Conductance) -> Option<Current>
The current at which this winding’s feedback overcomes a heat path of conductance g.
Thermal runaway is dP/dT > dQ_out/dT. For a constant-current winding P = I²R₂₀(1+αΔT)
so dP/dT = I²R₂₀α, and against a path that sheds g watts per kelvin the threshold is
exact:
I_crit = √( g / (R₂₀ α) )g is the conductance of the whole path to ambient, which for anything with joints
in it is not the surface’s. A motor whose winding reaches air through 0.9 W/K and
2.4 W/K of joints and then 0.294 W/K of convection has a series g of 0.203 W/K, not
0.294 — and the threshold falls from 4.95 A to 4.11 A, a 17% margin a lumped model
reports as present when it is not. That difference is the argument for
ThermalNetwork over one body.
Do not assemble g by hand. Those three numbers are a convection-only path, and the
real one on that motor is 0.220 W/K because the housing also radiates at its operating
temperature — so the hand-computed 4.11 A understates the true 4.28 A by 4%. Ask the
network: ThermalNetwork::path_conductance(node, at) takes the slope of its own solved
balance and therefore includes every path out, radiative terms and interior environments
alike. The hand formula was found to be wrong by a sizing tool written against 0.6.0,
which is what FRICTION.md 20 is about.
Returns None for a voltage-driven winding, which cannot run away: P = V²/R falls
as it warms, so the feedback has the opposite sign and there is no threshold to report.
Sourcepub fn dissipated_energy(&self) -> Energy
pub fn dissipated_energy(&self) -> Energy
Joules it has put onto the bus over the run.
Trait Implementations§
Source§impl Domain for Winding
impl Domain for Winding
Source§fn kind(&self) -> Kind
fn kind(&self) -> Kind
Nothing here is integrated: the dissipation is a closed-form function of the state, so there is no state to march and no stability limit to respect.
Source§fn ledger(&self) -> Ledger
fn ledger(&self) -> Ledger
What is left to spend, not what has passed through.
The joules published are gone from here and are being reported by whoever took them. Adding them back would make the total grow by the heat that moved, which this workspace has written down more than once as the most common way to author a domain that audits green and is wrong.
Source§fn readings(&self) -> Vec<Reading>
fn readings(&self) -> Vec<Reading>
What it is dissipating, at what resistance, and what it has spent.
The resistance is here because it is the reason the dissipation moves: at fixed current the power ratio is the resistance ratio, and a table with both columns shows that in a way a table with one cannot.
Source§fn books_balance(&self) -> bool
fn books_balance(&self) -> bool
Source§fn name(&self) -> &str
fn name(&self) -> &str
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 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 max_stable_dt(
&self,
now: Qty<0, 0, 1, 0, 0, 0, 0>,
) -> Qty<0, 0, 1, 0, 0, 0, 0>
fn max_stable_dt( &self, now: Qty<0, 0, 1, 0, 0, 0, 0>, ) -> Qty<0, 0, 1, 0, 0, 0, 0>
now and stay stable — a CFL
condition, a diffusion limit, a contact penetration budget. Read moreSource§fn residual(&self) -> f64
fn residual(&self) -> f64
Schedule::Iterative. Zero means converged.Source§fn checkpoint(&mut self)
fn checkpoint(&mut self)
Schedule::Iterative, and Simulation::advance says so rather than
silently iterating from the wrong state.Source§fn restore(&mut self)
fn restore(&mut self)
Domain::checkpoint.Source§fn supports_restore(&self) -> bool
fn supports_restore(&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