DPsim
Loading...
Searching...
No Matches
MNAStateSpaceContributor.cpp
1// SPDX-FileCopyrightText: 2026 Institute for Automation of Complex Power Systems, EONERC, RWTH Aachen University
2// SPDX-License-Identifier: MPL-2.0
3#include <dpsim/MNAStateSpaceContributor.h>
4
5#include <dpsim-models/EMT/EMT_Ph3_Capacitor.h>
6#include <dpsim-models/EMT/EMT_Ph3_Inductor.h>
7#include <dpsim-models/EMT/EMT_Ph3_Resistor.h>
8#include <dpsim-models/EMT/EMT_Ph3_TwoTerminalVTypeSSNComp.h>
9#include <dpsim-models/EMT/EMT_Ph3_TwoTerminalVTypeVariableSSNComp.h>
10#include <dpsim-models/EMT/EMT_Ph3_VoltageSource.h>
11#include <dpsim-models/EMT/EMT_VTypeSSNComp.h>
12#include <dpsim-models/SimPowerComp.h>
13#include <stdexcept>
14#include <utility>
15
16using namespace CPS;
17
18namespace DPsim {
19namespace {
20
21using SimPowerCompReal = CPS::SimPowerComp<CPS::Real>;
22
25Matrix buildTwoTerminalInterfaceVoltageMapping(SimPowerCompReal &component,
26 UInt mnaVectorSize) {
27 Matrix K = Matrix::Zero(3, mnaVectorSize);
28
29 if (component.terminalNotGrounded(1)) {
30 for (UInt phase = 0; phase < 3; ++phase)
31 K(phase, component.matrixNodeIndex(1, phase)) = 1.0;
32 }
33
34 if (component.terminalNotGrounded(0)) {
35 for (UInt phase = 0; phase < 3; ++phase)
36 K(phase, component.matrixNodeIndex(0, phase)) = -1.0;
37 }
38
39 return K;
40}
41
46void stampTwoTerminalCurrentInjectionMapping(const Matrix &K, Matrix &CdMna,
47 UInt stateOffset,
48 const Matrix &outputMatrix) {
49 CdMna.block(0, stateOffset, CdMna.rows(), outputMatrix.cols()) +=
50 -K.transpose() * outputMatrix;
51}
52
53void addThreePhaseAbcStateMetadata(StateSpaceMetadata &metadata,
54 UInt stateOffset) {
55 metadata.abcStateIndexTriples.push_back(
56 {stateOffset + 0, stateOffset + 1, stateOffset + 2});
57}
58
59class EMTPh3InductorStateSpaceContributor final
61public:
62 explicit EMTPh3InductorStateSpaceContributor(
63 std::shared_ptr<EMT::Ph3::Inductor> component)
64 : mComponent(std::move(component)) {}
65
66 UInt getStateCount() const override { return 3; }
67
68 void stamp(Matrix &AdLocal, Matrix &BdMna, Matrix &CdMna, UInt stateOffset,
69 UInt mnaVectorSize) const override {
70 const Matrix &conductance = mComponent->getMNAConductance();
71
72 const Matrix K =
73 buildTwoTerminalInterfaceVoltageMapping(*mComponent, mnaVectorSize);
74
75 // History state h:
76 // i[k+1] = G vIntf[k+1] + h[k]
77 // h[k+1] = h[k] + 2 G vIntf[k+1]
78 // Therefore: AdLocal = I, BdMna = 2 G K, CdMna = -K^T.
79 AdLocal.block(stateOffset, stateOffset, 3, 3) += Matrix::Identity(3, 3);
80
81 BdMna.block(stateOffset, 0, 3, mnaVectorSize) += 2.0 * conductance * K;
82
83 stampTwoTerminalCurrentInjectionMapping(K, CdMna, stateOffset,
84 Matrix::Identity(3, 3));
85 }
86
87 void contributeMetadata(StateSpaceMetadata &metadata,
88 UInt stateOffset) const override {
89 addThreePhaseAbcStateMetadata(metadata, stateOffset);
90 }
91
92private:
93 std::shared_ptr<EMT::Ph3::Inductor> mComponent;
94};
95
96class EMTPh3CapacitorStateSpaceContributor final
98public:
99 explicit EMTPh3CapacitorStateSpaceContributor(
100 std::shared_ptr<EMT::Ph3::Capacitor> component)
101 : mComponent(std::move(component)) {}
102
103 UInt getStateCount() const override { return 3; }
104
105 void stamp(Matrix &AdLocal, Matrix &BdMna, Matrix &CdMna, UInt stateOffset,
106 UInt mnaVectorSize) const override {
107 const Matrix &conductance = mComponent->getMNAConductance();
108
109 const Matrix K =
110 buildTwoTerminalInterfaceVoltageMapping(*mComponent, mnaVectorSize);
111
112 // History state h:
113 // i[k+1] = G vIntf[k+1] + h[k]
114 // h[k+1] = -h[k] - 2 G vIntf[k+1]
115 // Therefore: AdLocal = -I, BdMna = -2 G K, CdMna = -K^T.
116 AdLocal.block(stateOffset, stateOffset, 3, 3) -= Matrix::Identity(3, 3);
117
118 BdMna.block(stateOffset, 0, 3, mnaVectorSize) -= 2.0 * conductance * K;
119
120 stampTwoTerminalCurrentInjectionMapping(K, CdMna, stateOffset,
121 Matrix::Identity(3, 3));
122 }
123
124 void contributeMetadata(StateSpaceMetadata &metadata,
125 UInt stateOffset) const override {
126 addThreePhaseAbcStateMetadata(metadata, stateOffset);
127 }
128
129private:
130 std::shared_ptr<EMT::Ph3::Capacitor> mComponent;
131};
132
133class EMTPh3TwoTerminalVTypeSSNStateSpaceContributor final
134 : public MNAStateSpaceContributor {
135public:
136 EMTPh3TwoTerminalVTypeSSNStateSpaceContributor(
137 std::shared_ptr<EMT::VTypeSSNComp> component, Bool isVariable)
138 : mComponent(std::move(component)), mIsVariable(isVariable) {}
139
140 UInt getStateCount() const override { return mComponent->getStateCount(); }
141
142 Bool isVariable() const override { return mIsVariable; }
143
144 void stamp(Matrix &AdLocal, Matrix &BdMna, Matrix &CdMna, UInt stateOffset,
145 UInt mnaVectorSize) const override {
146 const UInt localStateCount = getStateCount();
147
148 const Matrix &discreteA = mComponent->getDiscreteA();
149 const Matrix &discreteB = mComponent->getDiscreteB();
150 const Matrix &outputC = mComponent->getC();
151
152 const Matrix K =
153 buildTwoTerminalInterfaceVoltageMapping(*mComponent, mnaVectorSize);
154
155 // History-coordinate state s = discreteA x + discreteB vIntf:
156 // s[k+1] = discreteA s[k] + (discreteA + I) discreteB vIntf[k+1]
157 // yHist[k] = C s[k]
158 // Therefore: AdLocal = discreteA, BdMna = (discreteA + I) discreteB K,
159 // CdMna = -K^T C.
160 AdLocal.block(stateOffset, stateOffset, localStateCount, localStateCount) +=
161 discreteA;
162
163 const Matrix inputUpdate =
164 (discreteA + Matrix::Identity(localStateCount, localStateCount)) *
165 discreteB;
166
167 BdMna.block(stateOffset, 0, localStateCount, mnaVectorSize) +=
168 inputUpdate * K;
169
170 stampTwoTerminalCurrentInjectionMapping(K, CdMna, stateOffset, outputC);
171 }
172
173 void contributeMetadata(StateSpaceMetadata &metadata,
174 UInt stateOffset) const override {
175 const UInt localStateCount = getStateCount();
176
177 for (auto abcBlock : mComponent->getLocalAbcStateIndexTriples()) {
178 for (auto &idx : abcBlock) {
179 if (idx >= localStateCount) {
180 throw std::runtime_error(
181 "SSN component returned an invalid abc state index.");
182 }
183
184 idx += stateOffset;
185 }
186
187 metadata.abcStateIndexTriples.push_back(abcBlock);
188 }
189 }
190
191private:
192 std::shared_ptr<EMT::VTypeSSNComp> mComponent;
193 Bool mIsVariable = false;
194};
195
196} // namespace
197
198MNAStateSpaceContributor::Ptr
199MNAStateSpaceContributorFactory::create(const MNAInterface::Ptr &component) {
200 if (!component)
201 return nullptr;
202
203 if (auto inductor =
204 std::dynamic_pointer_cast<EMT::Ph3::Inductor>(component)) {
205 return std::make_shared<EMTPh3InductorStateSpaceContributor>(inductor);
206 }
207
208 if (auto capacitor =
209 std::dynamic_pointer_cast<EMT::Ph3::Capacitor>(component)) {
210 return std::make_shared<EMTPh3CapacitorStateSpaceContributor>(capacitor);
211 }
212
213 if (auto variableSsn =
214 std::dynamic_pointer_cast<EMT::Ph3::TwoTerminalVTypeVariableSSNComp>(
215 component)) {
216 return std::make_shared<EMTPh3TwoTerminalVTypeSSNStateSpaceContributor>(
217 variableSsn, true);
218 }
219
220 if (auto ssn = std::dynamic_pointer_cast<EMT::Ph3::TwoTerminalVTypeSSNComp>(
221 component)) {
222 return std::make_shared<EMTPh3TwoTerminalVTypeSSNStateSpaceContributor>(
223 ssn, false);
224 }
225
226 if (std::dynamic_pointer_cast<EMT::Ph3::Resistor>(component))
227 return nullptr;
228
229 if (std::dynamic_pointer_cast<EMT::Ph3::VoltageSource>(component))
230 return nullptr;
231
232 throw std::invalid_argument(
233 "Unsupported component in MNA state-space extraction.");
234}
235
236MNAStateSpaceContributor::List MNAStateSpaceContributorFactory::createList(
237 const MNAInterface::List &components) {
238 MNAStateSpaceContributor::List contributors;
239
240 for (const auto &component : components) {
241 auto contributor = create(component);
242 if (contributor)
243 contributors.push_back(contributor);
244 }
245
246 return contributors;
247}
248
249} // namespace DPsim