Skip to content

Fix BUS_BRANCH export failure with keepOriginalTopology#3905

Open
cphili wants to merge 9 commits into
mainfrom
avoid_error_using_keeporiginaltopology
Open

Fix BUS_BRANCH export failure with keepOriginalTopology#3905
cphili wants to merge 9 commits into
mainfrom
avoid_error_using_keeporiginaltopology

Conversation

@cphili
Copy link
Copy Markdown
Member

@cphili cphili commented May 6, 2026

Please check if the PR fulfills these requirements

  • The commit message follows our guidelines
  • Tests for the changes have been added (for bug fixes / features)
  • Docs have been added / updated (for bug fixes / features)
  • A PR or issue has been opened in all impacted repositories (if any)

Does this PR already have an issue describing the problem?
#3851

What kind of change does this PR introduce?
Bug fix

What is the current behavior?
When exporting a network in IIDM format with BUS_BRANCH topology level and BUS_BRANCH_VOLTAGE_LEVEL_INCOMPATIBILITY_BEHAVIOR = KEEP_ORIGINAL_TOPOLOGY,
the export may fail in some cases where the chosen topology leads to an invalid IIDM configuration. In particular, when a voltage level originally in NODE_BREAKER topology is exported in BUS_BRANCH, some terminal references may become invalid (e.g. referencing elements that are not representable in BUS_BRANCH), leading to a runtime exception during serialization.

What is the new behavior (if this is a feature change)?
The export logic now consistently relies on checkVoltageLevelExportTopology to detect invalid BUS_BRANCH configurations. When such an incompatibility is detected:
If the behavior is THROW_EXCEPTION, an exception is still thrown
If the behavior is KEEP_ORIGINAL_TOPOLOGY, the export automatically falls back to the original topology (typically NODE_BREAKER)

Does this PR introduce a breaking change or deprecate an API?

  • Yes
  • No

What changes might users need to make in their application due to this PR? (migration steps)
None. The change is backward-compatible and only affects cases that previously resulted in invalid exports or runtime exceptions.

Other information:

…sent and keepOriginalTopology is enabled

Signed-off-by: Clement Philipot <clement.philipot@rte-france.com>
@cphili cphili changed the title Export the voltage level in node-breaker when busbar sections are present and keepOriginalTopology is enabled Automatically export voltage levels in NODE_BREAKER when keepOriginalTopology is enabled May 6, 2026
@cphili cphili changed the title Automatically export voltage levels in NODE_BREAKER when keepOriginalTopology is enabled Fix BUS_BRANCH export failure with keepOriginalTopology May 6, 2026
Signed-off-by: Clement Philipot <clement.philipot@rte-france.com>
@cphili cphili force-pushed the avoid_error_using_keeporiginaltopology branch from 07e9b8c to 68ffe1f Compare May 6, 2026 15:01
testWriteVersionedXml(network, options.setTopologyLevel(TopologyLevel.BUS_BREAKER), "fictitiousSwitchRef-bbk.xml", CURRENT_IIDM_VERSION);
testWriteVersionedXml(network, options.setTopologyLevel(TopologyLevel.BUS_BRANCH), "fictitiousSwitchRef-bbr.xml", CURRENT_IIDM_VERSION);
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These tests succeed also directly on main (without the TopologyLevelUtil changes). Therefore none of them covers the new case.

Comment on lines +29 to +31
if (context.getOptions().getBusBranchVoltageLevelIncompatibilityBehavior()
== ExportOptions.BusBranchVoltageLevelIncompatibilityBehavior.KEEP_ORIGINAL_TOPOLOGY && requiresNodeBreakerExport(vl)) {
exportTopologyLevel = TopologyLevel.NODE_BREAKER;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not the right place to add your new check. It should be added to checkVoltageLevelExportTopology.

You can change the first condition of this method to look like:

if (topologyLevel == TopologyLevel.BUS_BRANCH && nodeBreakerWithBusBarSection(vl)) {

With

private static boolean withNullConnectableBus(VoltageLevel vl) {
    return vl.getConnectableStream()
        .map(Connectable::getTerminals)
        .flatMap(List<Terminal>::stream)
        .anyMatch(t -> t.getBusView().getConnectableBus() == null);
    }

Then you can update it with your additional condition:

if (topologyLevel == TopologyLevel.BUS_BRANCH
    && (nodeBreakerWithBusBarSection(vl) || requiresNodeBreakerExport(vl)) {

Copy link
Copy Markdown
Member Author

@cphili cphili Jun 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The issue cannot be easily fixed in TopologyLevelUtil, since the error (a terminalRef linked to a busbar) cannot be detected at that stage. To properly fall back to exporting the original topology (NODE_BREAKER), these cases would need to be identified before serialization through a network pre-processing step, which adds complexity.
As an alternative, we can ensure that the export process does not fail, even if such inconsistencies are present.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The alternative is not viable: the export does not fail, but the exported file cannot be imported.

return exportTopologyLevel;
}

private static boolean requiresNodeBreakerExport(VoltageLevel vl) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not a suitable name. There are other cases when we need to export in node/breaker.

Comment on lines +43 to +44
&& vl.getConnectableStream()
.anyMatch(BusbarSection.class::isInstance);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The exception mentioned by the issue indicates that there's a terminalRef pointing to a busbar section.
If the VL is in node/breaker and has busbar section but no terminalRef pointing to them, the export is possible in bus/branch.

For instance, fictitiousSwitchRef.xml has a VL in node/breaker with a busbar section (vl="C", busbarSection="D") and can be exported in bus/branch (see fictitiousSwitchRef-bbr.xml). Since no terminalRef references "D", there's no problem.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that the example is covered by the testConversion() unit test (by last line of testConversion(Network network)).
With this PR current code, the test succeed because the behavior is not set to KEEP_ORIGINAL_TOPOLOGY, then else branch is executed. since no error was detected with the previous check, the VL is exported in bus/branch as expected by the unit test.
This demonstrates that requiresNodeBreakerExport(VoltageLevel vl) detects as problematic situations that are not.

cphili added 3 commits May 28, 2026 09:42
Signed-off-by: Clement Philipot <clement.philipot@rte-france.com>
Signed-off-by: Clement Philipot <clement.philipot@rte-france.com>
@sonarqubecloud
Copy link
Copy Markdown

cphili added 2 commits May 30, 2026 17:29
…OLOGY

Signed-off-by: Clement Philipot <clement.philipot@rte-france.com>
Signed-off-by: Clement Philipot <clement.philipot@rte-france.com>
.anyMatch(t -> t.getBusView().getConnectableBus() == null);
}

private static boolean isBusbarTerminalRefIssue(VoltageLevel vl) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have not tested it, but I think you can simply check if there is at least one busbar section referenced with this method:

    private static boolean isBusbarTerminalRefIssue(VoltageLevel vl) {
        if (vl.getTopologyKind() != TopologyKind.NODE_BREAKER) {
            return false;
        }
        return vl.getNodeBreakerView().getBusbarSectionStream()
                .anyMatch(bbs -> !bbs.getTerminal().getReferrers().isEmpty());
    }

Note that it would be better to have a more descriptive name for the method. Maybe something like "hasReferencedBusbarSections"?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the suggestion.
I tried this approach and added a test, but it fails: getReferrers() is still empty in this scenario.
This seems to indicate that the terminalRef relationships are not populated yet and this method cannot reliably detect the issue at this stage.

Signed-off-by: Clement Philipot <clement.philipot@rte-france.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Waiting for review

Development

Successfully merging this pull request may close these issues.

Avoid error related to a know unsupported topology in bus branch export when using "keep original topology" incompatibility behavior

3 participants