pub struct Mix { /* private fields */ }Expand description
A composite: substances and the fraction of the volume each occupies.
Volume and not mass, because volume is what the geometry gives and what every bound is written in.
The mass fractions are derived — see Mix::mass_fraction — and getting the two the wrong way
round is the classic mistake this type exists to prevent.
Implementations§
Source§impl Mix
impl Mix
Sourcepub fn of(parts: &[(Substance, f64)]) -> Result<Mix, String>
pub fn of(parts: &[(Substance, f64)]) -> Result<Mix, String>
A mixture from substances and their volume fractions.
The fractions must be positive and sum to one to within 1e-9. Refused rather than normalised,
and that is deliberate: fractions that do not sum to one are a transcription mistake, and
normalising them silently would turn 45% and 50% into 47.4% and 52.6% and answer a question
nobody asked. 1e-9 and not exact equality because 0.3 + 0.3 + 0.4 is not 1.0 in binary.
A single part is allowed and is not a mistake — a mixture of one is how a caller writes “this is not a composite after all” without changing the shape of the code around it, and every bound below collapses to the substance’s own value.
Sourcepub fn parts(&self) -> &[(Substance, f64)]
pub fn parts(&self) -> &[(Substance, f64)]
The substances and their volume fractions, in the order given.
Sourcepub fn density(&self) -> Density
pub fn density(&self) -> Density
Exact. ρ = Σ φᵢ ρᵢ, which is mass conservation and not a model.
Sourcepub fn mass_fraction(&self, i: usize) -> Option<f64>
pub fn mass_fraction(&self, i: usize) -> Option<f64>
What fraction of the mass the ith part is, or None if there is no such part.
wᵢ = φᵢ ρᵢ / ρ. The conversion that makes the difference between a correct specific heat and
one that is wrong by the density ratio.
Sourcepub fn specific_heat(&self) -> Option<SpecificHeat>
pub fn specific_heat(&self) -> Option<SpecificHeat>
Exact, and the field where the volume-and-mass confusion costs the most.
Volumetric heat capacity ρc is volume-additive — ρc = Σ φᵢ ρᵢ cᵢ — because a joule stored in
a cubic metre of composite is the joules stored in each part’s share of that cubic metre. Divide
by the mixture’s own density and the per-kilogram figure is mass-weighted:
c = Σ wᵢ cᵢ.
Volume-weighting c_p directly is the mistake, and how large it is depends entirely on which
pair — which is what makes it dangerous, because it is invisible on the example somebody checks
with. Measured, at half and half by volume:
aluminium + borosilicate 877.7 correct 877.0 volume-weighted 0.08% out
aluminium + copper 503.3 640.5 27.25%
copper + FR-4 507.4 742.5 46.34%The first pair have nearly the same density, so the two rules agree to a tenth of a percent and a
caller who tried it there would conclude the distinction does not matter. On a copper and FR-4
board — the case this module’s opening paragraph names — it is 46%. The error always runs toward
the lighter constituent, because volume weighting over-counts a light phase whose c_p per
kilogram is high.
None if any part does not state a specific heat, because a mixture containing an unknown is
unknown and not the average of what happens to be known.
Sourcepub fn heat_capacity(&self, volume: Volume) -> Option<HeatCapacity>
pub fn heat_capacity(&self, volume: Volume) -> Option<HeatCapacity>
What a volume of the mixture holds per kelvin. Exact, for the reason above.
Sourcepub fn conductivity_bounds(
&self,
) -> Option<(ThermalConductivity, ThermalConductivity)>
pub fn conductivity_bounds( &self, ) -> Option<(ThermalConductivity, ThermalConductivity)>
Voigt and Reuss, in that order: the arithmetic and harmonic means by volume fraction.
(k_low, k_high) — Reuss first, because a returned pair should read low to high.
These hold for any microstructure and both are attained, so they are the widest correct
answer and also the tightest one that assumes nothing. A laminate is the witness for both:
flux along the layers gives Voigt exactly and across them gives Reuss exactly, which
a_composite.rs measures on one block to machine precision.
None if any part’s conductivity is unknown.
Sourcepub fn hashin_shtrikman(
&self,
) -> Option<(ThermalConductivity, ThermalConductivity)>
pub fn hashin_shtrikman( &self, ) -> Option<(ThermalConductivity, ThermalConductivity)>
Hashin–Shtrikman, for a mixture of exactly two substances whose microstructure is statistically isotropic — a foam, a filled polymer, a packed powder.
(k_low, k_high), and both lie inside Mix::conductivity_bounds. The price of the tighter
pair is the isotropy assumption, so this is not a strictly better answer: a laminate is
anisotropic and its own exact conductivities fall outside these. Reaching for HS on a layered
material is the one way to be wrong with it.
k± = k_a + φ_b / ( 1/(k_b − k_a) + φ_a/(3 k_a) )with a the more conductive phase for the upper bound and the less for the lower. The bounds
are attained by coated-sphere assemblages, which is why they are bounds and not a fit.
None unless there are exactly two parts with known conductivities. Two parts of equal
conductivity give a degenerate pair, which is correct — both bounds are that conductivity.
Sourcepub fn shear_bounds(&self) -> Option<(Pressure, Pressure)>
pub fn shear_bounds(&self) -> Option<(Pressure, Pressure)>
Voigt and Reuss on the shear modulus, low first: (⟨1/G⟩⁻¹, ⟨G⟩).
G = E/(2(1+ν)) for each part, weighted by volume fraction. Both ends are attained, and by
the same witness in two directions — which is what makes this a range of achievable values rather
than a hedge:
- a laminate sheared in its layer planes carries a uniform shear strain, so the stresses add
and the effective modulus is
⟨G⟩exactly. That isC66in the layered-medium literature; - the same laminate sheared across its layers carries a uniform shear stress, so the strains
add and it is
⟨1/G⟩⁻¹exactly. That isC44.
Both are Backus’s 1962 results for a finely layered elastic medium, and both are measured against
marched wave speeds in a_layered_wave.rs — 5.5× apart for aluminium against PLA, from one
block, with each end converging at second order to 0.06% or better.
None if any part does not state its mechanical properties.
Sourcepub fn bulk_bounds(&self) -> Option<(Pressure, Pressure)>
pub fn bulk_bounds(&self) -> Option<(Pressure, Pressure)>
Voigt and Reuss on the bulk modulus, low first: (⟨1/K⟩⁻¹, ⟨K⟩).
K = E/(3(1−2ν)). Neither end is attained by a laminate, and no witness in this workspace attains
either — a laminate under hydrostatic stress has lateral constraint between its layers, so it
reaches neither the uniform-stress nor the uniform-strain state. They are correct bounds all the
same, and Mix::bulk_hashin_shtrikman is the pair to prefer for anything isotropic.
Stated because it is the difference between this pair and Mix::shear_bounds, whose ends both
are attained and measured. A bound that is reachable and a bound that merely holds are different
things to a caller choosing a number inside them.
Sourcepub fn shear_hashin_shtrikman(&self) -> Option<(Pressure, Pressure)>
pub fn shear_hashin_shtrikman(&self) -> Option<(Pressure, Pressure)>
Hashin–Shtrikman on the shear modulus for exactly two phases, low first.
G± = G_r + f_o / [ 1/(G_o − G_r) + 6 f_r (K_r + 2G_r) / (5 G_r (3K_r + 4G_r)) ]with r the reference phase — the stiffer one for the upper bound, the softer for the lower — and
o the other. Hashin and Shtrikman 1963, and the same trade the conductivity pair makes: tighter
than Voigt–Reuss, at the price of assuming the microstructure is statistically isotropic. For
aluminium against PLA at half and half it takes the range from 5.5-fold to 2.8-fold.
§The check that says this is the right algebra
Taken with the matrix as reference it is identically the Mori–Tanaka estimate for spherical
inclusions — a separately derived result written as a different rational function — and
a_mixture.rs measures the two agreeing to 2.2e-16 across two decades of inclusion fraction.
That equivalence is a theorem rather than a coincidence: the bound is attained by a coated-sphere
assemblage, which is what Mori–Tanaka describes.
§What no witness here attains
Unlike the conductivity pair, the elastic HS bounds are not bracketed from below by a
measurement in this workspace, and the reason is worth knowing. A resolved isotropic geometry has
to be driven by something, and an affine displacement on the boundary is a kinematically
admissible field — so its energy is an upper estimate of the effective modulus, above the true
value however fine the mesh. a_checkerboard.rs measures that estimate converging down to
1.005× the upper bound for a well-resolved board and states plainly that it cannot cross it.
That is evidence the bound is nearly tight and it is not the same as bracketing.
None unless there are exactly two parts with mechanical properties.
Sourcepub fn bulk_hashin_shtrikman(&self) -> Option<(Pressure, Pressure)>
pub fn bulk_hashin_shtrikman(&self) -> Option<(Pressure, Pressure)>
Hashin–Shtrikman on the bulk modulus for exactly two phases, low first.
K± = K_r + f_o / [ 1/(K_o − K_r) + 3 f_r / (3K_r + 4G_r) ]Everything Mix::shear_hashin_shtrikman says applies, including the Mori–Tanaka equivalence,
which for K is the more familiar of the two. Note the shear modulus of the reference phase
appears in a bound on K: a stiff inclusion resists the hydrostatic compression of its
surroundings partly in shear, so the two moduli do not separate.
Sourcepub fn p_wave_modulus_bounds(&self) -> Option<(Pressure, Pressure)>
pub fn p_wave_modulus_bounds(&self) -> Option<(Pressure, Pressure)>
Voigt and Reuss on the P-wave modulus M = λ + 2μ, low first: (⟨1/M⟩⁻¹, ⟨M⟩).
M = E(1−ν)/((1+ν)(1−2ν)), the modulus that relates stress to strain when the lateral strain is
held at zero — so it is the one a compression wave travels on and the one a thin layer bonded
between stiff neighbours actually feels.
Both ends are attained, and a first draft of this documentation said the high end was not — the measurement is what corrected it.
- a laminate compressed across its layers carries a uniform normal stress, so the compliances
add and
⟨1/M⟩⁻¹is exact. Backus’sC33, and the speed of a compression wave through the stack; - the same laminate compressed along its layers, with the lateral strain held at zero
pointwise, carries a uniform strain, so the stresses add and
⟨M⟩is exact.
The second needs that constraint said out loud, because it is what the Voigt bound is. A laminate
whose lateral contraction is free gives neither bound: the layers each want to contract differently
and the ones beside them prevent it, and the answer is Backus’s C11, which carries a correction
term in ⟨λ/M⟩ and for aluminium against PLA is 43.77 GPa against ⟨M⟩’s 53.98 — 18.9% below.
a_layered_wave.rs says why it does not measure that one: it needs the lateral strain zero on
average but free locally, and Waves::hold holds a component everywhere or nowhere.
None if any part does not state its mechanical properties.
Sourcepub fn fusion(&self) -> Result<Option<(Temperature, LatentHeat)>, String>
pub fn fusion(&self) -> Result<Option<(Temperature, LatentHeat)>, String>
Exact. The latent heat per kilogram of the mixture, when exactly one part melts.
L = w L_part at that part’s melting point, by mass conservation. This is the phase-change
composite — a wax in an aluminium matrix, a salt hydrate in a foam — and it is the case where a
mixture rule is not an approximation at all: the joules are the joules, and diluting the PCM
dilutes them in exact proportion to its mass fraction.
w and not the volume fraction, and here the difference is the whole answer rather than a
refinement. Wax at 814 kg/m³ filling 80% of the volume of an aluminium matrix is only
54.7% of the mass, so a composite whose wax stores 244 kJ/kg stores 133.4 — and using the
volume fraction would claim 195, 46% high, on the one property a buffer is bought for.
None if nothing melts. Err if two or more parts melt, because two melting points is not
one melting point and a composite with two plateaux cannot be described by a type that has one
melting_point field. Refused rather than answered with the larger, the first, or an average.
Sourcepub fn as_substance(
&self,
name: &str,
conductivity: ThermalConductivity,
emissivity: f64,
) -> Result<Substance, String>
pub fn as_substance( &self, name: &str, conductivity: ThermalConductivity, emissivity: f64, ) -> Result<Substance, String>
The mixture as one Substance, with the conductivity and emissivity the caller chose.
§Why those two are arguments
The conductivity, because no single value exists. The bounds are as far as physics goes
without the microstructure, and picking a point inside them is a modelling decision with a
reason attached — the midpoint of the Hashin–Shtrikman pair for a foam, the Voigt bound for
unidirectional fibres along the flux, a measured value if there is one. A value outside
Mix::conductivity_bounds is refused: it is not conservative or approximate, it is
impossible, and no microstructure realises it.
The emissivity, because a mixture has no surface. It is not a bulk property and it does not mix. A half-copper, half-FR-4 board is 0.05 if it is bare metal on the outside and 0.9 if it is green solder mask, and the volume fractions say nothing about which.
Expansion is volume-weighted, and that one is an approximation rather than a bound — the Voigt rule for CTE, which overestimates when the stiff phase is the one that expands less, because the stiff phase restrains the compliant one. Turner’s and Kerner’s rules weight by stiffness and are what a thermal-stress calculation wants; this is the number for a rough length change, and it is documented here rather than silently returned as if it were the exact ones above.
Err if any part’s thermal properties are unknown, if the conductivity is outside the bounds,
if the emissivity is not a fraction, or if more than one part melts.