Heat equation — explicit Euler
The 2-D heat equation ∂u/∂t = α∇²u, discretised by a five-point Laplacian and stepped forward by explicit Euler under periodic boundary conditions. Initial condition: seven hot spots. The integral of u is conserved (the test enforces drift < 0.1%); the visible motion is heat redistributing, not appearing or vanishing. Receipt below: Landauer floor vs TDP envelope, μ apparent.
view the kernel that wrote this receipt crates/mathground-view/src/heat.rs
//! 2-D heat equation — explicit forward-Euler stencil.
//!
//! ∂u/∂t = α ∇² u
//!
//! Discretised on a w × h grid with periodic boundary. One step per call.
//! Stable when `α · dt · (1/dx² + 1/dy²) ≤ 0.5`. V-class L0-closed.
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct HeatParams {
pub alpha: f32,
pub dt: f32,
pub dx: f32,
}
impl Default for HeatParams {
fn default() -> Self {
Self { alpha: 0.25, dt: 0.2, dx: 1.0 }
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HeatKernelReport {
pub grid_w: u32,
pub grid_h: u32,
pub bit_ops: u64,
}
/// One explicit forward-Euler step on a periodic w × h grid.
/// Reads from `u`, writes to `u_next`. Returns the kernel report.
pub fn heat_step(u: &[f32], u_next: &mut [f32], w: u32, h: u32, p: &HeatParams) -> HeatKernelReport {
let w_us = w as usize;
let h_us = h as usize;
debug_assert_eq!(u.len(), w_us * h_us);
debug_assert_eq!(u_next.len(), w_us * h_us);
let k = p.alpha * p.dt / (p.dx * p.dx);
for y in 0..h_us {
let yp = if y == 0 { h_us - 1 } else { y - 1 };
let yn = if y + 1 == h_us { 0 } else { y + 1 };
for x in 0..w_us {
let xp = if x == 0 { w_us - 1 } else { x - 1 };
let xn = if x + 1 == w_us { 0 } else { x + 1 };
let c = u[y * w_us + x];
let lap = u[y * w_us + xp]
+ u[y * w_us + xn]
+ u[yp * w_us + x]
+ u[yn * w_us + x]
- 4.0 * c;
u_next[y * w_us + x] = c + k * lap;
}
}
let cells = (w_us * h_us) as u64;
HeatKernelReport {
grid_w: w,
grid_h: h,
bit_ops: cells * 8 * 32,
}
}
/// Total energy (∫ u dx ≈ sum) — should be conserved exactly under
/// periodic BC with the linear stencil. Used for the convergence test.
pub fn integral(u: &[f32]) -> f32 {
u.iter().sum()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn periodic_bc_conserves_integral() {
let w = 32;
let h = 32;
let mut u = vec![0.0f32; (w * h) as usize];
// Localised hot spot.
u[(h / 2 * w + w / 2) as usize] = 100.0;
let mut u_next = vec![0.0f32; (w * h) as usize];
let p = HeatParams::default();
let s0 = integral(&u);
for _ in 0..1000 {
heat_step(&u, &mut u_next, w, h, &p);
std::mem::swap(&mut u, &mut u_next);
}
let s1 = integral(&u);
let rel = (s1 - s0).abs() / s0.abs();
assert!(rel < 1e-3, "heat: integral drift {rel}");
}
#[test]
fn diffusion_spreads_hot_spot() {
let w = 32;
let h = 32;
let mut u = vec![0.0f32; (w * h) as usize];
u[(h / 2 * w + w / 2) as usize] = 100.0;
let mut u_next = vec![0.0f32; (w * h) as usize];
let p = HeatParams::default();
for _ in 0..50 {
heat_step(&u, &mut u_next, w, h, &p);
std::mem::swap(&mut u, &mut u_next);
}
let centre = u[(h / 2 * w + w / 2) as usize];
let neighbour = u[(h / 2 * w + w / 2 + 1) as usize];
assert!(centre < 100.0 && neighbour > 0.0, "heat should diffuse");
}
}
This is the exact Rust file compiled into the WASM module the page just loaded. Every line that runs on your device is here. The receipt is a function of this code, not a bespoke benchmark.