FhSim  3.1.0
Marine systems simulation
Loading...
Searching...
No Matches
OBSERVERS section

The <OBSERVERS> section declares what should happen with simulation output: writing to file, logging to console, streaming over the network, rendering 3D graphics, or loading a custom observer plugin.

Overview

<OBSERVERS>
<FileOutput outputFile="results.csv" Select="objects:all"/>
<ConsoleLog/>
<Network endpoint="tcp://*:5555"/>
<Visualization frameRate="30"/>
<Plugin library="my_plugin.so" config="key=value"/>
</OBSERVERS>

Behaviour when <OBSERVERS> is absent: a FileOutput observer is created automatically if an output file path is configured (via CLI -o or XML). A Visualization observer is created for FhVis when the provider is an integrator.

Behaviour when <OBSERVERS> is present: only the listed observers are created. An empty <OBSERVERS/> tag runs with no observers at all (useful for benchmarking).


<FileOutput> — write CSV results

Writes simulation results to a semicolon-separated CSV file.

Attribute Required Default Description
outputFile No modeloutput.csv Output file path. Relative paths resolve from the working directory. CLI --output-file overrides this on the first <FileOutput> only; a later <FileOutput> keeps its own file and a warning is logged.
TOutput No (every step) Output schedule specification — controls when rows are written (see below).
Select No "objects:all" Filter expression selecting which signals to write (see below).

Output scheduling (TOutput)

By default, FileOutput writes one row per integrator step. The TOutput attribute lets you control which time points are written, using interpolation to produce evenly-spaced output independent of the integrator's internal steps.

Supported formats:

Format Description
"start:delta:end" Output from start with interval delta until end.
"start::end" Output every integrator step from start until end.
":delta:end" Output every delta from simulation start until end.
":delta:" Output every delta from simulation start until simulation end.
"start:delta" Output from start with interval delta until simulation end.
"start:end" Output only at start and end times.
"t1,t2,t3,..." Output at specific explicit times (interpolated).
"start:delta:Inf" Inf in the end position means "until simulation end".

Examples:

<!-- Output every 0.1 seconds from t=0 to t=60 -->
<FileOutput outputFile="results.csv" TOutput="0:0.1:60"/>
<!-- Output every 0.5 seconds for the entire simulation -->
<FileOutput outputFile="results.csv" TOutput=":0.5:"/>
<!-- Output at specific times -->
<FileOutput outputFile="results.csv" TOutput="0,1,5,10,30,60"/>
<!-- Output every integrator step between t=10 and t=20 -->
<FileOutput outputFile="results.csv" TOutput="10::20"/>

Inf (any capitalisation) is accepted only in the end position of a colon form — "0:0.1:Inf" and "10:Inf" are valid, "Inf:0.1:60" is not. It is read as the simulation end time, so it is equivalent to leaving the end field empty.

Note
Requested times outside [TStart, TEnd] are dropped. A schedule whose times all fall outside the simulated interval produces no rows.

When TOutput is omitted or empty, every integrator step is written.


Select filter expressions

The Select attribute controls which SimObject signals appear in the output file:

Expression Description
"objects:all" All ports and states of all SimObjects.
"objects:ports" Output ports of all SimObjects.
"objects:states" States of all SimObjects.
"object.spring1:all" All ports and states of the SimObject named spring1.
"object.spring1:ports" Output ports of the SimObject named spring1.
"object.spring1:states" States of the SimObject named spring1.
"object.spring1:port.ForceA" Only the ForceA output port of spring1.
"object.spring1:state.x" Only the x state of spring1.
"union(A, B)" Union of two expressions.
"intersect(A, B)" Intersection of two expressions.
"minus(A, B)" Expression A minus expression B (set difference).

The object selector uses the grammar object.<name>:<atom> or object.<name>:<atom>.<signal>, where <name> is the SimObject instance name (not its type) and <atom> is one of all, ports/port, or states/state. The plural form objects:<atom> selects across every SimObject and takes no name.

Warning
The grammar also has an objects.<Type>:<atom> form, but it cannot be used from <FileOutput Select>. The observer evaluates the expression against the output metadata, which carries no SimObject type, so objects.Mass:all parses without complaint and matches nothing — leaving a CSV with only a Time column. Select by instance name.

An unknown object name or signal name behaves the same way: it selects nothing rather than reporting an error. A malformed expression — a missing bracket, an unknown atom — is an error.

‍Signal names carry no scalar-index suffix. In a port.<signal> or state.<signal> selector, use the bare signal tag (e.g. Pos, ForceA) — not the _0/_1 scalar-index suffix that appears in the CSV header. A vector signal is selected once by its base name and expands to all its scalar columns automatically.

Examples:

<!-- Write all ports of spring1 AND all states of the SimObject named mass1 -->
<FileOutput outputFile="results.csv"
Select="union(object.spring1:ports, object.mass1:states)"/>
<!-- Write everything EXCEPT ports from the SimObject named mass1 -->
<FileOutput outputFile="results.csv"
Select="minus(objects:all, object.mass1:ports)"/>

CSV output format

The output file uses semicolon-separated values with this layout:

Time;Object1;Object1;Object2;...
;signal_0;signal_1;signal_0;...
1.000000e+00;2.500000e-01;0.000000e+00;-1.200000e+00;...
  • Row 1: Object names, one per scalar column. Time in the first column.
  • Row 2: Signal tag names with scalar index suffix (signalName_0, signalName_1, …). First column is empty.
  • Data rows: One row per simulation step, values in scientific notation.

Column order: ODE state columns first, then port output columns.

This format is also read back by the <Replay> state provider.


<ConsoleLog> — log to console

Logs step index, simulation time, and timing statistics each step.

<ConsoleLog/>

No attributes. Silently skipped when running without a console (e.g. from a DLL context).


<Network> — publish over ZeroMQ

Serialises each simulation snapshot and broadcasts it over a ZeroMQ PUB socket.

Attribute Required Default Description
endpoint Yes — ZeroMQ bind endpoint, e.g. tcp://*:5555.

When used as an input provider (a <Network> element inside <SIMULATION>, as in the subscriber example below), it takes these attributes. The observer form (inside <OBSERVERS>) reads only endpoint.

Attribute Required Default Description
endpoint Yes — The publisher's data endpoint, e.g. tcp://localhost:5555.
timeout No 5000 Connection timeout in milliseconds: how long the metadata request waits for the publisher.
pollPeriod No 100 Milliseconds. After this silent period a step ends as a success with no new data. Must be greater than 0.
dataTimeout No 5000 Milliseconds. When no data has arrived for this long, the run fails with a message naming the endpoint. A packet resets the time. Must be greater than 0.

A metadata handshake socket (REP) is automatically created on TCP port+1, allowing late-joining subscribers to request metadata before subscribing.

Example: endpoint="tcp://*:5555" → data on :5555, metadata on :5556.

Publisher / subscriber example

Publisher (simulation process):

<OBSERVERS>
<FileOutput outputFile="results.csv"/>
<Network endpoint="tcp://*:5555"/>
</OBSERVERS>

Subscriber (separate process):

<SIMULATION>
<Network endpoint="tcp://192.168.1.10:5555" timeout="10000"/>
</SIMULATION>
<OBSERVERS>
<FileOutput outputFile="received.csv"/>
</OBSERVERS>

<Visualization> — 3D rendering (FhVis only)

Renders the simulation in a 3D window. Only available in FhVis builds (compiled with FH_VISUALIZATION).

Attribute Required Default Description
frameRate No 30 Target render frame rate in Hz. CLI --frame-rate overrides this.
sceneManager No "DefaultSceneManager" Ogre3D scene manager type name.
<Visualization frameRate="60"/>

See 3D scene navigation for camera controls and keyboard shortcuts.


<Plugin> — custom observer plugin

Loads a shared library (.so / .dll) at runtime and delegates all observer calls to it.

Attribute Required Default Description
library Yes — Shared library filename. Relative paths resolve against observerPlugins/ in the working directory.
config No "" Opaque string forwarded to CreateObserver(config) in the plugin.
<Plugin library="my_plugin.so" config='{"key":"value"}'/>

Writing a plugin

A plugin is a shared library built against the installed fhsim package only. Include <fhsim/IStateObserverPlugin.h> and either implement the fhsim::IStateObserver interface or derive from fhsim::IStateObserverBase, which runs your work on its own thread. Link the package target (FhSim::fhsim in Conan). fhsim is a static library, so no host symbol is needed. Build the plugin against the same fhsim version as the host application: IStateObserverBase is compiled into the plugin, and its layout may change between versions. Export two C functions:

#include <fhsim/IStateObserverBase.h>
#include <fhsim/IStateObserverPlugin.h>
class MyObserver : public fhsim::IStateObserverBase
{
public:
MyObserver() : fhsim::IStateObserverBase(nullptr) {}
// The worker uses this class's members, so stop it before they are destroyed.
~MyObserver() override { StopWorker(); }
protected:
void Process(const fhsim::SimSnapshot& snapshot) override { /* worker thread */ }
};
extern "C" FH_PLUGIN_EXPORT fhsim::IStateObserver* CreateObserver(const char* config)
{
return new MyObserver();
}
extern "C" FH_PLUGIN_EXPORT void DestroyObserver(fhsim::IStateObserver* obs)
{
delete obs;
}

The contract:

  • CreateObserver(const char* config) is required. config is the config attribute, never null and valid only during the call. It must not return nullptr (the run then fails to start with std::runtime_error).
  • DestroyObserver(IStateObserver*) is optional but recommended. The host calls it instead of delete. Export it when the plugin links a different C++ runtime than the host (common on Windows). Without it the host calls delete through the virtual destructor.
  • FH_PLUGIN_EXPORT (defined in IStateObserverPlugin.h) is __declspec(dllexport) on Windows and empty elsewhere. A Windows DLL exports nothing by default, and the host looks both functions up by name.
  • A class derived from IStateObserverBase calls StopWorker() first in its own destructor when its worker uses its members (files, sockets, buffers). The base destructor runs too late.
  • Threads: CreateObserver runs once on the thread that builds the run. OnSimulationStart, OnUpdate, OnSimulationEnd, GetRequiredBufferDepth and WantsStop run on the simulation thread; OnUpdate runs every step and must only enqueue. With IStateObserverBase, Process, OnStart and OnEnd run on the worker thread. DestroyObserver runs on the thread that destroys the run, after OnSimulationEnd.

Build as a CMake MODULE library and place the resulting .so/.dll in the observerPlugins/ directory.

A plugin that reads numbers from XML attributes with fhsim::InputReader (<fhsim/InputReader.h>) must expect fhsim::InputValueError (<fhsim/InputValueError.h>, a std::runtime_error). The numeric getters throw it when an attribute is present but its text is not a valid value ("3abc", a word, an empty text, nan, a fraction where a whole number is wanted). A missing attribute is not an InputValueError. The getters that take a default value store it, log an info line and return false. The getters without a default value (GetDoubleAttribute, GetIntAttribute, GetStringAttribute and the array and vector forms) call the logger's ReportError, which is fatal and aborts the simulation (InputReader.cpp:231, :356, :395, :436, :476, :516). GetAttribute and GetAttributeSize only log an info line.

Catch the exception to recover, or let it end the run: what() names the attribute, the element path, the file and the text, and Attribute(), Text(), ElementPath() and File() return each part.

To turn SimMetadata::outputColumns into flat column indices, header labels (signal_0, signal_1, …) or (object, signal) lookups, construct a fhsim::OutputView (#include "fhsim/OutputView.h") in OnSimulationStart instead of re-implementing the arithmetic. It also evaluates a Select expression when given one. The example plugin in examples/plugin_observer_file/ shows the pattern.


Results HTML viewer

results.html plots the CSV written by <FileOutput> in a browser: pick the X signal and any number of Y signals, zoom, pan and hover. It is installed next to the executable — playpen/bin/results.html in the playpen layout, or share/fhsim/results.html in a plain install — so in the directory a run writes its results.csv into.

By hand. Open results.html and drop one or more results files onto it (or use the file picker). Several files can be loaded at once and are drawn in the same axes: the Y list is the union of the signal names across them, and every ticked file contributes a trace per selected signal, so the same selection compares two runs directly. Runs sampled on different time grids need no alignment. Selections can be stored as named presets, which are kept in the browser and hold signal names only, so a preset made on one run applies to the next.

From the command line. fhsim_plot writes a copy of the viewer with the data baked in and opens it in the default browser:

fhsim_plot results.csv # -> results_plot.html, opened
fhsim_plot runA/results.csv runB/results.csv # both runs in one plot
fhsim_plot results.csv -o /tmp/run.html --no-open

A diagnostics JSON file is dispatched to the diagnostics viewer instead, so the same command covers both:

fhsim_plot diagnostics.json

The data is baked into the page because a browser refuses to let a page opened from file:// read a file named in its own URL. Opening results.html itself and dropping the file on it is therefore equivalent, and the baked page needs no network and no server.

Note
Both viewers load Plotly from https://cdn.plot.ly. On a machine without internet access, put plotly-2.35.2.min.js next to the viewer and it is used instead.

Path resolution

Every relative path written inside an input file is resolved against the working directory, and FhSim chooses that directory itself at startup — before any XML path is resolved. This is the single rule behind outputFile, <Plugin library>, <Replay csvFile>, JsonOutput, InitialStatesFile / FinalStatesFile, <FromFile FileName> and LibName. Absolute paths are always used as-is.

How the working directory is chosen

At startup FhSim looks for a marker file named FHSIM_DIR_IDENTIFIER and moves into the first place that has one:

  1. The directory the process was launched from. If the marker is there, nothing changes.
  2. The directory containing the FhSim executable.
  3. The directory named by the FHSIM_DIR environment variable.

If none of them has the marker, the launch directory is kept and a message says so. The move is silent in normal output: when a marker sits next to the executable, running FhSim from an arbitrary directory writes its results next to the binary instead of where you started. Run with -c 4 (the highest console verbosity) to see which directories were searched and which one was chosen.

Note
Two paths are resolved before the move and are therefore relative to the directory you launched from: the input XML file named on the command line, and an output file given with -o / --output-file. Everything named inside the XML is relative to the directory chosen above. When in doubt, use absolute paths, or run FhSim from the directory that holds the marker file.
<working-dir>/ ← the directory holding FHSIM_DIR_IDENTIFIER
FHSIM_DIR_IDENTIFIER
model.xml
results.csv ← output files
SimObjectLibraries/
libfhsim_base.so ← SimObject libraries (LibName)
observerPlugins/
my_plugin.so ← observer plugin libraries

See also