Access to the runtime API

Access to the runtime API

The runtime API (Application Programming Interface) allows direct access to ongoing simulations within SPRING. Through this interface, external applications or coupled models can access state variables during the simulation and dynamically change input variables. This is particularly important for co-simulations (CoSim) and coupled process models.

 

CoSim coupling

The CoSim functionality allows an interface coupling between SPRING and external simulation systems. In this process, the data exchange occurs synchronously with the runtime of the simulation.

 

Typical applications are:

  • Coupling with thermal or energy-technical models

  • Integration of control and regulation algorithms

  • Exchange with external process simulations (e.g., heat exchangers, networks)

 

Access via wrapper

Access to the runtime API is provided through a proprietary wrapper that encapsulates the communication between SPRING and external applications.

 

The wrapper takes over in the process:

  • Initialization of the connection to the simulation

  • Access to model variables (e.g., pressure, temperature, concentration)

  • Setting boundary conditions or control variables

  • Synchronization of the time steps

 

The specific implementation is project-specific and typically takes place via:

  • C/C++ Interfaces

  • Script-based connections

  • or specialized integration modules

 

Instructions for creating a CoSim interface can be found here.

CoSim Interface Instructions

# SPRING Co-Simulation

 

Simple library to allow two simulation programs to exchange data every timestep through writing to text files. (Future implementations might also use a binary format, pipes, or shared memory.) Special care is taken to only read fully written files once they are available. (Further tests are needed if this approach is reliable.)

 

## Examples

Inside the folder `examples/` are two different programs to show how to implement the co-simulation using this library. The first program is called `echo` and will just take the input from the other program and write it back. The second program is called `fibonacci`. The Fibonacci numbers start with 1 and 1. Following numbers are the sum of the previous two numbers. This leads to the sequence 1,1,2,3,5,8,...

If paired with `echo` both programs will show the Fibonacci numbers.

 

### Compiling the examples

Both the library and examples need just a C++ compiler and CMake. The examples are set up to automatically include and compile the library. Just open the CMake project inside your favourite IDE and compile it. There are no CMake options to configure.

 

### Running the examples

The CoSim library will set default filenames for input and output. However, these will be the same by default in both libraries and thus it will not work. Instead, each program will need to set the filenames for input and output to match the ones of the other software. The examples will use command line parameters for this. Real simulation software might rather use configuration files instead. Additionally, one software needs to be the first to write and start waiting for CoSim input at a later timestep. To run the examples use the following on the command line (starting from the same folder because of filenames!):

 

```

> ./echo -n 10 -in fibonacci.cosim -out echo.cosim

```

 

```

> ./fibonacci -n 10 -skip 1 -in echo.cosim -out fibonacci.cosim

```

 

Notice how the input and output filenames are reversed for the two programs. `fibonacci` is set up to do one initialization step (`-skip 1`) before actually starting communication. (On Windows type `.\echo` or `echo.exe` instead.).

 

## Adding the CoSim library to your project

For CMake based projects just `include(...)` the `CMakeLists.txt` from the `cosim` folder into your own `CMakeLists.txt` (see `examples/`) and add `cosim` to the target link libraries of your own program, e.g.

```

...

include(../cosim/CMakeLists.txt)

add_executable(MyApp main.cpp)

target_link_libraries(MyApp cosim)

...

```

This will automatically compile the library with your project.

For other projects or Makefiles you can compile the CMake project for the library manually. For your project you need to link to the compiled library and add `cosim`'s `include/` folder to your include paths. (With the CMake approach discussed above the include path will be added automatically.) The CoSim library requires at least C++17.

 

In order to actually use the library just

```

#include <cosim.h>

```

in your C++ source file. Everything will be inside the namespace `cosim`.

 

### Configuring CMake options

* `MSVC_USE_STATIC_RUNTIME`: `OFF` by default. Set to `ON` to use static MSVC runtime libraries

instead of regular dynamic runtime (MSVC only).

* `USE_DELTA_H_DEVELOP_DIR`: `OFF` by default. Normally, at delta h libraries go to `C:\develop\lib64`

(if CoSim is checked out to `C:\develop\cosim`) for other programs to find.

* `BC_TYPE_CONFIG_HEADER`: Not set by default. Set to `SPRING` to automatically add the SPRING

boundary conditions stored at `config/config_SPRING.h`. This will copy the file to `include/cosim/config.h`

(see next chapter for configuring boundary conditions). You might want to add different options

to `BC_TYPE_CONFIG_HEADER` and check in other pre-configured header files into the GIT repository.

 

## Configuring a list of boundary conditions

By default, the CoSim library will just use user-defined numbers to identify different boundary conditions. For the sake of readability it is advisable to provide your own `enum`s instead. For this, create a file `include/cosim/config.h` (right inside the directory of the CoSim library). A short description is contained in `data_info.h`. For SPRING the config file might look like this:

```

// Content of cosim.h in folder include/cosim/.

// Provide include guards:

#ifndef COSIM_CONFIG_H

#define COSIM_CONFIG_H

 

// the define is required to let CoSim know about your enum

#define COSIM_DATA_TYPE_ENUM SPRING_KENN

 

// this can be an 'enum' or an 'enum class'

enum class SPRING_KENN

{

KNOT,

_1KON,

// ... more boundary condition types

};

 

#endif // COSIM_CONFIG_H

```

Using modern C++ the CoSim library automatically detects if the config file exists and includes it.

The data type of the boundary conditions can be accessed using `cosim::data_info::type_t`.

 

## Setting up a CoSim connection

The CoSim library does not provide any configuration file itself. This should be written in the common style of the simulation software and thus might differ. The configuration file should allow for setting the two filenames for input and output, the boundary conditions to be written and to be read, and the number of timesteps for initialization before starting the communication.

 

The main class for CoSim is `cosim::manager`. Setting up the co-simulation before the simulation time loop is quite simple (using the boundary conditions from the config file above as an example):

```

#include <cosim.h>

 

void someFunction(...)

{

...

std::string cosim_in = ...; // read from configuration file / command line parameter

std::string cosim_out = ...; // read from configuration file / command line parameter

...

cosim::manager cosim(cosim_in, cosim_out);

// add input boundary conditions (in order!) from configuration file

cosim::entity_nr_t node = 1; // some node/element number

cosim.addInDataInfo(node,SPRING_KENN::KNOT,"Inflow boundary condition");

node = 2;

cosim.addInDataInfo(node,SPRING_KENN::_1KON,"Fixed temperature");

...

// add output boundary conditions (also in order!) from configuration file

node = 42;

cosim.addOutDataInfo(node,SPRING_KENN::KNOT,"Outflow boundary condition");

...

}

```

The boundary conditions are read/written in exactly the order they are added to the `cosim::manager`. The last parameter is a textual description and not used by CoSim itself. It can be used to easily identify what a boundary condition is used for. Depending on the function used for writing the CoSim files the description is included; it might help with debugging some problems in communication.

 

## Communication inside the time loop

Every simulation software has some form of a loop to iterate over the timesteps. At least one simulation software participating in the co-simulation needs to be able to skip at least on timestep. Otherwise both simulations would wait to read from the other simulation at the beginning of the timestep. This initialization phase can be arbitrarily long and even both programs could have their own initialization phase. A good overview of how this can be handled can be seen in the two `examples/` provided with the library.

The common structure of the time loop could be the following:

```

#include <cosim>

 

void someFunction(...)

{

...

// initialize the CoSim manager

cosim::manager cosim(...);

...

for(...) // our time loop

{

if(isNotInitializingAnymore)

{

// read all external boundary conditions

cosim.read();

if(!cosim.isTerminated()) // only read boundary conditions if the other simulation is still running

{

// check if cosim.current_time matches our time

...

// adjust timestep to fit cosim.next_timestep

...

// read data

cosim::entity_nr_t node = 2;

... = cosim.data[SPRING_KENN::_1KON][node].value;

node = 1;

... = cosim.data[SPRING_KENN::KNOT][node].value;

...

}

}

// now run the normal timestep using the new boundary conditions

// or do substepping to match the timestep of the other simulation

...

if(isLastInitializationTimestepOrLater)

{

// prepare for the next timestep

cosim.prepareNextTimestep();

// write boundary conditions for the CoSim partner

cosim.write(currentTime,nextTimestepSize,values);

}

}

}

```

 

There are two ways to write the CoSim data for the other simulation program. The way shown here would work in the following way:

```

std::vector<double> values;

// add all values for the boundary conditions in order

values.push_back(-1000.0);

...

// call write

cosim.write(currentTime,nextTimestepSize,values);

```

When written in this way, CoSim will write a file with the entity number, the value, and the description from the `data_info` provided during the set-up of the `cosim::manager`.

Another way provides a little more flexibility:

```

// add all values for the boundary conditions in any order

cosim::entity_nr_t node = 42;

cosim.data[SPRING_KENN::KNOT][node] = cosim::data{node,-1000.0,"Description"};

...

// or do the following instead:

cosim.setValue(node, SPRING_KENN::KNOT, -1000.0, "Description");

...

// call write

cosim.write(currentTime,nextTimestepSize);

```

Notice that this call to `write()` only takes two parameters. You need to make sure to provide all values in `manager::data`. The previous call to `prepareNextTimestep()` will delete all values (`manager::data` is also used both for reading and for writing). If data is missing, CoSim will write `NaN` to the output file instead.

 

## Synchronizing timesteps

After reading boundary conditions by calling `read()` the current time and the next timestep size is set inside the `cosim::manager`. They can be accessed through the member variables `current_time` and `next_timestep`. The current time helps to check if both simulations are still in sync. Different strategies might be used for the next timestep. If the software allows for adaptive timesteps the minimum of both timestep predictions can be used. If the timestep of the other software is much larger, substepping could also be used. In any case, it is important that the next `current_time` is always the same for both simulations.

 

## Errors and warnings

The CoSim library does several checks when reading and writing. They mostly produce warnings and only rarely errors. Warnings occur if there are inconsistencies in the input or output files. An error is signaled if the file format when reading is incorrect.

By default warnings are printed to `std::cerr` and errors are printed as well, but additionally call `exit(-1)` to terminate the program. Every program can register their own error/warning handler instead by calling `manager::setErrorHandler()` or `manager::setWarningHandler()`, respectively. Both handlers need to have the function signature `void myHandler(const std::string &message)`.

Reasons to use your own error/warning handlers are to install your own logger or to throw an exception instead of terminating when an error occurs. As examples the default handlers can be found at the top of `include/cosim/manager.h`.

 

## File format

The CoSim file format starts with the following two lines:

```

TIME=x.xxxx

NEXT_TIMESTEP=x.xxxxx

```

`TIME` is the current simulation time in seconds and `NEXT_TIMESTEP` is the *timestep size* also in seconds. For debugging, the first two lines can be removed with a simple text editor. The following lines are in the CSV file format including a descriptive header line. The CSV portion of the file has 3 columns with the following order:

entity number, value, and description. Depending on programmer-provided information

the description column might be empty (but we'll still have a comma separator).

 

Once the simulation is finished, the CoSim file will just contain a single line with the word `END`. If the other simulation reads this, `isTerminated()` will return `true`. Note that calling `read()` will delete all current data and thus after reading `END` no data is available. This special file will be written automatically inside the destructor of `~manager()`. An additional loop will make sure that the other simulation can still write their CoSim file and no file will be left behind when both simulations have finished. This also means that the destructor will block until the other simulation has finished. (Simulations can also decide to destroy the CoSim `manager`and still continue the simulation standalone without any co-simulation.)