Developping the computational code

Complete the signature

The signature can be automatically generated through the OpenFLUID plugin for Eclipse, included in the SDK. However it is possible to write the signature "from scratch".

The signature has to be defined between the BEGIN_SIGNATURE_HOOK and END_SIGNATURE_HOOK macros.

Identification

The identification part of the signature must contain at least the ID of the simulation function. This ID will be used by the kernel to load functions. It is declared in the signature using the DECLARE_SIGNATURE_ID macro.
The others information that can be included for identification are:

Applications range

The informations about applications range is just indicative. it has no effects on coherence or computation. These information are :

Handled data

The data handled by the simulation functions are function data or spatially distributed data, can be read by the function, appended by the function, and/or modified by the function.
The function data are parameters passed to the simulation function, they usually are computation parameters. The distributed data are attached to spatial units, and can be simulation variables or units properties.

Read data must have rules associated when they are declared. These rules are :

Function data

Function data are attached to functions. The declaration macros take 3 arguments : the name of the data, the description of the data (not required), and the unit of the data (not required).

Input data

Input data are invariant information attached to units. Simulation functions declares handling input data on each unit class. The declaration macros take 4 arguments : the name of the data, the concerned unit class, the description of the data (not required), and the unit of the data (not required).

Simulation variables

Simulation variables are produced, read and modified by functions, and attached to distributed units. The declaration macros take 4 arguments : the name of the data, the concerned unit class, the description of the data (not required), and the unit of the data (not required).

To declare read variables :

To declare produced or modified variables :

These variables can be scalar (no dimension) or vector (1 dimension). When they are declared in the signature, the variables names suffixed by the [] symbol are vectors, the ones not suffixed are scalars ( myvariable is scalar and myothervariable[] is vector).

Distributed discrete events

If the simulation function uses distributed discrete events, they must be declared in the signature. The declaration macros take 1 arguments : the concerned unit class

To declare the use of discrete events:

Extra files

The simulation function can declare files that it loads. This can help users to furnish the files needed by the function, and also to the kernel to check the presence of the file if required.

To declare extra files:

Example

BEGIN_SIGNATURE_HOOK

  DECLARE_SIGNATURE_ID("water.surf-uz.runoff-infiltration.mseytoux");
  DECLARE_SIGNATURE_NAME("Morel-Seytoux production on surface units");
  DECLARE_SIGNATURE_DESCRIPTION("Production function computing infiltration and runoff at the surface of a unit 
                                 using the Morel-Seytoux method, based on the Green and Ampt method.");
  DECLARE_SIGNATURE_DOMAIN("hydrology");
  DECLARE_SIGNATURE_STATUS(openfluid::base::BETA);

  DECLARE_SIGNATURE_SDKVERSION;

  DECLARE_SIGNATURE_AUTHORNAME("Moussa R., Fabre J.-C.");
  DECLARE_SIGNATURE_AUTHOREMAIL("moussa@supagro.inra.fr, fabrejc@supagro.inra.fr");

  DECLARE_REQUIRED_VAR("water.atm-surf.H.rain","SU","rainfall height on SU","m");

  DECLARE_PRODUCED_VAR("water.surf.H.runoff","SU","runoff on the surface of the unit","m");
  DECLARE_PRODUCED_VAR("water.surf.H.infiltration","SU","infiltration through the surface of the unit","m");

  DECLARE_USED_PREVVAR("water.surf.Q.downstream-su","SU","output volume at the outlet of the upstream SUs","m3/s");

  DECLARE_REQUIRED_INPUTDATA("ks","SU","hydraulic conductivity when saturated","m/s");
  DECLARE_REQUIRED_INPUTDATA("thetares","SU","","m3/m3");
  DECLARE_REQUIRED_INPUTDATA("thetasat","SU","","m3/m3");
  DECLARE_REQUIRED_INPUTDATA("betaMS","SU","","");
  DECLARE_REQUIRED_INPUTDATA("hc","SU","","m");

  DECLARE_REQUIRED_INPUTDATA("thetaisurf","SU","","m3/m3");

  DECLARE_FUNCTION_PARAM("resstep","numerical resolution step for ponding time","");

END_SIGNATURE_HOOK

Write your code into the class methods

A simulation function defines a class, inherited from the openfluid::base::PluggableFunction class. The code have to be distributed into the different methods imposed by this PluggableFunction class. You can also develop other methods to structure your source code. To see how the source code is globally structured, see part Writing and empty simulation function.

Constructor and destructor

The constructor of the simulation function is called when the function is loaded. You may put here the initialization of you class attributes.
The destructor of the simulation function is called when the function is freed, at the end of the execution of the engine. You may put here instruction to free the memory you allocate for the needs of the simulation function (objects, pointed vars, ...).

Required methods to define

The class defining a simulation function must compound the following methods:

The initParams method should be used to retreive function parameters, read from the model.xml file (See Access to function parameters). Once read, the values should be stored into class attributes to be accessed by other methods.

The prepareData method should be used to do data pre-processing before the consistency checking.

The checkConsistency method is called during the global consistency checking phase. It should be used to add function own consistency checking.

The initializeRun method should be used to do data initialization, or to compute values that do not change during simulation.

The runStep method is called at each exchange time step. it should contain the computation code.

The finalizeRun method should be used to do post-processing after simulation. it is the last method ran.

Handling space

The spatial domain is represented by units of three different classes : openfluid::core::SurfaceUnit, openfluid::core::ReachSegment and openfluid::core::GroundwaterUnit. These classes are derivated from the openfluid::core::HydroObject super-class (unappropriate name, isn't it?). Each unit carries self information that can be exploited through accessor methods (see classes docs), and also properties and variables that can be exploited through special methods (see Access to function parameters and Access to input data).

The spatial domain can be accessed using in different ways: using the access method of the CoreRepository, or use the macros intended for handling spatial entities. The first way is for experimented functions developpers only, so only the second way will be developped here.

To parse all units of a specific class, you can use:

To parse a specific list of units of a specific class, you can use:


bool MyFunc::runStep(const openfluid::base::SimulationStatus* SimStatus)
{
  openfluid::core::Unit* SU;
  openfluid::core::Unit* UpSU;
  openfluid::core::UnitsPtrList_t* UpSUsList;

  DECLARE_UNITS_ORDERED_LOOP(1);
  DECLARE_UNITS_LIST_LOOP(25);

  BEGIN_UNITS_ORDERED_LOOP(1,"SU",SU)

    UpSUsList = SU->getFromUnits("SU");

    BEGIN_UNITS_LIST_LOOP(25,UpSUsList,UpSU)
      OPENFLUID_GetVariable(UpSU,"water.surf.Q.downstream-su",CurrentStep-1,&TmpValue);
    END_LOOP
    
  END_LOOP;
}

Handling time

The simulation time information is only available from the initializeRun , runStep , and finalizeRun methods. They are accessible through the openfluid::core::SimulationInfo and openfluid::core::SimulationStatus classes passed through methods parameters.
The information passed through these classes can be used to get the current time step, the value of the time step in second, the current real date and time, ...

bool MyFunc::runStep(const openfluid::base::SimulationStatus* SimStatus)
{
  int CurrentStep;
  openfluid::core::ScalarValue TmpValue;
  openfluid::core::Unit* SU;
  DECLARE_UNITS_ORDERED_LOOP(17);
  
  CurrentStep = SimStatus->getCurrentStep();  
  
  BEGIN_UNITS_ORDERED_LOOP(17,"SU",SU)
    OPENFLUID_GetVariable(SU,"the.requested.var",CurrentStep-1,&TmpValue);
  END_LOOP;      
}

The real time information is given through the openfluid::core::DateTime class.

Access to function parameters

Functions parameters are passed through the model.xml file. They can be accessed in the source code from the initParams method of the simulation function, unsing OPENFLUID_GetFunctionParameter . The requested parameter name must be the same as the one used in the model.xml file.

model.xml file:
<?xml version="1.0" standalone="yes"?>
<openfluid>
  <model>
   
    <function fileID="myfunction">
      <param name="myparam" value="2" />          
    </function>
    
  </model>
</openfluid>            

initParam method of the simulation function source code:
bool MyFunction::initParams(openfluid::core::FuncParamsMap_t Params)
{
  m_MyParam = 0; //default value
  OPENFLUID_GetFunctionParameter(Params,"myparam",&m_MyParam);

  return true;
}

To be reused in other part of the simulation function, the variable to store the value of function parameters should be declared as class variables. The parameters type can be string, double, integer, boolean, vector of string, vector of double, ... (see definitions of OPENFLUID_GetFunctionParameter to get more information about available types).

Access to variables

The values for the simulation variables are attached to the homogeneous units.

Types

Currently, the variables can be typed with one of the two defined types:

The scalar type is a double precision floating point type. The corresponding plain old type in C++ is double.

Access

The methods to access simulation variables exist in two flavours, for scalar and vector, depending on the type you pass for the value:

They can be accessed only from the runStep method.

bool MyFunction::runStep(const openfluid::base::SimulationStatus* SimStatus)
{
  int CurrentStep;
  openfluid::core::ScalarValue TmpValue;
  openfluid::core::Unit* SU;
  DECLARE_UNITS_ORDERED_LOOP(12);

  CurrentStep = SimStatus->getCurrentStep();

  BEGIN_UNITS_ORDERED_LOOP(12,"SU",SU)

    OPENFLUID_GetVariable(SU,"MyVar",CurrentStep,&TmpValue);
    TmpValue = TmpValue * 2;
    OPENFLUID_AppendVariable(SU,"MyVarX2",QOutput);

  END_LOOP

  return true;
}

Access to input data

In order to access to input data provided through standard input files, you can use the following methods. They are usable from initializeRun , runStep , and finalizeRun :

The name of the accessed input data must match the name given in the standard input files.

Access to discrete events

A discrete event is defined in the class openfluid::core::DistributedEvent. It is defined by a date and a list of key-value information that can be accessed by methods proposed by the openfluid::core::DistributedEvent class.
A collection of discrete events can be contained by an openfluid::core::EventCollection class.

To get a collection of events occuring during a period on a given unit, you should use the OPENFLUID_GetEvents method. It returns an openfluid::core::EventCollection that can be processed.
in order to process an event collection, you can parse it using specific macros: At each iteration, the current event can be processed.


Example of process of events occurring on the current time step:

bool MyFunction::runStep(const openfluid::base::SimulationStatus* SimStatus)
{
  openfluid::core::Unit* SU;
  openfluid::core::EventCollection EvColl;
  openfluid::core::DistributedEvent* Ev;
  std::list<openfluid::core::DistributedEvent* > *EvList;
  openfluid::core::DateTime BTime, ETime;
  DECLARE_EVENT_COLLECTION_LOOP;
  DECLARE_UNITS_ORDERED_LOOP(1);

  BTime = SimStatus->getCurrentTime();
  ETime = BTime + SimStatus->getTimeStep()-1;

  BEGIN_UNITS_ORDERED_LOOP(1,"SU",SU)

    EvColl.clear();

    OPENFLUID_GetEvents(SU,BTime,ETime,&EvColl);
    EvList = EvColl.getEventsList();

    BEGIN_EVENT_COLLECTION_LOOP(EvColl.getEventsList(),Ev)
      if (Ev->isInfoEquals("molecule","glyphosate"))
      {
        // process the event
      }
    END_LOOP;

  END_LOOP;

  return true;
}

Persistance of internal data

It may be interesting to keep the status of some internal variable from the a time step to the next one. In this case, the values of these variables must be stored as class attributes (they are persistant during the whole life of the simulation function). In order to ease the storage of distributed values, data structures are available to associate a unit ID to a value. These data structures exists for different types :

declaration of the ID-map structure in the .h file :

class MyFunction : public openfluid::base::PluggableFunction
{
  private:

    openfluid::core::IDScalarValueMap m_LastValue;

  public:
  
    // rest of the declaration of the class 

}

usage of the ID-map structure in the .cpp file :

bool MyFunction::runStep(const openfluid::base::SimulationStatus* SimStatus)
{
  int ID;
  openfluid::core::ScalarValue TmpValue;
  openfluid::core::Unit* SU;


  DECLARE_UNITS_ORDERED_LOOP(7);
  BEGIN_UNITS_ORDERED_LOOP(7,"SU",SU)

    ID = SU->getID();

    TmpValue = TmpValue + m_LastValue[ID]

    OPENFLUID_AppendVariable(SU,"MyVar",TmpValue);

    m_LastValue[ID] = TmpValue;

  END_LOOP

  return true;
}

Raise warnings and errors

In order to trace error and warnings during the run of a simulation, simulation functions car raise error and warning messages to inform the kernel that something wrong or critical had happened. An error stops the simulation the next time the kernel take the control, a warning does not stop the simulation. Error and warnings are reported in the simulation report (siminfo.out file). They both can be dated with the number of the time step when the warning or error occurs.

To raise a warning you can use OPENFLUID_RaiseWarning , to raise an error you can use OPENFLUID_RaiseError

As already said, an error stops the simulation the next time the kernel takes control of the simulation. It is strongly advised to force the control taking by the kernel by using a return false instruction justr after the raise of the error.

bool Myfunction::checkConsistency()
{
  openfluid::core::ScalarValue TmpValue;
  openfluid::core::Unit* SU;
  DECLARE_SU_ORDERED_LOOP(1);
  
  BEGIN_SU_ORDERED_LOOP(1,"SU",SU)

    OPENFLUID_GetInputData(SU,"MyVar",&TmpValue);
    
    if (TmpValue <= 0)
    {
      OPENFLUID_RaiseError("my.function","Wrong value for the MyProp distributed property on SU");
      return false;
    }    

  END_LOOP

  return true;
}

Access runtime environment

The runtime environment are informations about the context during execution of the simulation : input and output directories, options passed to the kernel at runtime (verbose/quiet mode, ...)...

They are accessible from simulation functions using the OPENFLUID_OPENFLUID_GetRunEnvironment method.

bool MyFunction::initializeRun(const openfluid::base::SimulationInfo* SimInfo)
{
  std::string InputDir;

  OPENFLUID_GetRunEnvironment("dir.input",&InputDir);

  // the current input directory is now available through the InputDir local variable 

  return true;
}

The keys for requesting runtime environment information are:

Available tools

The tools have been developped to help function developpers in the set up of data processing or numerical computation. They are available in the namespace openfluid::tools. To get more information on theses tools and how to you use it, your are invited to browse the header files (.h) provided with the SDK.

Calling Fortran source code

The C++ - Fortran interface is defined in the FortranCPP.h file, located in the tool part of the SDK. In order to execute Fortran code from a simulation function, this Fortran source code have to be wrapped into a subroutine that will be called from the C++ code of the simulation function. To help programmers to achieve this wrapping operation, the FortranCPP.h file defines macros. These macros allows calls of Fortran77 and Fortran90 source code. Youa re invited to browse the FortranCPP.h file to get more information about these macros.


Fortran source code (FSubr.f90):

subroutine displayvector(Fsize,vect)

implicit none

integer Fsize,ifrom
real*8 vect(Fsize)

write(*,*) 'size',Fsize
write(*,*) (Vect(i),i=1,Fsize)

return
end

Declaration block int the .cpp file, located just after the function signature (MyFunc.cpp):
BEGIN_EXTERN_FORTRAN
  EXTERN_FSUBROUTINE(toto)(FINT *Size, FREAL8 *Vect);
END_EXTERN_FORTRAN

Call of the fortran subroutine from the initializeRun method (MyFunc.cpp):
bool MyFunction::initializeRun(const openfluid::base::SimulationInfo* SimInfo)
{
  openfluid::core::VectorValue* MyVect;
  
  MyVect = new openfluid::core::VectorValue(15,9);
  int Size = MyVect->getSize();

  CALL_FSUBROUTINE(toto)(&Size,(MyVect->getData()));

  return true;
}


The compilation process of linked fortran code runs in 3 steps:

  1. separate compilation of Fortran source files (without linkage),
  2. compilation of the simulation function,
  3. full linkage of the object files (.o) into the simulation function plug-in.

Modified 'all' target in makefile for compiling and linking:
 all:
        gfortran -c $(GFLAGS) FSubr.f90
        $(CPP) -c $(SRCFILESROOT).cpp -o $(OBJPATH)/$(SRCFILESROOT).o -fPIC $(WXFLAGS) $(OPENFLUIDFLAGS)
        $(CPP) $(OBJPATH)/$(SRCFILESROOT).o FSubr.o $(WXLIBS) $(OPENFLUIDLIBS) -lgfortran -o $(BINPATH)/$(BINFILE).$(PLUGEXT) -shared $(LDFLAGS)

Generated using Doxygen 1.5.8
Creative Commons License Creative Commons By-NC-ND license