Reading Time: 10 minutes

FiPy is a Python-based framework for solving partial differential equations with the finite volume method. It is commonly used for diffusion, heat transfer, phase-field modeling, electrochemistry, fluid-related transport, and other problems that can be expressed through conservation equations.

Every FiPy simulation begins with a mesh. The mesh defines the physical domain, divides it into control volumes, identifies the boundaries between those volumes, and determines where numerical variables are stored. A carefully selected mesh can make a model accurate and efficient. A poor mesh can create unstable calculations, unrealistic gradients, or unnecessary computational cost.

FiPy supports simple Cartesian grids, non-uniform spacing, periodic domains, cylindrical and spherical coordinates, triangular structures, and meshes created with Gmsh. Understanding these options helps users select the simplest grid that still represents the physical problem correctly.

What Is a Mesh in FiPy?

A mesh is a discrete representation of a continuous physical region. Instead of solving a differential equation at every possible point, FiPy divides the domain into a finite number of cells and calculates approximate values within them.

Each cell acts as a control volume. Physical quantities such as temperature, concentration, pressure, or phase fraction are usually associated with cell centers. Transport between neighboring cells is calculated across the faces that separate them.

The mesh provides both geometry and topology. Geometry describes sizes, coordinates, areas, and volumes. Topology describes which cells share faces and how the elements are connected.

This information allows FiPy to transform a differential equation into a system of algebraic equations that a numerical solver can process.

Cells, Faces, and Vertices

FiPy meshes are built around three main structural components: cells, faces, and vertices.

Cells are the control volumes used by the finite volume method. A one-dimensional cell is a line segment. A two-dimensional cell may be a rectangle or triangle. A three-dimensional cell may be a box or another polyhedral volume.

Faces form the boundaries of cells. An interior face separates two neighboring cells. An exterior face lies on the outer boundary of the complete domain.

Vertices define the geometric points from which faces and cells are constructed. Most users do not manipulate vertices directly when working with regular grids, but they become important when importing or creating irregular geometry.

The relationships between these components determine how values and fluxes move through the simulation.

Why Mesh Structure Matters in the Finite Volume Method

The finite volume method integrates a governing equation over every cell. It then calculates how much of a physical quantity enters or leaves through the surrounding faces.

For a diffusion problem, FiPy estimates gradients between neighboring cell values and calculates the diffusive flux across each face. For a convection problem, the direction and magnitude of transport depend on face-centered velocities and normal vectors.

Cell size influences resolution. Face area influences the amount of flux. Cell volume affects accumulation and source terms. The distance between neighboring cell centers affects gradient approximations.

As a result, the mesh is not only a visual container. Its geometry directly enters the numerical equations.

Structured and Unstructured Meshes

A structured mesh arranges cells in a predictable pattern. Rectangular grids are the most common example. Cells form rows, columns, and layers, and their neighbors can be identified systematically.

Structured grids are easy to create and usually efficient to process. They are suitable for rectangular domains, layered systems, channels, and problems with simple boundaries.

An unstructured mesh does not follow a regular row-and-column arrangement. It may contain triangles, tetrahedra, or irregular polygons. Unstructured meshes can fit curved boundaries, holes, narrow features, and complex engineering geometry more accurately.

The flexibility of an unstructured mesh comes with additional cost. Mesh generation, quality control, boundary selection, and numerical processing become more complicated.

Creating a One-Dimensional Grid

The simplest FiPy mesh is Grid1D. It divides a line into a specified number of cells.

from fipy import Grid1D

mesh = Grid1D(nx=100, dx=0.01)

The parameter nx defines the number of cells. The parameter dx defines the width of each cell. In this example, the total domain length is one unit.

One-dimensional grids are useful for layered diffusion, heat transfer through a wall, concentration changes along a channel, and systems with strong symmetry. A 1D model is often much faster than a 2D or 3D simulation and may be sufficient when variations in other directions can be ignored.

Creating a Two-Dimensional Grid

A rectangular two-dimensional domain can be created with Grid2D.

from fipy import Grid2D

mesh = Grid2D(
    nx=50,
    ny=30,
    dx=0.02,
    dy=0.02
)

The mesh contains 50 cells along the x-axis and 30 cells along the y-axis. Each cell is 0.02 units wide and 0.02 units high.

This structure is suitable for planar heat transfer, material microstructures, diffusion across surfaces, electrostatic problems, and many phase-field examples.

A rectangular grid is usually the best starting point when the physical domain is simple. It is easy to visualize, refine, and test for mesh independence.

Creating a Three-Dimensional Grid

FiPy provides Grid3D for volumetric domains.

from fipy import Grid3D

mesh = Grid3D(
    nx=40,
    ny=30,
    nz=20,
    dx=0.01,
    dy=0.01,
    dz=0.01
)

Three-dimensional simulations can represent full spatial geometry, but they require much more memory and processing time. Doubling the number of cells in every direction can increase the total cell count by a factor of eight.

Before using a 3D grid, it is useful to check whether symmetry or dimensional reduction can provide an adequate result. A cylindrical or one-dimensional radial model may sometimes replace a much more expensive 3D simulation.

Uniform and Non-Uniform Spacing

A uniform grid uses cells of equal size along a given axis. This is convenient when the solution changes gradually throughout the domain.

Some problems contain sharp gradients near a boundary, interface, or source. Using a fine grid everywhere may be wasteful. FiPy can create non-uniform spacing by receiving a sequence of cell widths.

from fipy import Grid1D

mesh = Grid1D(
    dx=(0.01, 0.01, 0.02, 0.04, 0.08)
)

The cells become progressively wider. Smaller cells can be placed near an important boundary, while larger cells cover regions where the solution changes slowly.

Non-uniform grids reduce computational cost, but abrupt changes in cell size can reduce numerical quality. Cell dimensions should usually change gradually rather than jump between extremely different values.

Accessing Cell Centers

FiPy exposes cell coordinates through mesh.cellCenters. These coordinates are often used to define initial conditions, sources, spatial coefficients, and regions of interest.

x, y = mesh.cellCenters

A user can create a circular initial region by applying a logical condition to the coordinates:

initial_region = (
    (x - 0.5) ** 2 + (y - 0.5) ** 2
) < 0.1 ** 2

The resulting Boolean mask identifies cells whose centers fall inside the circle.

Cell-center coordinates are especially useful because most FiPy solution variables are stored as CellVariable objects.

Cell Variables and Mesh Association

A CellVariable stores one value for each cell in the mesh.

from fipy import CellVariable

temperature = CellVariable(
    name="temperature",
    mesh=mesh,
    value=300.0
)

The variable automatically receives the same number of values as the mesh has cells. It also gains access to mesh-based operations such as gradients, face interpolation, and constraints.

Values can be changed only in selected cells:

temperature.setValue(
    500.0,
    where=initial_region
)

This relationship between variables and meshes allows FiPy to manage geometry-dependent operations without requiring users to assemble every matrix manually.

Face Centers and Face Variables

Faces are important because fluxes pass through them. Their coordinates are available through mesh.faceCenters.

x_face, y_face = mesh.faceCenters

Face coordinates can be used to identify boundaries or apply a condition only to part of an edge.

FiPy also supports FaceVariable objects. These are useful for quantities naturally located on faces, such as flow velocity or externally specified flux.

A cell-centered variable can often be interpolated to faces through properties such as faceValue. Its gradient at faces can be accessed through faceGrad.

Choosing the correct location for a variable is important. Temperatures and concentrations are commonly cell-centered, while fluxes and transport velocities are often face-centered.

Cell Volumes, Face Areas, and Normals

FiPy calculates geometric properties automatically. Cell volumes are available through mesh.cellVolumes. In 2D, these values represent cell areas under the dimensional assumptions of the model.

Face areas influence the total amount of material, heat, or momentum passing through a boundary. Face normals identify the direction perpendicular to each face.

The orientation of a normal matters when calculating outward and inward fluxes. A positive flux in one direction may represent a negative flux relative to a neighboring cell.

These properties allow FiPy to apply conservation laws consistently across different grid shapes and coordinate systems.

Exterior and Interior Faces

FiPy separates faces into exterior and interior groups. Exterior faces form the outer boundary of the domain. Interior faces connect neighboring cells.

The corresponding masks are available as:

mesh.exteriorFaces
mesh.interiorFaces

Exterior faces are commonly used for boundary conditions. Interior faces participate in ordinary transport between cells.

Regular Cartesian grids also provide convenient directional masks such as facesLeft, facesRight, facesTop, and facesBottom.

temperature.constrain(
    350.0,
    where=mesh.facesLeft
)

temperature.constrain(
    300.0,
    where=mesh.facesRight
)

This applies fixed values to the left and right boundaries.

Creating Custom Boundary Masks

A physical condition may apply only to part of a boundary. Custom masks can combine standard face sets with coordinate conditions.

x_face, y_face = mesh.faceCenters

heated_section = (
    mesh.facesTop
    & (x_face > 0.4)
    & (x_face < 0.6)
)

temperature.constrain(
    400.0,
    where=heated_section
)

This example constrains only the central part of the upper boundary.

Custom masks can also select circular boundaries, interfaces, inlets, outlets, or faces near a specified coordinate. It is important to create masks from face coordinates when applying face-based boundary conditions. A cell mask and a face mask have different lengths and purposes.

Periodic Grid Structures

Periodic meshes connect opposite sides of a domain. A value leaving one side effectively re-enters through the other.

FiPy provides periodic grid classes for one, two, and three dimensions. Some variants apply periodicity only along selected axes.

Periodic grids are useful for repeated microstructures, crystal patterns, representative material volumes, wave-like systems, and phase-field simulations where artificial outer boundaries would distort the solution.

Users should remember that a periodic direction does not behave like an ordinary exterior boundary. Applying conflicting boundary constraints to the connected faces can produce an incorrect model.

Cylindrical and Spherical Grids

Cartesian grids are not always the most efficient choice. FiPy provides cylindrical and spherical mesh classes for systems with radial symmetry.

A cylindrical grid can represent heat transfer through a pipe wall, radial diffusion, or an axisymmetric reactor. Cell volumes and face areas change with radius, and FiPy accounts for this geometry automatically.

A spherical one-dimensional grid can model diffusion into a particle, heat transfer inside a sphere, or radial chemical transport.

These coordinate-specific meshes can replace expensive multidimensional calculations when the solution is symmetric. However, they should not be used when important angular variations are present.

Triangular and Skewed Meshes

FiPy includes triangular structures such as Tri2D. Triangular cells can provide a different discretization of a two-dimensional region and may be useful for testing numerical behavior.

Skewed grids intentionally move vertices away from an orthogonal arrangement. They can demonstrate how a solver responds to non-orthogonal geometry.

Strong skewness can reduce gradient accuracy and increase numerical error. A mesh with many poor-quality cells may perform worse than a coarser but well-shaped grid.

Mesh quality should therefore be evaluated separately from mesh resolution. Adding more distorted cells does not necessarily improve the solution.

Modeling Complex Geometry With Gmsh

Regular grids are limited when the domain contains curves, holes, irregular boundaries, or detailed three-dimensional features. FiPy can use Gmsh to generate or import more complex meshes.

A basic two-dimensional mesh can be created from Gmsh commands:

from fipy import Gmsh2D

mesh = Gmsh2D("""
    cellSize = 0.05;

    Point(1) = {0, 0, 0, cellSize};
    Point(2) = {1, 0, 0, cellSize};
    Point(3) = {1, 1, 0, cellSize};
    Point(4) = {0, 1, 0, cellSize};

    Line(1) = {1, 2};
    Line(2) = {2, 3};
    Line(3) = {3, 4};
    Line(4) = {4, 1};

    Curve Loop(1) = {1, 2, 3, 4};
    Plane Surface(1) = {1};
""")

Gmsh can also load geometry from a .geo file or import a generated .msh file. Keeping complex geometry in a separate file often makes the Python model easier to maintain.

Local Refinement

Complex problems often need small cells only in selected areas. Local refinement can improve resolution near interfaces, corners, boundaries, sources, narrow channels, or steep gradients.

Gmsh provides flexible controls for setting local element sizes. A fine region can be placed around an important feature while the remainder of the domain uses larger cells.

This approach is usually more efficient than refining the entire grid. However, transitions between fine and coarse regions should be gradual. Sudden size changes may reduce numerical quality or create poorly shaped cells.

Mesh Convergence Testing

A simulation result should not depend strongly on an arbitrary mesh size. Mesh convergence testing checks whether the solution becomes stable as the grid is refined.

A practical test uses at least two or three resolutions. The user compares an important quantity, such as maximum temperature, average concentration, interface position, or total flux.

If the result changes significantly after refinement, the original mesh was probably too coarse. If the difference becomes small, further refinement may not justify its computational cost.

Convergence should be tested for the output that matters to the study. A mesh may accurately predict an average value while still failing to capture a local maximum.

Meshes in Parallel Simulations

Large FiPy simulations can distribute a mesh across several processes. Each process works with a portion of the domain and exchanges information with neighboring portions.

Parallel meshes may contain local cells, overlap cells, and globally identified values. The size of a local NumPy array may therefore differ from the total number of cells in the complete mesh.

Users should be careful when directly indexing arrays or exporting values during parallel execution. Code that assumes every process owns the full mesh may work in serial mode but fail in parallel mode.

Parallel execution can reduce runtime for large problems, but communication overhead may limit the benefit for small meshes.

Inspecting and Visualizing a Mesh

A mesh should be inspected before solving a complex equation. Users can check cell counts, coordinate ranges, volumes, exterior masks, and boundary selections.

FiPy viewers can display a variable on the mesh:

from fipy import Viewer

viewer = Viewer(vars=temperature)
viewer.plot()

Visualization can reveal incorrect domain dimensions, unexpected cell spacing, missing regions, or poorly selected boundary masks.

For Gmsh-based geometry, it is useful to inspect the generated mesh in a dedicated visualization tool before beginning a long simulation.

Common Beginner Mistakes

One common mistake is confusing cell count with physical size. A grid with 100 cells does not have a length of 100 unless each cell is one unit wide.

Another mistake is applying a boundary constraint with a cell mask instead of a face mask. Boundary conditions generally act on exterior faces.

Users may also choose a grid that is too coarse to capture a gradient or unnecessarily fine for a simple problem.

Other common issues include incorrect coordinate assumptions, abrupt non-uniform spacing, poor Gmsh element quality, conflicting periodic conditions, and skipping convergence testing.

Choosing the Right FiPy Mesh

Mesh Type Best Use Main Limitation
Grid1D Layered and one-directional transport Cannot represent lateral variation
Grid2D Rectangular planar domains Limited to simple boundaries
Grid3D Full volumetric models High computational cost
Non-uniform grid Localized gradients and interfaces Requires careful spacing transitions
Periodic grid Repeated structures and unit cells Opposite boundaries are linked
Cylindrical or spherical grid Radially symmetric systems Invalid when angular variation matters
Gmsh mesh Curved and irregular geometry More complex generation and quality control

A Practical Mesh Selection Workflow

Begin by identifying the physical shape of the domain and the dimensions in which the solution can vary. Check whether symmetry can reduce a 3D problem to 2D or 1D.

Choose the simplest suitable mesh class. Estimate where strong gradients or interfaces may appear and adjust cell size accordingly.

Inspect cell centers, domain dimensions, volumes, and boundary masks. Visualize the mesh and apply a simple test variable before solving the complete equation.

Run the simulation on more than one resolution and compare the main results. Move to Gmsh, local refinement, or parallel processing only when the basic grid cannot meet the geometric or numerical requirements.

Conclusion

FiPy meshes define the geometry, connectivity, cell volumes, faces, and coordinates used by the finite volume method. They provide the structure on which variables, equations, fluxes, and boundary conditions operate.

Regular classes such as Grid1D, Grid2D, and Grid3D cover many common problems. Non-uniform, periodic, cylindrical, spherical, triangular, and Gmsh-based meshes support more specialized domains.

The most detailed mesh is not automatically the best one. A good mesh captures the required physical behavior while keeping computational cost manageable. Correct boundary masks, reasonable cell quality, appropriate coordinate systems, and convergence testing are all essential.

By choosing the simplest sufficient grid and refining only where necessary, FiPy users can build simulations that are easier to understand, faster to solve, and more reliable.