DPsim
Loading...
Searching...
No Matches
DP_Ph3_AvVoltSourceInverterStateSpace.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 <cmath>
5#include <stdexcept>
6
9
10using namespace CPS;
11
12namespace {
13const Real K23 = std::sqrt(2.0 / 3.0);
14const Real K32 = std::sqrt(1.5);
15} // namespace
16
18 String uid, String name, Logger::Level logLevel, Bool enableNegSeqControl)
19 : MixedVTypeVariableSSNComp(uid, name, enableNegSeqControl ? 10 : 8, 6,
20 logLevel),
21 mEnableNegSeqControl(enableNegSeqControl),
22 mVcD(mAttributes->create<Real>("vc_d")),
23 mVcQ(mAttributes->create<Real>("vc_q")),
24 mIrcD(mAttributes->create<Real>("irc_d")),
25 mIrcQ(mAttributes->create<Real>("irc_q")),
26 mPInst(mAttributes->create<Real>("p_inst")),
27 mQInst(mAttributes->create<Real>("q_inst")),
28 mOmegaPLL(mAttributes->create<Real>("omega_pll")),
29 mIrcNd(mAttributes->create<Real>("irc_n_d")),
30 mIrcNq(mAttributes->create<Real>("irc_n_q")) {
31 **mVcD = 0.0;
32 **mVcQ = 0.0;
33 **mIrcD = 0.0;
34 **mIrcQ = 0.0;
35 **mPInst = 0.0;
36 **mQInst = 0.0;
37 **mOmegaPLL = 0.0;
38 **mIrcNd = 0.0;
39 **mIrcNq = 0.0;
40}
41
43 Real lf, Real cf, Real rf, Real rc, Real omegaN, Real kpPLL, Real kiPLL,
44 Real omegaCutoff, Real pRef, Real qRef, Real kpPowerCtrl, Real kiPowerCtrl,
45 Real kpCurrCtrl, Real kiCurrCtrl, Real iRefNd, Real iRefNq) {
46 if (lf <= 0.0)
47 throw std::invalid_argument("Filter inductance lf must be positive.");
48
49 if (cf <= 0.0)
50 throw std::invalid_argument("Filter capacitance cf must be positive.");
51
52 if (rf < 0.0)
53 throw std::invalid_argument("Filter resistance rf must be non-negative.");
54
55 if (rc <= 0.0)
56 throw std::invalid_argument("Coupling resistance rc must be positive.");
57
58 if (omegaN <= 0.0)
59 throw std::invalid_argument(
60 "Nominal angular frequency omegaN must be positive.");
61
62 if (omegaCutoff < 0.0)
63 throw std::invalid_argument(
64 "Power-filter cutoff frequency omegaCutoff must be non-negative.");
65
66 if (kiPLL == 0.0)
67 throw std::invalid_argument("PLL integral gain kiPLL must be non-zero.");
68
69 if (kiPowerCtrl == 0.0)
70 throw std::invalid_argument(
71 "Power-control integral gain kiPowerCtrl must be non-zero.");
72
73 if (kiCurrCtrl == 0.0)
74 throw std::invalid_argument(
75 "Current-control integral gain kiCurrCtrl must be non-zero.");
76
77 mLf = lf;
78 mCf = cf;
79 mRf = rf;
80 mRc = rc;
81
82 mOmegaN = omegaN;
83 mKpPLL = kpPLL;
84 mKiPLL = kiPLL;
85
86 mOmegaCutoff = omegaCutoff;
87 mPRef = pRef;
88 mQRef = qRef;
89 mKpPowerCtrl = kpPowerCtrl;
90 mKiPowerCtrl = kiPowerCtrl;
91 mKpCurrCtrl = kpCurrCtrl;
92 mKiCurrCtrl = kiCurrCtrl;
93 mIRefNd = iRefNd;
94 mIRefNq = iRefNq;
95
96 const Matrix x0 = Matrix::Zero(stateSize(), 1);
97 const Matrix u0 = Matrix::Zero(6, 1);
98
99 Matrix aMatrix, bMatrix, cMatrix, dMatrix, eVector, fVector;
100 buildStateSpaceModel(x0, u0, aMatrix, bMatrix, cMatrix, dMatrix, eVector,
101 fVector);
102
103 MixedVTypeVariableSSNComp::setParameters(aMatrix, bMatrix, cMatrix, dMatrix,
104 eVector, fVector);
105}
106
107DP::Ph3::AvVoltSourceInverterStateSpace::NegSeqTerms
108DP::Ph3::AvVoltSourceInverterStateSpace::computeNegSeqTerms(
109 const Matrix &x, const Complex3 &vc, const Complex3 &uEnv,
110 const Complex3 &redistFactor, const Complex3 &projCoeff,
111 const Complex &expJPsi) const {
112 NegSeqTerms terms;
113
114 // Negative-sequence measurement from nV = vc_a + a^2*vc_b + a*vc_c.
115 Complex nV(0.0, 0.0);
116 Complex nU(0.0, 0.0);
117 for (Int p = 0; p < 3; ++p) {
118 nV += redistFactor[p] * vc[p];
119 nU += redistFactor[p] * uEnv[p];
120 }
121
122 const Complex nI = (nV - nU) / mRc;
123 terms.ircNDQ = 0.5 * K23 * expJPsi * std::conj(nI);
124
125 // hIrcN[p] is the conj(vc_p) coefficient of ircNDQ.
126 for (Int p = 0; p < 3; ++p)
127 terms.hIrcN[p] = 0.5 * K23 * expJPsi * projCoeff[p] / mRc;
128
129 const Real vRefNd = -mKpCurrCtrl * terms.ircNDQ.real() +
130 mKiCurrCtrl * x(GammaND, 0) + mKpCurrCtrl * mIRefNd;
131 const Real vRefNq = -mKpCurrCtrl * terms.ircNDQ.imag() +
132 mKiCurrCtrl * x(GammaNQ, 0) + mKpCurrCtrl * mIRefNq;
133 terms.vRefNDQ = Complex(vRefNd, vRefNq);
134 terms.vRefNEnv0 = K23 * std::conj(terms.vRefNDQ) * expJPsi;
135
136 return terms;
137}
138
139void DP::Ph3::AvVoltSourceInverterStateSpace::buildStateSpaceModel(
140 const Matrix &x, const Matrix &u, Matrix &A, Matrix &B, Matrix &C,
141 Matrix &D, Matrix &E, Matrix &F) const {
142 // Unpack the operating point. psi := theta0 - thetaN (never a state itself).
143 const Real psi = x(Psi, 0);
144 const Real phiPLL = x(PhiPLL, 0);
145 const Real pF = x(PFiltered, 0);
146 const Real qF = x(QFiltered, 0);
147 const Real phiD = x(PhiD, 0);
148 const Real phiQ = x(PhiQ, 0);
149 const Real gammaD = x(GammaD, 0);
150 const Real gammaQ = x(GammaQ, 0);
151
152 const Complex j(0.0, 1.0);
153
154 Complex3 vc;
155 Complex3 iF;
156 for (Int p = 0; p < 3; ++p) {
157 vc[p] = Complex(x(mVcReCol[p], 0), x(mVcImCol[p], 0));
158 iF[p] = Complex(x(mIfReCol[p], 0), x(mIfImCol[p], 0));
159 }
160 const Complex3 uEnv = {Complex(u(0, 0), u(1, 0)), Complex(u(2, 0), u(3, 0)),
161 Complex(u(4, 0), u(5, 0))};
162
163 // Positive-sequence projection coefficients and inverse-Park redistribution factors.
164 const Complex3 projCoeff = {Complex(1.0, 0.0), SHIFT_TO_PHASE_C,
166 const Complex3 redistFactor = {std::conj(projCoeff[0]),
167 std::conj(projCoeff[1]),
168 std::conj(projCoeff[2])};
169
170 const Complex rot = std::exp(-j * psi);
171 const Complex expJPsi = std::conj(rot);
172
173 // Positive-sequence dq measurements from pV = vc_a + a*vc_b + a^2*vc_c.
174 Complex pV(0.0, 0.0);
175 Complex pU(0.0, 0.0);
176 for (Int p = 0; p < 3; ++p) {
177 pV += projCoeff[p] * vc[p];
178 pU += projCoeff[p] * uEnv[p];
179 }
180 const Complex pI = (pV - pU) / mRc;
181
182 const Complex vcDQ = 0.5 * K23 * rot * pV;
183 const Complex ircDQ = 0.5 * K23 * rot * pI;
184 const Real vcD = vcDQ.real();
185 const Real vcQ = vcDQ.imag();
186 const Real ircD = ircDQ.real();
187 const Real ircQ = ircDQ.imag();
188
189 // gVc[p] = d(vcDQ)/d(vc_p); gIrcVc/gIrcU = d(ircDQ)/d(vc_p), d(ircDQ)/d(u_p).
190 Complex3 gVc;
191 Complex3 gIrcVc;
192 Complex3 gIrcU;
193 for (Int p = 0; p < 3; ++p) {
194 gVc[p] = 0.5 * K23 * rot * projCoeff[p];
195 gIrcVc[p] = gVc[p] / mRc;
196 gIrcU[p] = -gVc[p] / mRc;
197 }
198
199 // Power: p+jq = vcDQ*conj(ircDQ); psi-independent, so no Psi column below.
200 const Complex pq = vcDQ * std::conj(ircDQ);
201 const Real pInst = pq.real();
202 const Real qInst = pq.imag();
203
204 // Outer power control and inner current control, single dq frame.
205 const Real iRefD =
206 -mKpPowerCtrl * pF + mKiPowerCtrl * phiD + mKpPowerCtrl * mPRef;
207 const Real iRefQ =
208 mKpPowerCtrl * qF + mKiPowerCtrl * phiQ - mKpPowerCtrl * mQRef;
209 const Complex iRefDQ(iRefD, iRefQ);
210 const Complex gammaDQ(gammaD, gammaQ);
211
212 const Complex vRefDQ =
213 -mKpCurrCtrl * ircDQ + mKiCurrCtrl * gammaDQ + mKpCurrCtrl * iRefDQ;
214
215 // Positive-sequence bridge-voltage reference, distributed via the inverse Park.
216 const Complex vRefEnv0 = K23 * vRefDQ * expJPsi;
217
218 // Negative-sequence measurement + PI control (baseband +j*psi loop). Left at
219 // its zero default when the loop is off, which drops it out of everything below.
220 NegSeqTerms neg;
221 if (mEnableNegSeqControl)
222 neg = computeNegSeqTerms(x, vc, uEnv, redistFactor, projCoeff, expJPsi);
223
224 const Real ircND = neg.ircNDQ.real();
225 const Real ircNQ = neg.ircNDQ.imag();
226
227 // Total reference: positive (redistFactor) + negative (projCoeff) injection.
228 Complex3 vRef;
229 for (Int p = 0; p < 3; ++p)
230 vRef[p] = redistFactor[p] * vRefEnv0 + projCoeff[p] * neg.vRefNEnv0;
231
232 // RHS f(x,u) (x_dot = f(x,u)).
233 Complex3 vcDot;
234 Complex3 ifDot;
235 for (Int p = 0; p < 3; ++p) {
236 vcDot[p] =
237 iF[p] / mCf + (uEnv[p] - vc[p]) / (mCf * mRc) - j * mOmegaN * vc[p];
238 ifDot[p] = (vRef[p] - vc[p] - mRf * iF[p]) / mLf - j * mOmegaN * iF[p];
239 }
240
241 Matrix f = Matrix::Zero(stateSize(), 1);
242 f(Psi, 0) = mKpPLL * vcQ + mKiPLL * phiPLL;
243 f(PhiPLL, 0) = vcQ;
244 f(PFiltered, 0) = mOmegaCutoff * (pInst - pF);
245 f(QFiltered, 0) = mOmegaCutoff * (qInst - qF);
246 f(PhiD, 0) = mPRef - pF;
247 f(PhiQ, 0) = qF - mQRef;
248 f(GammaD, 0) = iRefD - ircD;
249 f(GammaQ, 0) = iRefQ - ircQ;
250 if (mEnableNegSeqControl) {
251 f(GammaND, 0) = mIRefNd - ircND;
252 f(GammaNQ, 0) = mIRefNq - ircNQ;
253 }
254 for (Int p = 0; p < 3; ++p) {
255 f(mVcReCol[p], 0) = vcDot[p].real();
256 f(mVcImCol[p], 0) = vcDot[p].imag();
257 f(mIfReCol[p], 0) = ifDot[p].real();
258 f(mIfImCol[p], 0) = ifDot[p].imag();
259 }
260
261 // Analytic Jacobian A = df/dx, B = df/du.
262 A = Matrix::Zero(stateSize(), stateSize());
263 B = Matrix::Zero(stateSize(), 6);
264
265 // PLL rows: only vcQ feeds them; d(vcQ)/dpsi = -vcD (d(vcDQ)/dpsi = -j*vcDQ).
266 A(Psi, Psi) = mKpPLL * (-vcD);
267 A(Psi, PhiPLL) = mKiPLL;
268 A(PhiPLL, Psi) = -vcD;
269 for (Int p = 0; p < 3; ++p) {
270 const Real dVcQdRe = gVc[p].imag();
271 const Real dVcQdIm = gVc[p].real();
272 A(Psi, mVcReCol[p]) = mKpPLL * dVcQdRe;
273 A(Psi, mVcImCol[p]) = mKpPLL * dVcQdIm;
274 A(PhiPLL, mVcReCol[p]) = dVcQdRe;
275 A(PhiPLL, mVcImCol[p]) = dVcQdIm;
276 }
277
278 // Power-filter rows: d(p+jq)/dv = dvcDQ/dv*conj(ircDQ) + vcDQ*conj(dircDQ/dv). No Psi column.
279 A(PFiltered, PFiltered) = -mOmegaCutoff;
280 A(QFiltered, QFiltered) = -mOmegaCutoff;
281 for (Int p = 0; p < 3; ++p) {
282 const Complex dpqVcRe =
283 gVc[p] * std::conj(ircDQ) + vcDQ * std::conj(gIrcVc[p]);
284 const Complex dpqVcIm =
285 (j * gVc[p]) * std::conj(ircDQ) + vcDQ * std::conj(j * gIrcVc[p]);
286 A(PFiltered, mVcReCol[p]) = mOmegaCutoff * dpqVcRe.real();
287 A(QFiltered, mVcReCol[p]) = mOmegaCutoff * dpqVcRe.imag();
288 A(PFiltered, mVcImCol[p]) = mOmegaCutoff * dpqVcIm.real();
289 A(QFiltered, mVcImCol[p]) = mOmegaCutoff * dpqVcIm.imag();
290
291 const Complex dpqURe = vcDQ * std::conj(gIrcU[p]);
292 const Complex dpqUIm = vcDQ * std::conj(j * gIrcU[p]);
293 B(PFiltered, mUReCol[p]) = mOmegaCutoff * dpqURe.real();
294 B(QFiltered, mUReCol[p]) = mOmegaCutoff * dpqURe.imag();
295 B(PFiltered, mUImCol[p]) = mOmegaCutoff * dpqUIm.real();
296 B(QFiltered, mUImCol[p]) = mOmegaCutoff * dpqUIm.imag();
297 }
298
299 A(PhiD, PFiltered) = -1.0;
300 A(PhiQ, QFiltered) = 1.0;
301
302 // Current-control integrators: gammaD_dot = iRefD - ircD, etc.
303 A(GammaD, PFiltered) = -mKpPowerCtrl;
304 A(GammaD, PhiD) = mKiPowerCtrl;
305 A(GammaD, Psi) = -ircQ;
306 A(GammaQ, QFiltered) = mKpPowerCtrl;
307 A(GammaQ, PhiQ) = mKiPowerCtrl;
308 A(GammaQ, Psi) = ircD;
309 for (Int p = 0; p < 3; ++p) {
310 const Real dIrcDdVcRe = gIrcVc[p].real();
311 const Real dIrcDdVcIm = -gIrcVc[p].imag();
312 const Real dIrcQdVcRe = gIrcVc[p].imag();
313 const Real dIrcQdVcIm = gIrcVc[p].real();
314 A(GammaD, mVcReCol[p]) = -dIrcDdVcRe;
315 A(GammaD, mVcImCol[p]) = -dIrcDdVcIm;
316 A(GammaQ, mVcReCol[p]) = -dIrcQdVcRe;
317 A(GammaQ, mVcImCol[p]) = -dIrcQdVcIm;
318
319 const Real dIrcDdURe = gIrcU[p].real();
320 const Real dIrcDdUIm = -gIrcU[p].imag();
321 const Real dIrcQdURe = gIrcU[p].imag();
322 const Real dIrcQdUIm = gIrcU[p].real();
323 B(GammaD, mUReCol[p]) = -dIrcDdURe;
324 B(GammaD, mUImCol[p]) = -dIrcDdUIm;
325 B(GammaQ, mUReCol[p]) = -dIrcQdURe;
326 B(GammaQ, mUImCol[p]) = -dIrcQdUIm;
327 }
328
329 // Negative-sequence current integrators: gammaND_dot = iRefNd - ircND, etc.
330 // psi enters via rotN = e^{+j*psi}: d(ircND)/dpsi = -ircNQ, d(ircNQ)/dpsi = ircND.
331 if (mEnableNegSeqControl) {
332 A(GammaND, Psi) = ircNQ;
333 A(GammaNQ, Psi) = -ircND;
334 for (Int p = 0; p < 3; ++p) {
335 A(GammaND, mVcReCol[p]) = -neg.hIrcN[p].real();
336 A(GammaND, mVcImCol[p]) = -neg.hIrcN[p].imag();
337 A(GammaNQ, mVcReCol[p]) = -neg.hIrcN[p].imag();
338 A(GammaNQ, mVcImCol[p]) = neg.hIrcN[p].real();
339 B(GammaND, mUReCol[p]) = neg.hIrcN[p].real();
340 B(GammaND, mUImCol[p]) = neg.hIrcN[p].imag();
341 B(GammaNQ, mUReCol[p]) = neg.hIrcN[p].imag();
342 B(GammaNQ, mUImCol[p]) = -neg.hIrcN[p].real();
343 }
344 }
345
346 // Filter capacitor rows (Vc_dot), fully decoupled per phase.
347 for (Int p = 0; p < 3; ++p) {
348 const Int reRow = mVcReCol[p];
349 const Int imRow = mVcImCol[p];
350 A(reRow, reRow) = -1.0 / (mCf * mRc);
351 A(reRow, imRow) = mOmegaN;
352 A(imRow, reRow) = -mOmegaN;
353 A(imRow, imRow) = -1.0 / (mCf * mRc);
354 A(reRow, mIfReCol[p]) = 1.0 / mCf;
355 A(imRow, mIfImCol[p]) = 1.0 / mCf;
356 B(reRow, mUReCol[p]) = 1.0 / (mCf * mRc);
357 B(imRow, mUImCol[p]) = 1.0 / (mCf * mRc);
358 }
359
360 // Filter inductor rows (If_dot): the per-phase coupling via vRef_p = redistFactor[p]*vRefEnv0.
361 RefSensitivities sens;
362
363 const Complex dVRefEnv0DPsi =
364 j * K23 * expJPsi * (mKpCurrCtrl * ircDQ + vRefDQ);
365
366 // d(vRefDQ)/dOwnVar for the six own-frame control states.
367 const Complex dVRefEnv0DpF = K23 * expJPsi * (mKpCurrCtrl * (-mKpPowerCtrl));
368 const Complex dVRefEnv0DqF = K23 * expJPsi * j * (mKpCurrCtrl * mKpPowerCtrl);
369 const Complex dVRefEnv0DPhiD = K23 * expJPsi * (mKpCurrCtrl * mKiPowerCtrl);
370 const Complex dVRefEnv0DPhiQ =
371 K23 * expJPsi * j * (mKpCurrCtrl * mKiPowerCtrl);
372 const Complex dVRefEnv0DGammaD = K23 * expJPsi * mKiCurrCtrl;
373 const Complex dVRefEnv0DGammaQ = K23 * expJPsi * j * mKiCurrCtrl;
374
375 sens.posOwn = {dVRefEnv0DPsi, dVRefEnv0DpF, dVRefEnv0DqF,
376 dVRefEnv0DPhiD, dVRefEnv0DPhiQ, dVRefEnv0DGammaD,
377 dVRefEnv0DGammaQ};
378
379 // d(vRefDQ)/d(vc_p), d(vRefDQ)/d(u_p) via vRefDQ's -kpCurrCtrl*ircDQ term.
380 for (Int p = 0; p < 3; ++p) {
381 sens.posVcRe[p] = K23 * expJPsi * (-mKpCurrCtrl * gIrcVc[p]);
382 sens.posVcIm[p] = K23 * expJPsi * (-mKpCurrCtrl * j * gIrcVc[p]);
383 sens.posURe[p] = K23 * expJPsi * (-mKpCurrCtrl * gIrcU[p]);
384 sens.posUIm[p] = K23 * expJPsi * (-mKpCurrCtrl * j * gIrcU[p]);
385 }
386
387 // Derivatives of vRefNEnv0 = K23*conj(vRefNDQ)*expJPsi wrt psi, the negative-loop
388 // states and vc_p/u_p.
389 if (mEnableNegSeqControl) {
390 sens.negPsi =
391 j * K23 * expJPsi *
392 (mKpCurrCtrl * std::conj(neg.ircNDQ) + std::conj(neg.vRefNDQ));
393 sens.negGammaND = K23 * expJPsi * mKiCurrCtrl;
394 sens.negGammaNQ = -j * K23 * expJPsi * mKiCurrCtrl;
395 for (Int p = 0; p < 3; ++p) {
396 const Complex base =
397 mKpCurrCtrl * std::conj(neg.hIrcN[p]) * K23 * expJPsi;
398 sens.negVcRe[p] = -base;
399 sens.negVcIm[p] = -j * base;
400 sens.negURe[p] = base;
401 sens.negUIm[p] = j * base;
402 }
403 }
404
405 buildInductorRows(redistFactor, projCoeff, sens, A, B);
406
407 // Offset E = f(x,u) - A*x - B*u.
408 E = f - A * x - B * u;
409
410 // SSN output: y_p = (u_p - vc_p)/Rc (exact, no relinearization needed).
411 C = Matrix::Zero(6, stateSize());
412 for (Int p = 0; p < 3; ++p) {
413 C(2 * p, mVcReCol[p]) = -1.0 / mRc;
414 C(2 * p + 1, mVcImCol[p]) = -1.0 / mRc;
415 }
416
417 D = Matrix::Zero(6, 6);
418 for (Int p = 0; p < 3; ++p) {
419 D(2 * p, mUReCol[p]) = 1.0 / mRc;
420 D(2 * p + 1, mUImCol[p]) = 1.0 / mRc;
421 }
422
423 F = Matrix::Zero(6, 1);
424}
425
426void DP::Ph3::AvVoltSourceInverterStateSpace::buildInductorRows(
427 const Complex3 &redistFactor, const Complex3 &projCoeff,
428 const RefSensitivities &sens, Matrix &A, Matrix &B) const {
429 for (Int pOut = 0; pOut < 3; ++pOut) {
430 const Int reRow = mIfReCol[pOut];
431 const Int imRow = mIfImCol[pOut];
432
433 // Own-phase direct terms: -(vc_p + Rf*iF_p)/Lf - j*omegaN*iF_p.
434 A(reRow, mVcReCol[pOut]) = -1.0 / mLf;
435 A(imRow, mVcImCol[pOut]) = -1.0 / mLf;
436 A(reRow, reRow) = -mRf / mLf;
437 A(imRow, imRow) = -mRf / mLf;
438 A(reRow, imRow) = mOmegaN;
439 A(imRow, reRow) = -mOmegaN;
440
441 // vRef_p coupling through the shared single-dq-frame control chain.
442 for (Int k = 0; k < 7; ++k) {
443 const Complex dVRef = redistFactor[pOut] * sens.posOwn[k];
444 A(reRow, mOwnCol[k]) += dVRef.real() / mLf;
445 A(imRow, mOwnCol[k]) += dVRef.imag() / mLf;
446 }
447 for (Int pSrc = 0; pSrc < 3; ++pSrc) {
448 const Complex dVRefVcRe = redistFactor[pOut] * sens.posVcRe[pSrc];
449 const Complex dVRefVcIm = redistFactor[pOut] * sens.posVcIm[pSrc];
450 A(reRow, mVcReCol[pSrc]) += dVRefVcRe.real() / mLf;
451 A(imRow, mVcReCol[pSrc]) += dVRefVcRe.imag() / mLf;
452 A(reRow, mVcImCol[pSrc]) += dVRefVcIm.real() / mLf;
453 A(imRow, mVcImCol[pSrc]) += dVRefVcIm.imag() / mLf;
454
455 const Complex dVRefURe = redistFactor[pOut] * sens.posURe[pSrc];
456 const Complex dVRefUIm = redistFactor[pOut] * sens.posUIm[pSrc];
457 B(reRow, mUReCol[pSrc]) += dVRefURe.real() / mLf;
458 B(imRow, mUReCol[pSrc]) += dVRefURe.imag() / mLf;
459 B(reRow, mUImCol[pSrc]) += dVRefUIm.real() / mLf;
460 B(imRow, mUImCol[pSrc]) += dVRefUIm.imag() / mLf;
461 }
462
463 // Negative-sequence injection coupling: vRef_p += projCoeff[pOut]*vRefNEnv0.
464 // The sensitivities stay zero when the loop is off, so this is a no-op then.
465 const Complex dNPsi = projCoeff[pOut] * sens.negPsi;
466 A(reRow, Psi) += dNPsi.real() / mLf;
467 A(imRow, Psi) += dNPsi.imag() / mLf;
468 if (mEnableNegSeqControl) {
469 const Complex dNGammaND = projCoeff[pOut] * sens.negGammaND;
470 A(reRow, GammaND) += dNGammaND.real() / mLf;
471 A(imRow, GammaND) += dNGammaND.imag() / mLf;
472 const Complex dNGammaNQ = projCoeff[pOut] * sens.negGammaNQ;
473 A(reRow, GammaNQ) += dNGammaNQ.real() / mLf;
474 A(imRow, GammaNQ) += dNGammaNQ.imag() / mLf;
475 }
476 for (Int pSrc = 0; pSrc < 3; ++pSrc) {
477 const Complex dNVcRe = projCoeff[pOut] * sens.negVcRe[pSrc];
478 const Complex dNVcIm = projCoeff[pOut] * sens.negVcIm[pSrc];
479 A(reRow, mVcReCol[pSrc]) += dNVcRe.real() / mLf;
480 A(imRow, mVcReCol[pSrc]) += dNVcRe.imag() / mLf;
481 A(reRow, mVcImCol[pSrc]) += dNVcIm.real() / mLf;
482 A(imRow, mVcImCol[pSrc]) += dNVcIm.imag() / mLf;
483
484 const Complex dNURe = projCoeff[pOut] * sens.negURe[pSrc];
485 const Complex dNUIm = projCoeff[pOut] * sens.negUIm[pSrc];
486 B(reRow, mUReCol[pSrc]) += dNURe.real() / mLf;
487 B(imRow, mUReCol[pSrc]) += dNURe.imag() / mLf;
488 B(reRow, mUImCol[pSrc]) += dNUIm.real() / mLf;
489 B(imRow, mUImCol[pSrc]) += dNUIm.imag() / mLf;
490 }
491 }
492}
493
495 Matrix E;
496 Matrix F;
497
498 // Relinearized every step (mirrors EMT/Ph1); change-check intentionally skipped.
499 buildStateSpaceModel(**mX, packComplex((**inputAttribute())), mA, mB, mC, mD,
500 E, F);
501
504
505 return true;
506}
507
509 const Matrix &u) const {
510 const Matrix &x = **mX;
511
512 const Real psi = x(Psi, 0);
513 const Complex rot = std::exp(Complex(0.0, -psi));
514 const Complex expJPsi = std::conj(rot);
515 const Complex3 projCoeff = {Complex(1.0, 0.0), SHIFT_TO_PHASE_C,
517
518 Complex pV(0.0, 0.0);
519 Complex pU(0.0, 0.0);
520 Complex nV(0.0, 0.0);
521 Complex nU(0.0, 0.0);
522 for (Int p = 0; p < 3; ++p) {
523 const Complex vc(x(mVcReCol[p], 0), x(mVcImCol[p], 0));
524 const Complex uEnv(u(mUReCol[p], 0), u(mUImCol[p], 0));
525 pV += projCoeff[p] * vc;
526 pU += projCoeff[p] * uEnv;
527 nV += std::conj(projCoeff[p]) * vc;
528 nU += std::conj(projCoeff[p]) * uEnv;
529 }
530 const Complex pI = (pV - pU) / mRc;
531
532 const Complex vcDQ = 0.5 * K23 * rot * pV;
533 const Complex ircDQ = 0.5 * K23 * rot * pI;
534
535 **mVcD = vcDQ.real();
536 **mVcQ = vcDQ.imag();
537 **mIrcD = ircDQ.real();
538 **mIrcQ = ircDQ.imag();
539 if (mEnableNegSeqControl) {
540 const Complex nI = (nV - nU) / mRc;
541 const Complex ircNDQ = 0.5 * K23 * expJPsi * std::conj(nI);
542 **mIrcNd = ircNDQ.real();
543 **mIrcNq = ircNDQ.imag();
544 }
545
546 **mPInst = **mVcD * **mIrcD + **mVcQ * **mIrcQ;
547 **mQInst = -**mVcD * **mIrcQ + **mVcQ * **mIrcD;
548
549 **mOmegaPLL = mOmegaN + mKpPLL * **mVcQ + mKiPLL * x(PhiPLL, 0);
550}
551
553 Real frequency) {
554 if (!mParametersSet)
555 throw std::logic_error("setParameters() must be called before "
556 "initializeFromNodesAndTerminals().");
557
558 // Balanced init: solve the phase-a operating point, replicate across phases.
559 const Real omega = 2.0 * PI * frequency;
560 const Complex j(0.0, 1.0);
561 const Complex powerRef(mPRef, mQRef);
562
563 const MatrixComp uInit = buildInitialInputFromNodes(frequency);
564 const Complex ua = uInit(0, 0);
565
566 Complex vc = ua;
567 Complex irc(0.0, 0.0);
568
569 for (Int iter = 0; iter < mInitializationMaxIterations; ++iter) {
570 if (std::abs(vc) < mInitializationTolerance) {
571 irc = Complex(0.0, 0.0);
572 break;
573 }
574
575 const Complex iNext = std::conj(powerRef / (1.5 * vc));
576 const Complex vcNext = ua + mRc * iNext;
577
578 irc = iNext;
579
580 if (std::abs(vcNext - vc) < mInitializationTolerance) {
581 vc = vcNext;
582 break;
583 }
584
585 vc = vcNext;
586 }
587
588 const Complex ifCurrent = j * omega * mCf * vc + irc;
589 const Complex vRef = vc + (mRf + j * omega * mLf) * ifCurrent;
590
591 // thetaN(0) = 0, so psi0 = theta0 exactly.
592 const Real psi0 = std::arg(vc);
593 const Complex rot0 = std::exp(-j * psi0);
594
595 const Complex vcDQ = K32 * vc * rot0;
596 const Real vcD = vcDQ.real();
597 const Real vcQ = vcDQ.imag();
598 const Complex ircDQ = K32 * irc * rot0;
599 const Real ircD = ircDQ.real();
600 const Real ircQ = ircDQ.imag();
601
602 const Real pInit = vcD * ircD + vcQ * ircQ;
603 const Real qInit = -vcD * ircQ + vcQ * ircD;
604
605 const Real phiPLL0 = (omega - mOmegaN) / mKiPLL;
606 const Real phiD0 = (ircD + mKpPowerCtrl * (pInit - mPRef)) / mKiPowerCtrl;
607 const Real phiQ0 = (ircQ - mKpPowerCtrl * (qInit - mQRef)) / mKiPowerCtrl;
608
609 const Real iRefD0 =
610 -mKpPowerCtrl * pInit + mKiPowerCtrl * phiD0 + mKpPowerCtrl * mPRef;
611 const Real iRefQ0 =
612 mKpPowerCtrl * qInit + mKiPowerCtrl * phiQ0 - mKpPowerCtrl * mQRef;
613
614 const Complex vRefDQ0 = K32 * vRef * rot0;
615 const Real gammaD0 =
616 (vRefDQ0.real() + mKpCurrCtrl * (ircD - iRefD0)) / mKiCurrCtrl;
617 const Real gammaQ0 =
618 (vRefDQ0.imag() + mKpCurrCtrl * (ircQ - iRefQ0)) / mKiCurrCtrl;
619
621 const MatrixComp ifAbc = Math::singlePhaseVariableToThreePhase(ifCurrent);
622
623 Matrix x0 = Matrix::Zero(stateSize(), 1);
624 x0(Psi, 0) = psi0;
625 x0(PhiPLL, 0) = phiPLL0;
626 x0(PFiltered, 0) = pInit;
627 x0(QFiltered, 0) = qInit;
628 x0(PhiD, 0) = phiD0;
629 x0(PhiQ, 0) = phiQ0;
630 x0(GammaD, 0) = gammaD0;
631 x0(GammaQ, 0) = gammaQ0;
632 // Balanced start: the negative-loop integrators stay at their Zero() seed.
633
634 for (Int p = 0; p < 3; ++p) {
635 x0(mVcReCol[p], 0) = vcAbc(p, 0).real();
636 x0(mVcImCol[p], 0) = vcAbc(p, 0).imag();
637 x0(mIfReCol[p], 0) = ifAbc(p, 0).real();
638 x0(mIfImCol[p], 0) = ifAbc(p, 0).imag();
639 }
640
641 **mX = x0;
642 **mIntfVoltage = uInit;
643 (**mIntfCurrent)(0, 0) = (ua - vc) / mRc;
644 (**mIntfCurrent)(1, 0) = (uInit(1, 0) - vcAbc(1, 0)) / mRc;
645 (**mIntfCurrent)(2, 0) = (uInit(2, 0) - vcAbc(2, 0)) / mRc;
646
649
650 SPDLOG_LOGGER_INFO(mSLog,
651 "\n--- Inverter SSN mixed real+per-phase-complex "
652 "initialization ---"
653 "\nInput u: {:s}"
654 "\nOutput y: {:s}"
655 "\nState x: {:s}"
656 "\nP/Q init: [{:.6e}, {:.6e}]"
657 "\nVc dq (phase a): [{:.6e}, {:.6e}]"
658 "\nIinj dq (phase a): [{:.6e}, {:.6e}]"
659 "\n--- Initialization finished ---",
662 Logger::matrixToString(**mX), pInit, qInit, vcD, vcQ, ircD,
663 ircQ);
664}
void setParameters(Real lf, Real cf, Real rf, Real rc, Real omegaN, Real kpPLL, Real kiPLL, Real omegaCutoff, Real pRef, Real qRef, Real kpPowerCtrl, Real kiPowerCtrl, Real kpCurrCtrl, Real kiCurrCtrl, Real iRefNd=0.0, Real iRefNq=0.0)
void initializeFromNodesAndTerminals(Real frequency) override
Initializes Component variables according to power flow data stored in Nodes.
AvVoltSourceInverterStateSpace(String uid, String name, Logger::Level logLevel=Logger::Level::off, Bool enableNegSeqControl=false)
Bool updateComponentParameters() override final
Rebuild A/B/C/D/E/F from the current state/input; returns true if the stamp changed.
Int stateSize() const
Total packed real state size: realStateCount + 2*complexStateCount.
void setParameters(const Matrix &A, const Matrix &B, const Matrix &C, const Matrix &D)
static Matrix packComplex(const MatrixComp &c)
Pack an m-vector of complex into a 2m real vector [Re0,Im0,Re1,Im1,...].
virtual MatrixComp buildInitialInputFromNodes(Real frequency)
Default: balanced envelope from v_terminal1 - v_terminal0.
Matrix mA
Continuous-time real model over the packed state and packed [Re,Im] input/output.
MixedVTypeVariableSSNComp(String uid, String name, Int realStateCount, Int complexStateCount, Logger::Level logLevel=Logger::Level::off)
String uid()
Returns unique id.
AttributeList::Ptr mAttributes
Attribute List.
spdlog::level::level_enum Level
Definition Logger.h:33
static String matrixCompToString(const MatrixComp &mat)
Definition Logger.cpp:37
static String matrixToString(const Matrix &mat)
Definition Logger.cpp:31
static MatrixComp singlePhaseVariableToThreePhase(Complex var_1ph)
To convert single phase complex variables (voltages, currents) to symmetrical three phase ones.
const Attribute< MatrixVar< Complex > >::Ptr mIntfCurrent
const Attribute< MatrixVar< Complex > >::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