Thermal diffusivity, live¶
How fast a temperature spreads through a phase, α = k / (ρ c_p), from its thermal conductivity k, its density ρ and its specific heat c_p: the coefficient of the heat equation ∂T/∂t = α∇²T. The time a temperature takes to reach a depth L grows as L² / α. Below, the library's law (lusine.properties.thermal_diffusivity) gives the diffusivity of the library's copper, ice and liquid water, each from its own rows, on your GPU as you read, and the distance is swept from 2 mm to 2 cm, a ninth of a millimetre a step, twenty steps a second, with the time to spread across it in each. Beside them the program holds the law to Incropera and DeWitt's tabulated diffusivities, and a phase swept through conductivities to the law's own inverse, ρ c_p α = k.
This page runs the program on your GPU through WebGPU, so it needs a browser with WebGPU (a recent Chrome, Edge, Safari or Firefox). Without it the program does not run and nothing is drawn below.
What it shows¶
- The strip above the controls is green while every check holds, and turns red for good once one fails.
- Spreading: the chart draws L² / α against the distance L for copper, ice and water, on a log scale, a point a step over one whole sweep: the three are the same curve, shifted by the ratio of their diffusivities, copper's about eight hundred times water's. Below it, the distance, and the time across a centimetre of copper, about a second, and of water, about eleven minutes.
- The phases: each phase's diffusivity, beside Incropera and DeWitt's for pure copper and water at 300 K (Fundamentals of Heat and Mass Transfer, 6th ed., tables A.1 and A.6).
- The checks, each held at every step: Incropera's values, the library's copper within 1% of their 117 × 10⁻⁶ m²/s and its water within 1.5% of their 0.147 × 10⁻⁶ m²/s (the library's water is at 298.15 K and its copper slightly denser); a centimetre, crossed in 0.5 to 1.5 s in copper and in ten to thirteen minutes in water; ρ c_p α is k, the swept phase's conductivity recovered within 0.0001%; scales, twice the density giving exactly half the diffusivity and twice the conductivity exactly twice.
Every number here is computed by the program on the GPU; the page only reads the values back and draws them. The phases, the tabulated values and the sweeps are the program's settings, which the host may write; this page offers no control to change them yet.
The program¶
The sweep, the law's answers and the checks, the panels the page draws, and the one procedure, main, run each step: the sweep moves on, the law is asked for each phase and the swept one, and the checks are taken.
// The thermal diffusivity's live page: the library's copper, ice and water, each its diffusivity from its own rows,
// the time a temperature takes to spread across a growing distance in each, against Incropera and DeWitt's tabulated
// diffusivities, and a phase swept through conductivities held to rho c_p alpha = k, with the page's checks computed
// here on the GPU and shown by the panels the program declares.
namespace thermal_diffusivity;
lusine.version version = { { number = 1 } };
// The reader's settings: the library's copper, ice and liquid water, each its heat row (conductivity and specific heat)
// and its mechanical row (density); Incropera and DeWitt's diffusivities of pure copper and of water at 300 K (their
// tables A.1 and A.6); the sweep: the distance it starts at, after none, and how far each step moves it; and a phase of
// 2000 kg/m3 and 1000 J/(kg K), its conductivity swept from 0.5 W/(m K) by 2.5 W/(m K) a step.
host struct {
relation<lusine.materials.thermal> copper_heat
= lusine.materials.thermal[lusine.materials.substances["copper"], "solid"];
relation<lusine.materials.mechanical_phases> copper_bulk
= lusine.materials.mechanical_phases[lusine.materials.substances["copper"], "solid"];
relation<lusine.materials.thermal> ice_heat = lusine.materials.thermal[lusine.materials.substances["water"], "solid"];
relation<lusine.materials.mechanical_phases> ice_bulk
= lusine.materials.mechanical_phases[lusine.materials.substances["water"], "solid"];
relation<lusine.materials.thermal> water_heat
= lusine.materials.thermal[lusine.materials.substances["water"], "liquid"];
relation<lusine.materials.mechanical_phases> water_bulk
= lusine.materials.mechanical_phases[lusine.materials.substances["water"], "liquid"];
float<m2/s> copper_tabulated = 0.000117 m2/s; float<m2/s> water_tabulated = 0.000000147 m2/s;
float<m> nearest = 0.002 m; float<m> stride = 0.0001125 m;
float<kg/m3> density = 2000.0 kg/m3; float<J/(kg K)> specific_heat = 1000.0 J/(kg K);
float<W/(m K)> least = 0.5 W/(m K); float<W/(m K)> conductivity_stride = 2.5 W/(m K);
} settings;
// The sweep: the step it is at, from 0 to 159 and round again, the distance there (none at the first step, the axis's
// start, and then from 2 mm to 2 cm), and the swept phase's
// conductivity.
struct { integer step = -1; float<m> distance; float<W/(m K)> conductivity; } sweep;
// Each phase's diffusivity, and the time a temperature takes to spread across the distance in it, L^2 / alpha; the
// times across a centimetre of copper and of water; and the swept phase's diffusivity and rho c_p alpha.
struct {
float<m2/s> copper; float<m2/s> ice; float<m2/s> water; float<s> copper_time; float<s> ice_time; float<s> water_time;
float<s> copper_centimetre; float<s> water_centimetre; float<m2/s> swept; float<W/(m K)> recovered;
} law;
// The checks: the library's copper within 1% of Incropera's and its water within 1.5% (the library's water is at 298.15
// K and its copper denser, 8960 kg/m3 to their 8933); a centimetre crossed in about a second in copper (0.5 to 1.5 s)
// and in ten to thirteen minutes in water; rho c_p alpha the conductivity within 0.0001% at every step; and twice the
// density exactly half the diffusivity, twice the conductivity exactly twice.
struct { bool tabulated = true; bool centimetre = true; bool recovers = true; bool scales = true; } checks;
// A strip of lamps the page draws, lit green while every check holds and red once one fails.
struct lamp { vector<float, 4> colour; };
lattice<lamp, 2> lamps(spacing = 1 m, boundary = wall);
vector<float, 4> lamp_colour(bool holds)
{
return holds ? vector<float, 4>(0.15, 0.6, 0.3, 1.0) : vector<float, 4>(0.75, 0.15, 0.15, 1.0);
}
// A phase's diffusivity from its own two rows.
float<m2/s> diffusivity(relation<lusine.materials.thermal> heat, relation<lusine.materials.mechanical_phases> bulk)
{
return lusine.properties.thermal_diffusivity(conductivity = heat.thermal_conductivity, density = bulk.density,
specific_heat = heat.specific_heat);
}
bool scales(float<W/(m K)> conductivity, float<kg/m3> density, float<J/(kg K)> specific_heat)
{
let once = lusine.properties.thermal_diffusivity(conductivity, density, specific_heat);
return lusine.properties.thermal_diffusivity(conductivity, density = 2.0 * density, specific_heat) == 0.5 * once
&& lusine.properties.thermal_diffusivity(conductivity = 2.0 * conductivity, density, specific_heat)
== 2.0 * once;
}
bool near(float<m2/s> got, float<m2/s> want, float share) { return abs(got / want - 1.0) < share; }
output table<lusine.widget> panels = {
lusine.series { { panel = "spreading", chart = "time to spread", quantity = law.copper_time, steps = 160,
against = sweep.distance, log = true, label = "copper" } },
lusine.series { { panel = "spreading", chart = "time to spread", quantity = law.ice_time, steps = 160,
against = sweep.distance, log = true, label = "ice" } },
lusine.series { { panel = "spreading", chart = "time to spread", quantity = law.water_time, steps = 160,
against = sweep.distance, log = true, label = "water" } },
lusine.value { { panel = "spreading", quantity = sweep.distance, label = "distance" } },
lusine.value { { panel = "spreading", quantity = law.copper_centimetre, label = "a centimetre of copper" } },
lusine.value { { panel = "spreading", quantity = law.water_centimetre, label = "a centimetre of water" } },
lusine.value { { panel = "the phases", quantity = law.copper, label = "copper" } },
lusine.value { { panel = "the phases", quantity = settings.copper_tabulated, label = "Incropera's copper" } },
lusine.value { { panel = "the phases", quantity = law.ice, label = "ice" } },
lusine.value { { panel = "the phases", quantity = law.water, label = "water" } },
lusine.value { { panel = "the phases", quantity = settings.water_tabulated, label = "Incropera's water" } },
lusine.value { { panel = "the checks", quantity = sweep.conductivity, label = "swept conductivity" } },
lusine.value { { panel = "the checks", quantity = law.recovered, label = "rho c_p alpha" } },
lusine.value { { panel = "the checks", quantity = checks.tabulated, label = "Incropera's values" } },
lusine.value { { panel = "the checks", quantity = checks.centimetre, label = "a centimetre" } },
lusine.value { { panel = "the checks", quantity = checks.recovers, label = "rho c_p alpha is k" } },
lusine.value { { panel = "the checks", quantity = checks.scales, label = "scales" } },
};
void main()
{
sweep.step = sweep.step >= 159 ? 0 : sweep.step + 1;
sweep.distance = sweep.step == 0 ? 0.0 m : settings.nearest + float(sweep.step - 1) * settings.stride;
sweep.conductivity = settings.least + float(sweep.step) * settings.conductivity_stride;
law.copper = diffusivity(heat = settings.copper_heat, bulk = settings.copper_bulk);
law.ice = diffusivity(heat = settings.ice_heat, bulk = settings.ice_bulk);
law.water = diffusivity(heat = settings.water_heat, bulk = settings.water_bulk);
law.copper_time = sweep.distance * sweep.distance / law.copper;
law.ice_time = sweep.distance * sweep.distance / law.ice;
law.water_time = sweep.distance * sweep.distance / law.water;
law.copper_centimetre = (0.0001 m2) / law.copper;
law.water_centimetre = (0.0001 m2) / law.water;
law.swept = lusine.properties.thermal_diffusivity(sweep.conductivity, settings.density, settings.specific_heat);
law.recovered = settings.density * settings.specific_heat * law.swept;
checks.tabulated = checks.tabulated && near(got = law.copper, want = settings.copper_tabulated, share = 0.01)
&& near(got = law.water, want = settings.water_tabulated, share = 0.015);
checks.centimetre = checks.centimetre && law.copper_centimetre > 0.5 s && law.copper_centimetre < 1.5 s
&& law.water_centimetre > 600.0 s && law.water_centimetre < 780.0 s;
checks.recovers = checks.recovers && abs(law.recovered / sweep.conductivity - 1.0) <= 0.000001;
checks.scales = checks.scales && scales(sweep.conductivity, settings.density, settings.specific_heat);
lamps.colour = lamp_colour(holds = checks.tabulated && checks.centimetre && checks.recovers && checks.scales);
}
The law's other proof (every phase in the library with all three numbers, and the inverse over phases at random) is on its proof page.