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
4#include <dpsim/MNAStateSpaceContributor.h>
5
6#include <dpsim-models/EMT/EMT_Ph3_Capacitor.h>
7#include <dpsim-models/EMT/EMT_Ph3_Inductor.h>
8#include <dpsim-models/EMT/EMT_Ph3_Resistor.h>
9#include <dpsim-models/EMT/EMT_Ph3_TwoTerminalVTypeSSNComp.h>
10#include <dpsim-models/EMT/EMT_Ph3_VoltageSource.h>
11#include <dpsim-models/SimPowerComp.h>
12#include <dpsim-models/Solver/MNAVariableCompInterface.h>
13
14#include <stdexcept>
15#include <utility>
16
17using namespace CPS;
18
19namespace DPsim {
20namespace {
21
22using SimPowerCompReal = CPS::SimPowerComp<CPS::Real>;
23
26Matrix buildTwoTerminalInterfaceVoltageMapping(SimPowerCompReal &component,
27 UInt mnaVectorSize) {
28 Matrix K = Matrix::Zero(3, mnaVectorSize);
29
30 if (component.terminalNotGrounded(1)) {
31 for (UInt phase = 0; phase < 3; ++phase)
32 K(phase, component.matrixNodeIndex(1, phase)) = 1.0;
33 }
34
35 if (component.terminalNotGrounded(0)) {
36 for (UInt phase = 0; phase < 3; ++phase)
37 K(phase, component.matrixNodeIndex(0, phase)) = -1.0;
38 }
39
40 return K;
41}
42
47void stampTwoTerminalCurrentInjectionMapping(const Matrix &K, Matrix &CdMna,
48 UInt stateOffset,
49 const Matrix &outputMatrix) {
50 CdMna.block(0, stateOffset, CdMna.rows(), outputMatrix.cols()) +=
51 -K.transpose() * outputMatrix;
52}
53
54Bool isVariableMNAComponent(const MNAInterface::Ptr &component) {
55 return std::dynamic_pointer_cast<MNAVariableCompInterface>(component) !=
56 nullptr;
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
87private:
88 std::shared_ptr<EMT::Ph3::Inductor> mComponent;
89};
90
91class EMTPh3CapacitorStateSpaceContributor final
93public:
94 explicit EMTPh3CapacitorStateSpaceContributor(
95 std::shared_ptr<EMT::Ph3::Capacitor> component)
96 : mComponent(std::move(component)) {}
97
98 UInt getStateCount() const override { return 3; }
99
100 void stamp(Matrix &AdLocal, Matrix &BdMna, Matrix &CdMna, UInt stateOffset,
101 UInt mnaVectorSize) const override {
102 const Matrix &conductance = mComponent->getMNAConductance();
103
104 const Matrix K =
105 buildTwoTerminalInterfaceVoltageMapping(*mComponent, mnaVectorSize);
106
107 // History state h:
108 // i[k+1] = G vIntf[k+1] + h[k]
109 // h[k+1] = -h[k] - 2 G vIntf[k+1]
110 // Therefore: AdLocal = -I, BdMna = -2 G K, CdMna = -K^T.
111 AdLocal.block(stateOffset, stateOffset, 3, 3) -= Matrix::Identity(3, 3);
112
113 BdMna.block(stateOffset, 0, 3, mnaVectorSize) -= 2.0 * conductance * K;
114
115 stampTwoTerminalCurrentInjectionMapping(K, CdMna, stateOffset,
116 Matrix::Identity(3, 3));
117 }
118
119private:
120 std::shared_ptr<EMT::Ph3::Capacitor> mComponent;
121};
122
123class EMTPh3TwoTerminalVTypeSSNStateSpaceContributor final
124 : public MNAStateSpaceContributor {
125public:
126 EMTPh3TwoTerminalVTypeSSNStateSpaceContributor(
127 std::shared_ptr<EMT::Ph3::TwoTerminalVTypeSSNComp> component,
128 Bool isVariable)
129 : mComponent(std::move(component)), mIsVariable(isVariable) {}
130
131 UInt getStateCount() const override { return mComponent->getStateCount(); }
132
133 Bool isVariable() const override { return mIsVariable; }
134
135 void stamp(Matrix &AdLocal, Matrix &BdMna, Matrix &CdMna, UInt stateOffset,
136 UInt mnaVectorSize) const override {
137 const UInt localStateCount = getStateCount();
138
139 const Matrix &discreteA = mComponent->getDiscreteA();
140 const Matrix &discreteB = mComponent->getDiscreteB();
141 const Matrix &outputC = mComponent->getC();
142
143 const Matrix K =
144 buildTwoTerminalInterfaceVoltageMapping(*mComponent, mnaVectorSize);
145
146 // History-coordinate state s = discreteA x + discreteB vIntf:
147 // s[k+1] = discreteA s[k] + (discreteA + I) discreteB vIntf[k+1]
148 // yHist[k] = C s[k]
149 // Therefore: AdLocal = discreteA, BdMna = (discreteA + I) discreteB K,
150 // CdMna = -K^T C.
151 AdLocal.block(stateOffset, stateOffset, localStateCount, localStateCount) +=
152 discreteA;
153
154 const Matrix inputUpdate =
155 (discreteA + Matrix::Identity(localStateCount, localStateCount)) *
156 discreteB;
157
158 BdMna.block(stateOffset, 0, localStateCount, mnaVectorSize) +=
159 inputUpdate * K;
160
161 stampTwoTerminalCurrentInjectionMapping(K, CdMna, stateOffset, outputC);
162 }
163
164private:
165 std::shared_ptr<EMT::Ph3::TwoTerminalVTypeSSNComp> mComponent;
166 Bool mIsVariable = false;
167};
168
169} // namespace
170
171MNAStateSpaceContributor::Ptr
172MNAStateSpaceContributorFactory::create(const MNAInterface::Ptr &component) {
173 if (!component)
174 return nullptr;
175
176 if (auto inductor =
177 std::dynamic_pointer_cast<EMT::Ph3::Inductor>(component)) {
178 return std::make_shared<EMTPh3InductorStateSpaceContributor>(inductor);
179 }
180
181 if (auto capacitor =
182 std::dynamic_pointer_cast<EMT::Ph3::Capacitor>(component)) {
183 return std::make_shared<EMTPh3CapacitorStateSpaceContributor>(capacitor);
184 }
185
186 if (auto ssn = std::dynamic_pointer_cast<EMT::Ph3::TwoTerminalVTypeSSNComp>(
187 component)) {
188 return std::make_shared<EMTPh3TwoTerminalVTypeSSNStateSpaceContributor>(
189 ssn, isVariableMNAComponent(component));
190 }
191
192 if (std::dynamic_pointer_cast<EMT::Ph3::Resistor>(component))
193 return nullptr;
194
195 if (std::dynamic_pointer_cast<EMT::Ph3::VoltageSource>(component))
196 return nullptr;
197
198 throw std::invalid_argument(
199 "Unsupported component in MNA state-space extraction.");
200}
201
202MNAStateSpaceContributor::List MNAStateSpaceContributorFactory::createList(
203 const MNAInterface::List &components) {
204 MNAStateSpaceContributor::List contributors;
205
206 for (const auto &component : components) {
207 auto contributor = create(component);
208 if (contributor)
209 contributors.push_back(contributor);
210 }
211
212 return contributors;
213}
214
215} // namespace DPsim