3. Code map: where everything lives¶
ASYNCH is about 36 000 lines of C. You do not need to read them all: about 80 % of the scientific behaviour lives in four files. This page tells you which ones, and in which order to read them.
3.1 The shortest reading path¶
Order |
File |
What you learn |
Size |
|---|---|---|---|
1 |
|
the life of a run, step by step |
360 lines |
2 |
|
the data: what a link, the globals, a solution list are |
600 lines |
3 |
|
what a model is: one C function computing dy/dt |
80 lines |
4 |
|
how links are scheduled asynchronously |
420 lines |
5 |
|
one time step of one link |
350 lines |
6 |
|
how a model id is wired to its equations, sizes, parameters |
4 000 lines (read only your model’s |
3.2 Directory layout¶
src/asynch_cli.c:main(), theasynchcommand-line programsrc/asynch_interface.c/.h: the public C API (Asynch_Init,Asynch_Parse_GBL,Asynch_Advance, …)src/asynch_api.c/.h: the C API for other languages: plain arrays and handles, custom models (chapter 10)src/structs.h: all core data structuressrc/globals.c:my_rank,np, the two global variables of the library
src/config_gbl.c: the.gblglobal filesrc/riversys.c: topology (.rvr), parameters (.prm), initial states; builds theLinkarraysrc/partition.c: splits the network between MPI processessrc/forcings.c,forcings_io.c: rain, evaporation and other forcings (files, binaries, database)
src/advance.c: the main loop, the asynchronous scheduler (chapter 4)src/rksteppers.c: initial step size, helpers shared by the stepperssrc/steppers/: one time step of one link:explicit.c(the standard one),explicit_index1*.c(dams),forced.c(imposed discharge)src/solvers/: the Runge-Kutta coefficients:rk3_2_dense.c(method 0),rk4_3_dense.c(1),dopri5_dense.c(2, the usual choice),radau.c(3, refused with an error)
src/models/definitions.c: the registry: sizes, unit conversions, precalculations, initial statessrc/models/equations.c: the right-hand sides dy/dt of every modelsrc/models/check_consistency.c: clamps states (e.g. no negative storage)src/models/check_state.c: discontinuity “state” detection (dams)src/models/output_constraints.c: filters applied to snapshot outputs
src/comm.c: MPI messages between processessrc/processdata.c,outputs.c,io.c: hydrographs, peak flows, snapshotssrc/db.c: PostgreSQL accesssrc/blas.c: small vector helpers (daxpy, dcopy, norms)src/assim/,assim_cli.c: data assimilation (built only if PETSc is found)
python/asynch/: the Python package (chapter 10):solver.py,model.py,config.py,io.py,_lib.pytests/: C unit tests (check_asynch.c), Python tests, the regression harness (chapter 9)examples/: runnable examples and their reference results;examples/python/: Python examplesdocs/: this documentation (guide in Markdown, reference manual in reStructuredText)
Every .c file in src/ is compiled: about 6 500 lines of old code that were not (issue M-01, chapter 8) were
removed in 2026; they remain in the git history.
3.3 The life of a run¶
main() in src/asynch_cli.c is a straight sequence of calls to the public API
(src/asynch_interface.c). Each call prints one of the lines you see on screen:
Asynch_Init: create the solver object; MPI rank and sizeAsynch_Parse_GBL: read the global file · screen: Reading global file… ·config_gbl.c: Read_Global_DataAsynch_Load_Network: read the .rvr, build the Link array and the parent/child pointers · screen: Loading network… ·riversys.cAsynch_Partition_Network: decide which process owns which link · screen: Partitioning network… ·partition.cAsynch_Load_Network_Parameters: read the .prm into link->params, convert units (ConvertParams) · screen: Loading parameters… ·riversys.cAsynch_Load_Dams: dams and reservoirs · screen: Reading dam and reservoir data…Asynch_Load_Numerical_Error_Data: tolerances; builds the Runge-Kutta method table · screen: Setting up numerical error data…Asynch_Initialize_Model: set link->differential, dim, … (InitRoutines), then the Precalculations · screen: Initializing model… ·definitions.cAsynch_Load_Initial_Conditions: y(t0) from .ini/.uini/.rec/.h5/database; ReadInitData fills derived states · screen: Loading initial conditions…Asynch_Load_Forcings: rain, evaporation, … · screen: Loading forcings…Asynch_Load_Save_Lists: which links write hydrographs and peaks · screen: Loading output data…Asynch_Finalize_Network: allocate per-link solution lists, MPI buffers · screen: Finalizing network…Asynch_Calculate_Step_Sizes: the first step size h of every link · screen: Calculating initial step sizes…Asynch_Prepare_*: open the temporary output filesAsynch_Advance: the simulation itself (chapter 4) ·advance.c: Advance()Asynch_Take_System_Snapshot: the final snapshotAsynch_Create_Output: merge the temporary files into the final .h5/.csv/.dat/databaseAsynch_Create_Peakflows_Output: write the .peaAsynch_Delete_Temporary_Files, Asynch_Free: clean up
Because it is a sequence of API calls, you can write your own main() that does the same thing and changes something in between,
e.g. overwriting parameters after Asynch_Load_Network_Parameters.
3.4 The core data structures (src/structs.h)¶
Link: one river link (channel segment + its hillslope). Everything is attached to it:
field |
meaning |
|---|---|
|
id used in input files; index in the |
|
the tree: upstream links and the downstream link ( |
|
number of states (unknowns) of the ODE at this link (7 for model 254) |
|
local parameters: read from |
|
function pointer to the model’s right-hand side, e.g. |
|
which stepper and which Runge–Kutta coefficients are used |
|
current step size, and time up to which the solution is known [min] |
|
the stored solution: a linked list of recent steps (see below) |
|
current value of each forcing (rain, evaporation, …) |
|
running maximum for the |
my points to data that exists only on the MPI process that owns the link.
RKSolutionList / RKSolutionNode: every accepted step of a link is stored as a node:
time t, state y_approx, and the RK stage values k. With the k values the solution
can be evaluated at any time inside the step (“dense output”). This is what makes
the asynchronous scheme possible: a downstream link can ask “what was the discharge of
my parent at t = 12.37 min?” even though the parent never stopped at that time. Nodes
are freed once every child has used them.
GlobalVars: everything from the .gbl file: model id, simulation time
(maxtime, in minutes), global parameters, file names, output settings.
AsynchSolver: the object that owns all of the above for one simulation (sys,
globals, my_sys = the links owned by this process, forcings, MPI buffers).
3.5 How a model plugs in¶
A model is identified by its number (model_uid in the code, first entry of the .gbl).
For model 254 you find it in these places:
function ( |
what it sets for model 254 |
|---|---|
|
12 global params, 8 params per link of which 3 are read from |
|
snapshot filter (its missing |
|
|
|
|
|
derived parameters |
|
derived initial states: |
|
the ODEs themselves |
05_model_254_explained.md walks through each of these with the physics.
3.6 Parallelism in one paragraph¶
With mpirun -n P there are P independent copies of the program (MPI processes),
each with its own memory. partition.c gives each process a set of links: whole
sub-basins, starting from the headwater links (“leaves”), so that most parent → child
connections stay inside one process. When a link’s parent belongs to another process,
the parent’s recent solution steps are sent over MPI (comm.c: Transfer_Data). Output
files are written by process 0 after gathering data from the others.