DPsim
Loading...
Searching...
No Matches
MNAStateSpaceContributor.cpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2026 Institute for Automation of Complex Power Systems, EONERC, RWTH Aachen University
2// SPDX-License-Identifier: MPL-2.0
5
40#include <stdexcept>
41#include <string>
42#include <utility>
43
44using namespace CPS;
45
46namespace DPsim {
47namespace {
48
49using SimPowerCompReal = CPS::SimPowerComp<CPS::Real>;
50using SimPowerCompComplex = CPS::SimPowerComp<CPS::Complex>;
51
52std::shared_ptr<CompositePowerComp<Real>>
53getSupportedRealComposite(const MNAInterface::Ptr &component) {
54 if (const auto composite =
55 std::dynamic_pointer_cast<EMT::Ph3::NetworkInjection>(component))
56 return composite;
57
58 if (const auto composite =
59 std::dynamic_pointer_cast<EMT::Ph3::PiLine>(component))
60 return composite;
61
62 if (const auto composite =
63 std::dynamic_pointer_cast<EMT::Ph3::RXLoad>(component))
64 return composite;
65
66 if (const auto composite =
67 std::dynamic_pointer_cast<EMT::Ph3::RxLine>(component))
68 return composite;
69
70 if (const auto composite =
71 std::dynamic_pointer_cast<EMT::Ph3::Shunt>(component))
72 return composite;
73
74 if (const auto composite =
75 std::dynamic_pointer_cast<EMT::Ph3::Transformer>(component))
76 return composite;
77
78 return nullptr;
79}
80
81std::shared_ptr<CompositePowerComp<Complex>>
82getSupportedComplexComposite(const MNAInterface::Ptr &component) {
83 if (const auto composite =
84 std::dynamic_pointer_cast<DP::Ph1::NetworkInjection>(component))
85 return composite;
86
87 if (const auto composite =
88 std::dynamic_pointer_cast<DP::Ph1::PiLine>(component))
89 return composite;
90
91 if (const auto composite =
92 std::dynamic_pointer_cast<DP::Ph1::RXLoad>(component))
93 return composite;
94
95 if (const auto composite =
96 std::dynamic_pointer_cast<DP::Ph1::RxLine>(component))
97 return composite;
98
99 if (const auto composite =
100 std::dynamic_pointer_cast<DP::Ph1::Shunt>(component))
101 return composite;
102
103 if (const auto composite =
104 std::dynamic_pointer_cast<DP::Ph1::Transformer>(component))
105 return composite;
106
107 return nullptr;
108}
109
112Matrix buildTwoTerminalInterfaceVoltageMapping(SimPowerCompReal &component,
113 UInt mnaVectorSize) {
114 Matrix K = Matrix::Zero(3, mnaVectorSize);
115
116 if (component.terminalNotGrounded(1)) {
117 for (UInt phase = 0; phase < 3; ++phase)
118 K(phase, component.matrixNodeIndex(1, phase)) = 1.0;
119 }
120
121 if (component.terminalNotGrounded(0)) {
122 for (UInt phase = 0; phase < 3; ++phase)
123 K(phase, component.matrixNodeIndex(0, phase)) = -1.0;
124 }
125
126 return K;
127}
128
131Matrix
132buildSinglePhaseComplexInterfaceVoltageMapping(SimPowerCompComplex &component,
133 UInt mnaVectorSize) {
134 if (mnaVectorSize % 2 != 0) {
135 throw std::logic_error(
136 "DP MNA state-space extraction requires a real-imaginary stacked "
137 "MNA vector with even size.");
138 }
139
140 const UInt complexOffset = mnaVectorSize / 2;
141 Matrix K = Matrix::Zero(2, mnaVectorSize);
142
143 if (component.terminalNotGrounded(1)) {
144 const UInt nodeIdx = component.matrixNodeIndex(1);
145 K(0, nodeIdx) = 1.0;
146 K(1, nodeIdx + complexOffset) = 1.0;
147 }
148
149 if (component.terminalNotGrounded(0)) {
150 const UInt nodeIdx = component.matrixNodeIndex(0);
151 K(0, nodeIdx) = -1.0;
152 K(1, nodeIdx + complexOffset) = -1.0;
153 }
154
155 return K;
156}
157
160Matrix
161buildThreePhaseComplexInterfaceVoltageMapping(SimPowerCompComplex &component,
162 UInt mnaVectorSize) {
163 if (mnaVectorSize % 2 != 0) {
164 throw std::logic_error(
165 "DP MNA state-space extraction requires a real-imaginary stacked "
166 "MNA vector with even size.");
167 }
168
169 const UInt complexOffset = mnaVectorSize / 2;
170 Matrix K = Matrix::Zero(6, mnaVectorSize);
171
172 for (UInt phase = 0; phase < 3; ++phase) {
173 if (component.terminalNotGrounded(1)) {
174 const UInt nodeIdx = component.matrixNodeIndex(1, phase);
175 K(2 * phase, nodeIdx) = 1.0;
176 K(2 * phase + 1, nodeIdx + complexOffset) = 1.0;
177 }
178
179 if (component.terminalNotGrounded(0)) {
180 const UInt nodeIdx = component.matrixNodeIndex(0, phase);
181 K(2 * phase, nodeIdx) = -1.0;
182 K(2 * phase + 1, nodeIdx + complexOffset) = -1.0;
183 }
184 }
185
186 return K;
187}
188
193void stampTwoTerminalCurrentInjectionMapping(const Matrix &K, Matrix &CdMna,
194 UInt stateOffset,
195 const Matrix &outputMatrix) {
196 CdMna.block(0, stateOffset, CdMna.rows(), outputMatrix.cols()) +=
197 -K.transpose() * outputMatrix;
198}
199
200Matrix realAugment(const MatrixComp &matrix) {
201 const Matrix::Index rows = matrix.rows();
202 const Matrix::Index cols = matrix.cols();
203
204 Matrix result = Matrix::Zero(2 * rows, 2 * cols);
205 result.topLeftCorner(rows, cols) = matrix.real();
206 result.topRightCorner(rows, cols) = -matrix.imag();
207 result.bottomLeftCorner(rows, cols) = matrix.imag();
208 result.bottomRightCorner(rows, cols) = matrix.real();
209
210 return result;
211}
212
213Matrix realAugment(const Complex &value) {
214 Matrix result = Matrix::Zero(2, 2);
215 result << value.real(), -value.imag(), value.imag(), value.real();
216 return result;
217}
218
219Matrix realAugmentInterleaved(const MatrixComp &matrix) {
220 Matrix result = Matrix::Zero(2 * matrix.rows(), 2 * matrix.cols());
221
222 for (Matrix::Index row = 0; row < matrix.rows(); ++row) {
223 for (Matrix::Index col = 0; col < matrix.cols(); ++col)
224 result.block<2, 2>(2 * row, 2 * col) = realAugment(matrix(row, col));
225 }
226
227 return result;
228}
229
230Complex calculateDPInductorPreviousCurrentFactor(const Complex &conductance) {
231 const Real omegaDtHalf = -conductance.imag() / conductance.real();
232 return Complex(1.0, -omegaDtHalf) / Complex(1.0, omegaDtHalf);
233}
234
235void setStateName(StateSpaceMetadata &metadata, UInt stateIndex,
236 const String &name) {
237 if (stateIndex >= metadata.stateNames.size())
238 throw std::runtime_error(
239 "MNA state-space contributor tried to set a state name outside the "
240 "extracted state vector.");
241
242 metadata.stateNames[stateIndex] = name;
243}
244
245void addThreePhaseAbcStateMetadata(StateSpaceMetadata &metadata,
246 UInt stateOffset, const String &baseName) {
247 setStateName(metadata, stateOffset + 0, baseName + "_a");
248 setStateName(metadata, stateOffset + 1, baseName + "_b");
249 setStateName(metadata, stateOffset + 2, baseName + "_c");
250
251 metadata.abcStateBlocks.push_back(
252 {{stateOffset + 0, stateOffset + 1, stateOffset + 2}, baseName});
253}
254
255void addSinglePhaseComplexStateMetadata(StateSpaceMetadata &metadata,
256 UInt stateOffset,
257 const String &baseName) {
258 setStateName(metadata, stateOffset + 0, baseName + "_re");
259 setStateName(metadata, stateOffset + 1, baseName + "_im");
260}
261
262void addComplexStateMetadata(StateSpaceMetadata &metadata, UInt stateOffset,
263 UInt complexStateCount,
264 const String &componentName) {
265 for (UInt idx = 0; idx < complexStateCount; ++idx) {
266 const String stateName = componentName + ".x" + std::to_string(idx);
267 setStateName(metadata, stateOffset + idx, stateName + "_re");
268 setStateName(metadata, stateOffset + complexStateCount + idx,
269 stateName + "_im");
270 }
271}
272
273void addRealStateMetadata(StateSpaceMetadata &metadata, UInt stateOffset,
274 UInt stateCount, const String &componentName) {
275 for (UInt idx = 0; idx < stateCount; ++idx)
276 setStateName(metadata, stateOffset + idx,
277 componentName + ".x" + std::to_string(idx));
278}
279
280class EMTPh3InductorStateSpaceContributor final
281 : public MNAStateSpaceContributor {
282public:
283 explicit EMTPh3InductorStateSpaceContributor(
284 std::shared_ptr<EMT::Ph3::Inductor> component)
285 : mComponent(std::move(component)) {}
286
287 UInt getStateCount() const override { return 3; }
288
289 void stamp(Matrix &AdLocal, Matrix &BdMna, Matrix &CdMna, UInt stateOffset,
290 UInt mnaVectorSize) const override {
291 const Matrix &conductance = mComponent->getMNAConductance();
292
293 const Matrix K =
294 buildTwoTerminalInterfaceVoltageMapping(*mComponent, mnaVectorSize);
295
296 // History state h:
297 // i[k+1] = G vIntf[k+1] + h[k]
298 // h[k+1] = h[k] + 2 G vIntf[k+1]
299 // Therefore: AdLocal = I, BdMna = 2 G K, CdMna = -K^T.
300 AdLocal.block(stateOffset, stateOffset, 3, 3) += Matrix::Identity(3, 3);
301
302 BdMna.block(stateOffset, 0, 3, mnaVectorSize) += 2.0 * conductance * K;
303
304 stampTwoTerminalCurrentInjectionMapping(K, CdMna, stateOffset,
305 Matrix::Identity(3, 3));
306 }
307
308 void contributeMetadata(StateSpaceMetadata &metadata,
309 UInt stateOffset) const override {
310 addThreePhaseAbcStateMetadata(metadata, stateOffset, mComponent->name());
311 }
312
313private:
314 std::shared_ptr<EMT::Ph3::Inductor> mComponent;
315};
316
317class EMTPh3CapacitorStateSpaceContributor final
318 : public MNAStateSpaceContributor {
319public:
320 explicit EMTPh3CapacitorStateSpaceContributor(
321 std::shared_ptr<EMT::Ph3::Capacitor> component)
322 : mComponent(std::move(component)) {}
323
324 UInt getStateCount() const override { return 3; }
325
326 void stamp(Matrix &AdLocal, Matrix &BdMna, Matrix &CdMna, UInt stateOffset,
327 UInt mnaVectorSize) const override {
328 const Matrix &conductance = mComponent->getMNAConductance();
329
330 const Matrix K =
331 buildTwoTerminalInterfaceVoltageMapping(*mComponent, mnaVectorSize);
332
333 // History state h:
334 // i[k+1] = G vIntf[k+1] + h[k]
335 // h[k+1] = -h[k] - 2 G vIntf[k+1]
336 // Therefore: AdLocal = -I, BdMna = -2 G K, CdMna = -K^T.
337 AdLocal.block(stateOffset, stateOffset, 3, 3) -= Matrix::Identity(3, 3);
338
339 BdMna.block(stateOffset, 0, 3, mnaVectorSize) -= 2.0 * conductance * K;
340
341 stampTwoTerminalCurrentInjectionMapping(K, CdMna, stateOffset,
342 Matrix::Identity(3, 3));
343 }
344
345 void contributeMetadata(StateSpaceMetadata &metadata,
346 UInt stateOffset) const override {
347 addThreePhaseAbcStateMetadata(metadata, stateOffset, mComponent->name());
348 }
349
350private:
351 std::shared_ptr<EMT::Ph3::Capacitor> mComponent;
352};
353
354class EMTPh3TwoTerminalVTypeSSNStateSpaceContributor final
355 : public MNAStateSpaceContributor {
356public:
357 EMTPh3TwoTerminalVTypeSSNStateSpaceContributor(
358 std::shared_ptr<EMT::VTypeSSNComp> component, Bool isVariable)
359 : mComponent(std::move(component)), mIsVariable(isVariable) {}
360
361 UInt getStateCount() const override { return mComponent->getStateCount(); }
362
363 Bool contributesToUpdatedMatrices() const override { return mIsVariable; }
364
365 void stamp(Matrix &AdLocal, Matrix &BdMna, Matrix &CdMna, UInt stateOffset,
366 UInt mnaVectorSize) const override {
367 const UInt localStateCount = getStateCount();
368
369 const Matrix &discreteA = mComponent->getDiscreteA();
370 const Matrix &discreteB = mComponent->getDiscreteB();
371 const Matrix &outputC = mComponent->getC();
372
373 const Matrix K =
374 buildTwoTerminalInterfaceVoltageMapping(*mComponent, mnaVectorSize);
375
376 // History-coordinate state s = discreteA x + discreteB vIntf:
377 // s[k+1] = discreteA s[k] + (discreteA + I) discreteB vIntf[k+1]
378 // yHist[k] = C s[k]
379 // Therefore: AdLocal = discreteA, BdMna = (discreteA + I) discreteB K,
380 // CdMna = -K^T C.
381 AdLocal.block(stateOffset, stateOffset, localStateCount, localStateCount) +=
382 discreteA;
383
384 const Matrix inputUpdate =
385 (discreteA + Matrix::Identity(localStateCount, localStateCount)) *
386 discreteB;
387
388 BdMna.block(stateOffset, 0, localStateCount, mnaVectorSize) +=
389 inputUpdate * K;
390
391 stampTwoTerminalCurrentInjectionMapping(K, CdMna, stateOffset, outputC);
392 }
393
394 void contributeMetadata(StateSpaceMetadata &metadata,
395 UInt stateOffset) const override {
396 const UInt localStateCount = getStateCount();
397 const String componentName = mComponent->name();
398
399 const auto localStateNames = mComponent->getLocalStateNames();
400
401 if (!localStateNames.empty() && localStateNames.size() != localStateCount) {
402 throw std::runtime_error(
403 "SSN component returned an invalid number of local state names.");
404 }
405
406 for (UInt idx = 0; idx < localStateCount; ++idx) {
407 if (!localStateNames.empty()) {
408 setStateName(metadata, stateOffset + idx,
409 componentName + "." + localStateNames[idx]);
410 }
411 }
412
413 for (auto abcBlock : mComponent->getLocalAbcStateBlocks()) {
414 if (abcBlock.name.empty()) {
415 throw std::runtime_error(
416 "SSN component returned an abc state block with an empty name.");
417 }
418
419 for (auto &idx : abcBlock.indices) {
420 if (idx >= localStateCount) {
421 throw std::runtime_error(
422 "SSN component returned an invalid abc state index.");
423 }
424
425 idx += stateOffset;
426 }
427
428 metadata.abcStateBlocks.push_back(
429 {abcBlock.indices, componentName + "." + abcBlock.name});
430 }
431 }
432
433private:
434 std::shared_ptr<EMT::VTypeSSNComp> mComponent;
435 Bool mIsVariable = false;
436};
437
438class EMTPh3TwoTerminalVTypeSplitSSNStateSpaceContributor final
439 : public MNAStateSpaceContributor {
440public:
441 explicit EMTPh3TwoTerminalVTypeSplitSSNStateSpaceContributor(
442 std::shared_ptr<EMT::Ph3::TwoTerminalVTypeSplitSSNComp> component)
443 : mComponent(std::move(component)) {}
444
445 UInt getStateCount() const override {
446 return mComponent->getSplitStateCount();
447 }
448
449 Bool contributesToUpdatedMatrices() const override {
450 return mComponent->requiresStateSpaceMatrixUpdate();
451 }
452
453 Bool requiresUpdate() const override {
454 return mComponent->requiresStateSpaceMatrixUpdate();
455 }
456
457 CPS::AttributeBase::List getAttributeDependencies() const override {
458 if (requiresUpdate())
459 return {mComponent->getSplitStateAttribute()};
460
461 return {};
462 }
463
464 void stamp(Matrix &AdLocal, Matrix &BdMna, Matrix &CdMna, UInt stateOffset,
465 UInt mnaVectorSize) const override {
466 const UInt localStateCount = getStateCount();
467 const Matrix &discreteA = mComponent->getSplitDiscreteA();
468 const Matrix &discreteB = mComponent->getSplitDiscreteB();
469 const Matrix &historyC = mComponent->getSplitHistoryC();
470 const Matrix K =
471 buildTwoTerminalInterfaceVoltageMapping(*mComponent, mnaVectorSize);
472
473 AdLocal.block(stateOffset, stateOffset, localStateCount, localStateCount) +=
474 discreteA;
475 BdMna.block(stateOffset, 0, localStateCount, mnaVectorSize) +=
476 discreteB * K;
477 stampTwoTerminalCurrentInjectionMapping(K, CdMna, stateOffset, historyC);
478 }
479
480 void contributeMetadata(StateSpaceMetadata &metadata,
481 UInt stateOffset) const override {
482 const UInt localStateCount = getStateCount();
483 const String componentName = mComponent->name();
484 const auto localStateNames = mComponent->getSplitLocalStateNames();
485
486 if (!localStateNames.empty() && localStateNames.size() != localStateCount) {
487 throw std::runtime_error(
488 "Split SSN component returned an invalid number of local state "
489 "names.");
490 }
491
492 for (UInt idx = 0; idx < localStateCount; ++idx) {
493 if (!localStateNames.empty())
494 setStateName(metadata, stateOffset + idx,
495 componentName + "." + localStateNames[idx]);
496 }
497
498 for (auto abcBlock : mComponent->getSplitLocalAbcStateBlocks()) {
499 if (abcBlock.name.empty()) {
500 throw std::runtime_error(
501 "Split SSN component returned an abc state block with an empty "
502 "name.");
503 }
504
505 for (auto &idx : abcBlock.indices) {
506 if (idx >= localStateCount) {
507 throw std::runtime_error(
508 "Split SSN component returned an invalid abc state index.");
509 }
510 idx += stateOffset;
511 }
512
513 metadata.abcStateBlocks.push_back(
514 {abcBlock.indices, componentName + "." + abcBlock.name});
515 }
516 }
517
518private:
519 std::shared_ptr<EMT::Ph3::TwoTerminalVTypeSplitSSNComp> mComponent;
520};
521
522class DPPh1InductorStateSpaceContributor final
523 : public MNAStateSpaceContributor {
524public:
525 explicit DPPh1InductorStateSpaceContributor(
526 std::shared_ptr<DP::Ph1::Inductor> component)
527 : mComponent(std::move(component)) {}
528
529 UInt getStateCount() const override { return 2; }
530
531 void stamp(Matrix &AdLocal, Matrix &BdMna, Matrix &CdMna, UInt stateOffset,
532 UInt mnaVectorSize) const override {
533 const Complex conductance = mComponent->getMNAConductance();
534 const Complex prevCurrentFactor =
535 calculateDPInductorPreviousCurrentFactor(conductance);
536
537 const Matrix K = buildSinglePhaseComplexInterfaceVoltageMapping(
538 *mComponent, mnaVectorSize);
539
540 // Complex history state h:
541 // i[k+1] = Y_L vIntf[k+1] + h[k]
542 // h[k+1] = alpha h[k] + (1 + alpha) Y_L vIntf[k+1]
543 // Therefore: AdLocal = alpha, BdMna = (1 + alpha) Y_L K,
544 // CdMna = -K^T in real-imaginary augmented form.
545 AdLocal.block(stateOffset, stateOffset, 2, 2) +=
546 realAugment(prevCurrentFactor);
547
548 BdMna.block(stateOffset, 0, 2, mnaVectorSize) +=
549 realAugment((Complex(1.0, 0.0) + prevCurrentFactor) * conductance) * K;
550
551 stampTwoTerminalCurrentInjectionMapping(K, CdMna, stateOffset,
552 Matrix::Identity(2, 2));
553 }
554
555 void contributeMetadata(StateSpaceMetadata &metadata,
556 UInt stateOffset) const override {
557 addSinglePhaseComplexStateMetadata(metadata, stateOffset,
558 mComponent->name());
559 }
560
561private:
562 std::shared_ptr<DP::Ph1::Inductor> mComponent;
563};
564
565class DPPh1CapacitorStateSpaceContributor final
566 : public MNAStateSpaceContributor {
567public:
568 explicit DPPh1CapacitorStateSpaceContributor(
569 std::shared_ptr<DP::Ph1::Capacitor> component)
570 : mComponent(std::move(component)) {}
571
572 UInt getStateCount() const override { return 2; }
573
574 void stamp(Matrix &AdLocal, Matrix &BdMna, Matrix &CdMna, UInt stateOffset,
575 UInt mnaVectorSize) const override {
576 const Complex conductance = mComponent->getMNAConductance();
577
578 const Matrix K = buildSinglePhaseComplexInterfaceVoltageMapping(
579 *mComponent, mnaVectorSize);
580
581 // Complex history state h:
582 // i[k+1] = Y_C vIntf[k+1] + h[k]
583 // h[k+1] = -h[k] - (Y_C + conj(Y_C)) vIntf[k+1]
584 // Therefore: AdLocal = -1, BdMna = -2 Re(Y_C) K,
585 // CdMna = -K^T in real-imaginary augmented form.
586 AdLocal.block(stateOffset, stateOffset, 2, 2) -= Matrix::Identity(2, 2);
587
588 BdMna.block(stateOffset, 0, 2, mnaVectorSize) -=
589 2.0 * conductance.real() * K;
590
591 stampTwoTerminalCurrentInjectionMapping(K, CdMna, stateOffset,
592 Matrix::Identity(2, 2));
593 }
594
595 void contributeMetadata(StateSpaceMetadata &metadata,
596 UInt stateOffset) const override {
597 addSinglePhaseComplexStateMetadata(metadata, stateOffset,
598 mComponent->name());
599 }
600
601private:
602 std::shared_ptr<DP::Ph1::Capacitor> mComponent;
603};
604
605class DPPh1TwoTerminalVTypeSSNStateSpaceContributor final
606 : public MNAStateSpaceContributor {
607public:
608 explicit DPPh1TwoTerminalVTypeSSNStateSpaceContributor(
609 std::shared_ptr<DP::VTypeSSNComp> component)
610 : mComponent(std::move(component)) {}
611
612 UInt getStateCount() const override {
613 return 2 * mComponent->getStateCount();
614 }
615
616 void stamp(Matrix &AdLocal, Matrix &BdMna, Matrix &CdMna, UInt stateOffset,
617 UInt mnaVectorSize) const override {
618 const UInt complexStateCount = mComponent->getStateCount();
619 const UInt realStateCount = getStateCount();
620
621 const MatrixComp &discreteA = mComponent->getDiscreteA();
622 const MatrixComp &discreteB = mComponent->getDiscreteB();
623 const MatrixComp outputC = mComponent->getC().cast<Complex>();
624
625 const Matrix K = buildSinglePhaseComplexInterfaceVoltageMapping(
626 *mComponent, mnaVectorSize);
627
628 // Complex history-coordinate state s = discreteA x + discreteB vIntf:
629 // s[k+1] = discreteA s[k] + (discreteA + I) discreteB vIntf[k+1]
630 // yHist[k] = C s[k]
631 // Therefore: AdLocal = discreteA, BdMna = (discreteA + I) discreteB K,
632 // CdMna = -K^T C in real-imaginary augmented form.
633 AdLocal.block(stateOffset, stateOffset, realStateCount, realStateCount) +=
634 realAugment(discreteA);
635
636 const MatrixComp inputUpdate =
637 (discreteA +
638 MatrixComp::Identity(complexStateCount, complexStateCount)) *
639 discreteB;
640
641 BdMna.block(stateOffset, 0, realStateCount, mnaVectorSize) +=
642 realAugment(inputUpdate) * K;
643
644 stampTwoTerminalCurrentInjectionMapping(K, CdMna, stateOffset,
645 realAugment(outputC));
646 }
647
648 void contributeMetadata(StateSpaceMetadata &metadata,
649 UInt stateOffset) const override {
650 addComplexStateMetadata(metadata, stateOffset, mComponent->getStateCount(),
651 mComponent->name());
652 }
653
654private:
655 std::shared_ptr<DP::VTypeSSNComp> mComponent;
656};
657
658class DPPh1MixedVTypeVariableSSNStateSpaceContributor final
659 : public MNAStateSpaceContributor {
660public:
661 explicit DPPh1MixedVTypeVariableSSNStateSpaceContributor(
662 std::shared_ptr<DP::Ph1::MixedVTypeVariableSSNComp> component)
663 : mComponent(std::move(component)) {}
664
665 UInt getStateCount() const override { return mComponent->getStateCount(); }
666
667 Bool contributesToUpdatedMatrices() const override { return true; }
668
669 void stamp(Matrix &AdLocal, Matrix &BdMna, Matrix &CdMna, UInt stateOffset,
670 UInt mnaVectorSize) const override {
671 const UInt localStateCount = getStateCount();
672
673 const Matrix &discreteA = mComponent->getDiscreteA();
674 const Matrix &discreteB = mComponent->getDiscreteB();
675 const Matrix &outputC = mComponent->getC();
676
677 const Matrix K = buildSinglePhaseComplexInterfaceVoltageMapping(
678 *mComponent, mnaVectorSize);
679
680 // Real packed history-coordinate state s = discreteA x + discreteB vIntf:
681 // s[k+1] = discreteA s[k] + (discreteA + I) discreteB vIntf[k+1]
682 // yHist[k] = C s[k]
683 // Therefore: AdLocal = discreteA, BdMna = (discreteA + I) discreteB K,
684 // CdMna = -K^T C.
685 AdLocal.block(stateOffset, stateOffset, localStateCount, localStateCount) +=
686 discreteA;
687
688 const Matrix inputUpdate =
689 (discreteA + Matrix::Identity(localStateCount, localStateCount)) *
690 discreteB;
691
692 BdMna.block(stateOffset, 0, localStateCount, mnaVectorSize) +=
693 inputUpdate * K;
694
695 stampTwoTerminalCurrentInjectionMapping(K, CdMna, stateOffset, outputC);
696 }
697
698 void contributeMetadata(StateSpaceMetadata &metadata,
699 UInt stateOffset) const override {
700 addRealStateMetadata(metadata, stateOffset, getStateCount(),
701 mComponent->name());
702 }
703
704private:
705 std::shared_ptr<DP::Ph1::MixedVTypeVariableSSNComp> mComponent;
706};
707
708class DPPh3InductorStateSpaceContributor final
709 : public MNAStateSpaceContributor {
710public:
711 explicit DPPh3InductorStateSpaceContributor(
712 std::shared_ptr<DP::Ph3::Inductor> component)
713 : mComponent(std::move(component)) {}
714
715 UInt getStateCount() const override { return 6; }
716
717 void stamp(Matrix &AdLocal, Matrix &BdMna, Matrix &CdMna, UInt stateOffset,
718 UInt mnaVectorSize) const override {
719 const MatrixComp &conductance = mComponent->getMNAConductance();
720 const Complex previousCurrentFactor =
721 mComponent->getPreviousCurrentFactor();
722 const MatrixComp identity = MatrixComp::Identity(3, 3);
723 const MatrixComp previousState = previousCurrentFactor * identity;
724 const MatrixComp inputUpdate =
725 (Complex(1.0, 0.0) + previousCurrentFactor) * conductance;
726 const Matrix K = buildThreePhaseComplexInterfaceVoltageMapping(
727 *mComponent, mnaVectorSize);
728
729 AdLocal.block(stateOffset, stateOffset, 6, 6) +=
730 realAugmentInterleaved(previousState);
731
732 BdMna.block(stateOffset, 0, 6, mnaVectorSize) +=
733 realAugmentInterleaved(inputUpdate) * K;
734
735 stampTwoTerminalCurrentInjectionMapping(K, CdMna, stateOffset,
736 Matrix::Identity(6, 6));
737 }
738
739 void contributeMetadata(StateSpaceMetadata &metadata,
740 UInt stateOffset) const override {
741 static constexpr std::array<const char *, 3> phaseNames = {"a", "b", "c"};
742
743 for (UInt phase = 0; phase < 3; ++phase) {
744 const String baseName = mComponent->name() + "_" + phaseNames[phase];
745 setStateName(metadata, stateOffset + 2 * phase, baseName + "_re");
746 setStateName(metadata, stateOffset + 2 * phase + 1, baseName + "_im");
747 }
748 }
749
750private:
751 std::shared_ptr<DP::Ph3::Inductor> mComponent;
752};
753
754class DPPh3MixedVTypeVariableSSNStateSpaceContributor final
755 : public MNAStateSpaceContributor {
756public:
757 explicit DPPh3MixedVTypeVariableSSNStateSpaceContributor(
758 std::shared_ptr<DP::Ph3::MixedVTypeVariableSSNComp> component)
759 : mComponent(std::move(component)) {}
760
761 UInt getStateCount() const override { return mComponent->getStateCount(); }
762
763 Bool contributesToUpdatedMatrices() const override { return true; }
764
765 void stamp(Matrix &AdLocal, Matrix &BdMna, Matrix &CdMna, UInt stateOffset,
766 UInt mnaVectorSize) const override {
767 const UInt localStateCount = getStateCount();
768 const Matrix &discreteA = mComponent->getDiscreteA();
769 const Matrix &discreteB = mComponent->getDiscreteB();
770 const Matrix &outputC = mComponent->getC();
771 const Matrix K = buildThreePhaseComplexInterfaceVoltageMapping(
772 *mComponent, mnaVectorSize);
773
774 AdLocal.block(stateOffset, stateOffset, localStateCount, localStateCount) +=
775 discreteA;
776
777 const Matrix inputUpdate =
778 (discreteA + Matrix::Identity(localStateCount, localStateCount)) *
779 discreteB;
780
781 BdMna.block(stateOffset, 0, localStateCount, mnaVectorSize) +=
782 inputUpdate * K;
783
784 stampTwoTerminalCurrentInjectionMapping(K, CdMna, stateOffset, outputC);
785 }
786
787 void contributeMetadata(StateSpaceMetadata &metadata,
788 UInt stateOffset) const override {
789 addRealStateMetadata(metadata, stateOffset, getStateCount(),
790 mComponent->name());
791 }
792
793private:
794 std::shared_ptr<DP::Ph3::MixedVTypeVariableSSNComp> mComponent;
795};
796
797} // namespace
798
800MNAStateSpaceContributorFactory::create(const MNAInterface::Ptr &component) {
801 if (!component)
802 return nullptr;
803
804 if (auto inductor =
805 std::dynamic_pointer_cast<EMT::Ph3::Inductor>(component)) {
806 return std::make_shared<EMTPh3InductorStateSpaceContributor>(inductor);
807 }
808
809 if (auto capacitor =
810 std::dynamic_pointer_cast<EMT::Ph3::Capacitor>(component)) {
811 return std::make_shared<EMTPh3CapacitorStateSpaceContributor>(capacitor);
812 }
813
814 if (auto variableSsn =
815 std::dynamic_pointer_cast<EMT::Ph3::TwoTerminalVTypeVariableSSNComp>(
816 component)) {
817 return std::make_shared<EMTPh3TwoTerminalVTypeSSNStateSpaceContributor>(
818 variableSsn, true);
819 }
820
821 if (auto splitSsn =
822 std::dynamic_pointer_cast<EMT::Ph3::TwoTerminalVTypeSplitSSNComp>(
823 component)) {
824 return std::make_shared<
825 EMTPh3TwoTerminalVTypeSplitSSNStateSpaceContributor>(splitSsn);
826 }
827
828 if (auto ssn = std::dynamic_pointer_cast<EMT::Ph3::TwoTerminalVTypeSSNComp>(
829 component)) {
830 return std::make_shared<EMTPh3TwoTerminalVTypeSSNStateSpaceContributor>(
831 ssn, false);
832 }
833
834 if (std::dynamic_pointer_cast<EMT::Ph3::Resistor>(component))
835 return nullptr;
836
837 if (std::dynamic_pointer_cast<EMT::Ph3::Switch>(component))
838 return nullptr;
839
840 if (std::dynamic_pointer_cast<EMT::Ph3::VoltageSource>(component))
841 return nullptr;
842
843 if (auto inductor = std::dynamic_pointer_cast<DP::Ph1::Inductor>(component)) {
844 return std::make_shared<DPPh1InductorStateSpaceContributor>(inductor);
845 }
846
847 if (auto capacitor =
848 std::dynamic_pointer_cast<DP::Ph1::Capacitor>(component)) {
849 return std::make_shared<DPPh1CapacitorStateSpaceContributor>(capacitor);
850 }
851
852 if (auto mixedVariableSsn =
853 std::dynamic_pointer_cast<DP::Ph1::MixedVTypeVariableSSNComp>(
854 component)) {
855 return std::make_shared<DPPh1MixedVTypeVariableSSNStateSpaceContributor>(
856 mixedVariableSsn);
857 }
858
859 if (auto ssn = std::dynamic_pointer_cast<DP::Ph1::TwoTerminalVTypeSSNComp>(
860 component)) {
861 return std::make_shared<DPPh1TwoTerminalVTypeSSNStateSpaceContributor>(ssn);
862 }
863
864 if (std::dynamic_pointer_cast<DP::Ph1::Resistor>(component))
865 return nullptr;
866
867 if (std::dynamic_pointer_cast<DP::Ph1::Switch>(component))
868 return nullptr;
869
870 if (std::dynamic_pointer_cast<DP::Ph1::VoltageSource>(component))
871 return nullptr;
872
873 if (auto inductor = std::dynamic_pointer_cast<DP::Ph3::Inductor>(component)) {
874 return std::make_shared<DPPh3InductorStateSpaceContributor>(inductor);
875 }
876
877 if (auto mixedVariableSsn =
878 std::dynamic_pointer_cast<DP::Ph3::MixedVTypeVariableSSNComp>(
879 component)) {
880 return std::make_shared<DPPh3MixedVTypeVariableSSNStateSpaceContributor>(
881 mixedVariableSsn);
882 }
883
884 if (std::dynamic_pointer_cast<DP::Ph3::Resistor>(component))
885 return nullptr;
886
887 if (std::dynamic_pointer_cast<DP::Ph3::VoltageSource>(component))
888 return nullptr;
889
890 throw std::invalid_argument(
891 "Unsupported component in MNA state-space extraction.");
892}
893
895 const MNAInterface::List &components) {
897
898 const auto appendContributor =
899 [&contributors](const MNAInterface::Ptr &component) {
900 auto contributor = MNAStateSpaceContributorFactory::create(component);
901
902 if (contributor)
903 contributors.push_back(std::move(contributor));
904 };
905
906 const auto appendCompositeContributors =
907 [&appendContributor](const auto &composite) {
908 for (const auto &subcomponent : composite->mnaSubComponents())
909 appendContributor(subcomponent);
910 };
911
912 for (const auto &component : components) {
913 if (const auto composite = getSupportedRealComposite(component)) {
914 appendCompositeContributors(composite);
915 continue;
916 }
917
918 if (const auto composite = getSupportedComplexComposite(component)) {
919 appendCompositeContributors(composite);
920 continue;
921 }
922
923 appendContributor(component);
924 }
925
926 return contributors;
927}
928
929} // namespace DPsim
std::vector< Ptr > List
Definition Attribute.h:123
std::vector< Ptr > List
std::shared_ptr< MNAInterface > Ptr
static MNAStateSpaceContributor::List createList(const CPS::MNAInterface::List &components)
std::shared_ptr< MNAStateSpaceContributor > Ptr
CPS::Complex Complex
Definition Definitions.h:19
CPS::Real Real
Definition Definitions.h:18
CPS::String String
Definition Definitions.h:20
CPS::Matrix Matrix
Definition Definitions.h:24
CPS::Bool Bool
Definition Definitions.h:21
CPS::UInt UInt
Definition Definitions.h:23
CPS::MatrixComp MatrixComp
Definition Definitions.h:25