Temperature transport and time stepping
The time loop is main_SphCon in main_SphCon.jl. Each step solves Stokes for the current buoyancy and viscosity, picks the step size from the new velocity, moves the markers, then advances temperature by advection, heating and diffusion. This page covers the loop order, where temperature is stored, the advection and diffusion schemes, and the step-size control.
Step order
The numbered blocks in the loop body run in this order:
| # | Block | Main calls |
|---|---|---|
| 1 | markers to grid | markers_to_grid |
| 2 | viscosity | composite_rheology (or the simpler laws) |
| 3 | melting, piles, basalt | melt_phase, basalt_evolution! |
| 4 | buoyancy (and the Yang-frame buoyancy in 3-D) | compute_buoyancy |
| 5 | Stokes, inside the Picard loop | see Solvers |
| 6, 7 | free surface, isostasy (optional) | free_surface |
| 8 | strain rate | compute_strain_rate |
| 9 | grain size (optional) | evolve_grain_size! |
| 10, 11 | time step, core-mantle boundary | compute_timestep, cmb_heat_flux |
| 12 | advect markers, then reseed every remeshEvery steps | advect_markers!, reseed_markers! |
| 13 | temperature: advection, sources, diffusion, hand-back | below |
| 14 to 20 | diagnostics, log, VTK, figures, checkpoint, NaN stop, time cap | diagnostics_row, save_vtu |
The step size is chosen after the Stokes solve, from the velocity that will actually be used to advect, and that same

The numbered blocks of main_SphCon in the order of the table, with the function each one calls. The velocity of the Stokes solve (block 5) sets the step size in block 10, and that same docs/scripts/fig_numerics_time_stepping_sections.jl.
Where temperature lives
| Run | Temperature carried by | Grid temperature before diffusion | Advection |
|---|---|---|---|
2-D, cfg.temperatureOnGrid = false (default) | markers | projection of marker | marker RK4 |
2-D, cfg.temperatureOnGrid = true | grid | finite-volume advection of last step's grid | advect_temperature_2d (cfg.tempAdvectionScheme) |
| 3-D, Yin patch | markers | projection of marker | marker RK4 |
| 3-D, Yang patch | grid | first-order upwind | advect_temperature_3d_upwind! |
In the marker mode, diffusion and heating are solved on the grid and only the increment goes back to the markers, conservatively (see Markers). In the grid mode the markers carry composition only, and their temperature is set from the grid each step so marker-based physics such as melting can still read it.
Grid temperature with tracers for composition is the arrangement described by Tackley (2008); temperatureOnGrid = true selects it. The marker mode keeps the energy step exact through measure-scaled delivery and needs sub-grid diffusion to stay stable. On the Hernlund & Tackley (2008) isoviscous case at Ra =

A 400 K warm disc, 350 km across, carried for 150 CFL steps through one fixed two-cell Stokes flow on a init_markers (advect_markers!, then markers_to_grid) and once on the grid with advect_temperature_2d_tvd!. The marker field keeps its 1900 K peak and sharp edges; the TVD field loses about 100 K of peak to numerical diffusion and widens by a cell on each side. Script: docs/scripts/fig_numerics_time_stepping_sections.jl.
Advection on the grid
Both 2-D schemes (advect_temperature_2d_upwind!, advect_temperature_2d_tvd! in utils/advect_temperature.jl) are explicit finite-volume updates that use the same discrete divergence as the continuity rows of the Stokes assembly (
so a uniform field stays exactly uniform whatever the discrete divergence of cell_measure and the energy diagnostics: an interior flow changes tests/test_advection_consistency.jl checks that a uniform field stays uniform).
The TVD scheme reconstructs each face value from the upwind cell with the van Leer limiter,
where _vanleer, _slope_ratio). Because
Use TVD, not upwind
cfg.tempAdvectionScheme defaults to "upwind", which is first order. Tackley (2008) reports that "the order of the advection scheme makes more difference than the number of cells", with this table (tetrahedral pattern, Ra = 7000, reference Nu = 3.4949):
| Cells | Advection order | Nu | Reference Nu |
|---|---|---|---|
| 24 576 | 1 | 3.62 | 3.4949 |
| 24 576 | 2 | 3.49 | 3.4949 |
| 196 608 | 1 | 3.57 | 3.4949 |
| 196 608 | 2 | 3.48 | 3.4949 |
Second order on the coarse grid beats first order on eight times the cells. In MILEVA, advecting a cfg.tempAdvectionScheme = "TVD" whenever temperatureOnGrid = true. In the same benchmark run the TVD path showed a budget residual of +0.011, slightly larger than the marker path.

A advect_temperature_2d_upwind! and advect_temperature_2d_tvd!. Upwind loses about half the amplitude to numerical diffusion; TVD keeps the peaks without overshoot, at the cost of small terraces on the flanks from the limiter.
In main_SphCon the 3-D Yang patch uses first-order upwind; the 3-D TVD scheme (advect_temperature_3d_tvd!) is what the 3-D example driver uses.
Diffusion and heating
diffuse_temperature (solvers/diffuse_temperature.jl) advances
in 2-D (the 3-D form adds the spherical
Conservative radial fluxes. The radial term is a flux difference across the two faces of each cell, with the face diffusivity and face radius inside the flux and division by the exact cell measure _cell_vol). With a depth-dependent
Wall conditions at the faces.
Time discretisation.
| Path | Default | Scheme |
|---|---|---|
2-D, cfg.useImplicitTemp = true | yes | cfg.implicitTheta (0.5, Crank-Nicolson; 1 is backward Euler) |
2-D, useImplicitTemp = false | forward Euler | |
| 3-D, as called from the time loop | yes | forward Euler |
3-D, implicit = true keyword | backward Euler, used by benchmark drivers |
The 2-D operator clamp_lo, clamp_hi).
3-D explicit diffusion
The explicit 3-D update is stable for cfg.dtMax below that bound. The 3-D stencil treats each patch's
In 3-D the patches are then coupled by the temperature Schwarz sweeps described on the Yin-Yang page.

Left: one 300 Myr step of diffuse_temperature on a 180 km cold sheet (boundary_power giving docs/scripts/fig_numerics_time_stepping_sections.jl.
Step size
compute_timestep (utils/compute_timestep.jl) sets
with cfg.cflSafety (default 0.35) and, in 3-D, also
so it grows by at most 15 % per step; the first step is dtMin, dtMin is used and a warning is logged (counter cfl_floor_hits()).
This is a global bound: the smallest cell anywhere against the fastest velocity anywhere. There is no diffusive limit, because the default 2-D diffusion is implicit. The 3-D bound uses
The velocity limiter (cfg.useVelocityLimit) rescales velocity and pressure whenever

Left: compute_timestep called step by step while the velocity of a fixed docs/scripts/fig_numerics_time_stepping_sections.jl.
Where in the code
| Topic | File | Function |
|---|---|---|
| Time loop | main_SphCon.jl | main_SphCon |
| Grid advection | utils/advect_temperature.jl | advect_temperature_2d, advect_temperature_2d_tvd!, advect_temperature_2d_upwind!, advect_temperature_3d_tvd!, advect_temperature_3d_upwind! |
| Diffusion | solvers/diffuse_temperature.jl | diffuse_temperature, _solve_crank_nicolson, _solve_explicit |
| Step size | utils/compute_timestep.jl | compute_timestep, cfl_floor_hits |
| Tests | tests/ | test_advection_consistency.jl, test_diffusion.jl, test_conduction_conservation.jl, test_nusselt_conduction.jl |