Plume planforms in a 3-D shell
Basally heated, isoviscous convection in a full spherical shell on the Yin-Yang grid. The Rayleigh number sets how many plumes the shell holds and how thin they are. At Ra = 10⁴ a few broad upwellings, at 10⁵ more of them, at 10⁶ many thin plumes rising from a thin hot boundary layer, separated by a network of cold sheets.
The driver is examples/yinyang_convection_3d.jl. It runs on exactly two MPI ranks and is meant for a cluster; the HPC page has the batch scripts.

A MILEVA run at Ra = 10⁶: the hot-temperature isosurface shows plumes rising from the core-mantle boundary and spreading beneath the surface.

MILEVA 3-D Yin-Yang spherical shell, hot-temperature isosurfaces at Ra = 10⁴, 10⁵, 10⁶. Plumes become more numerous and narrower as Ra grows.
The model
in a shell with
| Ra | Nu (H&T 3-D shell) | ||
|---|---|---|---|
| 3.85 | 42.3 | ||
| 7.27 | 160 | ||
| 15.9 | 625 |
These are the reference values for a time-averaged comparison.
Grid and solver
| item | value | where |
|---|---|---|
| grid per patch | Nr × Nth × 3 Nth: 64 × 32 × 96 (Ra 10⁴), 64 × 48 × 144 (10⁵), 96 × 72 × 216 (10⁶) | MILEVA_NR, MILEVA_NTH |
| radial spacing | refined toward both walls, cfg.amrStretchFactor = 2 | utils/setup_grid.jl |
| Yin-Yang overlap | basic overlap, no θ-buffer (cfg.δ_yy = 0), cfg.useMinOverlapGrid = false | Yin-Yang grid |
| MPI layout | Phase A: rank 0 = Yin, rank 1 = Yang | parallel/mpi_init.jl |
| Stokes | solve_stokes_distributed!(...; mode = :phase_a), i.e. multigrid_stokes_mpi! | solvers/solve_stokes_distributed.jl, solvers/multigrid_stokes_mpi.jl |
| inner solver | FGMRES (tol multigrid_stokes_mpi.jl, not the 5/5 of the serial path), Galerkin coarse operators, mgLevels = 4 | Multigrid |
| patch coupling | 8 Jacobi-Schwarz rounds per solve with damped Anderson acceleration (MILEVA_NSCHWARZ) | multigrid_stokes_mpi! |
| advection | second-order TVD (van Leer) on the grid, advect_temperature_3d_tvd! | utils/advect_temperature.jl |
| diffusion | backward Euler, wall temperatures held at the faces through a half-cell ghost | diffuse_temperature(...; implicit = true) |
| temperature seam | θ-wall cells overwritten with the peer's interpolated temperature after every step | yy_sync_T_walls_mpi! in parallel/yinyang_mpi.jl |
| time step | advective CFL in all three directions, safety 0.4; dtMax = | compute_timestep plus a longitude term in the driver |
Why exactly two ranks. Among the modes of solve_stokes_distributed!, Phase A is the only one that solves Stokes with multigrid; the multi-rank-per-patch modes are MUMPS-direct (:direct, :b4p) or block-Jacobi Krylov (:iter, :rich). MILEVA's rule is that 3-D Stokes is always multigrid, so the driver refuses any other rank count and the job scales through Julia threads within each rank, which the SCGS smoother uses.
Initial condition. The spherical conductive profile plus a perturbation
where MILEVA_SEED_AMP). Because the pattern is a function of physical position drawn from one seed, Yin and Yang start from the same field in their overlap, and because it has no preferred wavenumber the planform selects itself. The
Diagnostics
Every MILEVA_LOGEVERY steps the driver appends to diagnostics_3d.csv:
| column | definition |
|---|---|
t_diff | time in units of |
Vrms_nd | |
Nu_top, Nu_bot | area-weighted mean wall gradient |
Tmean_nd | volume-mean |
fgmres_iters, final_res | inner FGMRES iterations and relative residual of the last Schwarz round |
schwarz_res | smallest relative velocity change between Schwarz rounds; the solver returns that iterate |
divMax | the solver's own nondimensional divergence diagnostic (unpack_stokes_3d) |
The sums run over both patches with one MPI.Allreduce. Yin and Yang overlap by about 6 % of the sphere and that overlap is counted twice, so these are patch-weighted means rather than exact sphere integrals.
Running it
The Rayleigh number and everything else are environment variables (the full list is in the driver header). Locally, with the mpiexec that MPI.jl ships:
MILEVA_RA=1e5 JULIA_NUM_THREADS=4 \
julia --project=. -e 'using MPI; run(`$(mpiexec()) -n 2 $(Base.julia_cmd()) --project=. examples/yinyang_convection_3d.jl`)'On a cluster, the array script runs the three Rayleigh numbers and chains segments across the queue's time limit:
julia --project=. hpc/plan_3d.jl 32 # memory per rank for each case
hpc/submit_3d_chain.sh 4 # 4 chained 48 h segments of the arrayA segment stops cleanly before its time limit (MILEVA_WALLTIME_H), writing a checkpoint per rank; the next resumes with MILEVA_RESUME=latest. A case that reaches its end (MILEVA_TMAX diffusion times, default 0.2, or MILEVA_MAXSTEPS) writes FINISHED in its output directory and later segments skip it. Open the .pvd in ParaView; each step is a .vtm holding the Yin and Yang .vts blocks, with fields T, eta, V and mpi_patch.
Cost
Measured on a laptop with 2 threads per rank:
| grid per patch | DOFs per patch | Stokes per step | FGMRES its per solve | Schwarz residual |
|---|---|---|---|---|
| 8 × 8 × 24 (smoke) | 6 528 | 1.2 to 2.0 s | 34 to 35 | |
| 16 × 16 × 48 (smoke), 8 rounds | 50 688 | 11 to 13 s | 36 to 37 | |
| 16 × 16 × 48 (smoke), 4 rounds | 50 688 | 6 s | 36 to 37 |
The first step of every run also pays about 20 s of compilation. Halving the Schwarz rounds halves the cost and leaves the seam five times less converged; in those four steps
MILEVA_MAXSTEPS=5 MILEVA_LOGEVERY=1 sbatch --array=1 --time=02:00:00 hpc/yinyang_convection_3d.slurmand scale the seconds per step it reports by the step count the CFL condition implies.
Notes
Temperature lives on the grid and is advected by TVD; there is no marker field in this example.
Both patches are written in full, so in ParaView the overlap shows two values at one place, and the diagnostics count the overlap twice, as described above. Next to a patch's φ-walls, read temperatures from the peer patch, which holds them in its interior. See Yin-Yang grid for how the patches are coupled.
Smoke test
A quick check on 2 ranks × 2 threads:
MILEVA_NR=8 MILEVA_NTH=8 MILEVA_MGLEVELS=2 MILEVA_MAXSTEPS=3 MILEVA_LOGEVERY=1 \
julia --project=. -e 'using MPI; run(`$(mpiexec()) -n 2 $(Base.julia_cmd()) --project=. examples/yinyang_convection_3d.jl`)'3 steps at Ra = 10⁵ in 37 s of wall time. FGMRES converged every inner solve (34 to 35 iterations to a relative residual below FINISHED, and the dry run of hpc/yinyang_convection_3d.slurm through mpiexecjl ran and finished.