The following guidelines describe how to run a spatial or temporal convergence study with the notus.py script.
When verifying a numerical scheme, you want to confirm that the discretization error decreases at the expected rate when the mesh is refined (spatial convergence) or when the time step is reduced (temporal convergence). Done by hand, this means running the same case many times at increasing resolution, collecting the errors, and computing the observed order. The convergence mode of notus.py automates the whole loop: you describe the case once, and the script generates one simulation per refinement level, runs them, and prints the observed order of convergence.
This guide walks through the setup of a convergence case, the commands to launch it, how to read the results, and how to combine convergence with the @PARAMETER mechanism.
A convergence study compares the numerical error against a known reference solution at several resolutions. If a scheme is theoretically of order \(p\), dividing the mesh step by a geometric factor \(g\) should divide the error by about \(g^p\). The script reports the observed order so you can check it matches the theory.
Notus supports two kinds of study, selected with the --type option:
space — the mesh is refined at each step (N_CELLS_X, N_CELLS_Y increase).time — the time step is reduced at each step.The convergence settings are passed on the command line, not as #@ directives. The NTS file only needs two things.
For a spatial study, the mesh is injected by the script through N_CELLS_X and N_CELLS_Y; do not hard-code the cell count in the grid block:
grid {
grid_type regular;
number_of_cells (N_CELLS_X, N_CELLS_Y);
number_of_ghost_cells 2;
}
For a temporal study, the time step is injected the same way through TIME_STEP.
The error can only be computed if the case exposes a reference solution and asks Notus to export the corresponding error:
post_processing {
reference_solution velocity {
instructions {
@return (...analytic solution...);
}
}
validation {
reference_solution_error velocity;
}
grid_convergence_quantities reference_solution_error velocity;
}
reference_solution provides the exact field, reference_solution_error computes the error, and grid_convergence_quantities tells Notus to export that error so notus.py can build the convergence table. Without the last line, no order is computed.
The minimal command needs the study type and the coarsest mesh:
./notus.py convergence --type space --initial-mesh 16 16 path/to/case.nts
The main convergence options are:
| Argument | Role | If omitted on the command line |
|---|---|---|
--type space | Spatial study (mesh refinement). | required |
--initial-mesh NX NY [NZ] | Coarsest mesh (2 or 3 values). | required |
--geometric-factor G | Refinement ratio between two successive levels. | Taken from @GEOMETRIC_FACTOR in the NTS file. |
--count N | Number of refinement levels. | Taken from @STEPS in the NTS file. |
--geometric-factor and --count do not have a fixed default value: either you give them on the command line, or they are read from the NTS file directives @GEOMETRIC_FACTOR and @STEPS. With --initial-mesh 16 16 --geometric-factor 2 --count 4, the script runs four simulations on meshes 16×16, 32×32, 64×64 and 128×128.
For a temporal study the mesh stays fixed and the time step shrinks at each level, so --time-step becomes mandatory:
./notus.py convergence --type time --initial-mesh 64 64 --time-step 0.01 --geometric-factor 2 path/to/case.nts
| Argument | Role | Default |
|---|---|---|
--type time | Temporal study (time-step refinement). | required |
--time-step DT | Initial time step (required for time). | required |
--final-time T | Physical end time of the computation. | 1.0 |
--initial-mesh NX NY [NZ] | Mesh used for all levels. | required |
The script first launches one simulation per level (each Launch command line is one job) and reports its Status. Then it prints one convergence table per quantity, giving the error in three norms (L1, L2, L∞) and the observed order between successive meshes:
Mesh │ Velocity L1 error │ order │ Velocity L2 error │ order │ Velocity Linf error │ order ──────┼────────────────────────┼───────┼────────────────────────┼───────┼────────────────────────┼────── 16×16 │ 4.9470e-08 │ n/a │ 2.9522e-06 │ n/a │ 4.1791e-04 │ n/a 32×32 │ 1.3103e-08 │ 1.917 │ 7.9340e-07 │ 1.896 │ 1.2522e-04 │ 1.739 64×64 │ 3.3370e-09 │ 1.973 │ 2.0265e-07 │ 1.969 │ 3.4004e-05 │ 1.881
The first level always shows n/a, since there is no previous level to compare against. The order approaches the theoretical value (≈ 2 here) as the mesh is refined. Each table is also written to a .md file in the output directory (--directory, default convergence/).
A convergence study can be combined with @PARAMETER directives. The study is then run independently for every parameter combination: the parameter loop wraps the refinement loop, and one separate table (and one .md file) is produced per parameter set.
For example, a case declaring two parameters:
# @PARAMETER integer direction [1, 2] # @PARAMETER double mean_velocity [0.1, 0.5]
with three refinement levels produces 2 × 2 = 4 parameter sets, each refined over 3 meshes — that is 12 simulations in total. The output ends with four tables, one per set:
Parameter set: [integer]direction(1)-[double]mean_velocity(0.1) Mesh │ Velocity L1 error │ order │ Velocity L2 error │ order │ Velocity Linf error │ order ──────┼────────────────────────┼───────┼────────────────────────┼───────┼────────────────────────┼────── 16×16 │ 4.9470e-08 │ n/a │ 2.9522e-06 │ n/a │ 4.1791e-04 │ n/a 32×32 │ 1.3103e-08 │ 1.917 │ 7.9340e-07 │ 1.896 │ 1.2522e-04 │ 1.739 64×64 │ 3.3370e-09 │ 1.973 │ 2.0265e-07 │ 1.969 │ 3.4004e-05 │ 1.881 Parameter set: [integer]direction(1)-[double]mean_velocity(0.5) Mesh │ Velocity L1 error │ order │ Velocity L2 error │ order │ Velocity Linf error │ order ──────┼────────────────────────┼───────┼────────────────────────┼───────┼────────────────────────┼────── 16×16 │ 1.4070e-07 │ n/a │ 9.6134e-06 │ n/a │ 1.6560e-03 │ n/a 32×32 │ 4.1103e-08 │ 1.775 │ 2.9212e-06 │ 1.718 │ 5.6698e-04 │ 1.546 64×64 │ 1.0638e-08 │ 1.950 │ 7.6534e-07 │ 1.932 │ 1.6221e-04 │ 1.805 (... one table per remaining parameter set ...)
Each set is saved to its own file, named after the parameters it contains:
convergence/poiseuille_2D_test-[integer]direction(1)-[double]mean_velocity(0.1).md convergence/poiseuille_2D_test-[integer]direction(1)-[double]mean_velocity(0.5).md convergence/poiseuille_2D_test-[integer]direction(2)-[double]mean_velocity(0.1).md convergence/poiseuille_2D_test-[integer]direction(2)-[double]mean_velocity(0.5).md
This lets you check, in a single run, that the order of convergence holds across all parameter variants of a case.
Between two successive errors \(e_i\) and \(e_{i+1}\) on meshes refined by a factor \(g\), the local order is:
\[ p = \frac{\ln\!\left(e_{i+1} / e_i\right)}{\ln\!\left(1/g\right)} \]
When at least three levels are available, the script can refine this estimate with a Richardson extrapolation. The order is reported as n/a when it cannot be computed reliably (too few levels, or a non-monotonic or zero error).
--apply-ratio-to-time-step [E] (spatial studies only) — also shrinks the time step at each level, dividing it by \(g^{\,\text{level} \times E}\), where \(E\) is the ratio spatial order / temporal order. This keeps the temporal error from polluting a spatial study. When the flag is given without a value, E defaults to 1.-n, --mpi-procs N — force the number of MPI processes.-o, --directory DIR — output directory (default: convergence).--geometric-factor G — refinement ratio. If omitted, read from @GEOMETRIC_FACTOR in the NTS file.--count N — number of refinement levels. If omitted, read from @STEPS in the NTS file.Convergence also accepts the common submission options (dual-pass batch system: --dual-pass, --batch-template, --batch-command, --stage {submit,post-process}).
A complete example, launched on a cluster with the dual-pass job manager:
./notus.py convergence --dual-pass --batch-template path/to/template --batch-command "sbatch {BATCH_FILE}" --stage submit --type space --initial-mesh 250 100 --geometric-factor 2 path/to/case.nts
./notus.py convergence --dual-pass --batch-template path/to/template --batch-command "sbatch {BATCH_FILE}" --stage post-process --type space --initial-mesh 250 100 --geometric-factor 2 path/to/case.nts