|
FhSim
3.1.0
Marine systems simulation
|
The FhSim test library provides support for simulation testing in external SimObject/plugin projects.
fhsim::test::RunTest(const TestSpec&).TestSpec is default-constructible).TestJacobianMode::{No, Yes, Auto}, default Auto).TestResult) that includes simulation output, timing, and Jacobian outcome.ReadOnly, CreateIfMissing, Create) for regression workflows.TestSpec::diagnosticsJsonPath.| Goal | API |
|---|---|
| Run a simulation test | fhsim::test::RunTest(spec) |
| Compare run vs reference files | result.CompareRegression(spec) |
| Create baseline references | result.CreateBaselineReference(spec) |
| Compare two runs directly | result.CompareRegression(otherResult) |
| Assert on output values without disk I/O | fhsim::test::CapturingObserver |
Parse the <Integrator> configuration only | fhsim::test::ReadIntegratorConfig(dirname, cfg) (returns the engine-internal IntegratorConfig) |
Set this once in your test main():
testDir (root test folder)inPath (input XML + reference files)resPath (result files)logPath (log files)runPath (working directory for simulation launch)logLevelToScreen, logLevelToLogreferenceMode (ReadOnly by default)For project test binaries, expose this config through a helper such as GetTestRuntimeConfig() and use it in tests.
For ID ImplicitIntegration_Euler1imp, TestSpec resolves paths as:
ImplicitIntegration (part before first _)<inPath>/ImplicitIntegration/ImplicitIntegration_Euler1imp_in.xml<resPath>/ImplicitIntegration/ImplicitIntegration_Euler1imp_res.txt<logPath>/ImplicitIntegration/ImplicitIntegration_Euler1imp_log.txt<inPath>/ImplicitIntegration/ImplicitIntegration_Euler1imp.ref<inPath>/ImplicitIntegration/ImplicitIntegration_Euler1imp_ref.csvIf you pass pathPrefix to TestSpec, paths resolve under:
<pathPrefix>/in/...<pathPrefix>/out/...TestSpec::jacobianMode controls Jacobian verification inside RunTest:
TestJacobianMode::No → Jacobian check disabledTestJacobianMode::Yes → Jacobian check required (hard-fails if unavailable or failing)TestJacobianMode::Auto (default) → run when Jacobians are available, otherwise skipOptional Jacobian checker tuning:
Use ReferenceMode::CreateIfMissing during bootstrap and ReadOnly in CI.
CompareRegression(spec) uses two artifacts per test ID:
<ID>.ref<ID>_ref.csvFor test ID MyTest (group subdir MyTest):
<runtimeConfig.inPath>/MyTest/MyTest.ref<runtimeConfig.inPath>/MyTest/MyTest_ref.csvPerformance file (.ref) is a line-based key-value format:
#<key> <value>Supported keys:
cpu_ref (double): reference CPU time in seconds (informational only)wall_max (double): maximum allowed wall-clock time in seconds (hard limit)output_rms_max (double): maximum RMS error per output column vs _ref.csv_ref.csv uses the same semicolon-separated output format as standard simulation output.
The old constructor-based style still works:
The recommended style is explicit field assignment on a default-constructed struct:
jacobianMode, referenceModeOverride) without long constructor chains.TestSpec(id, cfg, simTime, maxRunTime, pathPrefix)spec.ID = idspec.runtimeConfig = cfgspec.simTime = simTimespec.maxRunTime = maxRunTimespec.pathPrefix = pathPrefixReadOnly: compare only, never write references.CreateIfMissing: write only missing reference artifacts, then compare.Create: always regenerate reference artifacts from current run.Resolution order:
TestSpec::referenceModeOverride (if set)TestRuntimeConfig::referenceModeCapturingObserver collects simulation snapshots in memory and provides named lookup for both output port values and ODE state values. Use it when a test needs to assert directly on simulation data without a result file on disk.
RunTest and the CSV-based regression workflow remain the right choice for regression tests. CapturingObserver is for tests that assert on specific numeric values step-by-step, or that want to skip file I/O entirely.
CapturingObserver is an IStateObserver. Register it with a SimulationManager built from the same input file RunTest would use:
If the test also needs regression comparison, keep ctx.outputPath = spec.outFile.string(). The FileOutputObserver and CapturingObserver coexist without conflict.
CapturingObserver exposes two lookup methods:
Use OutputAt for most tests. The XML output spec is the declared observable interface of the model. Output names (objectName, signalName) are stable across XML reorganisation. OutputAt accesses the same values that result.output and CompareRegression use, making it consistent with the regression workflow.
Use StateAt when:
<Output> element yet.StateAt is keyed by the objectName and tagName strings that the SimObject passes to ISimObjectCreator::AddState(). These are stable as long as the SimObject's state registration does not change, but they can be affected by reordering SimObjects in the XML.
| Situation | Use |
|---|---|
| XML declares an output column for this value | OutputAt / FinalOutput |
| Asserting on a value not in the XML output spec | StateAt / FinalState |
| Regression comparison across runs | RunTest + CompareRegression |
| Checking that the simulation completes without error | result.ok from RunTest |
The step index (0-based) runs over output steps only — internal integrator steps that do not produce output are not counted, matching the cadence of the CSV result file.
TestSpec: ID, timing limits, runtime config snapshot, resolved paths, Jacobian policy, optional diagnosticsJsonPath.TestResult: ok/error, timing metrics, resolved paths, parsed output, Jacobian run result.JacobianRunResult (in TestResult::jacobian): status (NotRequested, SkippedUnavailable, Passed, Failed, Error), diagnostics, and per-scope summaries.PerformanceMetrics: wall/cpu/sim-time/step counters and rates.RegressionReport: ok + accumulated diagnostic text.ResultFileData / ObjectTimeSeries: parsed output columns.FindSeries(result, objectName, tagName): locate a specific output column quickly.CapturingObserver: in-memory observer with OutputAt/StateAt/FinalOutput/FinalState accessors.Example:
Set TestSpec::diagnosticsJsonPath to persist unified diagnostics data after a successful RunTest(spec) call.
RunTest writes this file only when diagnostics are enabled in the simulation input (for example by adding <Diagnostics/> under <Integrator>).
The file contains:
metadata: state and port naming metadata (simObjectNames, stateNames, portObjectNames, portNames, portIndices).snapshots: by default the latest snapshot; full history when history accumulation is enabled by the integrator/diagnostics pipeline.For tests that need to verify a SimObject's analytical Jacobian independently of the RunTest pipeline, use CheckJacobians or CheckJacobiansFromXml from JacobianChecker.h (included transitively by FhsimTest.h and TestSpec.h):
Each element of the returned vector covers one *(state-point × scope)* combination, where scope is "system" plus one entry per SimObject that returns HasJacobians() == true.
JacobianCheckResult field | Type | Meaning |
|---|---|---|
ok | bool | All elements within tolerance |
label | string | Human-readable scope label |
scope | string | "system" or SimObject name |
sampleIndex | int | 0 = IC, >0 = random perturbation index |
timeIndex | int | Index into config.timePoints |
atTime | double | Simulation time of the check |
nStates | int | Jacobian block dimension |
maxAbsErr | double | Worst absolute error across all elements |
maxRelErr | double | Worst relative error across all elements |
mismatchCount | int | Number of elements exceeding tolerances |
violations | vector<JacobianElementError> | Up to maxViolationsReported worst elements |
error | string | Non-empty on setup/runtime failure |
Summary() | — | Formatted multi-line diagnostic string |
Each JacobianElementError carries row, col, analytical, numerical, absErr, and relErr.
To suppress log output in tests, use the platform-portable null device helper:
This avoids hard-coding platform-specific paths in test code.
| Symptom | Likely cause | Action |
|---|---|---|
| Missing input file | ID-to-path mapping mismatch | Verify test ID and <inPath>/<group>/<ID>_in.xml |
Missing .ref in ReadOnly mode | Baseline not created | Run once with CreateIfMissing or Create |
Missing _ref.csv with output_rms_max | Output reference absent | Generate baseline output reference |
| Wall-time budget exceeded | maxRunTime too low or simulation regressed | Raise budget for heavy tests or investigate performance |
| Output identity mismatch | Changed output column names/order | Update reference and/or adapt test expectations |
Jacobian check skipped in Auto | Model does not expose Jacobians | This is expected in Auto; use Yes to require Jacobians |
Jacobian hard-fail in Yes mode | Jacobians unavailable or numerical/analytical mismatch | Inspect result.jacobian.diagnostics and failing scope summaries |