Skip to main content

Mix

Struct Mix 

Source
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

Source

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.

Source

pub fn parts(&self) -> &[(Substance, f64)]

The substances and their volume fractions, in the order given.

Source

pub fn density(&self) -> Density

Exact. ρ = Σ φᵢ ρᵢ, which is mass conservation and not a model.

Source

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.

Source

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.

Source

pub fn heat_capacity(&self, volume: Volume) -> Option<HeatCapacity>

What a volume of the mixture holds per kelvin. Exact, for the reason above.

Source

pub fn mass_of(&self, volume: Volume) -> Mass

The mass of a volume of the mixture.

Source

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.

Source

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.

Source

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 is C66 in 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 is C44.

Both are Backus’s 1962 results for a finely layered elastic medium, and both are measured against marched wave speeds in a_layered_wave.rs5.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.

Source

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.

Source

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.

Source

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.

Source

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’s C33, 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.

Source

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.

Source

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.

Trait Implementations§

Source§

impl Clone for Mix

Source§

fn clone(&self) -> Mix

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Mix

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl PartialEq for Mix

Source§

fn eq(&self, other: &Mix) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl StructuralPartialEq for Mix

Auto Trait Implementations§

§

impl Freeze for Mix

§

impl RefUnwindSafe for Mix

§

impl Send for Mix

§

impl Sync for Mix

§

impl Unpin for Mix

§

impl UnsafeUnpin for Mix

§

impl UnwindSafe for Mix

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> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. 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> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
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.