The ink-drop experiment.
An ink drop spreads through molecular random walks.
The physical idea.
Think of the walkers as independent dye molecules released in a localized ink drop. This idealized model illustrates diffusion without fluid flow, convection or molecular interactions. Each molecule takes one independent step in one of four directions: left, right, up or down. The step length is a, measured in micrometers (µm), and the simulation time between steps is Δt = 1 second. After many steps, the predicted mean-square displacement is ⟨r²⟩ = 4Dt, where D = a²/(4Δt) is diffusivity (µm²/s), t is elapsed simulation time (s), and r is displacement from the starting point (µm). These illustrative values are not measured properties of a particular ink.
This example is based on my teaching code, demo2_many_walkers_diffusion.jl. I wrote the original code to show how random molecular motion leads to diffusion. The version on this page lets you follow the molecules and compare their mean-square displacement with the diffusion equation.
Try the simulation
Choose the number of molecules and the step length, then select Start. Each sweep moves every molecule once. Changing the number, step length or seed starts a new run. Playback speed changes how quickly you watch the simulation; it does not change the physical time between steps.
Where are the molecules?
Does the spreading agree with diffusion?
Recorded simulation
The original 100,000-molecule calculation and MP4 are retained here. The saved-state viewer advances in intervals of ten simulation steps; use the live simulation above to see every step and change parameters. The recorded calculation uses one unit of length and one unit of time per step.
Open the saved-state viewer and recorded movie
Precomputed NumPy results from the downloadable code; the browser scrubs saved states. Horizontal axis: x. Vertical axis: y. 100,000 walkers; 256 × 256 histogram bins from −128 to 128; color uses a square-root scale from probability 0 to 0.001 (values above 0.001 saturate).
Download measured diagnostics (CSV) ↗Model and numerical choices.
This example moves every molecule once per sweep on an unbounded lattice. My original teaching demonstration selects molecules asynchronously in a periodic box. The displayed histogram window is not a boundary. Mean-square displacement uses unwrapped coordinates.
Questions explored in this example.
Use the live controls above to increase the number of molecules and compare fluctuations in mean-square displacement. Then double the step length and compare diffusivity. Download the source below to run larger calculations.
My Python calculation, explained in steps.
Both versions implement the same model. Python uses NumPy. The walker example in Julia uses standard-library packages only. Scripts write CSV snapshots and diagnostics into the output folder. The random-number generators differ, so walker paths differ while their ensemble statistics should agree.
Download the Python notebook ↓ · Read the code in steps → Download Julia version ↓
Run the notebook cells in order for the explained calculation. The commands and complete script below are an optional command-line route; the script is linked at the end of the notebook lesson.
python -m pip install numpy
python walkers.py resultsRead the complete NumPy code
"""Random-walk diffusion example based on my teaching code demo2_many_walkers_diffusion.jl.
Purpose: show how random molecular steps produce diffusion.
Run: python walkers.py [output_directory]. Requires NumPy.
Each walker takes one unit cardinal step per sweep on an unbounded 2D lattice.
My original teaching code uses asynchronous moves in a periodic box.
This example moves every molecule once per sweep on an unbounded lattice.
MSD uses unwrapped coordinates; theoretical MSD=steps, D=1/4.
"""
import sys
from pathlib import Path
import numpy as np
def run(output='walkers_output'):
out=Path(output);out.mkdir(parents=True,exist_ok=True)
rng=np.random.default_rng(7);n=100000;pos=np.zeros((n,2),dtype=int)
dirs=np.array([[1,0],[-1,0],[0,1],[0,-1]]);rows=[]
for step in range(401):
if step:
pos+=dirs[rng.integers(0,4,n)]
if step%10==0:
msd=np.mean(np.sum(pos*pos,axis=1));rows.append([step,msd,step])
hist,_,_=np.histogram2d(pos[:,0],pos[:,1],bins=256,range=[[-128,128],[-128,128]])
np.savetxt(out/f'density_{step:04d}.csv',hist/n,delimiter=',')
np.savetxt(out/'diagnostics.csv',rows,delimiter=',',header='steps,msd,theory',comments='')
assert abs(rows[-1][1]/400-1)<.04
print('Walker MSD at 400 steps',rows[-1][1],'theory 400')
if __name__=='__main__':run(*sys.argv[1:2])
julia walkers.jl resultsRead the complete Julia code
# Random-walk diffusion example based on my teaching code demo2_many_walkers_diffusion.jl.
# Purpose: show how random molecular steps produce diffusion.
# julia walkers.jl [output_directory]; standard-library dependencies only.
# All walkers step each sweep, unbounded 2D lattice, unit steps. D=1/4.
# Julia/NumPy RNGs differ; compare ensemble statistics, not paths.
using Random, Statistics, DelimitedFiles, Printf
function run(output="walkers_output")
mkpath(output);rng=MersenneTwister(7);n=100000;pos=zeros(Int,n,2)
dirs=[1 0;-1 0;0 1;0 -1];rows=Vector{Float64}[]
for step=0:400
if step>0
for w=1:n
d=rand(rng,1:4);pos[w,1]+=dirs[d,1];pos[w,2]+=dirs[d,2]
end
end
if step%10==0
msd=mean(sum(abs2,pos;dims=2));push!(rows,[step,msd,step]);hist=zeros(256,256)
for w=1:n
a=floor(Int,pos[w,1]+128)+1;b=floor(Int,pos[w,2]+128)+1
# Histogram window only; walkers are not confined to this window.
a==257 && pos[w,1]==128 && (a=256)
b==257 && pos[w,2]==128 && (b=256)
1<=a<=256 && 1<=b<=256 && (hist[a,b]+=1/n)
end
writedlm(joinpath(output,@sprintf("density_%04d.csv",step)),hist,',')
end
end
open(joinpath(output,"diagnostics.csv"),"w") do io
println(io,"steps,msd,theory");writedlm(io,permutedims(hcat(rows...)),',')
end
@assert abs(rows[end][2]/400-1)<.04
println("Walker MSD at 400 steps ",rows[end][2]," theory 400")
end
if abspath(PROGRAM_FILE) == @__FILE__
run(ARGS...)
end
About these calculations.
I use these examples to explain numerical methods. I provide the parameters, boundary conditions and numerical checks with the results. These are teaching calculations, not predictions calibrated for a particular material.
← All worked examplesHow I develop my codes.
I start with a small calculation, check its result and then add the next part. These lessons follow the same sequence, with explanations and checks at each stage. Open the worked Python, Julia and C lessons →