Reading Time: 5 minutes

Diffusion is one of the most intuitive partial differential equations (PDEs) you can simulate: a sharp peak spreads out, a steep step smooths into a gentle transition, and gradients gradually disappear. If you’re learning FiPy, diffusion is also one of the best starting points because it maps cleanly onto FiPy’s core workflow: mesh → variable → equation → time loop → visualization.

In this guide you’ll solve the diffusion equation in FiPy from scratch, run a complete 1D simulation you can tweak safely, learn how to interpret the output, and then see how the same setup extends to 2D. The goal is practical confidence: you should be able to change resolution, time step, diffusion speed, and boundary conditions without breaking the model.

What Diffusion Means (Without the Heavy Math)

Diffusion describes how something spreads out due to concentration (or temperature) differences. Heat spreads through a rod. A drop of dye spreads in water. Mathematically, we often write diffusion in this form:

∂φ/∂t = ∇·(D ∇φ)

Here φ is the field you care about (temperature, concentration, etc.). The coefficient D controls how fast diffusion happens. Larger D means the profile smooths out more quickly.

Boundary conditions matter because PDEs need rules at the edges of the domain. For a first diffusion run, “no-flux” boundaries are common: nothing flows out of the domain. That makes the evolution easy to interpret because the system isn’t being forced by the boundaries.

How FiPy Represents Diffusion

FiPy solves PDEs using the finite volume method (FVM). You can think of your domain as being split into small cells. The unknown field lives on those cells (typically at the cell centers). Diffusion is implemented as flux between neighboring cells: high values push flow toward low values until the profile becomes smoother.

The practical FiPy building blocks are:

  • Mesh — the geometry and resolution (1D or 2D grid)
  • CellVariable — the field φ you solve for
  • TransientTerm() — the time derivative ∂φ/∂t
  • DiffusionTerm(coeff=D) — the diffusion operator
  • a time loop — repeatedly solve with dt and visualize snapshots

Before You Start: Minimal Environment Checklist

You need Python plus FiPy and a plotting library. If you’re using notebooks, the same code works; just run cells in order.

  • Python installed (a modern version is recommended)
  • A virtual environment (recommended)
  • FiPy installed (and its numerical dependencies)
  • Matplotlib installed for plotting

First Working Example: 1D Diffusion

We’ll model diffusion on a 1D domain from 0 to 1 using a uniform grid. The initial condition will be a step:
φ = 1 on the left half and φ = 0 on the right half. Diffusion will smooth that discontinuity over time.

The code below is a complete script you can run as-is.

# Solving Diffusion Equations with FiPy: 1D Example
# Save as: diffusion_1d_fipy.py

from fipy import Grid1D, CellVariable, TransientTerm, DiffusionTerm
import matplotlib.pyplot as plt

# --- 1) Mesh setup ---
nx = 200
dx = 1.0 / nx
mesh = Grid1D(nx=nx, dx=dx)

# --- 2) Variable (field) ---
phi = CellVariable(name="phi", mesh=mesh, value=0.0)

# Initial condition: step (left half = 1, right half = 0)
x = mesh.cellCenters[0]
phi.setValue(1.0, where=(x < 0.5))

# --- 3) Diffusion equation ---
D = 1.0
eq = TransientTerm() == DiffusionTerm(coeff=D)

# --- 4) Time stepping ---
dt = 1e-4
steps = 2000

snapshots = []
times = []
capture_at = {0, 50, 200, 800, 2000}

for step in range(steps + 1):
    if step in capture_at:
        snapshots.append(phi.value.copy())
        times.append(step * dt)

    eq.solve(var=phi, dt=dt)

# --- 5) Plot ---
plt.figure()
for y, t in zip(snapshots, times):
    plt.plot(x.value, y, label=f"t = {t:.4f}")

plt.xlabel("x")
plt.ylabel("phi")
plt.title("1D Diffusion in FiPy")
plt.legend()
plt.show()

What Each Block Does (So You Can Modify It Safely)

Mesh: your domain and resolution

Grid1D(nx=nx, dx=dx) creates a line split into nx cells. Smaller dx (larger nx) gives more detail but costs more computation. Your domain length is roughly nx * dx.

CellVariable: the field you solve for

phi is stored at cell centers. You start with an initial condition using setValue(). With diffusion, that initial shape is what you watch relax over time.

The equation: time change equals diffusion

This line defines the diffusion PDE in FiPy’s term syntax:

TransientTerm() == DiffusionTerm(coeff=D)

D controls how quickly the step smooths. If you increase D, you should see the transition zone widen faster.

The time loop: stepping forward

Each call to eq.solve(var=phi, dt=dt) advances the solution by one time step. We capture a few snapshots so you can see the evolution without plotting every single step.

How to Tell If Your Diffusion Run Looks Correct

With a step initial condition, diffusion should create a smooth transition region that gradually widens. The sharp jump should soften, and the curve should become less steep over time.

  • If the profile does not change: your loop may not be running as expected, or dt and steps produce too little total time.
  • If the profile becomes noisy or “blows up”: reduce dt, increase resolution, or simplify boundaries.
  • If everything looks shifted or clipped: verify your initial condition and confirm the x-axis matches your domain.

Choosing dt and dx: Practical Stability and Accuracy Tips

Even if the code runs, parameter choices affect quality and runtime. Three adjustments cover most “first model” issues:

  • Reduce dt if the evolution looks too jumpy, noisy, or inconsistent between runs. A quick test is to halve dt and double steps so total simulated time stays similar.
  • Increase nx if the curve looks jagged or the transition zone is poorly resolved. More cells usually means smoother profiles and better gradients, at a higher computational cost.
  • Treat larger D as a “faster system.” If diffusion is strong, you typically need smaller dt (or at least to verify results don’t change when you refine time).

A simple habit that prevents confusion: whenever you change dt or nx, rerun and compare two runs (coarse vs refined). If the curves are basically the same, you’re in a good regime for learning and iteration.

Six Quick Experiments That Teach You Diffusion Fast

  1. Change diffusion speed: try D = 0.1 and D = 5.0 and compare how quickly the step relaxes.
  2. Increase resolution: change nx from 200 to 400 and see how the transition region looks.
  3. Try a bump instead of a step: set phi = 1 only in a narrow region and watch it spread symmetrically.
  4. Capture more snapshots: add more indices to capture_at to see the evolution in finer detail.
  5. Run longer: increase steps and confirm the profile becomes more uniform over time.
  6. Save output: add plt.savefig("diffusion_1d.png", dpi=200) before plt.show().

Upgrading to 2D Diffusion (Minimal Change, Big Payoff)

Once 1D feels comfortable, moving to 2D is mostly about changing the mesh and the visualization. The equation structure stays the same. In 2D you’ll typically start with a “spot” (high value in a small region) and watch it spread into a smooth blob.

# 2D Diffusion sketch (core ideas)
from fipy import Grid2D, CellVariable, TransientTerm, DiffusionTerm
import matplotlib.pyplot as plt

nx = ny = 100
dx = dy = 1.0 / nx
mesh = Grid2D(nx=nx, ny=ny, dx=dx, dy=dy)

phi = CellVariable(name="phi", mesh=mesh, value=0.0)

x, y = mesh.cellCenters
phi.setValue(1.0, where=((x - 0.5)**2 + (y - 0.5)**2 < 0.05**2))

D = 1.0
eq = TransientTerm() == DiffusionTerm(coeff=D)

dt = 1e-4
for _ in range(500):
    eq.solve(var=phi, dt=dt)

# Visualize as an image
plt.figure()
plt.imshow(phi.value.reshape((ny, nx)), origin="lower")
plt.colorbar(label="phi")
plt.title("2D Diffusion in FiPy")
plt.show()

Two practical notes for 2D: first, it can be significantly slower than 1D because the number of cells grows as nx * ny. Second, image plots depend on reshaping; keep your (ny, nx) consistent.

Troubleshooting: Common First-Time Issues

Import errors or missing packages

  • Confirm FiPy is installed in the same environment where you run the script.
  • In notebooks, verify the kernel points to the correct interpreter.
  • Check that matplotlib imports cleanly.

The curve doesn’t change

  • Increase total simulated time: raise steps or dt slightly (carefully).
  • Make sure snapshots include later steps, not just the beginning.
  • Confirm you are actually calling eq.solve() inside the loop.

The solution looks unstable or noisy

  • Reduce dt (for example, from 1e-4 to 5e-5).
  • Increase resolution (nx) to better resolve gradients.
  • Test a smaller D to reduce the speed of evolution while learning.

It’s too slow

  • Start with fewer cells, then increase gradually once the workflow is clear.
  • Capture fewer snapshots and plot less frequently.
  • In 2D, reduce nx and ny while experimenting with setup.

Conclusion: The Diffusion Pattern You’ll Reuse Everywhere

Solving diffusion in FiPy teaches a repeatable structure you’ll use for many PDE models. Once you’re comfortable with how Grid1D/Grid2D, CellVariable, TransientTerm, and DiffusionTerm connect, you can extend the same workflow to convection–diffusion, reaction–diffusion, sources and sinks, parameter sweeps, and validation checks.

If you want the fastest learning loop, make one change at a time—D, nx, dt, or the initial condition — rerun, and explain what changed and why. That habit is what turns diffusion from a demo into a tool you can trust.