FhSim  3.1.0
Marine systems simulation
Loading...
Searching...
No Matches
Migrating to FhSim 3.0

This guide covers all breaking changes when upgrading from FhSim 2.x (main branch) to FhSim 3.0 (new_structure branch). Most downstream consumers are SimObject plugin libraries — the guide is structured with this audience first.

An automated migration script is available: see Using the migration script below.

‍v3 API stability. FhSim 3.0 is the current major version. The v3 XML input schema and the SimObject C++ API documented here are the stable target you should build against — the churn described in this guide is the one-time 2.x→3.0 break, not an ongoing state. Any future breaking change will be recorded in this guide with a Before/After mapping (per the project's deprecation policy) and announced on the issue tracker; retired names and attributes are also enforced by the documentation checks. There is no separate deprecation schedule beyond that.


Table of Contents

  1. Overview of changes
  2. For SimObject plugin authors *(most common case)*
  3. For engine embedders
  4. XML input file changes
  5. Dependency changes
  6. Using the migration script

Overview of changes

FhSim 3.0 introduces:

  • Target split: The monolithic FhSim::fhsim CMake target is split into purpose-built components. SimObject plugin authors now link the lighter FhSim::simobject target instead of the full engine.
  • Class renames: The Hungarian C prefix is removed from core utility classes (e.g., CPrintDuringExec → PrintDuringExec).
  • Header renames: Matching the class renames (e.g., CPrintDuringExec.h → PrintDuringExec.h).
  • XML schema update: <INTEGRATION> is renamed to <SIMULATION> (backward compatible with deprecation warning). The <Engine> and <Sundials> child elements are replaced by a new unified <Integrator Method="..."> element with structured sub-elements.
  • Dependency modernization: TinyXML replaced by libxml2, Sundials upgraded from 5.4 to 7.5, Eigen 5 added.
  • SimObject ODE callback update: OdeFcn is now const and no longer takes isMajorTimeStep. Once-per-accepted-step side effects should move to AcceptedStep(double T, const double* X).

For SimObject plugin authors

This section covers the typical consumer: a downstream library that defines SimObject subclasses and is compiled as a shared plugin (MODULE).

Step 1: Update CMake target linkage

Replace FhSim::fhsim with FhSim::simobject in your CMakeLists.txt:

Before (v2):

find_package(FhSim CONFIG REQUIRED)
target_link_libraries(my_simobjects PRIVATE FhSim::fhsim)

After (v3):

find_package(FhSim CONFIG REQUIRED)
target_link_libraries(my_simobjects PRIVATE FhSim::simobject)

The FhSim::simobject target provides everything a SimObject plugin needs: the SimObject base class, port definitions, ISimObjectCreator, utilities, and transitive dependencies (FhLib, libxml2, Eigen, and optionally Ogre).

Step 2: Update Conan requirements

In your conanfile.py or conanfile.txt, bump the FhSim version:

Before:

self.requires("fhsim/2.x.x@sintef/stable")
# or version-range form:
self.requires("fhsim/[^2.12.0]@sintef/stable")

After:

self.requires("fhsim/[^3.1.0]@sintef/stable")

‍Note: the first release on the 3.x line is 3.1.0, not 3.0.0 — fhsim_base/3.0.0 is an already-released reference on the 2.x line. Use the caret range [^3.1.0] rather than an exact pin.

Ensure your Conan profile has compiler.cppstd=20.

Step 3: Rename header includes

The following headers have been renamed (the C prefix is removed):

Old #include New #include
#include "CPrintDuringExec.h" #include <fhsim/PrintDuringExec.h>
#include "CFhSimMgr.h" #include <fhsim/FhSimMgr.h>
#include "CExceptions.h" #include <fhsim/Exceptions.h>
#include "CAttributePath.h" #include <fhsim/AttributePath.h>
#include "CInputReader.h" #include <fhsim/InputReader.h>

File names that are unchanged (no rename needed). Most now live in a sub-folder, so check the include path: <fhsim/simobject/SimObjectInclude.h>, <fhsim/simobject/SimObject.h>, <fhsim/simobject/ISimObjectCreator.h>, <fhsim/simobject/ICommonComputation.h>, <fhsim/simobject/ISignalPort.h>, <fhsim/simobject/KalmanObject.h>, <fhsim/simobject/ExternalLink.h>, <fhsim/simobject/PortDefs.h>, <fhsim/simobject/PortStructDefs.h>, <fhsim/NFhSim.h>, <fhsim/ISimObjectLogger.h>.

‍Note: dllLib.h was renamed to DllLib.h. If your SimObject DLL entry point file includes it, update to #include <fhsim/DllLib.h>.

Step 4: Rename class usages

Apply these find-and-replace operations across your C++ source files:

Old class name New class name
CPrintDuringExec PrintDuringExec
CFhSimMgr FhSimMgr
CExceptions Exceptions
CAttributePath AttributePath
CInputReader InputReader

The string key "CPrintDuringExec" is not renamed by the script. The key is now fhsim::kSimMgrKeyPrinter (value "PrintDuringExec", <fhsim/NFhSim.h>). Read it with creator->GetSharedResource<PrintDuringExec>(fhsim::kSimMgrKeyPrinter).

Classes that are unchanged (no action needed): SimObject, ExternalLink, KalmanObject, ISimObjectCreator, ICommonComputation, ISignalPort, SerialCommonComputation. (IStateUpdater was removed later, see below.)

Step 5a: Rename m_SimObjectName member (if accessed directly)

The protected member variable that stores the object's registered name was renamed in the SimObject base class:

Old New
m_SimObjectName m_simObjectName

This only affects code that accesses this member directly by name. Most code uses the public GetName() accessor, which is unchanged.

Step 5b: Update headers that use unqualified string

v3 removed the using std::string; declaration that v2 had at file scope in ISimObjectCreator.h. Any of your own headers that relied on this transitive using declaration will now fail to compile with ‘'string’ was not declared`.

Fix your headers by qualifying the type explicitly:

// Old (relied on ISimObjectCreator.h injecting 'using std::string;')
MyObject(string name, ISimObjectCreator* creator);
// New
MyObject(std::string name, ISimObjectCreator* creator);

Fix your **.cpp implementation files** by adding a using declaration after your includes, or by qualifying at usage sites:

#include "MyObject.h"
using std::string; // add this

Test stubs and mock ISimObjectCreator classes hit this most often — local Creator subclasses defined inside _Test.cpp files often override GetStringParam, GetDoubleParamArray, AddInport, AddOutportIndexed with bare string parameters. Find all occurrences with:

grep -rnP '(?<!::)\bstring\b' src/ tests/ --include='*.h' --include='*.cpp'

Then qualify each one as std::string.

Step 6: Update test code and test targets (if applicable)

The v2 test tools (fhSimTestTools/TestSetup.h, testTools::SetUpTest, testTools::RmsComparison) are not in v3. The v3 test library is <fhsim/testtools/FhsimTest.h>. See Test Tools Library for the full API.

CMake link target:

Before:

target_link_libraries(my_tests PRIVATE FhSim::fhsim-testtools)

After:

target_link_libraries(my_tests PRIVATE FhSim::fhsim_test)

Note the change from hyphen (-) to underscore (_).

Test code:

v2 v3
#include <fhSimTestTools/TestSetup.h> #include <fhsim/testtools/FhsimTest.h>
testTools::SetUpTest(...) fill a fhsim::test::TestSpec (ID, simTime, maxRunTime, runtimeConfig) and call fhsim::test::RunTest(spec)
testTools::RmsComparison(...) result.CompareRegression(spec)
<ID>_exp.txt (expected values) <ID>.ref (limits) and <ID>_ref.csv (expected output)

CompareRegression reads two files per test ID, in <inPath>/<group>/: <ID>.ref holds the limits (cpu_ref, wall_max, output_rms_max), and <ID>_ref.csv holds the expected output. The RMS tolerance of the v2 comparison is now output_rms_max in the .ref file. Write both files from a run you have checked: set spec.referenceModeOverride = fhsim::test::ReferenceMode::CreateIfMissing once, then commit the files and go back to the default ReadOnly. See Recommended workflow for the file format and the reference modes.

**GetTestRuntimeConfig() is yours to define.** FhSim does not provide it. The test executable builds one fhsim::test::TestRuntimeConfig in its main (test folder from argv, log levels) and returns it from a function that the tests call. FhSim's own tests do this in tests/core/fhSim/Run_test.cpp and TestRuntimeConfigAccess.cpp. See Runtime configuration (TestRuntimeConfig).

Registering the tests with ctest: find_package(FhSim) also loads FhTests.cmake, which provides fh_add_gtest_tests. It adds the ctest entries for a gtest executable. LABELS is required and takes exactly one type from unit, integration, smoke, regression, validation or perf:

fh_add_gtest_tests(my_tests LABELS unit)

See the header of tests/cmake/FhTests.cmake (in the fhsim source) for the other arguments, the test tiers and the timeouts.

Step 7: Link FhSim::fhsim_vis and update visualization includes (if applicable)

If your library compiles source files that include visualization headers (FhCamera.h, FhVisualization.h, etc.), you must explicitly link the FhSim::fhsim_vis component, which provides these headers in v3.

In the CMakeLists.txt that builds your SimObject library:

if(FHSIM_FH_VISUALIZATION)
target_link_libraries(${TARGET} PUBLIC FhSim::fhsim_vis)
endif()
Warning
**FhSim::fhsim_vis is a static library, and it requires FhSim::fhsim** (conanfile.py, component fhsim_vis). A plugin (MODULE) that links it takes its own copy of the library and of the engine code it pulls in, with its own static data. A static object that exists once in the host (a singleton) then exists twice, once in the host and once in your plugin. FhSim's own fhsim_vis_dll module does not link fhsim_vis for this reason (src/visual/dll/module/CMakeLists.txt). The classes of the <fhsim/visual/renderer/...> headers (FhCamera, FhVisualization, Hud) are compiled into FhSim::fhsim, and fhsim_vis holds the visualization observers and FhVisDll. Link FhSim::fhsim_vis only into an application that hosts the visualization, not into a plugin that only uses the objects the host gives it. Reach those objects through the shared resources (see below), not through a singleton of your own.

FHSIM_FH_VISUALIZATION is a CMake cache variable set by fhsim-variables.cmake (included automatically when find_package(FhSim) is called) when the fhsim package was built with with_visualization=True.

Include form for visualization headers:

The FhSim::fhsim_vis component registers include/ as its include root. Headers must therefore be included with the full fhsim/ prefix:

// CORRECT — include with the fhsim/ prefix:
#include <fhsim/visual/renderer/FhCamera.h> // ✓

Visualization class and shared-resource renames:

Old New
#include <CFhCamera.h> #include <fhsim/visual/renderer/FhCamera.h>
CFhCamera* cam FhCamera* cam
(CFhCamera*)creator->GetSharedResource("CFhCamera") creator->GetSharedResource<FhCamera>(fhsim::kSimMgrKeyFhCamera) (key "FhCamera", <fhsim/NFhSim.h>; typed, ADR 0013)
(CFhVis*)creator->GetSharedResource("CFhVis") creator->GetSharedResource<FhVisualization>(fhsim::kSimMgrKeyFhVisualization), see below
CInputManager, <CInputManager.h> Removed in 3.4.0, with OIS: read input with SDL2, see below
CFhCameraController, <CFhCameraController.h> Removed in 3.4.0: FhCameraControllerSDL (<fhsim/visual/ui/FhCameraControllerSDL.h>)

String literals such as "CFhCamera" and "CFhVis" are not renamed by the migration script; use the fhsim::kSimMgrKey* constants of <fhsim/NFhSim.h>.

**The host's visualization object (kSimMgrKeyFhVisualization):** the key holds an FhVisualization under FhVis and fhVisDll, and an FhVisualizationSDL under FhSimUI (<fhsim/NFhSim.h> has the table). Read it with the type of the host you run under, in RenderInit, and handle nullptr:

#include <fhsim/visual/renderer/FhVisualization.h>
void MyObject::RenderInit(Ogre::Root* root, ISimObjectCreator* creator)
{
m_vis = creator->GetSharedResource<FhVisualization>(fhsim::kSimMgrKeyFhVisualization);
// nullptr under FhSim or a no-visualization build; under FhSimUI (FhVisualizationSDL)
// also nullptr, and the read with the wrong type is reported as an error
}

Keyboard, mouse and joystick input: InputManager is removed (3.4.0). FhSim 3.0 to 3.3 had InputManager (<fhsim/visual/renderer/InputManager.h>, the renamed CInputManager), an OIS wrapper that FhVis's window created. FhSim no longer uses OIS: FhVis and FhSimUI both open SDL2 windows, initialise SDL's video and joystick (game controller) subsystems and pump SDL's events every frame. A SimObject that read a joystick or the keyboard through InputManager reads it from SDL2 instead:

  • require sdl/2.32.10 in its recipe and link SDL2::SDL2 (no transitive SDL2 comes with FhSim);
  • open the device in RenderInit (SDL_NumJoysticks, SDL_JoystickOpen, or SDL_GameControllerOpen for a game controller) and read it in RenderUpdate (SDL_JoystickGetAxis, SDL_JoystickGetButton, SDL_JoystickGetHat); for the keyboard, SDL_GetKeyboardState;
  • close it in RenderRelease.

Input works only under a host with an SDL window (FhVis, FhSimUI); fhVisDll's embedded panel gets no input.

Complete before/after example

Before (v2 SimObject source):

#include "CPrintDuringExec.h"
#include "CExceptions.h"
#include "SimObjectInclude.h"
class MyObject : public SimObject {
public:
MyObject(std::string name, ISimObjectCreator* creator);
void OdeFcn(const double t, const double* x,
double* xdot, const bool isMajor) override;
};

After (v3 SimObject source):

#include <fhsim/PrintDuringExec.h>
#include <fhsim/Exceptions.h>
#include <fhsim/simobject/SimObjectInclude.h>
class MyObject : public SimObject {
public:
MyObject(std::string name, ISimObjectCreator* creator);
void OdeFcn(const double t, const double* x,
double* xdot) const override;
void AcceptedStep(const double t, const double* x) override;
};

Additional SimObject API migration notes

  • Logic previously guarded by if (isMajorTimeStep) inside OdeFcn(...) should move to AcceptedStep(double T, const double* X).
  • PreOdeFcn(T, X) is the pre-step hook. It cannot write states: IStateUpdater is removed. Put a state rule (a limit, a normalisation) in OdeFcn as a derivative saturation or a correction term.
  • Port functions stay non-const. PortFunction and IndexedPortFunction are pointers to non-const SimObject member functions (<fhsim/simobject/PortDefs.h>), but OdeFcn is const. A const OdeFcn therefore cannot call your own output-port function. Move the computation into a const helper that both the port function and OdeFcn call, or into a common computation (CommonComputation is const, see below). Use const_cast only as a last resort.
  • Output is written at TStart, before the first step. The first row of the result file is evaluated from the initial state with the output-port functions, before any integrator step (IntegratorStateProvider::Advance, first call). A port that returns a value which OdeFcn caches in a member can then return NaN or an uninitialised value in the first row. Initialise the member in the constructor or in InitialConditionSetup, or compute the port value from the stateVec it is given.
  • ICommonComputation::ComputeFunction(...) is now const. If your implementation updates cached internals, use mutable members.
  • The CommonComputation callback typedef now expects a const SimObject member function.

The "IntegratorOptions" shared resource is gone

IntegratorOptionsCommon and the kSimMgrKeyIntegratorOptions ("IntegratorOptions") registry key were removed with the unified IntegratorConfig. A SimObject that asked the simulation manager for the integrator options now reads fhsim::IntegratorSetup under fhsim::kSimMgrKeyIntegratorSetup:

#include <fhsim/FhSimMgr.h>
#include <fhsim/IntegratorSetup.h>
#include <fhsim/NFhSim.h>
void MySimObject::FinalSetup(double, const double* const, ISimObjectCreator* const creator)
{
auto* mgr = creator->GetSharedResource<FhSimMgr>("FhSimMgr");
auto* setup = static_cast<fhsim::IntegratorSetup*>(
mgr->GetPtr(fhsim::kSimMgrKeyIntegratorSetup));
const double tEnd = setup->tEnd;
if (setup->method == "Euler_i") { /* … */ }
}
Removed (IntegratorOptionsCommon) Replacement (fhsim::IntegratorSetup)
GetStartTime() tStart
GetEndTime() tEnd
GetStepSize() step (the requested fixed step; 0 when the method steps adaptively)
GetMethod() method

method is the canonical v3 method name, so a comparison against a v2 name has to be updated too — "Euler1" becomes "Euler_i" and "Heun" becomes "Heun_i". See the method-name table under `<Engine>` / `<Sundials>` → `<Integrator Method="...">`.

Do not reach for kSimMgrKeyIntegrator in a setup hook. The integrator is built from the finished model, so it is registered only after every SimObject's Init(), FinalSetup() and InitialConditionSetup() have run; the pointer is null in all three and dereferencing it crashes. IFhIntegrator (now public, <fhsim/engine/IFhIntegrator.h>) is for reading the running integrator — current time, step size, diagnostics — from OdeFcn/AcceptedStep onwards.

Migration checklist:

  • [ ] All OdeFcn overrides updated to new signature (const, no isMajorTimeStep)
  • [ ] Major-step side effects moved to AcceptedStep(...)
  • [ ] ICommonComputation callbacks updated to const
  • [ ] "IntegratorOptions" lookups replaced by fhsim::kSimMgrKeyIntegratorSetup
  • [ ] Build and tests pass

For engine embedders

If your project embeds the full simulation engine (constructing FhSim objects directly), you still link FhSim::fhsim:

find_package(FhSim CONFIG REQUIRED)
target_link_libraries(my_app PRIVATE FhSim::fhsim)

The FhSim::fhsim target now depends on FhSim::simobject, so you get everything. Additional steps for engine embedders:

Additional class renames

Old class name New class name
CFhSim FhSim
CFhRtSim *(no replacement class — see CFhRtSim is gone: real time is now a pacing mode)*
CFhSimLicenceManager FhSimLicenceManager
CFhIntegrator CFhIntegrator *(unchanged — engine-internal)*

Additional header renames

Old #include New #include
#include "CFhSim.h" #include <fhsim/FhSim.h>
#include "CFhRtSim.h" *(remove — see CFhRtSim is gone: real time is now a pacing mode)*

CFhRtSim is gone: real time is now a pacing mode

There is no real-time class in v3, and no header to include. A v2 project that constructed a CFhRtSim to get a wall-clock-paced run does not get a renamed class — real time became a property of the run instead.

Two things replaced it:

  • In a scenario, set the simulationSpeed attribute on the <SIMULATION> element (1.0 = real time, 2.0 = twice real time, 0 = as fast as possible). This is the only route for a headless FhSim run. On the command line, FhVis accepts -s / --simulation-speed and FhSimUI accepts --rtf / --rt; FhSim has no pacing flag. See Real-time pacing.
  • In embedding code, the pacing lives in a decorator around the state provider rather than in the engine facade: fhsim::RealTimeStateProvider (src/engine/io/RealTimeStateProvider.h) wraps any IStateProvider and sleeps the remaining wall time after each step. If the model cannot keep up it warns and does not sleep — the same behaviour CFhRtSim::Simulate() had. Build it the way SimulationManagerFactory does and hand it to a fhsim::SimulationManager.

Note that this is a behavioural move as well as a rename: pacing is applied around the step loop, so it composes with any provider (live integration, <Replay>, <Network>) instead of being a separate simulator subclass.

Removed FhSim lifecycle and result methods

fhsim::SimulationManager (src/engine/io/SimulationManager.h) is now the one owner of the simulation lifecycle, and the loop methods that duplicated it on the engine facade are gone. FhSim::Step() remains: it advances exactly one integrator step.

Removed Replacement
FhSim::Integrate() Build a SimulationManager over an IntegratorStateProvider and call Run() (see src/apps/fhExe/main.cpp), or loop FhSim::Step() while it returns simRes_Success.
FhSim::StepToTFinal() Same as above.
FhSim::GetOutput(XOut, TOut) Register an observer (FileOutputObserver, or fhsim::test::CapturingObserver in tests) — the integrator no longer accumulates a state history.
FhSim::GetNumOutput() Same as above.
FhSim::PrintStateValues() The <Diagnostics><Settings><LogStates/></Settings></Diagnostics> diagnostic.
FhVisSDL::IntegrateVis() SimulationManager::Run() with a visualization observer registered (see src/apps/fhSimUI/FhSimUIMain.cpp).

IFhIntegrator lost the matching virtuals — StepToTFinal(), GetOutputCopy(), GetNumOutput() and PrintStateValues(). Custom integrators must drop those overrides; Step(), the time-span accessors, the state accessors, LogConfiguration() and GetDiagnostics() are unchanged.

New visualization target

If your application uses the FhSim visualization layer (SDL/Ogre rendering), link the new FhSim::fhsim_vis target:

target_link_libraries(my_vis_app PRIVATE FhSim::fhsim FhSim::fhsim_vis)

XML input file changes

<INTEGRATION> → <SIMULATION>

The <INTEGRATION> XML element has been renamed to <SIMULATION>. The old name is still accepted with a deprecation warning but will be removed in a future release.

<Engine> / <Sundials> → <Integrator Method="...">

The <Engine> and <Sundials> child elements inside <SIMULATION> are removed and replaced by a single unified <Integrator Method="..."> element. <Replay> and <Network> are unchanged.

Method names

Old (<Engine IntegratorMethod="N">) New (<Integrator Method="...">)
IntegratorMethod="0" (Euler1) Method="Euler_i"
IntegratorMethod="1" (StableSolver) Method="StableSolver_i"
IntegratorMethod="2" (RKCK45) Method="RK45_i"
IntegratorMethod="3" (RKDOPRI54) Method="DOPRI54_i"
IntegratorMethod="4" (Heun) Method="Heun_i"
IntegratorMethod="5" (RKF45) Method="RKF45_i"
IntegratorMethod="6" (BS23) Method="BS23_i"
IntegratorMethod="12" (Euler1imp) Method="BackwardEuler_i"
Sundials BDF (cvode_method="BDF") Method="BDF"
Sundials Adams (cvode_method="Adams") Method="Adams"
ARKode DIRK Method="DIRK"
ARKode ERK Method="ERK"

‍**RKCK45 changed coefficients.** In fhsim 2, RKCK45 (IntegratorMethod="2") ran the Runge-Kutta-Fehlberg coefficients, despite its name, and propagated the 4th-order solution. In fhsim 3, RK45_i runs the Cash-Karp coefficients and propagates the 5th-order solution. Results change at the level of the local error tolerance. In non-smooth models (contacts, impacts) where StepMax is longer than the event, they can change much more. To reproduce fhsim 2 results, use Method="RKF45_i", or set a StepMax that resolves the event.

Legacy Engine method names (RKCK45, Euler1imp, Euler1) are accepted as aliases (RKCK45 maps to RK45_i, Cash-Karp) with a deprecation warning. Use the canonical _i-suffixed names for Engine methods and plain names for Sundials methods.

Timing

Simulation start and end times move to a <Timing> sibling element:

<SIMULATION>
<Timing TStart="0.0" TEnd="100.0"/>
<Integrator Method="RK45_i">
<StepControl Step="0.01"/>
</Integrator>
</SIMULATION>

Step control and tolerances

Step size and tolerance settings move to a <StepControl> child of <Integrator>:

Old attribute (on <Engine> or <Sundials>) New (<StepControl> attribute)
dt / stepsize / Stepsize / Dt Step
abstol / AbsTol AbsTol
reltol / RelTol RelTol
HMax StepMax
HMin StepMin
(Sundials) initial step Step0
(Sundials) max steps MaxSteps

Other <tt><Engine></tt> attributes

v2 <Engine> attribute v3
NumCores <Integrator NumCores>. It takes "perSimObject", "perCpuCore" or a whole number of 1 or more.
InitialStatesFile, FinalStatesFile Stay on <Integrator> with the same names.
TOutput <OBSERVERS><FileOutput TOutput="..."/></OBSERVERS>. The syntax changed, see below. <Integrator TOutput> is an error.
FileOutput <OBSERVERS><FileOutput Select="..."/></OBSERVERS>. The v2 value was the selection of signals (for example objects:ports), not a file name. The file name is outputFile (default modeloutput.csv) or -o on the command line.
LogStates No v3 attribute. <Integrator> ignores it with a warning ("Unknown `<Integrator>` attribute ... ignored"). If you used it to see state values, write them to the result file with <FileOutput Select="objects:states"/>, or enable the LogStates diagnostic (see the diagnostics cookbook).
UseRSSNormInsteadOfInfNorm No v3 replacement. <Integrator> ignores it with a warning. No XML setting selects the root-sum-of-squares error norm, so the step-size controller always uses the maximum norm. A model that relied on the RSS norm may take other steps. Check its results, and adjust AbsTol and RelTol if needed.

The output schedule. v2 TOutput was "TStart, schedule, TEnd", for example "0, 0:0.2:100, 100". v3 <FileOutput TOutput> reads every plain number in a comma list as an output time, so a pasted v2 string gives extra rows (at TStart and TEnd). Drop the first and the last number. TStart and TEnd now live in <Timing>:

<!-- v2 -->
<Engine IntegratorMethod="2" TOutput="0, 0:0.2:100, 100" FileOutput="objects:ports"/>
<!-- v3 -->
<SIMULATION>
<Timing TStart="0" TEnd="100"/>
<Integrator Method="RK45_i"/>
</SIMULATION>
<OBSERVERS>
<FileOutput TOutput="0:0.2:100" Select="objects:ports"/>
</OBSERVERS>

An empty field takes the simulation start or end, so ":0.2:" also means every 0.2 s from TStart to TEnd. See Output scheduling (TOutput) for all forms.

Warning
With an <OBSERVERS> block, only the listed observers are created. FhVis opens a 3D view without a block, but with one it needs <Visualization/> in the list too.

**Select names one object at a time.** A signal can be named only for a single object: object.<name>:state.<Signal> works, but objects:state.<Signal> and objects.<Type>:state.<Signal> fail with "a signal can only be named for a single object". Use objects:states for all states, or list the objects with union(...). See OBSERVERS section.

Linear solver and Jacobian

Old attribute New element
lin_type="SPGMR" <LinearSolver Type="SPGMR"/>
jac_type="dense" / jtype="dense" <Jacobian Type="dense"/>
<SundialsExtra linear_solver="component"/> (early v3) <LinearSolver Type="SPGMR" Preconditioner="component"/>
<SundialsExtra jacobian_policy="hybrid"/> (early v3) <Jacobian Policy="hybrid"/>
<LinearSolver Type="component" GsSweeps="N" GsSymmetric="true"/> (early v3) <LinearSolver Type="component"/> — the two attributes tuned a fallback no model could reach and are now rejected

The two <SundialsExtra> pseudo-keys selected model-side settings rather than SUNDIALS API calls; they are now rejected with an error that names the replacement attribute.

Sundials-specific passthrough (<SundialsExtra>)

For advanced Sundials settings not covered by the schema, add a <SundialsExtra> child element with arbitrary attribute-value pairs that are forwarded to the Sundials API:

<Integrator Method="DIRK">
<StepControl AbsTol="1e-8" RelTol="1e-6"/>
<LinearSolver Type="DENSE"/>
<Jacobian Type="dense"/>
<SundialsExtra ARKodeSetTableNum="BACKWARD_EULER_1_1" ARKodeSetFixedStep="0.005"/>
</Integrator>

Complete before/after example

Before (v2 Engine):

<INTEGRATION>
<Engine IntegratorMethod="2" t_start="0" t_end="100"
stepsize="0.01" HMax="0.05"/>
</INTEGRATION>

After (v3):

<SIMULATION>
<Timing TStart="0.0" TEnd="100.0"/>
<Integrator Method="RK45_i">
<StepControl Step="0.01" StepMax="0.05"/>
</Integrator>
</SIMULATION>

Before (v2 Sundials BDF):

<INTEGRATION>
<Sundials t_start="0" t_end="100" dt="0.1"
reltol="1e-6" abstol="1e-6"
sundials_method="cvodes" cvode_method="BDF"
jtype="dense" lin_type="SPGMR"/>
</INTEGRATION>

After (v3):

<SIMULATION>
<Timing TStart="0.0" TEnd="100.0"/>
<Integrator Method="BDF">
<StepControl Step="0.1" AbsTol="1e-6" RelTol="1e-6"/>
<LinearSolver Type="SPGMR"/>
<Jacobian Type="dense"/>
</Integrator>
</SIMULATION>

New <OBSERVERS> section

FhSim 3.0 introduces an observer/provider pipeline. Observers can be configured via a new optional <OBSERVERS> XML section:

<OBSERVERS>
<FileOutput outputFile="results.csv"/>
<ConsoleLog/>
</OBSERVERS>

See OBSERVERS section for full documentation.

Validation rules and migration pitfalls

Condition Behaviour
Only <SIMULATION> present ✅ Preferred — no warning
Only <INTEGRATION> present ⚠️ Accepted with deprecation warning
Both <INTEGRATION> and <SIMULATION> present ❌ Fatal error
Neither present ❌ Fatal error
<Integrator Method="..."> missing for Engine/Sundials simulations ❌ Fatal error
Unknown Method value ❌ Fatal error
Legacy method alias (RKCK45, Euler1imp, Euler1) ⚠️ Accepted with deprecation warning

Additional strict validation:

  • <Engine> and <Sundials> inside <SIMULATION> are rejected with a message pointing to <Integrator Method="...">; they are no longer parsed into anything.
  • <Integrator MaxOrder> is rejected: the attribute lives on <StepControl MaxOrder>.
  • <Integrator TOutput> is rejected: the output schedule lives on <OBSERVERS><FileOutput TOutput="..."/></OBSERVERS>. Without an <OBSERVERS> block the -o output file receives every step.
  • Unknown child elements inside <SIMULATION> fail fast.
  • Unknown child elements inside <OBSERVERS> fail fast.

FMU note:

  • FMU workflows should use the full <SIMULATION> + <Timing> + <Integrator> form. FhFmuExport reads the exported <DefaultExperiment> — startTime, stopTime, tolerance and stepSize — from that form only: startTime/stopTime come from <Timing TStart TEnd>, and stepSize/tolerance from the Step and RelTol attributes of <Integrator><StepControl>. A model still written in the legacy form (<INTEGRATION>, or <SIMULATION> with <Engine StepSize> / <Sundials dt>) exports an empty <DefaultExperiment> element, so the importing co-simulation master is given no stop time and no suggested communication step size and has to be told both by hand. The port mapping itself is not affected — FmuPortMappingReader does read the legacy attributes, which is why the export succeeds and only the advertised experiment defaults go missing. If you hit migration issues in FMU export, contact the FhSim team.

Dependency changes

For SimObject authors

Most SimObject plugins use the InputReader / XmlDoc abstraction layer for XML access. The interface of these classes is unchanged — you do not need to modify your XML-reading code even though the underlying implementation switched from TinyXML to libxml2.

If your SimObject used TinyXML API directly (e.g., TiXmlElement*, TiXmlDocument), you must switch to the libxml2 API or (preferably) use the InputReader / XmlDoc wrappers instead.

TinyXML → libxml2 API mapping

The libxml2 API uses C-style allocation — always free strings returned by xmlGetProp / xmlNodeListGetString with xmlFree().

TinyXML (v2) libxml2 (v3)
#include <tinyxml.h> #include <libxml/parser.h> + #include <libxml/tree.h>
TiXmlDocument doc(path); doc.LoadFile() xmlDocPtr doc = xmlReadFile(path, nullptr, XML_PARSE_NONET);
doc.ErrorDesc() No direct equivalent; libxml2 writes to stderr by default
doc.RootElement() xmlDocGetRootElement(doc)
elem->Value() reinterpret_cast<const char*>(node->name)
elem->Attribute("x") xmlChar* v = xmlGetProp(node, BAD_CAST "x"); xmlFree(v);
elem->FirstChildElement("tag") Iterate node->children, skip node->type != XML_ELEMENT_NODE
child->NextSiblingElement() Iterate node->next, skip non-element nodes
attr->Name() reinterpret_cast<const char*>(attr->name)
attr->Value() xmlChar* v = xmlNodeListGetString(doc, attr->children, 1); xmlFree(v);
(no explicit free needed) xmlFreeDoc(doc) after all nodes are done

TinyXML → <tt>fhsim::xml::XmlDoc</tt> (recommended)

<fhsim/XmlDoc.h> wraps libxml2 in a small C++ API (namespace fhsim::xml). It owns the memory for you: there is no xmlFree, and no node to free. An XmlNode is a non-owning handle that is valid while its XmlDoc lives.

TinyXML (v2) fhsim::xml (v3)
TiXmlDocument doc(path); doc.LoadFile() auto doc = XmlDoc::FromFile(path, &error); then if (!doc) for failure
doc.RootElement() doc.Root()
elem->Value() node.Name()
elem->Attribute("x") node.Attribute("x") (empty string if missing) or node.OptAttribute("x") (std::optional)
elem->FirstChildElement("tag") node.FirstChildElement("tag")
child->NextSiblingElement() child.NextSiblingElement()
a for loop over the children for (auto child : node.ChildElements("tag"))

Also remove tinyxml::tinyxml from any target_link_libraries() call in your CMakeLists.txt — the migration script (--only cmake) does this automatically.

Conan dependency summary

Old (v2) New (v3) Impact on consumers
tinyxml/2.6.2 libxml2/[~2.15] ([~2.13] up to 3.3.x) Transitive; no action unless you used TinyXML API directly
sundials/5.4.0 sundials/7.5.0 Transitive; only affects engine embedders using Sundials API directly
— eigen/[~5.0.0] New transitive dependency on simobject target
— sdl/2.32.10 Only with with_visualization=True
— ImGui, built into ogre3d's OgreOverlay Only with with_visualization=True. Up to 3.3.x a separate imgui/[<1.92] requirement; do not link imgui::imgui from 3.4.0
— zeromq/4.3.5 Internal to fhsim; not visible to consumers
— miniz/[~3.0.2] Internal to fhsim; not visible to consumers

fhsim_environment 2 → 3

A library that uses fhsim_environment moves to its 3.x line at the same time as fhsim. Two things change in the consumer (see the 3.0.0 entry of the fhsim_environment CHANGELOG):

  • Include root. The public headers are installed under include/fhsim_environment/, and the recipe's includedirs is plain include. Include them as #include <fhsim_environment/EnvironmentProvider.h>, not by bare file name.
  • Transitive headers. If your own installed headers include an environment header, require the package with transitive_headers=True, so the libraries that use yours get the include path too (fhsim_base does, in its conanfile.py):
self.requires("fhsim_environment/[>=3.2.7 <4]@sintef/stable", transitive_headers=True)

In CMake, link the target fhsim_environment::environment. fhsim_environment 3 itself requires fhsim/[>=3.4.0 <4], so its consumers move to fhsim 3.4 or newer.

Conan profile requirements

Ensure your Conan profile specifies:

[settings]
compiler.cppstd=20

Using the migration script

An automated migration script is provided at scripts/migrate_to_v3.py. It performs header renames, class renames, CMake target updates, and XML element renames across your project.

Usage

# Dry-run (preview changes without modifying files):
python scripts/migrate_to_v3.py /path/to/your/project
# Apply changes:
python scripts/migrate_to_v3.py /path/to/your/project --apply
# Process only specific file types:
python scripts/migrate_to_v3.py /path/to/your/project --apply --only cpp
python scripts/migrate_to_v3.py /path/to/your/project --apply --only cmake
python scripts/migrate_to_v3.py /path/to/your/project --apply --only xml

What the script does

  1. C++ files (.cpp, .h, .hpp):
    • Renames #include directives (e.g., "CPrintDuringExec.h" → "PrintDuringExec.h")
    • Renames class identifiers (e.g., CPrintDuringExec → PrintDuringExec)
  2. CMake files (CMakeLists.txt, *.cmake):
    • Replaces FhSim::fhsim with FhSim::simobject (SimObject target migration)
    • Replaces FhSim::fhsim-testtools and FhSim::fhsim_testtools with FhSim::fhsim_test
  3. XML files (.xml):
    • Replaces <INTEGRATION> / </INTEGRATION> with <SIMULATION> / </SIMULATION>
    • Converts a self-closing <Engine .../> or <Sundials .../> to <Timing/> and <Integrator> (see XML input file changes).
    • Writes the v2 output schedule (TOutput, without TStart and TEnd) and the v2 FileOutput selection as <OBSERVERS><FileOutput TOutput=... Select=.../></OBSERVERS> after </SIMULATION>. If the file already has an <OBSERVERS> block, it adds a TODO comment with the <FileOutput> instead.
    • Leaves a TODO comment for LogStates and UseRSSNormInsteadOfInfNorm: they have no v3 attribute and are not copied to <Integrator>.
  4. Conan files (conanfile.py, conanfile.txt):
    • Updates fhsim version requirement pattern

What the script does NOT handle (manual steps required)

The following changes cannot be automated safely and must be done by hand after running the script:

  1. **string → std::string in headers** — the regex for word-boundary replacement would produce too many false positives. See Step 5b: Update headers that use unqualified string above.
  2. Review of the <Engine> / <Sundials> conversion — the script converts self-closing elements only, so an <Engine> or <Sundials> element with a closing tag stays as it was. Attributes the script does not know are not copied. Check each converted file against the mapping tables and examples in XML input file changes, and read the TODO comments the script leaves.
  3. **GetSharedResource("CFhCamera") string literal** — string literal content is not renamed by the class-rename pass, and GetSharedResource now takes the type as a template argument: creator->GetSharedResource<FhCamera>(fhsim::kSimMgrKeyFhCamera). Search manually:
    grep -rn 'GetSharedResource.*CFhCamera\|SetPtr.*CFhCamera' src/
  4. **FhSim::fhsim_vis CMake link guard** — the script does not add new target_link_libraries calls. See Step 7: Link FhSim::fhsim_vis and update visualization includes (if applicable).
  5. Conan version-range syntax — the script handles common patterns but always verify the conanfile after running it.

Important notes

  • Always run in dry-run mode first to review proposed changes.
  • The CMake target replacement (FhSim::fhsim → FhSim::simobject) handles both the mixed-case form (FhSim::fhsim) and the all-lowercase Conan CMakeDeps alias form (fhsim::fhsim). If you embed the engine rather than authoring a plugin, skip the CMake pass or restore FhSim::fhsim linkage manually where needed.
  • Back up your project or ensure clean git state before running with --apply.
  • The script does not modify files in .git/, build/, or node_modules/ directories.