Skip to content

Contributing ​

MILEVA is small enough that one person can hold all of it, and it stays that way if every change leaves the code with one way to do each thing and a test that shows the thing is done right. The conventions below keep it that way.

The path of a change. The suite is run three times because a solver with a non-deterministic iteration can pass by luck; any red or flaky result, or a new test that also passes on the old code, sends the change back.

One live path, no cruft ​

  • Keep one path live at a time. When an alternative is tried and loses (a smoother, a coupling scheme, a solver variant), delete it rather than leaving it behind a flag or as a half-wired scaffold. A few research options remain available behind flags: the bicubic Yin-Yang sampler (_USE_BICUBIC_YY[]), the φ-buffer (MILEVA_PHI_BUFFER=1), the minimum-overlap grid (cfg.useMinOverlapGrid), the MILEVA_NO_MDPI toggle and the alternative operator forms (cfg.useDeviatoricStress = false, cfg.useCurvatureTerms = false); see Yin-Yang grid and Discretization. If the result of an attempt that was dropped is worth keeping, record it in the commit message or the comment of the code that was kept, with the numbers.

  • No unused configuration. A SimConfig field that nothing reads misleads the user who sets it. If a feature is declared but not implemented, validate! refuses it with a message that says so (as it does for cfg.useAMR and cfg.useDruckerPrager), and tests/test_config_matrix.jl checks that refusal.

  • No silent no-ops. A function that cannot do its job on a given branch throws, naming what to use instead.

  • New physics defaults to off. tests/test_config_matrix.jl asserts that no major physics option is on by default, so a new flag does not change every existing run.

  • Project-specific physics goes through the hooks, not new fields in SimConfig: initialMarkerHook, markerStepHook and restartHook let a driver paint an initial composition, keep per-parcel bookkeeping or branch from a restored state without the generic loop knowing about it.

Every operator change keeps the identities green ​

Any change to solvers/stokes_assembly.jl, solvers/stokes_assembly_3d.jl, solvers/diffuse_temperature.jl, utils/compute_strain_rate.jl, utils/compute_diagnostics.jl or the marker transport must keep these tests passing, unchanged:

TestWhat it guards
work equals dissipation, tests/test_annulus_operator.jl and tests/test_stokes_3d_variable_eta.jlthe momentum operator is ∇⋅(2ηε˙) at variable viscosity
rigid rotation is an exact null mode, tests/test_annulus_operator.jlthe curvature terms of the spherical operator
Nu = 1 on a conducting state and Nu_top == Nu_bot, tests/test_nusselt_conduction.jlthe wall flux is evaluated where the boundary condition is held
discrete energy budget closes, tests/test_nusselt_conduction.jlthe Dirichlet walls neither add nor remove heat outside the reported fluxes
one marker step conserves energy, tests/test_marker_energy.jlthe marker hand-back delivers exactly the energy the grid solver computed

Do not loosen a tolerance in these files to make a change pass. If a change moves one of them, find out why before going further.

More rules for operators:

  • Use the local cell width. On a stretched radial grid grid.dr is the smallest cell width; use grid.drVec[i] and grid.drFVec[i].

  • Keep the 3-D assemblies in step. solvers/stokes_assembly_3d.jl and solvers/stokes_assembly_3d_local.jl are structural mirrors; a term added to one goes into the other, and tests/test_assembly_3d_mirror.jl must stay green.

  • Terms that must cancel are switched together. The adiabatic sink and the viscous-dissipation source of the compressible energy equation are both gated by cfg.useShearHeating; carrying one without the other injects or drains energy the model does not have. The same logic applies to any pair of terms that are only consistent jointly.

Check that a new test can fail ​

A new test should be shown to fail on the code it protects against: revert the change locally, run the test, and see it fail. See Testing for how to choose a property that responds to what you want to check.

Geometry, resolution and solvers ​

  • 2-D means the spherical annulus. Examples, tests of production physics and benchmarks use cfg.geometry = "spherical_annulus" (the default). The quantitative references for the 2-D path are spherical-annulus ones: Hernlund and Tackley (2008) and the annulus column of Fleury et al. (2024).

  • Science resolution starts at Nr = 64. Coarser grids are for smoke tests and identity tests, and are labelled as such wherever their output appears.

  • 3-D Stokes is multigrid. cfg.solverType = "multigrid" and cfg.mgLevels ≥ 2, with Nr and Nt that halve cleanly; a 3-D direct LU needs more memory than a laptop has.

  • Changing a default changes results. Switching a solver default, a pressure pin or an advection scheme can shift answers by several per cent (the dense and point pressure pins differed by 7 per cent in velocity on a production field). Make it a deliberate, measured change with the effect recorded.

Reporting results ​

  • Report what was measured, with its evidence: solver residuals, Picard sweep counts (picard_n, picard_rel), budget_resid, Nu_top against Nu_bot. A step whose nonlinear iteration hit its cap is not a converged solution.

  • Comparisons with Hernlund and Tackley (2008) are verification against published results, whose values come from one 32 × 256 grid. State the seeded planform (cfg.m_wavenumber) with any V_rms, because several steady planforms exist and they differ in V_rms.

  • Do not extrapolate a quantity whose convergence order you have not measured.

Interfaces that others depend on ​

  • diagnostics.csv: the column order in DIAG_CSV_COLUMNS (utils/diagnostics_csv.jl) is fixed and post-processing keys on the header names. Append new columns at the end; write NaN for an undefined value, never 0.

  • Markers: every per-marker vector is listed in MARKER_VECTOR_FIELDS. A new marker field goes into the struct, that tuple, the positional constructors, init_markers, reseeding (with an inheritance rule) and the checkpoint, or the marker set desynchronises.

  • Checkpoints: new persistent state must be saved by save_checkpoint and restored in the resume block of main_SphCon.jl, and a resumed run must reproduce a continuous one.

Code style ​

  • Julia only.

  • Comments explain why, and carry the measurement that justified a choice (the number, the grid, the date). Cite the method source where an algorithm is taken from the literature (for example Kageyama and Sato 2004 for the Yin-Yang grid, Tackley 2008 for the Yin-Yang Stokes solver, Trottenberg et al. for multigrid).

  • Keep the include order of include/mileva.jl acyclic: a file may use only what is defined above it.

  • Build the documentation with bin/mileva docs and update the page that describes any behaviour you change.