Skip to content

Fix three ECCO power-bond bugs and add regression tests#803

Open
joakimono wants to merge 1 commit into
masterfrom
bugfix/ecco-bonds
Open

Fix three ECCO power-bond bugs and add regression tests#803
joakimono wants to merge 1 commit into
masterfrom
bugfix/ecco-bonds

Conversation

@joakimono

Copy link
Copy Markdown

Fix three ECCO power-bond bugs

Background

Three bugs were present in the ECCO algorithm's power-bond support.
Two caused crashes or spurious errors at runtime; one caused a silent
double-registration in a test that happened to pass only by accident.


Bug 1 — add_power_bond() never exposes bond variables for getting

File: src/cosim/algorithm/ecco_algorithm.cpp

What went wrong

ecco_algorithm::impl::add_power_bond() stores four variable_id values
(the two force/velocity pairs that make up the bond) but never calls
expose_for_getting() on any of them.

adjust_step_size() later calls sim->get_real(ref) on all four variables
every step. get_real() reads from the simulator's get-cache, which only
contains variables that were previously registered via expose_for_getting().
For FMU output variables that also appear in a connect_variables()
connection, this registration happened to occur as a side-effect. For FMU
input variables (chassis.force, wheel.in_vel) it never did, because
connect_variables() registers inputs only for setting, not for getting.

The crash was therefore latent for the two bond-input variables:

terminate: Variable with reference N not found in exposed variables

Why it was masked

The default file_observer (constructed without a config) calls
initialize_default() which exposes all non-local variables for getting,
accidentally making the bond inputs readable. Any test or application using
the unconfigured observer never saw the crash.

How it is triggered

A file_observer constructed from a LogConfig.xml that omits the bond
input variables (force, in_vel) exposes only the listed variables. When
adjust_step_size() then tries to read the unlisted inputs, the get-cache
lookup throws.

Fix

Call expose_for_getting() for all four bond variables at the top of
add_power_bond(), before storing them. The call is idempotent — if an
observer already exposed the variable, the second registration is a no-op.

// ecco_algorithm.cpp — add_power_bond()
simulators_.at(input_a.simulator).sim->expose_for_getting(input_a.type, input_a.reference);
simulators_.at(output_a.simulator).sim->expose_for_getting(output_a.type, output_a.reference);
simulators_.at(input_b.simulator).sim->expose_for_getting(input_b.type, input_b.reference);
simulators_.at(output_b.simulator).sim->expose_for_getting(output_b.type, output_b.reference);

Regression test

tests/ecco_algorithm_powerbond_logconfig_test.cpp — loads the quarter-truck
via inject_system_structure, then attaches a file_observer_config that
deliberately omits chassis.force and wheel.in_vel from the logged variable
list, and runs a short simulation. Before the fix this test crashes; after the
fix it completes cleanly.


Bug 2 — add_power_bonds() validation check fires inside the per-bond loop

File: src/cosim/osp_config_parser.cpp

What went wrong

After each call to systemStructure.add_power_bond(), the function checked:

if (uniqueCount != numPowerBonds) { throw ...; }

uniqueCount was computed up front from all connection entries across
all bonds, so with two bonds it always equalled 2. But numPowerBonds
was queried from systemStructure inside the loop, so after the first
iteration it was only 1. The check 2 != 1 fired immediately, making any
system with two or more power bonds impossible to load.

Fix

Move the sort + uniqueCount + validation block outside the loop so it runs
once, after all bonds have been registered:

    for (const auto& pbName : uniquePowerBondNames) {
        ...
        systemStructure.add_power_bond(pbName, powerbond);
    }                                           // ← loop ends here

    // validation runs once, with all bonds present
    std::sort(powerBondNames.begin(), powerBondNames.end());
    auto uniqueCount = ...;
    auto numPowerBonds = static_cast<int>(systemStructure.get_power_bonds().size());
    if (uniqueCount != numPowerBonds) { throw ...; }

Regression test

tests/ecco_algorithm_two_bond_test.cpp — loads
OspSystemStructure_MultiBond.xml, which defines two independent
chassis+wheel pairs (chassis1/wheel1 and chassis2/wheel2) each
connected by a separate power bond (bond1, bond2). Before the fix,
load_osp_config() throws on this file; after the fix the config loads and
the simulation runs for 0.1 s without error.

tests/data/fmi2/quarter_truck/OspSystemStructure_MultiBond.xml is the new
test data file. Each pair is physically independent; the same Chassis.fmu and
Wheel.fmu are used for both, so no new FMUs are required.


Bug 3 — ecco_algorithm_multi_bond_test uses wrong variable refs and double-registers the bond

File: tests/ecco_algorithm_multi_bond_test.cpp

What went wrong

Two mistakes in the test:

  1. Duplicate bond registration. inject_system_structure() already parses
    the power bond from OspSystemStructure.xml and calls add_power_bond()
    internally. The test then called add_power_bond() a second time manually,
    producing two energy-residual accounting entries for the same bond and
    doubling the power-residual calculation.

  2. Wrong hardcoded value references. The manually constructed variable_id
    objects used refs 19, 22, 26, 24. Inspection of the FMU model descriptions
    shows these are ECCO step-size tuning parameters
    (stepSizeCtrlSafetyFactor, timeStepSlowStart, etc.), not the bond
    variables. The correct refs are:

    Variable FMU causality Value reference
    chassis.velocity output 23
    chassis.force input 4
    wheel.out_spring_damper_f output 15
    wheel.in_vel input 7

    The test passed despite the wrong refs only because the default
    csv_observer exposed all variables for getting, masking Bug 1 for
    the bond-input refs, and the wrong parameter refs happened not to crash
    the simulation.

Fix

Remove the duplicate add_power_bond() call. Correct the variable_id
refs used for the time_series_observer observations. Remove the two dead
output vectors (wheelGVels, groundVels) that were allocated but never
filled.

Bug 1 (ecco_algorithm.cpp): add_power_bond() stored all four bond variable
IDs but never called expose_for_getting() on them. adjust_step_size() then
called get_real() on all four, which throws 'Variable with reference N not
found in exposed variables' for any FMU-input variable not previously
exposed. The default file_observer accidentally masked this by exposing all
variables; a LogConfig-restricted observer omitting bond input variables
(force, in_vel) triggered the crash. Fix: call expose_for_getting() for all
four variables at the top of add_power_bond().

Bug 2 (osp_config_parser.cpp): the uniqueCount != numPowerBonds validation
in add_power_bonds() was placed inside the per-bond loop. With two bonds
configured it fired after the first iteration with numPowerBonds==1 and
uniqueCount==2, throwing a spurious error. Fix: move the check outside the
loop so it runs after all bonds have been registered.

Bug 3 (ecco_algorithm_multi_bond_test.cpp): the test called add_power_bond()
manually after inject_system_structure() had already registered the bond from
the XML (double registration), and used wrong hardcoded value references
(19/22/26/24 are ECCO step-size parameters, not bond variables). Fix: remove
the duplicate add_power_bond() call and correct the variable refs to
chassis.velocity=23, chassis.force=4, wheel.out_spring_damper_f=15,
wheel.in_vel=7.

New tests:
- ecco_algorithm_powerbond_logconfig_test: loads quarter-truck via
  inject_system_structure with a file_observer_config that deliberately omits
  the FMU-input bond variables; verifies no crash (regresses Bug 1).
- ecco_algorithm_two_bond_test: loads OspSystemStructure_MultiBond.xml (two
  independent chassis+wheel pairs, two bonds) and simulates 0.1 s; verifies
  no spurious throw (regresses Bug 2).
- OspSystemStructure_MultiBond.xml: new test data for the two-bond scenario.
@joakimono joakimono requested a review from kyllingstad July 9, 2026 12:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant