|
FhSim
3.1.0
Marine systems simulation
|
This page is a decision-oriented guide to making simulations run faster. It does not repeat the full option reference — for exact attributes and defaults see SIMULATION section, and for analytical Jacobians see Implicit Integration with Analytical Jacobians.
Before tuning, first confirm where the time goes. Enable diagnostics (see Diagnostics cookbook) to see whether the step size is collapsing, whether the Jacobian is being rebuilt often, or whether a particular state is forcing small steps.
RK45_i) with sensible tolerances is usually fastest.BDF, DIRK) avoids the tiny steps explicit methods need.For the full method list (canonical names, order, explicit/implicit, Engine vs Sundials backend) and a "which method when" table, see the method reference in SIMULATION section.
Set tolerances no tighter than your accuracy requirement: over-tight AbsTol/RelTol in <StepControl> forces small steps and more work.
The linear-solver choice dominates cost for large implicit systems. There are two disjoint vocabularies, and which one applies depends on the integrator backend:
BDF, DIRK) — <LinearSolver Type="..."> takes only SPGMR, DENSE or BAND. The structured, model-side solvers below are then selected with <LinearSolver Preconditioner="...">, which is accepted for Type="SPGMR" only; a direct DENSE/BAND solver takes no preconditioner._i family, e.g. BackwardEuler_i) — <LinearSolver Type="..."> takes the structured names below directly.So the rules of thumb in this table name the value of Preconditioner for a Sundials run and of Type for an Engine run (full reference in SIMULATION section):
| System | Suggested structured solver |
|---|---|
| Small (< ~100 states) | dense |
| Large and sparse | sparse (METIS-ordered) |
| Chain-coupled (cables, moorings) | band, block_tridiagonal, or near_tridiagonal |
| Many weakly-coupled blocks | component |
| Very large, iterative convergence acceptable | iterative / iterative_gmres |
| Not sure | auto (lets FhSim decide from structure and size) |
A typical stiff, large, sparse configuration therefore reads:
RK45_i and friends) a <LinearSolver> element is irrelevant and is ignored with a warning.For large models, a sparse Jacobian avoids dense factorisation cost. Set <Jacobian Type="sparse"/>; to also visualise the coupling, add SparsityImageSize to the same element — <Jacobian Type="sparse" SparsityImageSize="512"/> — and check the structure is as expected (a dense-looking pattern means little is gained from a sparse solver). Only the first <Jacobian> element is read, so keep it to one.
Providing an analytical Jacobian (rather than the numerical default) is often the single biggest win for stiff systems — see Implicit Integration with Analytical Jacobians.
<Integrator NumCores="..."> parallelises SimObject evaluation:
| Value | Meaning |
|---|---|
1 | Single-threaded (default). |
perCpuCore | One thread per CPU core. |
perSimObject | One thread per state-carrying SimObject. |
| N | At most N threads. |
The engine caps the thread count at the machine's hardware threads and at the number of SimObjects, and logs (at Info) when it does. It then times the first serial evaluations, SimObject by SimObject, and tries parallel evaluation only if both
Otherwise it stays serial and starts no worker thread. If it tries, it times parallel evaluations too and keeps the faster. The choice is logged at Info ("Model evaluation:
median ... us serial, largest SimObject ..."). Both ways compute the same values: the output does not depend on NumCores.
<Integrator ParallelEvaluation="..."> overrides the choice:
| Value | Meaning |
|---|---|
auto | As above (default). |
always | Evaluate in parallel from the first evaluation, without timing, whenever NumCores > 1 and the model has at least two SimObjects. For models whose expensive SimObjects do not wait for each other, such as several independent net cages, that the 250 µs threshold keeps serial. |
never | Evaluate serially; no thread is ever started, whatever NumCores says. |
The output is byte-identical in all three modes and for any NumCores.
While a model is evaluated in parallel, each idle worker thread spins for up to 1 ms waiting for the next evaluation before it sleeps, so a running parallel model keeps its cores busy. Waking a sleeping thread costs tens of microseconds; with a shorter spin the workers fell asleep between evaluations and parallel evaluation was 2–4 times slower.
Parallelism pays off when several SimObjects are expensive and do not wait for each other: eight independent 128-panel net cages (280 µs per evaluation) run about 4 times faster on 8 cores. Trawl models do not gain: their cables and nets read each other's ports and common computations, so in parallel they mostly wait (2–10 times slower per evaluation), and they evaluate in 40–110 µs, below the threshold. The threshold matters because a process that has once started a second thread runs single-threaded code slower for the rest of the run (12–15 % on a trawl), even after that thread has stopped.
Thread-safety note. With
NumCores > 1, SimObjectOdeFcn/port evaluations run concurrently. Shared computations must be registered as a common computation so the engine serialises and caches them (it wraps them in a mutex); do not share mutable state between SimObjects through other means.
Writing state/observer output every step is costly. Increase the output interval (dt / TOutput) and limit observers to what you actually need (see OBSERVERS section).
FhSim 3 has no built-in CPU profiler. Use an external profiler (perf, VTune, Instruments) for hot-spot analysis, and the diagnostics JSON/HTML viewer (Diagnostics cookbook) to understand solver behaviour over time.
<Integrator>, <LinearSolver>, <Jacobian> reference.