DPsim
Loading...
Searching...
No Matches
EMT_Ph3_SSN_InductionMotor.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
3
4#include <algorithm>
5#include <cmath>
6#include <stdexcept>
7
8#include <Eigen/Eigenvalues>
9#include <Eigen/LU>
10
12
13using namespace CPS;
14
16 Logger::Level logLevel)
19 mStatorResistance(0.0), mRotorResistance(0.0), mLs(0.0), mLr_dash(0.0),
20 mLm(0.0), mRotorInertia(0.0), mMechanicalDamping(0.0),
30 mElectricalPower(mAttributes->create<Real>("electrical_power")),
31 mReactivePower(mAttributes->create<Real>("reactive_power")),
32 mElectricalTorque(mAttributes->create<Real>("electrical_torque")),
34 mAttributes->create<Real>("mechanical_load_torque")),
35 mMechanicalSpeedLog(mAttributes->create<Real>("mechanical_speed")),
36 mMechanicalSpeedPu(mAttributes->create<Real>("mechanical_speed_pu")),
37 mElectricalAngleLog(mAttributes->create<Real>("electrical_angle")),
38 mSlip(mAttributes->create<Real>("slip")),
39 mStatorCurrentD(mAttributes->create<Real>("stator_current_d")),
40 mStatorCurrentQ(mAttributes->create<Real>("stator_current_q")),
41 mStatorVoltageD(mAttributes->create<Real>("stator_voltage_d")),
42 mStatorVoltageQ(mAttributes->create<Real>("stator_voltage_q")),
44 mAttributes->create<Real>("stator_current_magnitude")),
46 mAttributes->create<Real>("stator_voltage_magnitude")) {
47
48 **mIntfVoltage = Matrix::Zero(mInputSize, 1);
49 **mIntfCurrent = Matrix::Zero(mOutputSize, 1);
50
51 **mElectricalPower = 0.0;
52 **mReactivePower = 0.0;
53 **mElectricalTorque = 0.0;
55 **mMechanicalSpeedLog = 0.0;
56 **mMechanicalSpeedPu = 0.0;
57 **mElectricalAngleLog = 0.0;
58 **mSlip = 0.0;
59 **mStatorCurrentD = 0.0;
60 **mStatorCurrentQ = 0.0;
61 **mStatorVoltageD = 0.0;
62 **mStatorVoltageQ = 0.0;
65}
66
68 return {"psi_sd", "psi_sq", "psi_dr",
69 "psi_qr", "mechanical_speed", "electrical_angle"};
70}
71
73 Real nominalFrequency, Int polePairs, Real statorResistance,
74 Real rotorResistance, Real statorInductance, Real rotorInductance,
75 Real mutualInductance, Real rotorInertia, Real mechanicalDamping,
76 Real mechanicalTorque, Real initialElectricalAngle,
77 Bool autoInitializeMechanicalTorque) {
78
79 if (nominalFrequency <= 0.0)
80 throw std::invalid_argument("Nominal frequency must be positive.");
81 if (polePairs <= 0)
82 throw std::invalid_argument("The number of pole pairs must be positive.");
83
84 if (statorResistance < 0.0)
85 throw std::invalid_argument("Stator resistance is invalid.");
86 if (rotorResistance < 0.0)
87 throw std::invalid_argument("Rotor resistance is invalid.");
88
89 if (statorInductance <= 0.0 || rotorInductance <= 0.0 ||
90 mutualInductance <= 0.0)
91 throw std::invalid_argument("Motor winding inductances must be positive.");
92
93 if (statorInductance <= mutualInductance ||
94 rotorInductance <= mutualInductance)
95 throw std::invalid_argument(
96 "Each total winding inductance must exceed its mutual inductance.");
97
98 if (rotorInertia <= 0.0)
99 throw std::invalid_argument("Rotor inertia must be positive.");
100 if (mechanicalDamping < 0.0)
101 throw std::invalid_argument("Mechanical damping must be non-negative.");
102
103 mNominalFrequency = nominalFrequency;
104 mPolePairs = polePairs;
106 2.0 * PI * mNominalFrequency / static_cast<Real>(mPolePairs);
107
108 mLm = mutualInductance;
109 mLs = statorInductance;
110 mLr_dash = rotorInductance;
111
112 mRotorResistance = rotorResistance;
113 mStatorResistance = statorResistance;
114
115 mRotorInertia = rotorInertia;
116 mMechanicalDamping = mechanicalDamping;
117 mMechanicalTorque = mechanicalTorque;
118 mInitialElectricalAngle = initialElectricalAngle;
119 mAutoInitializeMechanicalTorque = autoInitializeMechanicalTorque;
120
122
123 // Establish dimensions here. The actual local affine model is built after
124 // initialization at a physical operating point.
126 Matrix::Zero(mStateSize, mInputSize),
127 Matrix::Zero(mOutputSize, mStateSize),
128 Matrix::Zero(mOutputSize, mInputSize),
129 Matrix::Zero(mStateSize, 1),
130 Matrix::Zero(mOutputSize, 1));
131}
132
134 mInductanceMatrix.setZero();
135
136 Real denom = mLr_dash * mLs - mLm * mLm;
137 if (denom < DOUBLE_EPSILON)
138 throw std::invalid_argument(
139 "The induction motor inductance matrix is singular.");
140
141 // State order: [sd, sq, rd', kq'].
142 mInverseInductanceMatrix << mLr_dash, 0.0, -mLm, 0.0, 0.0, mLr_dash, 0.0,
143 -mLm, -mLm, 0.0, mLs, 0.0, 0.0, -mLm, 0.0, mLs;
144
145 mInverseInductanceMatrix *= 1. / denom;
146
147 Eigen::FullPivLU<Matrix> decomposition(mInverseInductanceMatrix);
148 if (!decomposition.isInvertible())
149 throw std::invalid_argument(
150 "The induction motor inverse inductance matrix is singular.");
151
152 mInductanceMatrix = decomposition.inverse();
153
154 Eigen::SelfAdjointEigenSolver<Matrix> eigenSolver(mInductanceMatrix);
155 if (eigenSolver.info() != Eigen::Success ||
156 eigenSolver.eigenvalues().minCoeff() <= 0.0)
157 throw std::invalid_argument(
158 "The induction motor inductance matrix is not positive definite.");
159
160 mResistanceMatrix.setZero();
163}
164
166 mMechanicalTorque = mechanicalTorque;
168}
169
171 Real relativeStep, Real absoluteStep) {
172 if (relativeStep <= 0.0)
173 throw std::invalid_argument(
174 "Relative finite-difference step must be positive.");
175 if (absoluteStep <= 0.0)
176 throw std::invalid_argument(
177 "Absolute finite-difference step must be positive.");
178
179 mJacobianRelativeStep = relativeStep;
180 mJacobianAbsoluteStep = absoluteStep;
181}
182
184 Real electricalAngle) const {
185 electricalAngle = std::remainder(electricalAngle, 2.0 * PI);
186
187 Matrix transform(2, 3);
188 const Real scale = std::sqrt(2.0 / 3.0);
189
190 transform.row(0) << scale * std::cos(electricalAngle),
191 scale * std::cos(electricalAngle - 2.0 * PI / 3.0),
192 scale * std::cos(electricalAngle + 2.0 * PI / 3.0);
193
194 transform.row(1) << -scale * std::sin(electricalAngle),
195 -scale * std::sin(electricalAngle - 2.0 * PI / 3.0),
196 -scale * std::sin(electricalAngle + 2.0 * PI / 3.0);
197
198 return transform;
199}
200
202 Real electricalAngle) const {
203 // For balanced abc quantities, the inverse of the orthonormal two-axis
204 // transform is its transpose.
205 return getParkTransformMatrix(electricalAngle).transpose();
206}
207
208Matrix
210 Matrix speedMatrix = Matrix::Zero(mElectricalStateSize, mElectricalStateSize);
211
212 speedMatrix(2, 3) = -electricalSpeed;
213 speedMatrix(3, 2) = electricalSpeed;
214
215 return speedMatrix;
216}
217
219 const Matrix &x, const Matrix &u, Matrix &stateDerivative) const {
220 if (x.rows() != mStateSize || x.cols() != 1)
221 throw std::invalid_argument(
222 "Induction motor state vector has an invalid dimension.");
223 if (u.rows() != mInputSize || u.cols() != 1)
224 throw std::invalid_argument(
225 "Induction motor input vector has an invalid dimension.");
226
227 stateDerivative.setZero(mStateSize, 1);
228
229 const Matrix flux = x.block(0, 0, mElectricalStateSize, 1);
230 const Real mechanicalSpeed = x(MechanicalSpeed, 0);
231 const Real electricalSpeed = static_cast<Real>(mPolePairs) * mechanicalSpeed;
232
233 const Matrix current = mInverseInductanceMatrix * flux;
234 const Real statorCurrentD = current(0, 0);
235 const Real statorCurrentQ = current(1, 0);
236
237 const Matrix voltageDq = getParkTransformMatrix(0.0) * u; //squirrel-cage
238
239 Matrix windingVoltage = Matrix::Zero(mElectricalStateSize, 1);
240 windingVoltage(0, 0) = voltageDq(0, 0);
241 windingVoltage(1, 0) = voltageDq(1, 0);
242 windingVoltage(2, 0) = 0.0; // rotor short-circuited
243 windingVoltage(3, 0) = 0.0;
244
245 const Matrix fluxDerivative = (-mResistanceMatrix * mInverseInductanceMatrix +
246 buildSpeedMatrix(electricalSpeed)) *
247 flux +
248 windingVoltage;
249
250 stateDerivative.block(0, 0, mElectricalStateSize, 1) = fluxDerivative;
251
252 const Real electricalTorque =
253 static_cast<Real>(mPolePairs) *
254 (flux(PsiSd, 0) * statorCurrentQ - flux(PsiSq, 0) * statorCurrentD);
255
256 // Current is positive entering the machine. In generator operation the
257 // electromagnetic torque is therefore normally negative. The applied shaft
258 // torque is positive in the direction of rotation.
259 stateDerivative(MechanicalSpeed, 0) =
260 (mMechanicalTorque + electricalTorque -
262 (mechanicalSpeed -
263 mNominalMechanicalSpeed)) / //steady state: synchronous speed as equilibrium point or slip?
265
266 stateDerivative(ElectricalAngle, 0) = electricalSpeed;
267}
268
270 const Matrix &u,
271 Matrix &output) const {
272 (void)u;
273
274 if (x.rows() != mStateSize || x.cols() != 1)
275 throw std::invalid_argument(
276 "Induction motor state vector has an invalid dimension.");
277
278 const Matrix flux = x.block(0, 0, mElectricalStateSize, 1);
279 const Matrix current = mInverseInductanceMatrix * flux;
280
281 Matrix statorCurrentDq(2, 1);
282 statorCurrentDq << current(0, 0), current(1, 0);
283
284 // Current entering the stator terminals, as required by the V-type stamp.
285 output = getInverseParkTransformMatrix(0.0) * statorCurrentDq;
286}
287
289 const Matrix &x, const Matrix &u, Matrix &A, Matrix &B, Matrix &C,
290 Matrix &D) const {
291 A.setZero(mStateSize, mStateSize);
292 B.setZero(mStateSize, mInputSize);
293 C.setZero(mOutputSize, mStateSize);
294 D.setZero(mOutputSize, mInputSize);
295
296 Matrix fPlus = Matrix::Zero(mStateSize, 1);
297 Matrix fMinus = Matrix::Zero(mStateSize, 1);
298 Matrix gPlus = Matrix::Zero(mOutputSize, 1);
299 Matrix gMinus = Matrix::Zero(mOutputSize, 1);
300
301 for (Int column = 0; column < mStateSize; ++column) {
302 const Real step =
304 mJacobianRelativeStep * std::max(1.0, std::abs(x(column, 0)));
305
306 Matrix xPlus = x;
307 Matrix xMinus = x;
308 xPlus(column, 0) += step;
309 xMinus(column, 0) -= step;
310
311 evaluateStateDerivative(xPlus, u, fPlus);
312 evaluateStateDerivative(xMinus, u, fMinus);
313 evaluateOutput(xPlus, u, gPlus);
314 evaluateOutput(xMinus, u, gMinus);
315
316 A.col(column) = (fPlus - fMinus) / (2.0 * step);
317 C.col(column) = (gPlus - gMinus) / (2.0 * step);
318 }
319
320 for (Int column = 0; column < mInputSize; ++column) {
321 const Real step =
323 mJacobianRelativeStep * std::max(1.0, std::abs(u(column, 0)));
324
325 Matrix uPlus = u;
326 Matrix uMinus = u;
327 uPlus(column, 0) += step;
328 uMinus(column, 0) -= step;
329
330 evaluateStateDerivative(x, uPlus, fPlus);
331 evaluateStateDerivative(x, uMinus, fMinus);
332 evaluateOutput(x, uPlus, gPlus);
333 evaluateOutput(x, uMinus, gMinus);
334
335 B.col(column) = (fPlus - fMinus) / (2.0 * step);
336 D.col(column) = (gPlus - gMinus) / (2.0 * step);
337 }
338}
339
341 const Matrix &x, const Matrix &u, Matrix &A, Matrix &B, Matrix &C,
342 Matrix &D, Matrix &E, Matrix &F) const {
343 calculateNumericalJacobians(x, u, A, B, C, D);
344
345 Matrix stateDerivative = Matrix::Zero(mStateSize, 1);
346 Matrix output = Matrix::Zero(mOutputSize, 1);
347
348 evaluateStateDerivative(x, u, stateDerivative);
349 evaluateOutput(x, u, output);
350
351 E = stateDerivative - A * x - B * u;
352 F = output - C * x - D * u;
353}
354
367
369 const Matrix &x = **mX;
370 const Matrix flux = x.block(0, 0, mElectricalStateSize, 1);
371 const Matrix current = mInverseInductanceMatrix * flux;
372 const Matrix voltageDq = getParkTransformMatrix(0.0) * u;
373
374 Matrix statorCurrentDq(2, 1);
375 statorCurrentDq << current(0, 0), current(1, 0);
376
377 const Real electricalTorque =
378 static_cast<Real>(mPolePairs) *
379 (flux(PsiSd, 0) * current(1, 0) - flux(PsiSq, 0) * current(0, 0));
380
381 const Real electricalPower = voltageDq(0, 0) * statorCurrentDq(0, 0) +
382 voltageDq(1, 0) * statorCurrentDq(1, 0);
383 const Real reactivePower = voltageDq(1, 0) * statorCurrentDq(0, 0) -
384 voltageDq(0, 0) * statorCurrentDq(1, 0);
385 const Real mechanicalLoadTorque =
388
389 **mElectricalPower = electricalPower;
390 **mReactivePower = reactivePower;
391 **mElectricalTorque = electricalTorque;
392 **mMechanicalLoadTorque = mechanicalLoadTorque;
395 **mElectricalAngleLog = std::remainder(x(ElectricalAngle, 0), 2.0 * PI);
398 **mStatorCurrentD = current(0, 0);
399 **mStatorCurrentQ = current(1, 0);
400 **mStatorVoltageD = voltageDq(0, 0);
401 **mStatorVoltageQ = voltageDq(1, 0);
402 **mStatorCurrentMagnitude = statorCurrentDq.norm();
403 **mStatorVoltageMagnitude = voltageDq.norm();
404}
405
407 Real frequency) {
408 if (!mParametersSet)
409 throw std::logic_error(
410 "setParameters() must be called before initialization.");
411
412 const MatrixComp initialVoltagePhasor = buildInitialInputFromNodes(frequency);
413 const Matrix initialVoltageAbc = initialVoltagePhasor.real();
414
415 Matrix x0 = Matrix::Zero(mStateSize, 1);
416 x0(MechanicalSpeed, 0) = 0.0;
418
419 **mX = x0;
420 **mIntfVoltage = initialVoltageAbc;
421
424 **mIntfCurrent = mW * (**mIntfVoltage) + mYHist;
426
427 SPDLOG_LOGGER_INFO(mSLog,
428 "Cold-start initialization: standstill, zero flux.");
429}
430
432
434 Matrix stateDerivative = Matrix::Zero(mStateSize, 1);
435 evaluateStateDerivative(**mX, **mIntfVoltage, stateDerivative);
436 return stateDerivative;
437}
438
442
void evaluateOutput(const Matrix &x, const Matrix &u, Matrix &output) const
Matrix buildSpeedMatrix(Real electricalSpeed) const
std::vector< String > getLocalStateNames() const override
void initializeFromNodesAndTerminals(Real frequency) override
Initializes Component variables according to power flow data stored in Nodes.
void evaluateStateDerivative(const Matrix &x, const Matrix &u, Matrix &stateDerivative) const
void setParameters(Real nominalFrequency, Int polePairs, Real statorResistance, Real rotorResistance, Real statorInductance, Real rotorInductance, Real mutualInductance, Real rotorInertia, Real mechanicalDamping, Real mechanicalTorque, Real initialElectricalAngle=0.0, Bool autoInitializeMechanicalTorque=true)
Configure the sixth-order machine.
SSN_InductionMotor(String uid, String name, Logger::Level logLevel=Logger::Level::off)
void updateLogAttributes(const Matrix &u) const override
Update derived attributes used for logging/inspection.
void buildStateSpaceModel(const Matrix &x, const Matrix &u, Matrix &A, Matrix &B, Matrix &C, Matrix &D, Matrix &E, Matrix &F) const
Matrix getInverseParkTransformMatrix(Real electricalAngle) const
Matrix getParkTransformMatrix(Real electricalAngle) const
void setNumericalLinearizationParameters(Real relativeStep, Real absoluteStep)
void calculateNumericalJacobians(const Matrix &x, const Matrix &u, Matrix &A, Matrix &B, Matrix &C, Matrix &D) const
TwoTerminalVTypeVariableSSNComp(String uid, String name, Logger::Level logLevel=Logger::Level::off)
const Attribute< Matrix >::Ptr mX
Definition EMT_SSNComp.h:43
Matrix calculateHistoryVector() const override final
void setParameters(const Matrix &A, const Matrix &B, const Matrix &C, const Matrix &D)
void updateStateSpaceModel() override final
Hook for variable/time-varying SSN components.
String uid()
Returns unique id.
AttributeList::Ptr mAttributes
Attribute List.
spdlog::level::level_enum Level
Definition Logger.h:33
const Attribute< MatrixVar< Real > >::Ptr mIntfCurrent
const Attribute< MatrixVar< Real > >::Ptr mIntfVoltage
bool mParametersSet
Flag indicating that parameters are set via setParameters() function.
Logger::Log mSLog
Component logger.
#define PI
Definition Definitions.h:43
#define DOUBLE_EPSILON
Definition Definitions.h:14
Eigen::Matrix< Real, Eigen::Dynamic, Eigen::Dynamic, Eigen::ColMajor > Matrix
Dense matrix for real numbers.
Definition Definitions.h:81
std::string String
Definition Definitions.h:65
double Real
Definition Definitions.h:62
int Int
Definition Definitions.h:61
Eigen::Matrix< Complex, Eigen::Dynamic, Eigen::Dynamic, Eigen::ColMajor > MatrixComp
Dense matrix for complex numbers.
Definition Definitions.h:84
bool Bool
Definition Definitions.h:64