DPsim
Loading...
Searching...
No Matches
StateSpaceModalAnalysis.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 <Eigen/Eigenvalues>
5#include <cmath>
6#include <dpsim/StateSpaceModalAnalysis.h>
7#include <limits>
8#include <stdexcept>
9
10namespace DPsim {
11
12namespace {
13
14Matrix parkTransformDQ0(Real theta) {
15 Matrix transform(3, 3);
16
17 const Real k = std::sqrt(2.0 / 3.0);
18 const Real k0 = 1.0 / std::sqrt(3.0);
19
20 transform.row(0) << k * std::cos(theta), k * std::cos(theta - 2.0 * PI / 3.0),
21 k * std::cos(theta + 2.0 * PI / 3.0);
22
23 transform.row(1) << -k * std::sin(theta),
24 -k * std::sin(theta - 2.0 * PI / 3.0),
25 -k * std::sin(theta + 2.0 * PI / 3.0);
26
27 transform.row(2) << k0, k0, k0;
28
29 return transform;
30}
31
32} // namespace
33
34StateSpaceModalAnalysis::StateSpaceModalAnalysis(
35 const MNAStateSpaceExtractor &extractor)
36 : mExtractor(extractor) {}
37
39 if (!mExtractor.isInitialized())
40 throw std::logic_error("StateSpaceModalAnalysis requires an initialized "
41 "MNAStateSpaceExtractor.");
42
43 const Matrix Ad = buildDiscreteStateMatrixInAnalysisFrame();
44
45 if (Ad.rows() == 0) {
46 mDiscreteEigenvalues.resize(0);
47 mContinuousEigenvalues.resize(0);
48 return;
49 }
50
51 Eigen::EigenSolver<Matrix> eigenSolver(Ad, false);
52
53 if (eigenSolver.info() != Eigen::Success)
54 throw std::runtime_error(
55 "StateSpaceModalAnalysis: eigenvalue computation failed.");
56
57 mDiscreteEigenvalues = eigenSolver.eigenvalues();
58
59 mContinuousEigenvalues.resize(mDiscreteEigenvalues.rows());
60
61 for (Eigen::Index idx = 0; idx < mDiscreteEigenvalues.rows(); ++idx)
62 mContinuousEigenvalues(idx) =
63 mapDiscreteToContinuous(mDiscreteEigenvalues(idx));
64}
65
66Matrix
67StateSpaceModalAnalysis::buildDiscreteStateMatrixInAnalysisFrame() const {
68 const Matrix &nativeAd = mExtractor.getDiscreteStateMatrix();
69
70 if (mAnalysisFrame == StateSpaceAnalysisFrame::Native)
71 return nativeAd;
72
73 if (mAnalysisFrame == StateSpaceAnalysisFrame::GlobalDQ0) {
74 if (!mExtractor.hasExtractionTime()) {
75 throw std::logic_error(
76 "GlobalDQ0 modal analysis requires a valid extraction timestamp.");
77 }
78
79 if (mGlobalOmega <= 0.0) {
80 throw std::logic_error(
81 "GlobalDQ0 modal analysis requires a positive frame angular speed.");
82 }
83
84 const Real time = mExtractor.getLastExtractionTime();
85 const Real timeStep = mExtractor.getTimeStep();
86
87 const Real thetaNow = mGlobalTheta0 + mGlobalOmega * time;
88 const Real thetaNext = thetaNow + mGlobalOmega * timeStep;
89
90 const Matrix transformNow = buildGlobalDq0Transformation(thetaNow);
91 const Matrix transformNext = buildGlobalDq0Transformation(thetaNext);
92
93 // For a time-dependent discrete coordinate transformation
94 // xGlobalDq0[k] = T[k] xNative[k], the transformed transition matrix is
95 // AdGlobalDq0[k] = T[k+1] AdNative[k] T[k]^{-1}.
96 //
97 // The Park transform is power-invariant, so T^{-1} = T^T.
98 return transformNext * nativeAd * transformNow.transpose();
99 }
100
101 throw std::logic_error("Unsupported state-space analysis frame.");
102}
103
104Matrix StateSpaceModalAnalysis::buildGlobalDq0Transformation(Real theta) const {
105 const UInt stateCount = mExtractor.getStateCount();
106
107 Matrix transform = Matrix::Identity(stateCount, stateCount);
108
109 const Matrix park = parkTransformDQ0(theta);
110
111 for (const auto &abcBlock : mExtractor.getMetadata().abcStateIndexTriples) {
112 for (UInt row = 0; row < 3; ++row) {
113 for (UInt col = 0; col < 3; ++col) {
114 transform(abcBlock[row], abcBlock[col]) = park(row, col);
115 }
116 }
117 }
118
119 return transform;
120}
121
122CPS::Complex
123StateSpaceModalAnalysis::mapDiscreteToContinuous(const CPS::Complex &z) const {
124 const CPS::Complex one(1.0, 0.0);
125 const CPS::Complex denominator = z + one;
126
127 if (std::abs(denominator) <= DOUBLE_EPSILON)
128 return CPS::Complex(std::numeric_limits<Real>::infinity(), 0.0);
129
130 return (2.0 / mExtractor.getTimeStep()) * (z - one) / denominator;
131}
132
133} // namespace DPsim
void update()
Update modal quantities from the current extracted state matrix.