Skip to content

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:

#BlockMain calls
1markers to gridmarkers_to_grid
2viscositycomposite_rheology (or the simpler laws)
3melting, piles, basaltmelt_phase, basalt_evolution!
4buoyancy (and the Yang-frame buoyancy in 3-D)compute_buoyancy
5Stokes, inside the Picard loopsee Solvers
6, 7free surface, isostasy (optional)free_surface
8strain ratecompute_strain_rate
9grain size (optional)evolve_grain_size!
10, 11time step, core-mantle boundarycompute_timestep, cmb_heat_flux
12advect markers, then reseed every remeshEvery stepsadvect_markers!, reseed_markers!
13temperature: advection, sources, diffusion, hand-backbelow
14 to 20diagnostics, log, VTK, figures, checkpoint, NaN stop, time capdiagnostics_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 Δt moves markers and temperature.

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 Δt advects the markers (block 12) and the temperature (block 13). Schematic; the boxes carry the function names of the loop. Script: docs/scripts/fig_numerics_time_stepping_sections.jl.

Where temperature lives ​

RunTemperature carried byGrid temperature before diffusionAdvection
2-D, cfg.temperatureOnGrid = false (default)markersprojection of marker Tmarker RK4
2-D, cfg.temperatureOnGrid = truegridfinite-volume advection of last step's grid Tadvect_temperature_2d (cfg.tempAdvectionScheme)
3-D, Yin patchmarkersprojection of marker Tmarker RK4
3-D, Yang patchgridfirst-order upwindadvect_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 = 105, Nr = 64, the two modes gave the same Nusselt number (8.97 on the grid with TVD, 8.88 on markers), so the choice of mode does not set that result.

A 400 K warm disc, 350 km across, carried for 150 CFL steps through one fixed two-cell Stokes flow on a 64×256 annulus without diffusion, once on the markers of 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 (rm-weighted radial faces, 1/(rΔθ) in angle), and evaluate the advective form

Tcn+1=Tcn−Δt[∇⋅(VT)c−Tcn(∇⋅V)c],

so a uniform field stays exactly uniform whatever the discrete divergence of V. The bracket is divided by the exact cell measure (rF,i+1m+1−rF,im+1)/(m+1) rather than the midpoint rCmΔr (the whole increment is rescaled by their ratio, so the divergence stays a multiple of the continuity residual), and the fluxes therefore telescope in the measure of cell_measure and the energy diagnostics: an interior flow changes ∑cTcVc by round-off only. The 3-D versions do the same with the spherical metric factors (1/r2)∂r(r2VrT) and (1/(rsin⁡θ))∂θ(sin⁡θVθT) (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,

Tface=Tup+12ψ(ρ)(Tdown−Tup),ψ(ρ)=ρ+|ρ|1+|ρ|,

where ρ is the ratio of consecutive gradients (_vanleer, _slope_ratio). Because ψ≤2, each face value stays between its two neighbouring cell averages, so the scheme adds no new extrema. Radial ghosts reflect the wall value, Tghost=2Tbc−Twall cell, and that same ghost is the second neighbour of the first interior cells (2 and Nr−1), so both cells next to an interior face reconstruct it with one stencil and its flux is counted once.

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):

CellsAdvection orderNuReference Nu
24 57613.623.4949
24 57623.493.4949
196 60813.573.4949
196 60823.483.4949

Second order on the coarse grid beats first order on eight times the cells. In MILEVA, advecting a cos⁡4θ pattern once around the annulus on a 16×256 grid leaves about half of its amplitude with upwind while TVD keeps all of it, neither overshooting (figure below). Numerical diffusion acts as extra heat transport and raises the Nusselt number. Set 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 cos⁡4θ temperature pattern carried once round a 16×256 annulus by rigid rotation at Courant number 0.5, with 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

∂T∂t=1rm∂∂r(rmκ∂T∂r)+κr2∂2T∂θ2+Hρcp

in 2-D (the 3-D form adds the spherical θ and φ terms). H collects radiogenic heating and the volumetric sources passed in by the loop (latent heat, heat pipe, and the adiabatic and dissipation terms under ALA, which are carried together or not at all).

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 ∫rmdr (_cell_vol). With a depth-dependent κ this conserves energy where κ(r)∇2T does not; with constant κ the two are algebraically identical.

Wall conditions at the faces. Tcmb and Tsurf are imposed at the walls Rin and Rout through a half-cell ghost: the wall flux is k(Tbc−Twall cell)/(12Δr), and cells 1 and Nr are ordinary flow cells. The discrete energy budget therefore closes exactly (see Energy budget).

Time discretisation.

PathDefaultScheme
2-D, cfg.useImplicitTemp = trueyesθ-scheme (I−θΔtL)Tn+1=(I+(1−θ)ΔtL)Tn+ΔtH with θ = cfg.implicitTheta (0.5, Crank-Nicolson; 1 is backward Euler)
2-D, useImplicitTemp = falseforward Euler
3-D, as called from the time loopyesforward Euler
3-D, implicit = true keywordbackward Euler, used by benchmark drivers

The 2-D operator L and its LU factorisation are cached; the factor is reused while Δt is unchanged, which happens in fixed-step benchmarks but rarely under CFL control. Wall Dirichlet values enter the right-hand side as sources with the full Δt, since they are constant over the step. After the solve, temperatures are clamped to [max(Tsurf−50,10),Tcmb+600] K; benchmarks in non-dimensional temperature must pass their own bounds in 3-D (clamp_lo, clamp_hi).

3-D explicit diffusion

The explicit 3-D update is stable for Δt≲Δxmin2/(6κ); on a stretched grid with thin boundary cells keep cfg.dtMax below that bound. The 3-D stencil treats each patch's φ-edges as periodic, so near those edges read temperatures from the peer patch.

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 (Nr=64, κΔt/Δr2≈4.7), against 600 small backward-Euler steps. Crank-Nicolson, the default, is second order but leaves an undamped cell-scale oscillation when the step is this large; backward Euler damps it at the cost of first-order accuracy. Under CFL control the diffusion number is usually far smaller. Right: steady conduction in the default shell with constant κ and with the ALA diffusivity κ0(1+z2/2). The conservative radial flux form passes the same power through both walls, boundary_power giving Pin/Pout = 1.000000 in both cases, although the profiles differ. Script: docs/scripts/fig_numerics_time_stepping_sections.jl.

Step size ​

compute_timestep (utils/compute_timestep.jl) sets

ΔtCFL=cΔxminmax|V|,Δxmin=min(Δrmin,rminΔθ),

with c = cfg.cflSafety (default 0.35) and, in 3-D, also rminsin⁡θminΔφ in the minimum. The new step is

Δt=min(dtMax,ΔtCFL,1.15Δtprev),

so it grows by at most 15 % per step; the first step is ΔtCFL clamped to [dtMin,dtMax] (defaults 106 and 2×1013 s). If the CFL step falls below 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 Vr and Vθ only, not Vφ.

The velocity limiter (cfg.useVelocityLimit) rescales velocity and pressure whenever Vmax exceeds a cap. It is off by default; with it off, an exceedance is only reported.

Left: compute_timestep called step by step while the velocity of a fixed 64×256 flow is scaled by a prescribed factor (1, a ramp to 10, then 0.2). The step follows the CFL limit down at once, and after the velocity drops it grows by 15 % per step, taking 27 steps to reach the new limit. Right: the two lengths in Δxmin for the default shell; the radial cell width is the smaller at every Nr with Nt=4Nr, and stretching with β=3 makes it about three times smaller still. Script: docs/scripts/fig_numerics_time_stepping_sections.jl.

Where in the code ​

TopicFileFunction
Time loopmain_SphCon.jlmain_SphCon
Grid advectionutils/advect_temperature.jladvect_temperature_2d, advect_temperature_2d_tvd!, advect_temperature_2d_upwind!, advect_temperature_3d_tvd!, advect_temperature_3d_upwind!
Diffusionsolvers/diffuse_temperature.jldiffuse_temperature, _solve_crank_nicolson, _solve_explicit
Step sizeutils/compute_timestep.jlcompute_timestep, cfl_floor_hits
Teststests/test_advection_consistency.jl, test_diffusion.jl, test_conduction_conservation.jl, test_nusselt_conduction.jl