-
Configuration File
Mesh File
Restart File
Solver Setup
Physical Definition
Markers and Boundary Conditions
Convective Schemes
Slope Limiters and Shock Resolution
History and Solution Output
Linear Solvers and Preconditioners
Basics of Multi-Zone Computations
Execution
Aeroacoustic analysis of unsteady pressure
Flamelet Solver — Python Wrapper Wall Boundary Conditions
Output Fields Reference
Error Messages
-
Setup Guide: Steady RANS for External Aerodynamics
Setup Guide: Incompressible Flows
Setup Guide: Unsteady Flows and DES
Setup Guide: Laminar-Turbulent Transition
Setup Guide: Turbomachinery
Setup Guide: Heat Transfer and Conjugate Heat Transfer
Setup Guide: Species Transport and Combustion
Setup Guide: Shape Optimization with the Discrete Adjoint
Setup Guide: Non-Ideal Compressible Flows
-
Option Reference
Problem Definition
FluidModel
Data-driven fluid model parameters
Freestream Conditions
Reference Conditions
Boundary Markers
Time-marching
Linear solver definition
Convergence
Multi-grid
Spatial Discretization
Adjoint and Gradient
Sobolev Gradient Solver Parameters
Input/output files and formats
Dynamic mesh definition
Aeroelastic Simulation (Typical Section Model)
Wind Gust
Equivalent Area
Grid deformation
Deformable mesh
FEM flow solver definition
FEA solver
Multizone definition
Radiation solver
Heat solver
Visualize Control Volumes
Inverse design problem
Unsupported options
FFD point inversion
Automatic Differentation options
Python Options
Hybrid_RANSLES Options
Output Options
libROM options
Renamed and Removed Options
-
GIT-ting Started
Developing SU2 on GitHub (Internal Developers)
Running Regression Tests
Code Review
Code Structure
Style Guide
Advanced AD Techniques
Containers for Development
Writing Unit Tests
Integrating Tracy Profiler with SU2
Automatic Differentiation (AD)
Code Reference (Doxygen)
Debugging SU2
Documentation Improvements and Status
Glossary
Docs Check
Debugging SU2
This page shows practical ways to find bugs in SU2: crashes, wrong results, divergence, data races and slow code.
Tips marked (advice) are general practice, not features of SU2. Words such as “zone”, “halo point” or “reducer strategy” are explained in the Glossary.
- Build for debugging
- Check the config file first
- Reproduce with a small case
- Use a debugger
- Sanitizers
- OpenMP data races
- Floating point exceptions
- Find where a NaN comes from
- Algorithmic differentiation
- Profiling
- Valgrind
Build for debugging
Make a separate build folder for debugging, so your normal build stays fast (advice):
./meson.py setup build_debug --buildtype=debug --prefix=$PWD/install_debug
./ninja -C build_debug install
The build type changes what the compiler does:
| Build type | Optimization | Debug symbols (-g) |
assert checks |
|---|---|---|---|
release (default) |
yes | no | off |
debugoptimized |
yes (-O2) |
yes | off |
debug |
no | yes | on |
Notes:
- SU2 sets
b_ndebug=trueinmeson.build, which turnsassertoff. Only--buildtype=debugturns it back on (SU2 adds-UNDEBUG). To keep the checks with optimization, add the meson option-Db_ndebug=falseto adebugoptimizedbuild (advice). - With
asserton, SU2 also runs some extra checks that are inside#ifndef NDEBUG. For exampleCSysMatrixchecks the sizes of the vectors in the matrix-vector product, and prints a message when it regularizes a small pivot. debugoptimizedis a good default for gdb and profilers: the code is fast enough for real cases, and the stack traces have function names and lines (advice). Some variables show as “optimized out”; usedebugwhen you need them.--warnlevel=3turns on most compiler warnings. Check your change with--warnlevel=3 --werrorlocally;--werrormakes warnings fail the build (advice). Most CI configurations use warning level 3, but the current.github/workflows/regression.ymlexports the CPU flags as itswerroroutput by mistake, so those builds do not currently receive--werror.-Dcpu-arch(defaultnative) sets-march.-Dcpu-arch=(empty) removes it. This helps with tools that do not know the newest instructions, see Valgrind.- SU2 always compiles with
-ffast-math -fno-finite-math-only(when the compiler accepts these flags). NaN checks withstd::isnanstill work. Other effects of fast math are described in Floating point exceptions.
Build options in meson_options.txt that matter when debugging:
| Option | Use |
|---|---|
-Dwith-mpi=disabled |
Serial build. Simpler to debug, and needed for the address sanitizer jobs of the CI. |
-Dwith-omp=true |
OpenMP build, for thread problems. |
-Denable-tests=true |
Builds the unit tests, see Running Unit Tests. |
-Denable-mixedprec=true |
Linear algebra in single precision. Try without it if a linear solver fails. |
-Denable-autodiff=true, -Denable-directdiff=true |
AD builds, see Algorithmic differentiation. |
-Dtracy_enable=true |
Tracy profiler, see Profiling. |
-Denable-gprof=true |
gprof profiling, see Profiling. |
To change the options of an existing build folder, use ./meson.py setup build_debug --reconfigure <options>.
See Build SU2 on Linux/MacOS for all build steps.
Check the config file first
Many problems are config problems. Check the config before you look at the code.
The dry run
SU2_CFD -d case.cfg
-d (or --dryrun) starts CDummyDriver. SU2 reads and checks the whole config file, sets up the solvers on an
empty geometry, prints the available volume and history output fields, and stops. It does not read the mesh
points, so it is fast even for large cases. The mesh file must still exist, because SU2 reads the number of
dimensions from it.
Read the error box
When SU2 stops on an error, it prints a box like this (from SU2_MPI::Error):
Error in "void CConfig::SetConfig_Parsing(std::istream&)":
-------------------------------------------------------------------------
Line 12 KIND_TURB_MODLE: invalid option name. Check current SU2 options in config_template.cfg.
Did you mean KIND_TURB_MODEL?
------------------------------ Error Exit -------------------------------
- The first line names the C++ function that stopped. Search for the message in that function to see the exact check.
- The parser collects all errors of the file, up to 30, before it stops. Fix them from the top.
- Errors about the config file come in three kinds: an unknown option name (with a “Did you mean” guess), an
option that appears twice, and a bad value (
invalid option value, with the list of allowed values). - Other errors come later, from the checks in
CConfig::SetPostprocessingand from the solvers.
The Error Messages page lists every SU2_MPI::Error message with a link to its code. If an
old config file stops with “invalid option name”, look for the option in
Renamed and Removed Options. The Option Reference lists all
current options.
Reproduce with a small case
Debugging is faster on a small case that runs in seconds (advice). Good sources are the regression cases in
TestCases/. Running Regression Tests explains how to run one test.
When the test script runs a case, it writes a copy of the config file with the number of iterations changed,
named <config>.cfg.autotest, and the screen output to <config>.log. Both stay in the case folder. To repeat
exactly what the test did, run the .autotest file by hand, for example in gdb:
cd euler/naca0012
gdb --args SU2_CFD inv_NACA0012_Roe.cfg.autotest
Lower ITER (or INNER_ITER, OUTER_ITER, TIME_ITER) to the first iteration that shows the problem
(advice).
Use a debugger
Serial runs
gdb --args SU2_CFD case.cfg
(gdb) run
(gdb) bt
After a crash, bt prints the call stack. To stop where SU2 reports an error, set a breakpoint before run
(advice). SU2_MPI::Error ends with MPI_Abort in an MPI build, and with exit in a build without MPI:
(gdb) break MPI_Abort # build with MPI
(gdb) break exit # build without MPI
To stop at a solver function, use its full name, for example break CSolver::SetResidual_RMS (advice).
Runs with MPI
Some bugs only show in parallel, for example at the halo points. Three ways to use gdb with MPI (advice):
-
One terminal per rank (needs a display):
mpirun -np 2 xterm -e gdb --args SU2_CFD case.cfg -
Only one rank in gdb, the others run normally (Open MPI syntax):
mpirun -np 1 gdb --args SU2_CFD case.cfg : -np 1 SU2_CFD case.cfg -
Attach to a running rank: start the run, find the process id with
psorpgrep SU2_CFD, thengdb -p <pid>. This is useful for a run that hangs. Runbtin each rank to see where they wait.
Only one rank prints the error box: the lowest rank that called SU2_MPI::Error. Other ranks may have failed
too.
The Python wrapper
The Python wrapper (built with -Denable-pywrapper=true) runs the same C++ code. Debug it with
gdb --args python3 your_script.py (advice).
Sanitizers
Sanitizers are compiler tools that check the program while it runs:
- the address sanitizer (asan) finds memory errors, such as reading past the end of an array, use after free and leaks;
- the thread sanitizer (tsan) finds data races between threads.
The program runs slower, and stops with a report on the first error.
How the CI uses them
The workflow .github/workflows/regression.yml has these jobs:
| Build job | Configurations | Main build flags | Test job |
|---|---|---|---|
| Build SU2 (asan) | BaseNoMPI-asan, ReverseNoMPI-asan |
--buildtype=debugoptimized -Dwith-mpi=disabled; the reverse build also has --optimization=1 -Denable-autodiff=true -Denable-normal=false |
Address Sanitizer Tests: serial_regression.py, serial_regression_AD.py with --asan |
| Build SU2 (tsan) | BaseOMP-tsan, ReverseOMP-tsan |
--buildtype=debugoptimized -Dwith-omp=true -Denable-mixedprec=true -Denable-tecio=false; the reverse build also has -Denable-autodiff=true -Denable-normal=false |
Thread Sanitizer Tests: hybrid_regression.py, hybrid_regression_AD.py with --tsan |
The sanitizer flags are not in the table, because the build containers set them:
ghcr.io/su2code/su2/build-su2-asan and build-su2-tsan, made from the
Docker-Builds repository. The tsan container has a compiler and
OpenMP library built for the thread sanitizer.
What --asan and --tsan do
The test scripts read these arguments with parse_args() in TestCase.py. With one of them:
- tests with
enabled_with_asan = False(orenabled_with_tsan = False) are skipped; - the number of iterations is cut to at most 2 after the restart iteration, because sanitized runs are slow;
- a test fails only if SU2 returns an error. The results are not compared with the stored values.
Run a sanitizer on your computer
The easiest way is the CI container, see Container Development. It also explains how to work inside the tsan container.
You can also use the meson option b_sanitize (advice):
./meson.py setup build_asan --buildtype=debugoptimized -Dwith-mpi=disabled -Db_sanitize=address
./meson.py setup build_tsan --buildtype=debugoptimized -Dwith-omp=true -Db_sanitize=thread
Use a build without MPI for asan, like the CI: MPI libraries give many reports that are not SU2 bugs (advice). For tsan with OpenMP, the OpenMP library must also be built for tsan. Otherwise you get false reports, which is why the CI uses a special container (advice).
OpenMP data races
A data race happens when two threads write to the same memory, or one writes while another reads, without synchronization. The result then changes from run to run.
How to look for one:
- Run the case with threads:
SU2_CFD -t 2 case.cfg(-tsets the number of OpenMP threads per MPI rank). The hybrid regression tests use-t 2. - Run it several times with the same number of threads, and compare the history files. Results that change between identical runs point to a race (advice). Small differences between different thread counts can also come from a different order of sums, and are not always a bug (advice).
- Set
EDGE_COLORING_GROUP_SIZE= 0. This forces the reducer strategy in the flow and scalar solvers: the edge fluxes are stored first, then summed for each point, so the edge loop has no shared writes. If the problem disappears, look at the edge loops and the edge coloring. - Run the case in a tsan build, see Sanitizers.
At start-up, SU2 prints either “All ranks use edge coloring.” or a warning that some ranks use the fallback (the reducer strategy), with the coloring efficiency.
Write thread code only with the macros of Common/include/parallelization/omp_structure.hpp
(SU2_OMP_PARALLEL, SU2_OMP_FOR_STAT, END_SU2_OMP_FOR, SU2_OMP_MASTER, BEGIN_SU2_OMP_SAFE_GLOBAL_ACCESS, …).
A block such as SU2_OMP_PARALLEL or SU2_OMP_FOR_STAT needs its END_SU2_OMP_... macro. When you configure a build with both
-Dwith-omp=true and -Denable-autodiff=true, meson runs the OpDiLib syntax checker (rules in
su2omp.syntax.json) and stops if the macros are not used correctly.
See Parallelism for how the threads share the work.
Floating point exceptions
SU2 has no config option or build option to stop on floating point exceptions (division by zero, overflow, invalid operations that make a NaN).
SU2_CFD/src/SU2_CFD.cpp has two commented lines for this. To use them, remove the comment marks from both,
and rebuild:
#include <fenv.h> // near the top of the file
feenableexcept(FE_INVALID | FE_OVERFLOW | FE_DIVBYZERO ); // in main()
The program then stops with the signal SIGFPE at the first such operation. Run it in gdb to see where.
Limits (advice):
feenableexceptis a GNU extension (Linux with glibc).- With
-ffast-maththe compiler assumes that math operations do not trap. It may move or add operations, so a trap can come from code that is correct. Adebugbuild has no optimization, so it gives cleaner results. - On x86,
-ffast-mathmay also link code that sets denormal numbers to zero. Results with very small numbers can then differ from a build without fast math. - Libraries called before or by SU2 (MPI, BLAS) can also trigger the trap.
Find where a NaN comes from
Where SU2 stops
SU2 checks for NaN in a few places and stops with SU2 has diverged (NaN detected).:
CSolver::SetResidual_RMS, on the root mean square residual of each equation. It also stops when the residual is larger than 10^20 (SU2 has diverged (Residual > 10^20 detected).).COutput(SU2_CFD/src/output/COutput.cpp), on the history fields used for the convergence check. Here SU2 stops without saving the solution.- The DG (discontinuous Galerkin) solver (
CFEM_DG_EulerSolver) has its own checks.
The NEMO (non-equilibrium) solvers also check each flux and source term with CNumerics::CheckResidualNaNs.
The check happens after the iteration is done, so the NaN can come from anywhere in that iteration. The steps below narrow it down.
Write the solution before it breaks
Write the volume files at every iteration, and keep one file per iteration:
OUTPUT_WRT_FREQ= 1
WRT_VOLUME_OVERWRITE= NO
VOLUME_OUTPUT= SOLUTION, PRIMITIVE, RESIDUAL, LIMITER, MESH_QUALITY, MPI
The last file written before the error shows the state just before the NaN. Useful volume output groups:
| Group | Fields | What to look for |
|---|---|---|
RESIDUAL |
RES_DENSITY, RES_MOMENTUM-X, …, RES_TKE, … |
Where the residual grows first. |
LIMITER |
LIMITER_PRESSURE, …, LIMITER_TKE, … |
Points where the limiter goes to 0 (strong gradients, shocks). |
PRIMITIVE |
PRESSURE, TEMPERATURE, MACH, EDDY_VISCOSITY, … |
Negative or extreme values. |
MESH_QUALITY |
ORTHOGONALITY, ASPECT_RATIO, VOLUME_RATIO |
Bad cells near the problem. |
MPI |
RANK |
Problems on a partition border point to halo exchange bugs. |
The groups that exist depend on the solver. Run SU2_CFD -d case.cfg to print them, or see the
Output Fields Reference.
In a long run, restart from a solution saved a few iterations before the failure, and write the files at every iteration only from there (advice).
History fields to watch
RMS_RESandMAX_RESgroups: which equation grows first.NONPHYSICAL_POINTS: the number of points where the flow solver found a non-physical state (for example a negative pressure or density). At these points the compressible solver goes back to the solution of the previous iteration. A growing number often comes before a NaN. At the end of the run SU2 also prints a warning with this number.MIN_CFL,MAX_CFL,AVG_CFL(groupCFL_NUMBER, withCFL_ADAPT= YES): a CFL number that keeps growing while the residual grows.LINSOLgroup (for exampleLINSOL_ITER,LINSOL_RESIDUAL): a linear solver that does not converge.
Add them with HISTORY_OUTPUT and SCREEN_OUTPUT.
Common checks
These help to tell a bug from a hard case (advice):
- Lower
CFL_NUMBER, or useCFL_ADAPT= YES. - Run first order (
MUSCL_FLOW= NO,MUSCL_TURB= NO). If that works, look at the gradients and limiters. - Run serial and in parallel. If only the parallel run fails, look at the halo exchange.
- Run with a
debugbuild, soassertchecks are on.
Algorithmic differentiation
The discrete adjoint has its own debugging tools: the tape test mode --tapetests of the test scripts, the
options CHECK_TAPE_TYPE and CHECK_TAPE_VARIABLES (for builds with -Dcodi-tape=Tag), the tape statistics of
WRT_AD_STATISTICS, and the CoDiPack build options. A debug or debugoptimized AD build also turns on the
internal checks of CoDiPack (CODI_EnableAssert).
See Automatic Differentiation for how to use them.
Profiling
Built-in timing
WRT_PERFORMANCE= YES prints a performance summary at the end of SU2_CFD: the time spent in preprocessing,
computation and output, the time per iteration, points per core and ghost points per core. It also prints the
write speed of each output file. The history fields ITER_TIME and WALL_TIME (group WALL_TIME) give the time
per iteration.
Tracy
Tracy shows a timeline of the instrumented code sections. The build option is
-Dtracy_enable=true; use it with --buildtype=debugoptimized (meson prints a warning otherwise). Mark code
sections with SU2_ZONE_SCOPED or SU2_ZONE_SCOPED_N("name") from Common/include/tracy_structure.hpp. These
macros are empty when Tracy is off.
perf
On Linux, perf samples the whole program without changes to the code (advice):
perf record -g SU2_CFD case.cfg
perf report
Use a debugoptimized build, so the report has function names and lines (advice). For an MPI run, profile
one rank, for example with mpirun -np 1 perf record -g SU2_CFD case.cfg : -np 3 SU2_CFD case.cfg (Open MPI
syntax) (advice).
gprof
-Denable-gprof=true adds -pg to the SU2_CFD executables (see
Build SU2 on Linux/MacOS). Only the main file SU2_CFD.cpp and the link step get the
flag, not the library SU2core with the solver code. So the call graph of gprof is mostly empty. Prefer perf or
Tracy (advice).
Valgrind
Valgrind (memcheck) finds memory errors like the address sanitizer, without a special build, but it is much slower (advice). Notes:
- The default build uses
-march=native. Valgrind may not know the newest vector instructions (for example AVX-512) and then stops with “unrecognised instruction”. Build with-Dcpu-arch=(empty) or an older architecture for valgrind runs (advice). - Use a build without MPI, or expect many reports from the MPI library (advice).
- Use a very small case and few iterations (advice).
valgrind --track-origins=yes SU2_CFD case.cfg
Last checked against SU2 develop 11c177b904.