Zugriff auf die Laufzeit-API

Zugriff auf die Laufzeit-API

Die Laufzeit-API (Application Programming Interface) ermöglicht den direkten Zugriff auf laufende Simulationen innerhalb von SPRING. Über diese Schnittstelle können externe Anwendungen oder gekoppelte Modelle während der Simulation auf Zustandsgrößen zugreifen sowie Eingangsgrößen dynamisch verändern. Dies ist insbesondere für Co-Simulationen (CoSim) und gekoppelte Prozessmodelle von zentraler Bedeutung.

 

CoSim-Kopplung

Die CoSim-Funktionalität erlaubt eine Schnittstellenkopplung zwischen SPRING und externen Simulationssystemen. Dabei erfolgt der Datenaustausch synchron zur Laufzeit der Simulation.

 

Typische Anwendungsfälle sind:

  • Kopplung mit thermischen oder energietechnischen Modellen

  • Integration von Regelungs- und Steuerungsalgorithmen

  • Austausch mit externen Prozesssimulationen (z. B. Wärmetauscher, Netze)

 

Zugriff über Wrapper

Der Zugriff auf die Laufzeit-API erfolgt über einen proprietären Wrapper, der die Kommunikation zwischen SPRING und externen Anwendungen kapselt.

 

Der Wrapper übernimmt dabei:

  • Initialisierung der Verbindung zur Simulation

  • Zugriff auf Modellvariablen (z. B. Druck, Temperatur, Konzentration)

  • Setzen von Randbedingungen oder Steuergrößen

  • Synchronisation der Zeitschritte

 

Die konkrete Implementierung ist projektspezifisch und erfolgt typischerweise über:

  • C/C++ Schnittstellen

  • Skriptbasierte Anbindungen

  • oder spezialisierte Integrationsmodule

 

Eine Anleitung zur Erstellung einer CoSim-Schnittstelle findet sich hier.

Anleitung CoSim-Schnittstelle

# SPRING-Co-Simulation

Einfache Bibliothek, die es zwei Simulationsprogrammen ermöglicht, bei jedem Zeitschritt Daten auszutauschen, indem diese in Textdateien geschrieben werden.

(Zukünftige Implementierungen könnten auch ein binäres Format, Pipes oder Shared Memory nutzen.) Besondere Sorgfalt wird darauf verwendet, nur vollständig geschriebene Dateien zu lesen, sobald sie verfügbar sind. (Weitere Tests sind erforderlich, wenn überprüft werden soll, ob dieser Ansatz zuverlässig ist.)

 

## Beispiele

Im Ordner 'examples/' befinden sich zwei verschiedene Programme, die zeigen, wie die Co-Simulation mit dieser Bibliothek umgesetzt werden kann. Das erste Programm heißt 'echo' und wird einfach die Eingabe vom anderen Programm nehmen und zurückschreiben. Das zweite Programm heißt 'fibonacci'. Die Fibonacci-Zahlen beginnen mit 1 und 1. Die folgenden Zahlen sind die Summe der beiden vorherigen Zahlen. Dies führt zu der Folge 1,1,2,3,5,8,...

In Kombination mit `echo` zeigen beide Programme die Fibonacci-Zahlen an.

 

### Kompilieren der Beispiele

Sowohl die Bibliothek als auch die Beispiele benötigen lediglich einen C++-Compiler und CMake. Die Beispiele sind so eingerichtet, dass sie die Bibliothek automatisch einbinden und kompilieren. Öffnen Sie einfach das CMake-Projekt in Ihrer bevorzugten IDE und kompilieren Sie es. Es müssen keine CMake-Optionen konfiguriert werden.

 

### Ausführen der Beispiele

Die CoSim-Bibliothek legt Standarddateinamen für die Ein- und Ausgabe fest. Diese sind in beiden Bibliotheken standardmäßig identisch, sodass dies nicht funktionieren wird.

Stattdessen muss jedes Programm die Dateinamen für Eingabe und Ausgabe so festlegen, dass sie mit denen der anderen Software zusammenpassen.

Die Beispiele verwenden hierfür Befehlszeilenparameter. Echte Simulationssoftware würde stattdessen eher Konfigurationsdateien verwenden. Darüber hinaus muss eine Software die erste sein, die schreibt und beginnt, auf CoSim-Eingaben zu einem späteren Zeitschritt zu warten.

Um die Beispiele auszuführen, geben Sie Folgendes in der Befehlszeile ein (aus demselben Ordner, wegen der Dateinamen!):

```

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

```

 

```

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

```

Beachten Sie, dass die Namen der Eingabe- und Ausgabedateien bei den beiden Programmen vertauscht sind. `fibonacci`ist so eingerichtet, dass es einen Initialisierungsschritt (`-skip 1`) durchführt, bevor die Kommunikation tatsächlich beginnt.

(Unter Windows geben Sie stattdessen `.\echo` oder `echo.exe` ein.)

 

## Hinzufügen der CoSim-Bibliothek zu Ihrem Projekt

Bei CMake-basierten Projekten fügen Sie einfach die Datei `CMakeLists.txt` aus dem Ordner `cosim` per `include(...)` in Ihre eigene `CMakeLists.txt` ein (siehe `examples/`) und fügen Sie `cosim` zu den Ziel-Link-Bibliotheken Ihres eigenen Programms hinzu, z. B.

```

...

include(../cosim/CMakeLists.txt)

add_executable(MyApp main.cpp)

target_link_libraries(MyApp cosim)

...

```

Dadurch wird die Bibliothek automatisch mit Ihrem Projekt kompiliert.

 

Für andere Projekte oder Makefiles können Sie das CMake-Projekt für die Bibliothek manuell kompilieren. Für Ihr Projekt müssen Sie einen Link zur kompilierten Bibliothek erstellen und den Ordner `include/` von `cosim` zu Ihren Include-Pfaden hinzufügen. (Bei dem oben beschriebenen CMake-Ansatz wird der Include-Pfad automatisch hinzugefügt.) Die CoSim-Bibliothek erfordert mindestens C++17.

Um die Bibliothek tatsächlich zu nutzen, müssen Sie lediglich

```

#include <cosim.h>

```

in Ihre C++-Quelldatei einfügen. Alles befindet sich im Namespace `cosim`.

 

### Konfigurieren der CMake-Optionen

* `MSVC_USE_STATIC_RUNTIME`: Standardmäßig auf `OFF` gesetzt. Setzen Sie diese Option auf `ON`, um statische MSVC-Laufzeitbibliotheken anstelle der regulären dynamischen Laufzeitbibliotheken zu verwenden (nur MSVC).

* `USE_DELTA_H_DEVELOP_DIR`: Standardmäßig auf `OFF` gesetzt. Normalerweise werden die Delta-H-Bibliotheken unter `C:\develop\lib64`abgelegt (wenn CoSim unter `C:\develop\cosim` ausgecheckt ist), damit andere Programme sie finden können.

* `BC_TYPE_CONFIG_HEADER`: Standardmäßig nicht gesetzt. Setze auf `SPRING`, um die Randbedingungen hinzuzufügen, die in `config/config_SPRING.h` gespeichert sind. Dadurch wird die Datei nach `include/cosim/config.h` kopiert (siehe nächstes Kapitel zur Konfiguration von Randbedingungen). Möglicherweise möchten Sie weitere Optionen zu `BC_TYPE_CONFIG_HEADER` hinzufügen und andere vorkonfigurierte Header-Dateien in das GIT-Repository einchecken.

 

## Konfigurieren einer Liste von Randbedingungen

Standardmäßig verwendet die CoSim-Bibliothek lediglich benutzerdefinierte Nummern, um verschiedene Randbedingungen zu identifizieren. Aus Gründen der Lesbarkeit empfiehlt es sich, stattdessen eigene `enum`s zu definieren. Erstellen Sie dazu eine Datei `include/cosim/config.h` (direkt im Verzeichnis der CoSim-Bibliothek). Eine kurze Beschreibung finden Sie in `data_info.h`. Für SPRING könnte die Konfigurationsdatei wie folgt aussehen:

```

// Inhalt von cosim.h im Ordner include/cosim/.

// Include-Guards bereitstellen:

#ifndef COSIM_CONFIG_H

#define COSIM_CONFIG_H

 

// Die Definition ist erforderlich, damit CoSim Ihre Enumeration erkennt

#define COSIM_DATA_TYPE_ENUM SPRING_KENN

 

// Dies kann eine 'enum' oder eine 'enum class' sein

enum class SPRING_KENN

{

KNOT,

_1KON,

// ... weitere Arten von Randbedingungen

};

 

#endif // COSIM_CONFIG_H

```

Mit modernem C++ erkennt die CoSim-Bibliothek automatisch, ob die Konfigurationsdatei vorhanden ist

und bindet sie ein.

 

Auf den Datentyp der Randbedingungen kann über `cosim::data_info::type_t` zugegriffen werden.

 

## Einrichten einer CoSim-Verbindung

Die CoSim-Bibliothek stellt selbst keine Konfigurationsdatei bereit. Diese sollte im üblichen Stil der Simulationssoftware erstellt werden und kann daher variieren. Die Konfigurationsdatei sollte das Festlegen der beiden Dateinamen für Eingabe und Ausgabe, der zu schreibenden und zu lesenden Randbedingungen sowie der Anzahl der Zeitschritte für die Initialisierung vor Beginn der Kommunikation ermöglichen.

Die Hauptklasse für CoSim ist `cosim::manager`. Das Einrichten der Co-Simulation vor der Simulationszeitschleife ist recht einfach (am Beispiel der Randbedingungen aus der obigen Konfigurationsdatei):

 

```

#include <cosim.h>

 

void someFunction(...)

{

...

std::string cosim_in = ...; // aus Konfigurationsdatei / Befehlszeilenparameter lesen

std::string cosim_out = ...; // aus Konfigurationsdatei / Befehlszeilenparameter lesen

...

cosim::manager cosim(cosim_in, cosim_out);

// Eingangsrandbedingungen (in der richtigen Reihenfolge!) aus der Konfigurationsdatei hinzufügen

cosim::entity_nr_t node = 1; // eine Knoten-/Elementnummer

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

node = 2;

cosim.addInDataInfo(node,SPRING_KENN::_1KON,"Feste Temperatur");

...

// Ausgabe-Randbedingungen (ebenfalls in der Reihenfolge!) aus der Konfigurationsdatei hinzufügen

node = 42;

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

...

}

```

Die Randbedingungen werden genau in der Reihenfolge gelesen/geschrieben, in der sie dem `cosim::manager` hinzugefügt werden.

Der letzte Parameter ist eine textuelle Beschreibung und wird von CoSim selbst nicht verwendet. Er kann dazu dienen, einfach zu erkennen, wofür eine Randbedingung verwendet wird. Je nach der für das Schreiben der CoSim-Dateien verwendeten Funktion wird die Beschreibung eingeschlossen; sie könnte bei der Fehlersuche bei einigen Kommunikationsproblemen helfen.

 

## Kommunikation innerhalb der Zeitschleife

Jede Simulationssoftware verfügt über eine Art Schleife, um die Zeitschritte zu durchlaufen. Mindestens eine Simulationssoftware, die an der Co-Simulation teilnimmt, muss in der Lage sein, mindestens einen Zeitschritt zu überspringen. Andernfalls würden beide Simulationen am Anfang des Zeitschritts darauf warten, von der anderen Simulation zu lesen. Diese Initialisierungsphase kann beliebig lang sein, und sogar beide Programme könnten ihre eigene Initialisierungsphase haben. Eine gute Übersicht darüber, wie dies gehandhabt werden kann, ist in den beiden mit der Bibliothek bereitgestellten `examples/` zu sehen.

Die allgemeine Struktur der Zeitschleife könnte wie folgt aussehen:

```

#include <cosim>

 

void someFunction(...)

{

...

// Initialisierung des CoSim-Managers

cosim::manager cosim(...);

...

for(...) // unsere Zeitschleife

{

if(isNotInitializingAnymore)

{

// alle externen Randbedingungen lesen

cosim.read();

if(!cosim.isTerminated()) // Randbedingungen nur lesen, wenn die andere Simulation noch läuft

{

// prüfen, ob cosim.current_time mit unserer Zeit übereinstimmt

...

// Zeit Schritt an cosim.next_timestep anpassen

...

// Daten lesen

cosim::entity_nr_t node = 2;

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

node = 1;

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

...

}

}

// nun den normalen Zeitschritt unter Verwendung der neuen Randbedingungen ausführen

// oder führe eine Unterteilung durch, um den Zeitschritt an den der anderen Simulation anzupassen

...

if(isLastInitializationTimestepOrLater)

{

// Bereite den nächsten Zeitschritt vor

cosim.prepareNextTimestep();

// Randbedingungen für den CoSim-Partner schreiben

cosim.write(currentTime,nextTimestepSize,values);

}

}

}

```

Es gibt zwei Möglichkeiten, die CoSim-Daten für das andere Simulationsprogramm zu schreiben. Die hier gezeigte Methode würde wie folgt funktionieren:

```

std::vector<double> values;

// alle Werte für die Randbedingungen der Reihe nach hinzufügen

values.push_back(-1000.0);

...

// write aufrufen

cosim.write(currentTime,nextTimestepSize,values);

```

Bei dieser Schreibweise erstellt CoSim eine Datei mit der Entitätsnummer, dem Wert und die Beschreibung aus den `data_info`, die bei der Einrichtung von `cosim::manager` angegeben wurden.

 

Eine andere Methode bietet etwas mehr Flexibilität:

```

// Alle Werte für die Randbedingungen in beliebiger Reihenfolge hinzufügen

cosim::entity_nr_t node = 42;

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

...

// oder alternativ Folgendes tun:

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

...

// write aufrufen

cosim.write(currentTime,nextTimestepSize);

```

Beachten Sie, dass dieser Aufruf von `write()` nur zwei Parameter benötigt. Sie müssen sicherstellen, dass alle Werte in `manager::data` vorhanden sind. Der vorherige Aufruf von `prepareNextTimestep()`löscht alle Werte (`manager::data` wird sowohl zum Lesen als auch zum Schreiben verwendet). Fehlen Daten, schreibt CoSim stattdessen `NaN` in die Ausgabedatei.

 

## Synchronisierung der Zeitschritte

Nach dem Einlesen der Randbedingungen durch den Aufruf von `read()` werden die aktuelle Zeit und die nächste Schrittgröße in `cosim::manager` festgelegt. Der Zugriff erfolgt über die Mitgliedsvariablen `current_time` und `next_timestep`. Die aktuelle Zeit hilft bei der Überprüfung, ob beide Simulationen noch synchron sind. Für den nächsten Zeitschritt könnten unterschiedliche Strategien verwendet werden.. Wenn die Software adaptive Zeitschritte zulässt, kann das Minimum beider Schrittzeitvorhersagen verwendet werden. Ist der Schrittzeit der anderen Software deutlich größer, könnte auch ein Substepping verwendet werden. In jedem Fall ist es wichtig, dass die nächste `current_time` für beide Simulationen immer gleich ist.

 

## Fehler und Warnungen

Die CoSim-Bibliothek führt beim Lesen und Schreiben mehrere Prüfungen durch. Diese führen meist zu Warnungen und nur selten zu Fehlern. Warnungen treten auf, wenn Unstimmigkeiten in den Eingabe- oder Ausgabedateien auftreten. Ein Fehler wird gemeldet, wenn das Dateiformat beim Lesen falsch ist.

Standardmäßig werden Warnungen an `std::cerr` und Fehler ebenfalls ausgegeben, aber bei Fehlern wird zusätzlich `exit(-1)` aufgerufen, um das Programm zu beenden.

Jedes Programm kann stattdessen seinen eigenen Fehler-/Warnungshandler registrieren, indem es `manager::setErrorHandler()` bzw. `manager::setWarningHandler()` aufruft. Beide Handler müssen die Signatur `void myHandler(const std::string &message)` haben.

 

Gründe für die Verwendung eigener Fehler-/Warnungshandler sind die Integration eines eigenen Loggers oder um beim Auftreten eines Fehlers eine Ausnahme auszulösen, anstatt das Programm zu beenden. Als Beispiele sind die Standard-Handler oben in `include/cosim/manager.h` zu finden.

 

## Dateiformat

Das CoSim-Dateiformat beginnt mit den folgenden zwei Zeilen:

```

TIME=x.xxxx

NEXT_TIMESTEP=x.xxxxx

```

`TIME` ist die aktuelle Simulationszeit in Sekunden und `NEXT_TIMESTEP` ist die *Schrittweite, ebenfalls in Sekunden. Zur Fehlersuche können die ersten beiden Zeilen mit einem einfachen Texteditor entfernt werden. Die folgenden Zeilen liegen im CSV-Dateiformat vor und enthalten eine beschreibende Kopfzeile. Der CSV-Teil der Datei hat 3 Spalten in folgender Reihenfolge:

Entitätsnummer, Wert und Beschreibung. Je nach den vom Programmierer bereitgestellten Informationen kann die Beschreibungsspalte leer sein (es wird jedoch weiterhin ein Komma als Trennzeichen verwendet).

Sobald die Simulation beendet ist, enthält die CoSim-Datei nur noch eine einzige Zeile mit dem Wort `END`. Wenn die andere Simulation dies liest, gibt sie `isTerminated()` `true` zurück.

Beachten Sie, dass der Aufruf von `read()` alle aktuellen Daten löscht und somit nach dem Lesen von `END` keine Daten mehr verfügbar sind. Diese spezielle Datei wird automatisch im Destruktor von `~manager()` geschrieben.

Eine zusätzliche Schleife wird sicherstellen, dass die andere Simulation weiterhin ihre CoSim-Datei schreiben kann und keine Datei zurückbleibt, wenn beide Simulationen abgeschlossen sind. Das bedeutet auch, dass der Destruktor blockiert, bis die andere Simulation beendet ist. (Simulationen können auch beschließen, den CoSim-`manager` und die Simulation dennoch eigenständig ohne jegliche Co-Simulation fortzusetzen.)