Skip to content

EMT::Ph3::AvVoltageSourceInverterDQ segfaults with withTrafo=false: mVirtualNodes[3] dereferenced when only 3 are allocated #604

Description

@ysrji

Summary

EMT::Ph3::AvVoltageSourceInverterDQ constructed with withTrafo = false segfaults during
initialisation. On that branch the constructor allocates three virtual nodes, but four sites
dereference mVirtualNodes[3] unconditionally.

Tested at 1b063c54298a46645c04c5ca156ad8178e37f486 (main, 2026-07-21), built from source with
Python bindings, EMT Ph3 / MNA, dt = 100 us.

Reproducer

examples/Notebooks/Circuits/EMT_Slack_PiLine_VSI_with_PF_Init.ipynb runs clean as committed. Change
only the with_trafo argument:

pv = dpsimpy.emt.ph3.AvVoltageSourceInverterDQ(
    "pv", "pv", dpsimpy.LogLevel.debug, with_trafo=False   # was True
)

and remove the now-inapplicable set_transformer_parameters(...) call. The process dies with SIGSEGV
after MnaSolverFactory ... creating KLUAdapter solver implementation, i.e. inside
initializeFromNodesAndTerminals, before the first step. Nothing else in the notebook is touched.

Cause

dpsim-models/src/EMT/EMT_Ph3_AvVoltageSourceInverterDQ.cpp, constructor:

if (withTrafo) {
  setVirtualNodeNumber(4);
  ...
} else {
  setVirtualNodeNumber(3);
}

With the transformer, index 3 is the filter-interface node. Without it, index 3 is one past the end.
These four dereferences are not guarded by mWithConnectionTransformer:

  • L277 Real theta = std::arg(mVirtualNodes[3]->initialSingleVoltage());
  • L291 matrixStateInit(0, 0) = std::arg(mVirtualNodes[3]->initialSingleVoltage());
  • L292 matrixOutputInit(0, 0) = std::arg(mVirtualNodes[3]->initialSingleVoltage());
  • L413 vcdq = parkTransformPowerInvariant(theta, **mVirtualNodes[3]->mVoltage);

The first three are in initializeFromNodesAndTerminals (where the crash lands); L413 is in the
per-step control update, so it would fire every step if initialisation were survived.

Note the surrounding code already handles the no-transformer case correctly — e.g. L263-266 picks
the terminal node instead of mVirtualNodes[3] for mSubResistorC, and L217-236 branches properly
on the initial-voltage calculation. It appears only these four references were missed.

Suggested fix

In each of the four places, use the node that is electrically equivalent when there is no transformer,
which is the one mSubResistorC is already connected to on that branch — mTerminals[0]->node().
Something of the shape:

auto filterIfNode = mWithConnectionTransformer
                        ? mVirtualNodes[3]
                        : mTerminals[0]->node();

then use filterIfNode at all four sites. That keeps the with-transformer path bit-identical.

Why it matters

For a low-voltage island there is no step-up transformer to model, so withTrafo = false is the
physically correct construction, and it is reachable from the Python bindings with no warning. A
guard, or an explicit "not supported" throw, would at least turn a segfault into something
diagnosable.

Happy to open a PR if the suggested shape looks right.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

Projects

  • Status
    Todo

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions