Skip to content

Fortran/C++ Interface ​

RELAP5 (Fortran) and the engine (C++) run in one process. They exchange data through one shared block of memory and call each other through functions with C linkage.

Shared data: r5pars ​

The Fortran module r5par (src/Relap/R5Par.for) declares the exchanged variables and places them in the named common block r5pars:

fortran
       module r5par
       ...
       COMMON/r5pars/ liq_t,
      &  sys_pressure,
       ...
       end module r5par

The C++ header src/ADSIDAC/Global/fmodule.h declares a struct with the same leading members in the same order and names it r5pars_, which is the symbol a Fortran compiler gives to the common block r5pars:

cpp
extern "C" struct
{
    double liq_t[500];
    double sys_pressure[500];
    ...
    bool gendataout;
} r5pars_;

Both sides therefore address the same storage. Fortran code reaches it with use r5par; C++ code includes fmodule.h and reads or writes r5pars_.<name>. The struct ends at gendataout. The common block continues with logical flags that only the Fortran side uses (the initialization flags that resetini.for resets between sequences).

The shared part of the two declarations has to stay identical in order, type and array length. The members are grouped by type (doubles, then long, then int, then character arrays, then logicals), and a new shared member goes into the group of its type on both sides, before the Fortran-only flags. The type pairs are double and real*8, long and integer*8, int and integer*4, char[n] and character*n, bool and logical*1. The array lengths are fixed: 500 for volumes, junctions, trips, interactive and control variables, 100 for mesh temperatures and 10 for heat structures.

The data flow through the block is:

  1. C++ writes the RELAP5 card numbers it needs into the _cn arrays (SystemModule::initiationForSimulation).
  2. R5ParFind (Fortran) resolves them to RELAP5 indices in the _obj arrays.
  3. At every evaluation point R5ParUpdate (Fortran) fills the value arrays (liq_t, sys_pressure, mass_fr, ...).
  4. C++ reads the values, and sets flags such as cgennode, cnbranch, csnostop and terminateall, which RELAP5 reads on its next step.

Calling Fortran from C++ ​

A Fortran subroutine name is visible to C++ as name_. The C++ side declares it with C linkage:

cpp
extern "C" { void relap5_(); }          // RELAP5 entry point, RelapSolver.cpp
extern "C" { void finteract_(int, double); }   // InteractiveVariable.h

Fortran passes arguments by reference by default. finteract receives its two arguments by value, so the subroutine declares them with the value attribute (src/Relap/finteract.for):

fortran
       subroutine finteract(nid, fvalue)
       integer, value :: nid
       real*8, value :: fvalue
       intrva(nid) = fvalue

Subroutines that need a name which does not depend on the compiler use bind(C, name='...'), as the restart buffer interface does (src/Relap/restart_membuf.f90): ads_membuf_get_, ads_membuf_set_, ads_set_membuf_mode_.

Calling C++ from Fortran ​

RELAP5 calls the engine from dtstep.f with plain call statements:

fortran
          call ActTE
          call R5ParUpdate
          cncount = ncount
          call Interfc

The C++ side defines the matching functions with C linkage, in lower case and with a trailing underscore (src/ADSIDAC/ADSIDAC.cpp). Each one forwards to the active SolverCallback:

Fortran callC++ functionCallback
call Interfcinterfc_()onEvaluationPoint()
call ActTEactte_()onTimestepAction()
call SecInisecini_()onScenarioStart()
call ActOnceactonce_()onFirstInit()
call Initiate_branchinginitiate_branching_()onBranchingPoint()
call SetRNodesetrnode_()none (empty function)

dtstep.f calls Interfc when the hydrodynamic time has advanced by at least 0.5 s since the last call ((timehy - timesno) .ge. 0.5), so the engine evaluates the crew and the branching rules about every 0.5 s of simulated time, whatever time step RELAP5 uses internally.

ADS-IDAC Engine Documentation