DPsim
Loading...
Searching...
No Matches
EMT_Ph3_SSN_GFM.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
9
10using namespace CPS;
11
13 : TwoTerminalVTypeVariableSSNComp(uid, name, logLevel), mLf(0.0), mCf(0.0),
14 mRf(0.0), mRc(0.0), mOmegaN(0.0), mNominalVoltage(0.0), mPRef(0.0),
15 mQRef(0.0), mVirtualInertia(0.0), mDampingCoefficient(0.0),
16 mVoltageDroopGain(0.0), mReactiveIntegralGain(0.0), mKpVoltage(0.0),
17 mKiVoltage(0.0), mKpCurrent(0.0), mKiCurrent(0.0),
18 mActiveDampingGain(0.0), mPowerFilterCutoff(0.0), mDelayBandwidth(0.0),
19 mVirtualResistance(0.0), mVirtualReactance(0.0),
20 mGridCurrentFeedforward(1.0), mReactivePowerDroop(0.0),
21 mReactiveDroopCutoff(0.0), mVoltageSetpoint(0.0),
22 mJacobianRelativeStep(1e-6), mJacobianAbsoluteStep(1e-8),
23 mPInst(mAttributes->create<Real>("p_inst")),
24 mQInst(mAttributes->create<Real>("q_inst")),
25 mOmegaGFM(mAttributes->create<Real>("omega_gfm")),
26 mThetaGFM(mAttributes->create<Real>("theta_gfm")),
27 mVoltageMagnitudeGFM(mAttributes->create<Real>("voltage_magnitude_gfm")),
28 mVcD(mAttributes->create<Real>("vc_d")),
29 mVcQ(mAttributes->create<Real>("vc_q")),
30 mIGridD(mAttributes->create<Real>("i_grid_d")),
31 mIGridQ(mAttributes->create<Real>("i_grid_q")),
32 mIfD(mAttributes->create<Real>("if_d")),
33 mIfQ(mAttributes->create<Real>("if_q")),
34 mVoltageReferenceD(mAttributes->create<Real>("v_ref_d")),
35 mVoltageReferenceQ(mAttributes->create<Real>("v_ref_q")) {
36
37 **mIntfVoltage = Matrix::Zero(3, 1);
38 **mIntfCurrent = Matrix::Zero(3, 1);
39
40 **mPInst = 0.0;
41 **mQInst = 0.0;
42 **mOmegaGFM = 0.0;
43 **mThetaGFM = 0.0;
44 **mVoltageMagnitudeGFM = 0.0;
45
46 **mVcD = 0.0;
47 **mVcQ = 0.0;
48
49 **mIGridD = 0.0;
50 **mIGridQ = 0.0;
51
52 **mIfD = 0.0;
53 **mIfQ = 0.0;
54
55 **mVoltageReferenceD = 0.0;
56 **mVoltageReferenceQ = 0.0;
57}
58std::vector<String> EMT::Ph3::SSN_GFM::getLocalStateNames() const {
59 return {
60 "p_filtered",
61 "q_filtered",
62 "omega",
63 "theta",
64 "voltage_magnitude",
65
66 "voltage_integrator_d",
67 "voltage_integrator_q",
68
69 "current_integrator_d",
70 "current_integrator_q",
71
72 "delay_voltage_d",
73 "delay_voltage_q",
74
75 "vc_a",
76 "vc_b",
77 "vc_c",
78
79 "if_a",
80 "if_b",
81 "if_c",
82 };
83}
84
85std::vector<EMT::SSNComp::LocalAbcStateBlock>
87 return {
88 {{static_cast<Int>(VcA), static_cast<Int>(VcB), static_cast<Int>(VcC)},
89 "vc"},
90
91 {{static_cast<Int>(IfA), static_cast<Int>(IfB), static_cast<Int>(IfC)},
92 "if"},
93 };
94}
95
97 Real lf, Real cf, Real rf, Real rc, Real nominalVoltage, Real omegaN,
98 Real pRef, Real qRef, Real virtualInertia, Real dampingCoefficient,
99 Real voltageDroopGain, Real reactiveIntegralGain, Real kpVoltage,
100 Real kiVoltage, Real kpCurrent, Real kiCurrent, Real activeDampingGain,
101 Real powerFilterCutoff, Real delayBandwidth) {
102
103 if (lf <= 0.0)
104 throw std::invalid_argument("Filter inductance lf must be positive.");
105
106 if (cf <= 0.0)
107 throw std::invalid_argument("Filter capacitance cf must be positive.");
108
109 if (rf < 0.0)
110 throw std::invalid_argument("Filter resistance rf must be non-negative.");
111
112 if (rc <= 0.0)
113 throw std::invalid_argument("Coupling resistance rc must be positive.");
114
115 if (nominalVoltage <= 0.0)
116 throw std::invalid_argument("Nominal voltage must be positive.");
117
118 if (omegaN <= 0.0)
119 throw std::invalid_argument(
120 "Nominal angular frequency omegaN must be positive.");
121
122 if (virtualInertia <= 0.0)
123 throw std::invalid_argument("Virtual inertia must be positive.");
124
125 if (dampingCoefficient < 0.0)
126 throw std::invalid_argument("Damping coefficient must be non-negative.");
127
128 if (kiVoltage == 0.0)
129 throw std::invalid_argument(
130 "Voltage-controller integral gain must be non-zero.");
131
132 if (kiCurrent == 0.0)
133 throw std::invalid_argument(
134 "Current-controller integral gain must be non-zero.");
135
136 if (powerFilterCutoff < 0.0)
137 throw std::invalid_argument(
138 "Power-filter cutoff frequency must be non-negative.");
139
140 if (delayBandwidth <= 0.0)
141 throw std::invalid_argument("Delay bandwidth must be positive.");
142
143 mLf = lf;
144 mCf = cf;
145 mRf = rf;
146 mRc = rc;
147
148 mNominalVoltage = nominalVoltage;
149 mOmegaN = omegaN;
150
151 mPRef = pRef;
152 mQRef = qRef;
153
154 mVirtualInertia = virtualInertia;
155 mDampingCoefficient = dampingCoefficient;
156 mVoltageDroopGain = voltageDroopGain;
157 mReactiveIntegralGain = reactiveIntegralGain;
158
159 mKpVoltage = kpVoltage;
160 mKiVoltage = kiVoltage;
161
162 mKpCurrent = kpCurrent;
163 mKiCurrent = kiCurrent;
164
165 mActiveDampingGain = activeDampingGain;
166 mPowerFilterCutoff = powerFilterCutoff;
167 mDelayBandwidth = delayBandwidth;
168
169 // Only establish the dimensions here. Linearizing at x = 0, u = 0 is
170 // invalid for this model because omega, voltage magnitude and the Park
171 // transformation do not represent a physical operating point there.
172 // The first actual local model is created after operating-point
173 // initialization in initializeFromNodesAndTerminals().
174 VTypeVariableSSNComp::setParameters(Matrix::Zero(mStateSize, mStateSize),
175 Matrix::Zero(mStateSize, mInputSize),
176 Matrix::Zero(mOutputSize, mStateSize),
177 Matrix::Zero(mOutputSize, mInputSize),
178 Matrix::Zero(mStateSize, 1),
179 Matrix::Zero(mOutputSize, 1));
180}
181
183 Real absoluteStep) {
184
185 if (relativeStep <= 0.0)
186 throw std::invalid_argument(
187 "Relative finite-difference step must be positive.");
188
189 if (absoluteStep <= 0.0)
190 throw std::invalid_argument(
191 "Absolute finite-difference step must be positive.");
192
193 mJacobianRelativeStep = relativeStep;
194 mJacobianAbsoluteStep = absoluteStep;
195}
196
198 Real virtualReactance) {
199 if (virtualResistance < 0.0)
200 throw std::invalid_argument("Virtual resistance must be non-negative.");
201
202 mVirtualResistance = virtualResistance;
203 mVirtualReactance = virtualReactance;
204}
205
207 mGridCurrentFeedforward = scale;
208}
209
211 if (droopGain < 0.0)
212 throw std::invalid_argument("Reactive-droop gain must be non-negative.");
213 if (cutoff < 0.0)
214 throw std::invalid_argument("Reactive-droop cutoff must be non-negative.");
215
216 mReactivePowerDroop = droopGain;
217 mReactiveDroopCutoff = cutoff;
218}
219
220Matrix EMT::Ph3::SSN_GFM::getParkTransformMatrix(Real theta) const {
221
222 theta = std::remainder(theta, 2.0 * PI);
223 Matrix transform(2, 3);
224
225 constexpr Real scale = 2.0 / 3.0;
226
227 transform.row(0) << scale * std::cos(theta),
228 scale * std::cos(theta - 2.0 * PI / 3.0),
229 scale * std::cos(theta + 2.0 * PI / 3.0);
230
231 transform.row(1) << -scale * std::sin(theta),
232 -scale * std::sin(theta - 2.0 * PI / 3.0),
233 -scale * std::sin(theta + 2.0 * PI / 3.0);
234
235 return transform;
236}
237
238Matrix EMT::Ph3::SSN_GFM::getInverseParkTransformMatrix(Real theta) const {
239
240 theta = std::remainder(theta, 2.0 * PI);
241 Matrix transform(3, 2);
242
243 transform << std::cos(theta), -std::sin(theta),
244
245 std::cos(theta - 2.0 * PI / 3.0), -std::sin(theta - 2.0 * PI / 3.0),
246
247 std::cos(theta + 2.0 * PI / 3.0), -std::sin(theta + 2.0 * PI / 3.0);
248
249 return transform;
250}
251
252Real EMT::Ph3::SSN_GFM::regularizedOmega(Real omega) const {
253 constexpr Real minimumOmega = 1.0;
254
255 if (std::abs(omega) >= minimumOmega)
256 return omega;
257
258 return omega >= 0.0 ? minimumOmega : -minimumOmega;
259}
260
261void EMT::Ph3::SSN_GFM::evaluateStateDerivative(const Matrix &x,
262 const Matrix &u,
263 Matrix &stateDerivative) const {
264
265 if (x.rows() != mStateSize || x.cols() != 1)
266 throw std::invalid_argument(
267 "SSN_GFM state vector has an invalid dimension.");
268
269 if (u.rows() != mInputSize || u.cols() != 1)
270 throw std::invalid_argument(
271 "SSN_GFM input vector has an invalid dimension.");
272
273 stateDerivative.setZero(mStateSize, 1);
274
275 const Real pFiltered = x(PFiltered, 0);
276 const Real qFiltered = x(QFiltered, 0);
277 const Real omega = x(Omega, 0);
278 const Real theta = x(Theta, 0);
279 const Real voltageMagnitude = x(VoltageMagnitude, 0);
280
281 const Real voltageIntegratorD = x(VoltageIntegratorD, 0);
282 const Real voltageIntegratorQ = x(VoltageIntegratorQ, 0);
283
284 const Real currentIntegratorD = x(CurrentIntegratorD, 0);
285 const Real currentIntegratorQ = x(CurrentIntegratorQ, 0);
286
287 const Real delayVoltageD = x(DelayVoltageD, 0);
288 const Real delayVoltageQ = x(DelayVoltageQ, 0);
289
290 const Matrix vcAbc = x.block(VcA, 0, 3, 1);
291 const Matrix ifAbc = x.block(IfA, 0, 3, 1);
292
293 const Matrix parkTransform = getParkTransformMatrix(theta);
294 const Matrix inverseParkTransform = getInverseParkTransformMatrix(theta);
295
296 // Positive physical grid current means inverter injection into the grid.
297 const Matrix iGridAbc = (vcAbc - u) / mRc;
298
299 const Matrix vcDq = parkTransform * vcAbc;
300 const Matrix ifDq = parkTransform * ifAbc;
301 const Matrix iGridDq = parkTransform * iGridAbc;
302
303 const Real vcD = vcDq(0, 0);
304 const Real vcQ = vcDq(1, 0);
305
306 const Real ifD = ifDq(0, 0);
307 const Real ifQ = ifDq(1, 0);
308
309 const Real iGridD = iGridDq(0, 0);
310 const Real iGridQ = iGridDq(1, 0);
311
312 // Capacitor current.
313 const Real iCapD = ifD - iGridD;
314 const Real iCapQ = ifQ - iGridQ;
315
316 // The Park transformation is amplitude invariant (2/3 scaling).
317 // Therefore three-phase instantaneous power requires the factor 3/2.
318 const Real pInstantaneous = 1.5 * (vcD * iGridD + vcQ * iGridQ);
319
320 const Real qInstantaneous = 1.5 * (vcQ * iGridD - vcD * iGridQ);
321
322 const Real pccVoltageMagnitude = std::sqrt(vcD * vcD + vcQ * vcQ);
323
324 // ----------------------------------------------------------------------
325 // 1. Measurement filters
326 // ----------------------------------------------------------------------
327
328 stateDerivative(PFiltered, 0) =
329 mPowerFilterCutoff * (pInstantaneous - pFiltered);
330
331 stateDerivative(QFiltered, 0) =
332 mPowerFilterCutoff * (qInstantaneous - qFiltered);
333
334 // ----------------------------------------------------------------------
335 // 2. VSG algorithm
336 //
337 // J * omega_dot =
338 // (P_ref - P_e) / omega
339 // - D * (omega - omega_n)
340 //
341 // theta_dot = omega
342 // ----------------------------------------------------------------------
343
344 stateDerivative(Omega, 0) = ((mPRef - pFiltered) / regularizedOmega(omega) -
345 mDampingCoefficient * (omega - mOmegaN)) /
346 mVirtualInertia;
347
348 stateDerivative(Theta, 0) = omega;
349
350 // ----------------------------------------------------------------------
351 // 3. Reactive-power/voltage excitation controller
352 //
353 // E_dot =
354 // Kq * (Q_ref - Q_e)
355 // + Ku * (U_n - U_pcc)
356 // ----------------------------------------------------------------------
357
358 if (mReactiveDroopCutoff > 0.0) {
359 // Proportional Q-V droop (opt-in, grid-connected): E lags a droop target
360 // E_set + Dq*(Qref-Qf). No integral, so no reactive windup on a stiff grid.
361 const Real droopTarget =
362 mVoltageSetpoint + mReactivePowerDroop * (mQRef - qFiltered);
363 stateDerivative(VoltageMagnitude, 0) =
364 mReactiveDroopCutoff * (droopTarget - voltageMagnitude);
365 } else {
366 // Integral excitation (default, islanded).
367 stateDerivative(VoltageMagnitude, 0) =
368 mReactiveIntegralGain * (mQRef - qFiltered) +
369 mVoltageDroopGain * (mNominalVoltage - pccVoltageMagnitude);
370 }
371
372 // EMF minus virtual-impedance drop Zv * if (Zv = Rv + jXv; zero = islanded).
373 // if (a filter-current state) avoids the 1/Rc sensitivity of iGrid.
374 const Real voltageReferenceD =
375 voltageMagnitude - (mVirtualResistance * ifD - mVirtualReactance * ifQ);
376 const Real voltageReferenceQ =
377 -(mVirtualResistance * ifQ + mVirtualReactance * ifD);
378
379 const Real voltageErrorD = voltageReferenceD - vcD;
380 const Real voltageErrorQ = voltageReferenceQ - vcQ;
381
382 stateDerivative(VoltageIntegratorD, 0) = voltageErrorD;
383 stateDerivative(VoltageIntegratorQ, 0) = voltageErrorQ;
384
385 // ----------------------------------------------------------------------
386 // 4. Voltage controller
387 //
388 // The feed-forward and decoupling terms correspond to:
389 //
390 // Cf * dv_d/dt = if_d - ig_d + omega * Cf * v_q
391 // Cf * dv_q/dt = if_q - ig_q - omega * Cf * v_d
392 // ----------------------------------------------------------------------
393
394 const Real currentReferenceD =
395 mGridCurrentFeedforward * iGridD - omega * mCf * vcQ +
396 mKpVoltage * voltageErrorD + mKiVoltage * voltageIntegratorD;
397
398 const Real currentReferenceQ =
399 mGridCurrentFeedforward * iGridQ + omega * mCf * vcD +
400 mKpVoltage * voltageErrorQ + mKiVoltage * voltageIntegratorQ;
401
402 const Real currentErrorD = currentReferenceD - ifD;
403 const Real currentErrorQ = currentReferenceQ - ifQ;
404
405 stateDerivative(CurrentIntegratorD, 0) = currentErrorD;
406 stateDerivative(CurrentIntegratorQ, 0) = currentErrorQ;
407
408 // ----------------------------------------------------------------------
409 // 5. Current controller and active damping
410 //
411 // Lf * dif_d/dt =
412 // v_inv_d - vc_d - Rf * if_d + omega * Lf * if_q
413 //
414 // Lf * dif_q/dt =
415 // v_inv_q - vc_q - Rf * if_q - omega * Lf * if_d
416 // ----------------------------------------------------------------------
417
418 const Real converterVoltageReferenceD =
419 vcD - omega * mLf * ifQ + mKpCurrent * currentErrorD +
420 mKiCurrent * currentIntegratorD - mActiveDampingGain * iCapD;
421
422 const Real converterVoltageReferenceQ =
423 vcQ + omega * mLf * ifD + mKpCurrent * currentErrorQ +
424 mKiCurrent * currentIntegratorQ - mActiveDampingGain * iCapQ;
425
426 // ----------------------------------------------------------------------
427 // 6. First-order converter/digital-delay approximation
428 // ----------------------------------------------------------------------
429
430 stateDerivative(DelayVoltageD, 0) =
431 mDelayBandwidth * (converterVoltageReferenceD - delayVoltageD);
432
433 stateDerivative(DelayVoltageQ, 0) =
434 mDelayBandwidth * (converterVoltageReferenceQ - delayVoltageQ);
435
436 Matrix converterVoltageDq(2, 1);
437 converterVoltageDq << delayVoltageD, delayVoltageQ;
438
439 // ----------------------------------------------------------------------
440 // 7. Electrical filter model in abc coordinates
441 //
442 // vc_dot = (if + (u - vc) / Rc) / Cf
443 // if_dot = (v_inv - vc - Rf * if) / Lf
444 // ----------------------------------------------------------------------
445
446 const Matrix converterVoltageAbc = inverseParkTransform * converterVoltageDq;
447
448 const Matrix vcDerivative = (ifAbc + (u - vcAbc) / mRc) / mCf;
449
450 const Matrix ifDerivative = (converterVoltageAbc - vcAbc - mRf * ifAbc) / mLf;
451
452 stateDerivative.block(VcA, 0, 3, 1) = vcDerivative;
453 stateDerivative.block(IfA, 0, 3, 1) = ifDerivative;
454}
455
456void EMT::Ph3::SSN_GFM::evaluateOutput(const Matrix &x, const Matrix &u,
457 Matrix &output) const {
458
459 if (x.rows() != mStateSize || x.cols() != 1)
460 throw std::invalid_argument(
461 "SSN_GFM state vector has an invalid dimension.");
462
463 if (u.rows() != mInputSize || u.cols() != 1)
464 throw std::invalid_argument(
465 "SSN_GFM input vector has an invalid dimension.");
466
467 const Matrix vcAbc = x.block(VcA, 0, 3, 1);
468
469 // SSN current entering the component.
470 //
471 // The physical inverter injection current is:
472 //
473 // i_injection = (vc - u) / Rc
474 //
475 // The component current used by the SSN stamp is the opposite:
476 //
477 // y = -i_injection = (u - vc) / Rc
478 output = (u - vcAbc) / mRc;
479}
480
481void EMT::Ph3::SSN_GFM::calculateNumericalJacobians(const Matrix &x,
482 const Matrix &u, Matrix &A,
483 Matrix &B, Matrix &C,
484 Matrix &D) const {
485
486 A.setZero(mStateSize, mStateSize);
487 B.setZero(mStateSize, mInputSize);
488 C.setZero(mOutputSize, mStateSize);
489 D.setZero(mOutputSize, mInputSize);
490
491 Matrix fPlus = Matrix::Zero(mStateSize, 1);
492 Matrix fMinus = Matrix::Zero(mStateSize, 1);
493
494 Matrix gPlus = Matrix::Zero(mOutputSize, 1);
495 Matrix gMinus = Matrix::Zero(mOutputSize, 1);
496
497 // State Jacobians A and C.
498 for (Int column = 0; column < mStateSize; ++column) {
499 const Real step =
500 mJacobianAbsoluteStep +
501 mJacobianRelativeStep * std::max(1.0, std::abs(x(column, 0)));
502
503 Matrix xPlus = x;
504 Matrix xMinus = x;
505
506 xPlus(column, 0) += step;
507 xMinus(column, 0) -= step;
508
509 evaluateStateDerivative(xPlus, u, fPlus);
510 evaluateStateDerivative(xMinus, u, fMinus);
511
512 evaluateOutput(xPlus, u, gPlus);
513 evaluateOutput(xMinus, u, gMinus);
514
515 A.col(column) = (fPlus - fMinus) / (2.0 * step);
516 C.col(column) = (gPlus - gMinus) / (2.0 * step);
517 }
518
519 // Input Jacobians B and D.
520 for (Int column = 0; column < mInputSize; ++column) {
521 const Real step =
522 mJacobianAbsoluteStep +
523 mJacobianRelativeStep * std::max(1.0, std::abs(u(column, 0)));
524
525 Matrix uPlus = u;
526 Matrix uMinus = u;
527
528 uPlus(column, 0) += step;
529 uMinus(column, 0) -= step;
530
531 evaluateStateDerivative(x, uPlus, fPlus);
532 evaluateStateDerivative(x, uMinus, fMinus);
533
534 evaluateOutput(x, uPlus, gPlus);
535 evaluateOutput(x, uMinus, gMinus);
536
537 B.col(column) = (fPlus - fMinus) / (2.0 * step);
538 D.col(column) = (gPlus - gMinus) / (2.0 * step);
539 }
540}
541
542void EMT::Ph3::SSN_GFM::buildStateSpaceModel(const Matrix &x, const Matrix &u,
543 Matrix &A, Matrix &B, Matrix &C,
544 Matrix &D, Matrix &E,
545 Matrix &F) const {
546
547 calculateNumericalJacobians(x, u, A, B, C, D);
548
549 Matrix stateDerivative = Matrix::Zero(mStateSize, 1);
550 Matrix output = Matrix::Zero(mOutputSize, 1);
551
552 evaluateStateDerivative(x, u, stateDerivative);
553 evaluateOutput(x, u, output);
554
555 // Local affine offsets:
556 //
557 // E = f(x0,u0) - A*x0 - B*u0
558 // F = g(x0,u0) - C*x0 - D*u0
559 E = stateDerivative - A * x - B * u;
560 F = output - C * x - D * u;
561}
562
564 Matrix eVector;
565 Matrix fVector;
566
567 buildStateSpaceModel(**mX, **mIntfVoltage, mA, mB, mC, mD, eVector, fVector);
568
569 setStateOffset(eVector);
570 setOutputOffset(fVector);
571
572 // The dq/abc transformations and nonlinear GFM controls make the local
573 // state-space model time varying. The SSN equivalent must therefore be
574 // recomputed every simulation step.
575 return true;
576}
577
579
580 const Matrix &x = **mX;
581
582 const Real theta = x(Theta, 0);
583
584 const Matrix parkTransform = getParkTransformMatrix(theta);
585
586 const Matrix vcAbc = x.block(VcA, 0, 3, 1);
587 const Matrix ifAbc = x.block(IfA, 0, 3, 1);
588
589 const Matrix iGridAbc = (vcAbc - u) / mRc;
590
591 const Matrix vcDq = parkTransform * vcAbc;
592 const Matrix ifDq = parkTransform * ifAbc;
593 const Matrix iGridDq = parkTransform * iGridAbc;
594
595 const Real vcD = vcDq(0, 0);
596 const Real vcQ = vcDq(1, 0);
597
598 const Real iGridD = iGridDq(0, 0);
599 const Real iGridQ = iGridDq(1, 0);
600
601 const Real ifD = ifDq(0, 0);
602 const Real ifQ = ifDq(1, 0);
603
604 **mVcD = vcD;
605 **mVcQ = vcQ;
606
607 **mIGridD = iGridD;
608 **mIGridQ = iGridQ;
609
610 **mIfD = ifD;
611 **mIfQ = ifQ;
612
613 **mPInst = 1.5 * (vcD * iGridD + vcQ * iGridQ);
614 **mQInst = 1.5 * (vcQ * iGridD - vcD * iGridQ);
615
616 **mOmegaGFM = x(Omega, 0);
617 **mThetaGFM = theta;
618 **mVoltageMagnitudeGFM = x(VoltageMagnitude, 0);
619
620 // Reference the controller tracks, including the virtual-impedance drop.
621 **mVoltageReferenceD = x(VoltageMagnitude, 0) -
622 (mVirtualResistance * ifD - mVirtualReactance * ifQ);
623 **mVoltageReferenceQ = -(mVirtualResistance * ifQ + mVirtualReactance * ifD);
624}
625
627
628 if (!mParametersSet)
629 throw std::logic_error("setParameters() must be called before "
630 "initializeFromNodesAndTerminals().");
631
632 const Real omegaInitialization = 2.0 * PI * frequency;
633 const Complex imaginaryUnit(0.0, 1.0);
634
635 const Complex powerReference(mPRef, mQRef);
636
637 // Terminal voltage phasors using the inherited convention:
638 //
639 // u = terminal1 - terminal0
640 const MatrixComp uPhasor = buildInitialInputFromNodes(frequency);
641
642 MatrixComp vcPhasor = uPhasor;
643 MatrixComp iInjectionPhasor = MatrixComp::Zero(3, 1);
644
645 // Determine capacitor-voltage and injected-current phasors through the
646 // coupling resistance:
647 //
648 // vc = u + Rc * iInjection
649 for (Int iteration = 0; iteration < mInitializationMaxIterations;
650 ++iteration) {
651
652 const Complex vcA = vcPhasor(0, 0);
653
654 if (std::abs(vcA) < mInitializationTolerance) {
655 iInjectionPhasor.setZero();
656 break;
657 }
658
659 // With peak-valued phase phasors and the amplitude-invariant Park
660 // transform, total three-phase power is
661 //
662 // S = 1.5 * V_phase_peak * conj(I_phase_peak).
663 const Complex currentA = std::conj(powerReference / (1.5 * vcA));
664
665 MatrixComp nextInjectionCurrent(3, 1);
666
667 nextInjectionCurrent << currentA, currentA * SHIFT_TO_PHASE_B,
668 currentA * SHIFT_TO_PHASE_C;
669
670 const MatrixComp nextVcPhasor = uPhasor + mRc * nextInjectionCurrent;
671
672 iInjectionPhasor = nextInjectionCurrent;
673
674 if ((nextVcPhasor - vcPhasor).norm() < mInitializationTolerance) {
675 vcPhasor = nextVcPhasor;
676 break;
677 }
678
679 vcPhasor = nextVcPhasor;
680 }
681
682 // Capacitor-current relation:
683 //
684 // if = iGrid + j*omega*Cf*vc
685 const MatrixComp ifPhasor =
686 iInjectionPhasor + imaginaryUnit * omegaInitialization * mCf * vcPhasor;
687
688 // Converter bridge voltage:
689 //
690 // vInv = vc + (Rf + j*omega*Lf)*if
691 const MatrixComp converterVoltagePhasor =
692 vcPhasor + (mRf + imaginaryUnit * omegaInitialization * mLf) * ifPhasor;
693
694 const Matrix vcAbc0 = vcPhasor.real();
695 const Matrix ifAbc0 = ifPhasor.real();
696 const Matrix iGridAbc0 = iInjectionPhasor.real();
697 const Matrix converterVoltageAbc0 = converterVoltagePhasor.real();
698
699 // Align the dq frame with the internal EMF behind the virtual impedance,
700 // E0 = vc + (Rv + jXv)*if. At the operating point the voltage-loop reference
701 // is E0 in dq, so aligning to it makes both loop errors vanish (true
702 // equilibrium) even for nonzero Zv. Zv = 0 reduces to aligning with vc.
703 const Complex virtualImpedance(mVirtualResistance, mVirtualReactance);
704 const MatrixComp emfPhasor = vcPhasor + virtualImpedance * ifPhasor;
705 const Real theta0 = std::arg(emfPhasor(0, 0));
706
707 const Matrix parkTransform = getParkTransformMatrix(theta0);
708
709 const Matrix vcDq0 = parkTransform * vcAbc0;
710 const Matrix ifDq0 = parkTransform * ifAbc0;
711 const Matrix iGridDq0 = parkTransform * iGridAbc0;
712 const Matrix converterVoltageDq0 = parkTransform * converterVoltageAbc0;
713
714 const Real vcD0 = vcDq0(0, 0);
715 const Real vcQ0 = vcDq0(1, 0);
716
717 const Real ifD0 = ifDq0(0, 0);
718 const Real ifQ0 = ifDq0(1, 0);
719
720 const Real iGridD0 = iGridDq0(0, 0);
721 const Real iGridQ0 = iGridDq0(1, 0);
722
723 const Real pInitial = 1.5 * (vcD0 * iGridD0 + vcQ0 * iGridQ0);
724
725 const Real qInitial = 1.5 * (vcQ0 * iGridD0 - vcD0 * iGridQ0);
726
727 const Real iCapD0 = ifD0 - iGridD0;
728 const Real iCapQ0 = ifQ0 - iGridQ0;
729
730 Matrix x0 = Matrix::Zero(mStateSize, 1);
731
732 x0(PFiltered, 0) = pInitial;
733 x0(QFiltered, 0) = qInitial;
734
735 x0(Omega, 0) = omegaInitialization;
736 x0(Theta, 0) = theta0;
737
738 // Internal EMF magnitude behind the virtual impedance (d-axis aligned).
739 x0(VoltageMagnitude, 0) = std::abs(emfPhasor(0, 0));
740
741 // Proportional-droop setpoint: the operating EMF, so the droop is centered at
742 // the initial point (E_dot = 0 when Qf = Qref at t = 0).
743 mVoltageSetpoint = x0(VoltageMagnitude, 0);
744
745 // Voltage-loop references at the operating point, same form as
746 // evaluateStateDerivative(). With the E0-aligned frame both errors are ~0.
747 const Real voltageReferenceD0 =
748 x0(VoltageMagnitude, 0) -
749 (mVirtualResistance * ifD0 - mVirtualReactance * ifQ0);
750 const Real voltageReferenceQ0 =
751 -(mVirtualResistance * ifQ0 + mVirtualReactance * ifD0);
752 const Real voltageErrorD0 = voltageReferenceD0 - vcD0;
753 const Real voltageErrorQ0 = voltageReferenceQ0 - vcQ0;
754
755 // Voltage controller:
756 //
757 // iRefD =
758 // iGridD - omega*Cf*vcQ
759 // + KpV*(E-vcD)
760 // + KiV*xiVd
761 //
762 // Set iRefD = ifD and solve for xiVd.
763 x0(VoltageIntegratorD, 0) =
764 (ifD0 - mGridCurrentFeedforward * iGridD0 +
765 omegaInitialization * mCf * vcQ0 - mKpVoltage * voltageErrorD0) /
766 mKiVoltage;
767
768 // iRefQ =
769 // iGridQ + omega*Cf*vcD
770 // + KpV*(0-vcQ)
771 // + KiV*xiVq
772 //
773 // Set iRefQ = ifQ and solve for xiVq.
774 x0(VoltageIntegratorQ, 0) =
775 (ifQ0 - mGridCurrentFeedforward * iGridQ0 -
776 omegaInitialization * mCf * vcD0 - mKpVoltage * voltageErrorQ0) /
777 mKiVoltage;
778
779 // Current controller steady-state integrators.
780 //
781 // vInvD =
782 // vcD - omega*Lf*ifQ
783 // + KpI*(iRefD-ifD)
784 // + KiI*xiId
785 // - Kc*iCapD
786 x0(CurrentIntegratorD, 0) =
787 (converterVoltageDq0(0, 0) - vcD0 + omegaInitialization * mLf * ifQ0 +
788 mActiveDampingGain * iCapD0) /
789 mKiCurrent;
790
791 // vInvQ =
792 // vcQ + omega*Lf*ifD
793 // + KpI*(iRefQ-ifQ)
794 // + KiI*xiIq
795 // - Kc*iCapQ
796 x0(CurrentIntegratorQ, 0) =
797 (converterVoltageDq0(1, 0) - vcQ0 - omegaInitialization * mLf * ifD0 +
798 mActiveDampingGain * iCapQ0) /
799 mKiCurrent;
800
801 // At steady state, the first-order delay output equals its input.
802 x0(DelayVoltageD, 0) = converterVoltageDq0(0, 0);
803 x0(DelayVoltageQ, 0) = converterVoltageDq0(1, 0);
804
805 x0.block(VcA, 0, 3, 1) = vcAbc0;
806 x0.block(IfA, 0, 3, 1) = ifAbc0;
807
808 // Store the initialized state and terminal voltage.
809 **mX = x0;
810 **mIntfVoltage = uPhasor.real();
811
812 // Do not call updateComponentParameters() directly here.
813 //
814 // updateStateSpaceModel() performs:
815 // 1. updateComponentParameters()
816 // 2. recomputeDiscreteModel()
817 //
818 // This initializes mdA, mdB, mdE and mW.
820
821 // The MNA right-side-vector stamp uses mYHist. It must therefore already
822 // be valid before the first network solution.
824
825 // Use the same Norton-equivalent equation as the SSN network stamp:
826 //
827 // y = W*u + yHist
828 //
829 // This avoids an initialization mismatch between mIntfCurrent and the
830 // actual component stamp.
831
832 **mIntfCurrent = mW * (**mIntfVoltage) + mYHist;
833
835
836 Matrix stateDerivative = Matrix::Zero(mStateSize, 1);
837
838 evaluateStateDerivative(**mX, **mIntfVoltage, stateDerivative);
839
840 const Matrix nonlinearOutput = [&]() {
841 Matrix output = Matrix::Zero(mOutputSize, 1);
842 evaluateOutput(**mX, **mIntfVoltage, output);
843 return output;
844 }();
845
846 const Matrix ssnOutput = mW * (**mIntfVoltage) + mYHist;
847
848 SPDLOG_LOGGER_INFO(
849 mSLog,
850 "\n--- SSN GFM initialization ---"
851 "\nInput voltage u: {:s}"
852 "\nInterface current y: {:s}"
853 "\nState x: {:s}"
854 "\nState derivative norm: {:.6e}"
855 "\nNonlinear output: {:s}"
856 "\nSSN output: {:s}"
857 "\nOutput mismatch norm: {:.6e}"
858 "\nW norm: {:.6e}"
859 "\nHistory-vector norm: {:.6e}"
860 "\nP/Q initial: [{:.6e}, {:.6e}]"
861 "\nVc dq: [{:.6e}, {:.6e}]"
862 "\nIGrid dq: [{:.6e}, {:.6e}]"
863 "\nIf dq: [{:.6e}, {:.6e}]"
864 "\nConverter voltage dq: [{:.6e}, {:.6e}]"
865 "\n--- SSN GFM initialization finished ---",
868 stateDerivative.norm(), Logger::matrixToString(nonlinearOutput),
869 Logger::matrixToString(ssnOutput), (nonlinearOutput - ssnOutput).norm(),
870 mW.norm(), mYHist.norm(), pInitial, qInitial, vcD0, vcQ0, iGridD0,
871 iGridQ0, ifD0, ifQ0, converterVoltageDq0(0, 0),
872 converterVoltageDq0(1, 0));
873}
874
876
878 Matrix stateDerivative = Matrix::Zero(mStateSize, 1);
879
880 evaluateStateDerivative(**mX, **mIntfVoltage, stateDerivative);
881
882 return stateDerivative;
883}
884
886
void setNumericalLinearizationParameters(Real relativeStep, Real absoluteStep)
Configure finite-difference steps for local linearization.
SSN_GFM(String uid, String name, Logger::Level logLevel=Logger::Level::off)
Matrix getStateDerivative() const
Bool updateComponentParameters() override final
Matrix getInterfaceCurrent() const
std::vector< SSNComp::LocalAbcStateBlock > getLocalAbcStateBlocks() const override final
Matrix getInterfaceVoltage() const
void updateLogAttributes(const Matrix &u) const override final
Update derived attributes used for logging/inspection.
void setReactivePowerDroop(Real droopGain, Real cutoff)
Opt-in proportional Q-V droop replacing the integral excitation. droopGain is Dq [V/var]; cutoff [rad...
void setParameters(Real lf, Real cf, Real rf, Real rc, Real nominalVoltage, Real omegaN, Real pRef, Real qRef, Real virtualInertia, Real dampingCoefficient, Real voltageDroopGain, Real reactiveIntegralGain, Real kpVoltage, Real kiVoltage, Real kpCurrent, Real kiCurrent, Real activeDampingGain, Real powerFilterCutoff, Real delayBandwidth)
Configure the GFM model.
std::vector< String > getLocalStateNames() const override final
void setGridCurrentFeedforward(Real scale)
Scale on the voltage-loop grid-current feedforward (default 1).
void setVirtualImpedance(Real virtualResistance, Real virtualReactance=0.0)
Opt-in virtual output impedance Zv = Rv + jXv, subtracted from the EMF reference across the filter cu...
void initializeFromNodesAndTerminals(Real frequency) override final
Initializes Component variables according to power flow data stored in Nodes.
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)
static constexpr Real mInitializationTolerance
void updateStateSpaceModel() override final
Hook for variable/time-varying SSN components.
static constexpr Int mInitializationMaxIterations
String uid()
Returns unique id.
AttributeList::Ptr mAttributes
Attribute List.
spdlog::level::level_enum Level
Definition Logger.h:33
static String matrixToString(const Matrix &mat)
Definition Logger.cpp:31
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 SHIFT_TO_PHASE_C
Definition Definitions.h:47
#define SHIFT_TO_PHASE_B
Definition Definitions.h:46
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
std::complex< Real > Complex
Definition Definitions.h:63
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