Annus Mirabilis · Interactive critical edition in preparation
The spreading laboratory
Watch particles spread from one point into a bell curve.
An executable model
The spreading laboratory
Choose an interval and ask how likely a tracer is to finish inside it. Changing a field only edits a request. The plot and table change together after you apply valid settings.
Read the curve as numbers, at 17 points
| RMS distances from zero | Displacement (μm) | Density (per μm) | Share of the peak height |
|---|---|---|---|
| −4 | −3.707 | 0.00014441 | 0.034% |
| −3.5 | −3.2437 | 0.00094165 | 0.22% |
| −3 | −2.7803 | 0.0047821 | 1.1% |
| −2.5 | −2.3169 | 0.018914 | 4.4% |
| −2 | −1.8535 | 0.058258 | 13.5% |
| −1.5 | −1.3901 | 0.13975 | 32.5% |
| −1 | −0.92676 | 0.26109 | 60.7% |
| −0.5 | −0.46338 | 0.37989 | 88.2% |
| 0 | 0 | 0.43047 | 100% |
| 0.5 | 0.46338 | 0.37989 | 88.2% |
| 1 | 0.92676 | 0.26109 | 60.7% |
| 1.5 | 1.3901 | 0.13975 | 32.5% |
| 2 | 1.8535 | 0.058258 | 13.5% |
| 2.5 | 2.3169 | 0.018914 | 4.4% |
| 3 | 2.7803 | 0.0047821 | 1.1% |
| 3.5 | 3.2437 | 0.00094165 | 0.22% |
| 4 | 3.707 | 0.00014441 | 0.034% |
Static worked example
CurrentThese numbers match the current settings.
Model note
- Primary output diffusionCoefficient: Host calculation (diffusion.stokesEinsteinD). Owner diffusion.stokesEinsteinD.
- Primary outputs activeDiffusionCoefficient, positionCoordinate1d, comparisonTimes, gridTimeStep: Host calculation (bm06.evaluate). Owner bm06.evaluate.
- Primary outputs rmsDisplacement1d, comparisonRms: Host calculation (diffusion.rmsDisplacement). Owner diffusion.rmsDisplacement.
- Primary outputs meanSquareDisplacement1d, meanRadius2d, rmsRadius2d, meanRadius3d, rmsRadius3d: Host calculation (diffusion.moments). Owner diffusion.moments.
- Primary output intervalProbability: Host calculation (diffusion.intervalProbability). Owner diffusion.intervalProbability.
- Primary output probabilityDensity: Host calculation (diffusion.gaussianPropagator). Owner diffusion.gaussianPropagator.
- Primary output mostLikelyRadius2d: Host calculation (diffusion.mostLikelyRadius2d). Owner diffusion.mostLikelyRadius2d.
- Primary outputs gridDensity, stabilityRatio: Host calculation (diffusion.ftcs1d). Owner diffusion.ftcs1d.
- Primary outputs cellMasses, cellProbabilities, maxCellMassDifference, wallContact: Host calculation (diffusion.ftcsAnalyticComparison). Owner diffusion.ftcsAnalyticComparison.
- Accepted input revision 1.
- Snapshot version 1.
- Not modeled: The ballistic short-time regime and inertia.
- Show the code
Accepted result: RMS displacement 0.92676 micrometres; selected interval probability 71.943 percent.
293.15 K · 1 mPa·s · radius 0.5 μm · elapsed 1 s
Constants: modern SI (2019). Viscosity is a declared input, not inferred from temperature. D is calculated from these physical inputs.
| Diffusion coefficient | 0.42944 μm²/s |
|---|---|
| RMS displacement | 0.92676 μm |
| Mean square displacement | 0.85888 μm² |
| Selected closed interval | [−1, 1] μm |
| Probability inside that interval | 71.943% |
Density is measured per unit length; probability is a dimensionless area. A taller curve is not a larger total probability.
A tiny sphere in water wanders at random, so after a given time it could be anywhere nearby, most likely close to where it began. The spread follows a bell curve, grows as the square root of the time, and is smaller for bigger spheres and thicker liquids.
Section 4 solves the diffusion equation for particles that all start at one point: the fraction found between x and x + dx after a time t is the Gaussian law of errors, exp(−x2/4Dt) dx/√(4πDt), and the root-mean-square displacement along one axis is λx = √(2Dt). Section 3 had found D for spheres of radius P in a liquid of viscosity k, D = (RT/N)/(6πkP), so Section 5 joins the two: λx = √t √((RT/N)/(3πkP)). The instrument evaluates this with today's constants for a sphere of radius 0.5 μm in water of viscosity 1.0 mPa·s at 293.15 K: D = 0.429 μm2/s and, after 1 s, λx = 0.927 μm, with a 71.9 percent chance of lying within 1 μm of the start. Measured in a plane or in space, the distance from the start has root-mean-square values √(4Dt) = 1.31 μm and √(6Dt) = 1.61 μm. At t = 0 the distribution is a single point, which the instrument reports as that limit rather than as a curve. An optional grid solves the same equation step by step in a box with walls, and refuses a time step too long for the scheme to stay stable.
First the diffusion coefficient. Section 3 balances the osmotic push of the suspended spheres against the drag of the liquid, which Stokes gave as 6πηa times the speed for a sphere of radius a in a liquid of viscosity η. In today's symbols the balance gives D = kBT/(6πηa), with kB Boltzmann's constant and T the temperature. Here kBT = 1.381 × 10−23 × 293.15 = 4.047 × 10−21 J, and the drag factor is 6πηa = 6 × 3.1416 × 0.001 × 0.5 × 10−6 = 9.425 × 10−9 kg/s. Their ratio is D = 4.047 × 10−21/(9.425 × 10−9) = 4.294 × 10−13 m2/s, or 0.4294 μm2/s. Now the spread. Particles that all start at x = 0 are found, after a time t, spread as a bell curve with mean square 2Dt: 2 × 0.4294 × 1 = 0.859 μm2 after one second, so the root-mean-square displacement is √0.859 = 0.927 μm. The chance of lying within 1 μm of the start is the area under the bell curve between −1 μm and +1 μm. One root-mean-square distance either side holds 68.3 percent; 1 μm is 1/0.927 = 1.08 of them, and the area out to 1.08 is erf(1.08/√2) = 0.719, or 71.9 percent. Double the time and the mean square doubles, so the spread grows by √2, to 1.31 μm after 2 s; after 60 s it is √60 = 7.75 times as far, 7.18 μm. Make the sphere four times larger and D falls to a quarter, so the spread halves. In a plane, add the two independent axes: the mean square distance is 2Dt + 2Dt = 4Dt = 1.72 μm2, a root mean square of 1.31 μm, while the most likely distance is √(2Dt) = 0.927 μm and the mean distance √(πDt) = 1.16 μm. In space the mean square is 6Dt, a root mean square of 1.61 μm, and the mean distance is √(16Dt/π) = 1.48 μm. Einstein's own example takes N = 6 × 1023, water at 17 °C with a viscosity of 1.35 × 10−2 in his units (1.35 mPa·s), and spheres 0.001 mm across: λx = 8 × 10−5 cm, or 0.8 μm in a second, and about 6 μm in a minute.
Einstein wrote k for the viscosity and P for the radius. He kept R and N apart because N was the unknown the paper hoped to measure: Section 5 ends by solving for it, N = (t/λx2)(RT/(3πkP)), and by hoping that a researcher will soon decide the question. Perrin's measurements of 1908 and 1909 confirmed the square-root law and gave N near 7 × 1023. The three-dimensional root mean square is Einstein's too: he remarks that the total displacement has root-mean-square value λx√3, which is √(6Dt). The two-dimensional radial laws and the grid are later aids, not in the paper; the grid's refusal above DΔt/Δx2 = 1/2 is a property of the explicit numerical method, not of the physics. Stokes's law for the drag on a sphere dates from 1851.
One setup, three observation times
| Elapsed time | RMS displacement |
|---|---|
| 1 s | 0.92676 μm |
| 10 s | 2.9307 μm |
| 60 s | 7.1786 μm |
The same spread, as a 2D or 3D radius
A radius is not a signed coordinate: it is never negative, and the growing circumference (2D) or surface area (3D) of positions at a given distance changes which average is largest.
| Quantity | 2D radius | 3D radius |
|---|---|---|
| Mean radius ⟨r⟩ | 1.1615 μm | 1.4789 μm |
| RMS radius √⟨r²⟩ | 1.3106 μm | 1.6052 μm |
| Most likely radius | 0.92676 μm | not modeled |
The mean 2D radius ⟨r⟩ = √(πDt) is smaller than the RMS radius √⟨r²⟩ = √(4Dt): the extra factor of r in the 2D density weights larger radii more heavily than a signed 1D coordinate does, so the two averages of the same spread disagree.
The RMS 2D radius √⟨r²⟩ = √(4Dt) is the square root of the mean squared distance from the start, not the mean distance itself. Squaring before averaging always weights the tail more than averaging the radius directly.
The most likely 2D radius, where the density p(r, t) = (r/2Dt) exp(−r²/4Dt) peaks, is √(2Dt): the same number as the 1D RMS displacement. The growing circumference of available positions at radius r (proportional to r itself) exactly cancels the falling Gaussian density near the start, moving the peak away from the origin.
The mean 3D radius ⟨r⟩ = 4√(Dt/π) counts positions on a growing sphere (area proportional to r²), pulling the average distance from the start out further than the 2D circumference case.
The RMS 3D radius √⟨r²⟩ = √(6Dt) reflects three independent coordinate directions, each contributing its own 2Dt to the mean squared displacement (compare the 1D case's single 2Dt).
What this model leaves out
Dilute spherical tracers in a homogeneous Newtonian liquid; no drift or particle interactions.
Low Reynolds number and times long compared with momentum relaxation are assumed, not checked.
The analytic curve describes an unbounded line; the numerical grid has reflecting walls.
These are consequences of a model, not measurements or evidence that the model describes nature.
No FrankenSim artifact is used here. A verified historical transcription and the full critical edition are still in preparation.
Show the calculation owner and source identity
Density, interval probability and displacement come from src/physics/reference/diffusion/distributions.ts. The grid comes from src/physics/reference/diffusion/ftcs.ts. One worker operation assembles all views.
source:sha256:b442417dee336e3f328b9a588ca3b1993e25ae8a53b73aa0f198a33cecf812c4
The source link opens current main, which may differ from this build. The digest above identifies the evaluator sources used by this page.
Show the code
Audited TypeScript reference evaluator: the owner on this device, or the host fallback for a FrankenSim capability.
gaussianPropagator · src/physics/reference/diffusion/distributions.ts · revision workspace · sha256:08378ddf8b6720db47f4949e26c3bb6de95c5ab5ba5851fe4b52b651ef62f55a
This is the function that produced the current snapshot.
In words
On an unbounded line the probability density is a Gaussian whose width grows as the square root of elapsed time.
Mathematics
Implementation
export function gaussianPropagator(x: number, t: number, D: number): Evaluation {
if (!Number.isFinite(x) || !validDt(D, t))
return outside(
"probabilityDensity",
"gaussianPropagator",
"finite x, D >= 0, t >= 0",
"Use finite position and nonnegative diffusivity and time.",
);
if (D === 0 || t === 0) return pointMass("probabilityDensity", "gaussianPropagator");
const sigma = scale(D, t);
const z = x / sigma;
if (!(sigma > 0) || !Number.isFinite(sigma))
return outside(
"probabilityDensity",
"gaussianPropagator",
"representable spread",
"The spread is outside the numerical range.",
"numerical",
);
return number(
"probabilityDensity",
"gaussianPropagator",
Math.exp(-Math.log(sigma) - 0.5 * Math.log(2 * Math.PI) - 0.5 * z * z),
undefined,
undefined,
true,
);
}Audited TypeScript reference evaluator: the owner on this device, or the host fallback for a FrankenSim capability.
intervalProbability · src/physics/reference/diffusion/distributions.ts · revision workspace · sha256:fdf333ecee819e1097ee25fb89e2caea663dd27fc5f8818d3a28cd8a15ff9a56
This function computes the listed outputs when it runs.
In words
The probability of landing between two points is the integral of that Gaussian.
Mathematics
Implementation
/** Closed intervals include the atom at t=0, including the interval [0,0]. */
export function intervalProbability(x1: number, x2: number, t: number, D: number): Evaluation {
const owner = "intervalProbability";
if (![x1, x2].every(Number.isFinite) || x1 > x2 || !validDt(D, t))
return outside(
"intervalProbability",
owner,
"x1 <= x2, D >= 0, t >= 0",
"Use ordered finite interval endpoints and nonnegative diffusivity and time.",
);
if (D === 0 || t === 0) return number("intervalProbability", owner, x1 <= 0 && x2 >= 0 ? 1 : 0);
if (x1 === x2) return number("intervalProbability", owner, 0);
const s = 2 * Math.sqrt(D) * Math.sqrt(t);
if (!(s > 0) || !Number.isFinite(s))
return outside(
"intervalProbability",
owner,
"representable spread",
"The spread is outside the numerical range.",
"numerical",
);
const lo = x1 / s;
const hi = x2 / s;
const width = (x2 - x1) / s;
const mid = lo + width / 2;
let p: number;
// Very narrow intervals need direct integration, even when both tails are small.
if (width > 0 && width * (1 + Math.abs(mid)) < 0.01) {
const nodes = [0.1834346424956498, 0.525532409916329, 0.7966664774136267, 0.9602898564975363];
const weights = [0.362683783378362, 0.3137066458778873, 0.2223810344533745, 0.1012285362903763];
let sum = 0;
for (let i = 0; i < 4; i++) {
const offset = (width / 2) * nodes[i]!;
sum += weights[i]! * (Math.exp(-((mid - offset) ** 2)) + Math.exp(-((mid + offset) ** 2)));
}
p = (width / (2 * Math.sqrt(Math.PI))) * sum;
} else
p =
lo >= 0
? 0.5 * (tail(lo) - tail(hi))
: hi <= 0
? 0.5 * (tail(-hi) - tail(-lo))
: 0.5 * (central(hi) - central(lo));
if (p < 0 || p > 1)
return outside(
"intervalProbability",
owner,
"probability range",
"Numerical evaluation did not produce an admissible probability.",
"numerical",
);
return number("intervalProbability", owner, p, undefined, undefined, true);
}Audited TypeScript reference evaluator: the owner on this device, or the host fallback for a FrankenSim capability.
erf · src/physics/reference/special/erf.ts · revision workspace · sha256:58ba0038d43059073d60c1af94ee48c8052c076685e393ce5eeab85362edda77
This function computes the listed outputs when it runs.
In words
The error function is the integral that turns a Gaussian density into an interval probability.
Mathematics
Implementation
export function erf(x: number): number {
validate(x);
if (x < 0) return -erf(-x);
return x < 1.5 ? erfPositive(x) : 1 - erfcPositive(x);
}Audited TypeScript reference evaluator: the owner on this device, or the host fallback for a FrankenSim capability.
erfc · src/physics/reference/special/erf.ts · revision workspace · sha256:45a75bb57d3658953e37bdc7ea1e9f0abbd6296180b6101f231d0631f5cb0922
This function computes the listed outputs when it runs.
In words
The complementary error function evaluates the Gaussian tail without subtracting from one.
Mathematics
Implementation
export function erfc(x: number): number {
validate(x);
return x < 0 ? 2 - erfcPositive(-x) : erfcPositive(x);
}Audited TypeScript reference evaluator: the owner on this device, or the host fallback for a FrankenSim capability.
radialPropagator2d · src/physics/reference/diffusion/distributions.ts · revision workspace · sha256:58040898d39023e9e0e937741b8bb0885bad8976be85b6817c2b5ff3ef0916b2
This function computes the listed outputs when it runs.
In words
In two dimensions the radial density is not itself a Gaussian.
Mathematics
Implementation
export function radialPropagator2d(r: number, t: number, D: number): Evaluation {
return radial(r, t, D, 2);
}Audited TypeScript reference evaluator: the owner on this device, or the host fallback for a FrankenSim capability.
radialPropagator3d · src/physics/reference/diffusion/distributions.ts · revision workspace · sha256:37f9cc144c032a6104ceaa5b2020114d14a16a314620ad493149e5ee1bfdb6e8
This function computes the listed outputs when it runs.
In words
In three dimensions the radial density is not itself a Gaussian.
Mathematics
Implementation
export function radialPropagator3d(r: number, t: number, D: number): Evaluation {
return radial(r, t, D, 3);
}Audited TypeScript reference evaluator: the owner on this device, or the host fallback for a FrankenSim capability.
ftcs1d · src/physics/reference/diffusion/ftcs.ts · revision workspace · sha256:c04f80e8182597a114b7d20f492eb86053f90b4cc9e7cd299f516a6b44eb4755
This function computes the listed outputs when it runs.
In words
Advance a one-dimensional density with an explicit three-point Laplacian. Refuse when the stability ratio exceeds one half.
Mathematics
Implementation
export function ftcs1d(p: FtcsParameters, budget = REFERENCE_FTCS_BUDGET): Computation<FtcsFrames> {
const { n, frames, stepsPerFrame, D, dx, dt, profile } = p;
if (!integer(n, 3) || !integer(frames, 1) || !integer(stepsPerFrame, 1))
return invalid(
["n", "frames", "stepsPerFrame"],
"Use at least three cells and positive whole-number frame and step counts.",
);
if (![D, dx, dt].every(Number.isFinite))
return {
kind: "refused",
refusal: makeRefusal("nonfinite-input", { parameterIds: ["D", "dx", "dt"] }),
};
if (D < 0 || dx <= 0 || dt <= 0 || ![0, 1, 2].includes(profile))
return invalid(
["D", "dx", "dt", "profile"],
"Use nonnegative D, positive dx and dt, and profile 0, 1, or 2.",
);
// This exact operation order is part of the planned upstream conformance contract.
const r = D === 0 ? 0 : (D * dt) / (dx * dx);
if (!Number.isFinite(r) || (D > 0 && r === 0))
return numerical("The stability ratio is outside binary64 range; the grid was not advanced.");
if (r > 0.5) {
const dtMax = (dx * dx) / (2 * D);
const dxMin = Math.sqrt(2 * D * dt);
const dMax = (dx * dx) / (2 * dt);
if (![dtMax, dxMin, dMax].every((v) => Number.isFinite(v) && v > 0))
return numerical("The dimensional stability repairs are outside binary64 range.");
// Mathematical bounds can round to a value with r=0.5000000000000001.
// Preserve dtMax in the explanation; make each offered action executable.
const dtRepair = stableRepair(dtMax, false, (v) => (D * v) / (dx * dx));
const dxRepair = stableRepair(dxMin, true, (v) => (D * dt) / (v * v));
const dRepair = stableRepair(dMax, false, (v) => (v * dt) / (dx * dx));
if (dtRepair === null || dxRepair === null || dRepair === null)
return numerical(
"No representable repair was found near the dimensional stability boundary.",
);
return {
kind: "refused",
refusal: makeRefusal(
"ftcs-unstable",
{ parameterIds: ["dt", "dx", "D"] },
{
details: { ratio: r, limit: 0.5, dtMax },
rankedRepairs: [
{
label: "Reduce the time step to the explicit scheme's limit.",
action: { parameterId: "dt", value: dtRepair },
},
{
label: "Use a coarser spatial grid.",
action: { parameterId: "dx", value: dxRepair },
},
{
label: "Choose a smaller diffusivity; this changes the physical setup.",
action: { parameterId: "D", value: dRepair },
},
],
},
),
};
}
const stepCount = (frames - 1) * stepsPerFrame;
const limited = budgetCheck(n * stepCount, 8 * n * (frames + 2), budget);
if (limited) return limited;
const elapsedTime = stepCount * dt;
if (!Number.isFinite(elapsedTime) || (stepCount > 0 && elapsedTime === 0))
return numerical("Elapsed model time is outside binary64 range.");
const field = new Float64Array(n);
if (profile === 0) field[Math.floor(n / 2)] = 1 / dx;
else if (profile === 1) field.fill(1, 0, Math.floor(n / 2));
else {
field[Math.floor(n / 4)] = 0.5 / dx;
field[Math.floor((3 * n) / 4)] = 0.5 / dx;
}
if (!field.every(Number.isFinite))
return numerical("The initial cell density is outside binary64 range.");
const derivative = new Float64Array(n);
const values = new Float64Array(n * frames);
values.set(field);
for (let frame = 1; frame < frames; frame++) {
if (!advanceInPlace(field, derivative, r, stepsPerFrame))
return numerical("A cell became nonfinite or negative; no partial frames are returned.");
values.set(field, frame * n);
}
return {
kind: "accepted",
data: Object.freeze({
values,
shape: Object.freeze([frames, n] as const),
stepCount,
elapsedTime,
stabilityRatio: r,
boundary: "zero-flux",
ownerId: "diffusion.ftcs1d",
}),
};
}The explanation
Full explanation
After a time t a particle could be anywhere nearby, most likely close to where it began. The spread follows a bell curve whose width grows as the square root of the time and is smaller for bigger spheres and thicker liquids.
Show every step of the investigation
Choose an interval and ask how likely a particle is to finish inside it, then compare the bell curve with the stepped numerical model. Doubling the viscosity halves the diffusion coefficient and narrows the spread by a factor of √2, not by half.
An explanatory model, not an observation of nature. This embed starts from the laboratory’s worked defaults, not a saved run. Presentation options change the surrounding guide, never the numerical inputs.