Markers
MILEVA carries composition and material history on Lagrangian markers (tracers) that move through the Eulerian grid. In the 2-D default configuration markers also carry temperature; see Temperature transport for the alternative of advecting temperature on the grid. The code lives in markers/: init_markers.jl (storage and initial placement), advect_markers.jl (Runge-Kutta advection), markers_to_grid.jl (projection), interp_grid_to_markers.jl (hand-back), subgrid_diffusion.jl and reseed_markers.jl.
Storage
Markers (markers/init_markers.jl) is a struct of plain vectors, one per field, with the position in polar or spherical coordinates:
| Field | Meaning |
|---|---|
r, th, ph | radius (m), angle or colatitude, longitude (3-D only) |
T | temperature (K) |
d | grain size (m) |
C, pile | continent and thermochemical-pile fractions |
basalt, depletion | basalt fraction and melt depletion |
eps_acc | accumulated strain |
erupt_time | time basalt was last deposited at the surface |
hpe | heat-producing elements relative to primitive mantle |
volatile | passive volatile concentration (ppm), no feedback |
w | persistent weight: the area (2-D) or volume (3-D) the marker stands for |
cstage, cmox, cmred, root0 | per-parcel state for driver hooks, inert unless a hook writes them |
MARKER_VECTOR_FIELDS lists every per-marker vector. Any code that culls, subsets or appends markers must touch all of them together, because the hot loops run with @inbounds and a field left behind desynchronises the set.

Left: the 17 per-marker vectors of MARKER_VECTOR_FIELDS, each a Float64 vector, so a 2-D marker costs 136 bytes (3-D adds the longitude). Right: memory of the marker set at the default density, 9 markers per cell in 2-D (docs/scripts/fig_numerics_markers_sections.jl.
Initial placement and weights
init_markers places cfg.markersPerCell markers per cell, rounded to a square grid.rF, grid.drVec).
Each marker starts with weight cell_area_2d in markers/init_markers.jl, which calls cell_measure in utils/geometry.jl;

Markers from init_markers in an 11-cell (31°) sector of a
Advection
advect_markers! integrates cfg.advectionScheme = "RK4", default) or the midpoint rule ("RK2"). In 2-D, with
where the stages are evaluated at interp_velocity, interp_velocity_3d in utils/interp_velocity.jl), stretched-grid aware through searchsortedfirst on grid.rF and grid.r. Final radii are clamped 10 m inside the walls.
In 3-D, markers that leave the Yin patch's parametric rectangle after the step are converted into the other patch's coordinates with the Kageyama-Sato transform (_yinyang_exchange_markers!); the very rare marker that lands outside both rectangles at a corner is clamped back inside and counted.

Left: 34 markers carried by advect_markers! (RK4) for 600 steps at the default Courant number through one fixed two-cell Stokes flow on a cflSafety, and its order is reduced below four by the piecewise-bilinear velocity interpolation. Script: docs/scripts/fig_numerics_markers_sections.jl.
Projection to the grid
markers_to_grid scatters marker values to cell centres with bilinear (2-D) or trilinear (3-D) weights, scaled by each marker's weight:
The MarkerScatterWS.W for the hand-back below. Threads accumulate into private buffers that are summed afterwards.

A smooth field init_markers and projected with markers_to_grid on a docs/scripts/fig_numerics_markers_sections.jl.
Handing the temperature increment back
When temperature lives on markers, each step projects marker temperature to the grid, solves advection-free diffusion and heating there, and returns only the increment
Measure-scaled delivery. Plain bilinear interpolation of delivery_scale(grid, cfg, ws) returns main_SphCon.jl delivers
an identity: the markers gain exactly the energy the grid solver computed. The factor is clamped to cfg.deliveryScaleMax (default 2). The cap keeps a sparsely populated wall cell (for example
Sub-grid diffusion. A grid increment moves a cell's mean temperature but leaves the spread of marker temperatures inside the cell alone, so that spread has no dissipation channel and can grow into cell-scale buoyancy noise. subgrid_temperature_correction! (Gerya & Yuen 2003) relaxes each marker toward the grid temperature at its position on the local diffusion time,
with cfg.subgridDiffusivity (default 1; 0 disables). The heat this moves,
tests/test_marker_energy.jl asserts the closure: one marker temperature step conserves energy to tests/test_quiescence.jl records the price: exact conservation turns a uniform heat source into about 4 % lateral structure in the warming (converging with

Left: the energy a grid increment hands to the markers, relative to the energy the grid computed, for five increments on a init_markers (circles) measure-scaled delivery is exact to deliveryScaleMax = 2, mostly in the wall rows; the cap then withholds up to budget_resid records, while plain delivery is wrong node by node and its total errs in both directions. Right: subgrid_temperature_correction! applied to markers carrying 50 K of random scatter about a smooth field; the scatter decays as docs/scripts/fig_numerics_markers_sections.jl.
Reseeding
Divergent flow empties cells and convergent flow crowds them. reseed_markers! runs every cfg.remeshEvery steps (default 5, when cfg.useAdaptiveRemesh is true, the default) after advection, and is exactly conservative for every carried field:
Death. A cell with more than
cfg.reseedNmaxmarkers (default 36) keeps the firstreseedNmax; the removed weight is shared equally among the survivors, and the survivors' values are shifted so thatover the cell is unchanged. Bounded fields stay inside their bounds by redistributing any overshoot among the other survivors. Birth. A cell with fewer than
cfg.reseedNminmarkers (default 4) gets new markers at jittered positions. Weight is redistributed, not created: all markers of the cell share the old total equally. New markers start at the weighted cell mean and the cell is then shifted as above.Empty cells. Only a cell with no marker at all creates weight, with values taken from the grid fields passed in. This is the one non-conservative operation left in the marker scheme.
Births inherit the cell-averaged basalt and depletion passed in by the caller, so reseeding does not dilute composition. In 2-D, cell membership is decided by one shared helper, marker_cell_index_2d, from the cell faces, so reseeding and the composition binning in the physics routines agree.

The initial markers of a reseedNmin and 112 empty completely; with reseed_markers! every 5 steps no cell ends below 4. Of the 300 reseeding calls, 11 met an empty cell; in the others the total weight, docs/scripts/fig_numerics_markers_sections.jl.
Markers in 3-D
In 3-D, _init_markers_3d creates a Yin and a Yang copy of the marker set at the same parametric coordinates. Advection (_advect_3d!) and projection (markers_to_grid for MutableGrid3D) use the Yin patch fields, and the patch exchange converts coordinates when a marker leaves the parametric rectangle. Yang's temperature is advected on its own grid (see Temperature transport).
3-D markers
The 3-D marker path is less exercised than the 2-D one. The sub-grid diffusion step is 2-D only, and the delivery cap withholds about 7 % of the increment at Yin's edge nodes, which have half-cell support. The 3-D example advects temperature on the grid instead.

Left: the bottom layer of the 3-D marker set from init_markers on a yy_yang_to_yin (blue). Right: after shifting the Yin markers 30° in longitude, the 1536 that left the patch rectangle (orange, Yin coordinates) are converted by _yinyang_exchange_markers! into Yang coordinates (blue); mapping them back reproduces their positions to docs/scripts/fig_numerics_markers_sections.jl.
Where in the code
| Topic | File | Function |
|---|---|---|
| Storage, initial placement | markers/init_markers.jl | Markers, init_markers, MARKER_VECTOR_FIELDS |
| Advection | markers/advect_markers.jl | advect_markers!, _advect_2d!, _advect_3d!, _yinyang_exchange_markers! |
| Velocity interpolation | utils/interp_velocity.jl | interp_velocity, interp_velocity_3d |
| Projection | markers/markers_to_grid.jl | markers_to_grid, MarkerScatterWS |
| Hand-back | markers/interp_grid_to_markers.jl | interp_grid_to_markers!, delivery_scale |
| Sub-grid diffusion | markers/subgrid_diffusion.jl | subgrid_temperature_correction! |
| Reseeding | markers/reseed_markers.jl | reseed_markers!, marker_cell_index_2d |
| Cell measures | utils/geometry.jl, markers/init_markers.jl | cell_measure, cell_area_2d, cell_volume_3d |
| Tests | tests/ | test_markers.jl, test_marker_energy.jl, test_projection_noise.jl, test_quiescence.jl, test_depletion_projection.jl |