Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 65 additions & 11 deletions docs/hugo/content/en/docs/Models/power-electronics.md
Original file line number Diff line number Diff line change
Expand Up @@ -414,29 +414,29 @@ The terminal input is the PCC voltage envelope of the three phases,
u = \begin{bmatrix} U_a & U_b & U_c \end{bmatrix}^\top ,
```

and the state vector concatenates the 8 real control states ahead of the 6 complex per-phase envelopes,
and the state vector concatenates the 6 complex per-phase envelopes ahead of the 8 real control states, keeping the carrier-band and baseband blocks separate,

```math
\mathbf{x} =
\big[\,
V_{c,a} \;\;
V_{c,b} \;\;
V_{c,c} \;\;
I_{f,a} \;\;
I_{f,b} \;\;
I_{f,c} \;\;
\psi \;\;
\phi_{\mathrm{PLL}} \;\;
P \;\;
Q \;\;
\phi_d \;\;
\phi_q \;\;
\gamma_d \;\;
\gamma_q \;\;
V_{c,a} \;\;
V_{c,b} \;\;
V_{c,c} \;\;
I_{f,a} \;\;
I_{f,b} \;\;
I_{f,c}
\gamma_q
\,\big]^\top ,
```

where $\psi := \theta_{\mathrm{PLL}} - \omega_n t$ again denotes the deviation of the PLL angle from the nominal carrier phase, retained as a state to preserve relinearization accuracy. Each per-phase envelope contributes its real and imaginary parts to the packed real vector, yielding 20 real states in total.
where $\psi := \theta_{\mathrm{PLL}} - \omega_n t$ again denotes the deviation of the PLL angle from the nominal carrier phase, retained as a state to preserve relinearization accuracy. Each per-phase envelope contributes its real and imaginary parts to the packed real vector, yielding 20 real states in total, or 22 with the optional negative-sequence loop described below.

The model output is the per-phase interface current injected into the MNA system,

Expand Down Expand Up @@ -568,7 +568,7 @@ R_f I_{f,p}

the phases being coupled only through the shared control chain, that is, through $V_{\mathrm{ref},p}$.

At each simulation step the nonlinear model is linearized about the current operating point into the affine state-space form, with the 8 real control states and the real and imaginary parts of the 6 complex per-phase envelopes packed into a single real 20-vector,
At each simulation step the nonlinear model is linearized about the current operating point into the affine state-space form, with the real and imaginary parts of the 6 complex per-phase envelopes and the 8 real control states packed into a single real 20-vector,

```math
\dot{\mathbf{x}}
Expand All @@ -590,7 +590,61 @@ At each simulation step the nonlinear model is linearized about the current oper

which is subsequently discretized and stamped into the DP MNA system.

Because the controller operates in a single positive-sequence $dq$ frame, only the positive-sequence component of an unbalanced terminal is regulated. The negative-sequence response is present in the per-phase filter envelopes but is not itself a control state, and the $2\omega_n$ ripple it would otherwise induce in the $dq$ frame is therefore not represented. A dual-sequence controller with a dedicated negative-sequence frame remains the subject of future work.
In this default configuration the controller operates in a single positive-sequence $dq$ frame, so only the positive-sequence component of an unbalanced terminal is regulated. The negative-sequence response is present in the per-phase filter envelopes but is not itself a control state, and the $2\omega_n$ ripple it would otherwise induce in the $dq$ frame is therefore not represented.

## Optional negative-sequence current control

The component accepts an `enableNegSeqControl` constructor flag, `false` by default. Setting it adds a second, negative-sequence current-control loop alongside the positive-sequence one, giving the dual-sequence structure of Yazdani and Iravani, chapter 8. The flag exists because the two configurations answer different questions: with the loop off the model has the same 20 states and the same eigenvalue count as its `EMT::Ph3` counterpart and is the right object for a cross-domain comparison, while with it on the model gains 2 states and can regulate an unbalanced terminal.

The negative-sequence quantities are obtained by projecting the same three envelopes onto the conjugate sequence set,

```math
\underline{V}_c^- = V_{c,a} + a^2 V_{c,b} + a\, V_{c,c},
\qquad
\underline{I}_{rc}^- = \frac{\underline{V}_c^- - \underline{U}^-}{R_c}.
```

A negative-sequence component rotates backwards relative to the PLL frame, so in envelope terms its $dq$ image follows from conjugating the projected phasor and rotating by $+\psi$ rather than $-\psi$,

```math
I_{rc,dq}^- = \tfrac{1}{2}\sqrt{\tfrac{2}{3}}\, e^{\,j\psi}\, \overline{\underline{I}_{rc}^-} .
```

Both sequence images are therefore baseband quantities, and the negative-sequence loop costs only the two real integrator states $\gamma_{nd}$, $\gamma_{nq}$, with no second carrier and no $2\omega_n$ term anywhere in the model. The loop itself is the same PI structure as the positive-sequence inner loop,

```math
\dot{\gamma}_{nd} = i_{nd,\mathrm{ref}} - i_{rc,nd},
\qquad
\dot{\gamma}_{nq} = i_{nq,\mathrm{ref}} - i_{rc,nq},
```

```math
v_{nd,\mathrm{ref}}
=
K_{p,I}(i_{nd,\mathrm{ref}} - i_{rc,nd}) +
K_{i,I}\gamma_{nd},
\qquad
v_{nq,\mathrm{ref}}
=
K_{p,I}(i_{nq,\mathrm{ref}} - i_{rc,nq}) +
K_{i,I}\gamma_{nq},
```

reusing the inner-loop gains $K_{p,I}$ and $K_{i,I}$. Its output is redistributed to the per-phase bridge voltages through the sequence-orthogonal set, and adds to the positive-sequence command of the previous section,

```math
V_{\mathrm{ref},p}
=
\bar{a}_p \sqrt{\tfrac{2}{3}}\, V_{\mathrm{ref},dq}\, e^{j\psi}
+
a_p \sqrt{\tfrac{2}{3}}\, \overline{V_{\mathrm{ref},dq}^-}\, e^{j\psi},
\qquad
a_{a/b/c} = \{1,\; a,\; a^2\}.
```

The two references $i_{nd,\mathrm{ref}}$ and $i_{nq,\mathrm{ref}}$ are the last two arguments of `setParameters` and default to zero, which makes the loop a negative-sequence suppressor; a non-zero pair commands a deliberate negative-sequence injection instead, as required by some unbalanced fault ride-through grid codes. The measured $i_{rc,nd}$ and $i_{rc,nq}$ are exposed as the `irc_n_d` and `irc_n_q` attributes and stay at zero when the loop is disabled.

The state vector grows to 22 by appending the two integrators after the control block, so that the envelope and positive-sequence control indices are unaffected by the flag. Under the single-line-to-ground fault of the accompanying notebook, enabling the loop suppresses the negative-sequence component of the injected current by about 40 percent while moving the positive-sequence component by less than 0.1 percent.

## References

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,63 +3,84 @@

#pragma once

#include <array>

#include <dpsim-models/DP/DP_Ph3_MixedVTypeVariableSSNComp.h>

namespace CPS {
namespace DP {
namespace Ph3 {

/// Averaged grid-following VSI SSN port of EMT::Ph3::AvVoltSourceInverterStateSpace: 8 real control states (single PLL, single positive-sequence dq frame) plus 6 complex per-phase envelope states (Vc_a/b/c, If_a/b/c).
/// @brief Averaged grid-following VSI (SSN), optional dual-sequence current control.
class AvVoltSourceInverterStateSpace final
: public MixedVTypeVariableSSNComp,
public SharedFactory<AvVoltSourceInverterStateSpace> {
private:
/// Packed state indices: envelope block, then the contiguous control block.
/// GammaND/GammaNQ exist only when the negative-sequence loop is enabled.
enum StateIndex : Int {
Psi = 0,
PhiPLL = 1,
PFiltered = 2,
QFiltered = 3,
PhiD = 4,
PhiQ = 5,
GammaD = 6,
GammaQ = 7,
VcARe = 8,
VcAIm = 9,
VcBRe = 10,
VcBIm = 11,
VcCRe = 12,
VcCIm = 13,
IfARe = 14,
IfAIm = 15,
IfBRe = 16,
IfBIm = 17,
IfCRe = 18,
IfCIm = 19
VcARe = 0,
VcAIm = 1,
VcBRe = 2,
VcBIm = 3,
VcCRe = 4,
VcCIm = 5,
IfARe = 6,
IfAIm = 7,
IfBRe = 8,
IfBIm = 9,
IfCRe = 10,
IfCIm = 11,
Psi = 12,
PhiPLL = 13,
PFiltered = 14,
QFiltered = 15,
PhiD = 16,
PhiQ = 17,
GammaD = 18,
GammaQ = 19,
GammaND = 20,
Comment thread
leonardocarreras marked this conversation as resolved.
GammaNQ = 21
};

static constexpr Int mVcReCol[3] = {VcARe, VcBRe, VcCRe};
static constexpr Int mVcImCol[3] = {VcAIm, VcBIm, VcCIm};
static constexpr Int mIfReCol[3] = {IfARe, IfBRe, IfCRe};
static constexpr Int mIfImCol[3] = {IfAIm, IfBIm, IfCIm};
static constexpr Int mUReCol[3] = {0, 2, 4};
static constexpr Int mUImCol[3] = {1, 3, 5};

Real mLf;
Real mCf;
Real mRf;
Real mRc;

Real mOmegaN;
Real mKpPLL;
Real mKiPLL;

Real mOmegaCutoff;
Real mPRef;
Real mQRef;
Real mKpPowerCtrl;
Real mKiPowerCtrl;
Real mKpCurrCtrl;
Real mKiCurrCtrl;
/// One complex quantity per phase.
using Complex3 = std::array<Complex, 3>;

/// Enables the baseband negative-sequence current-control loop (+2 states).
const Bool mEnableNegSeqControl;

static constexpr std::array<Int, 3> mVcReCol = {VcARe, VcBRe, VcCRe};
static constexpr std::array<Int, 3> mVcImCol = {VcAIm, VcBIm, VcCIm};
static constexpr std::array<Int, 3> mIfReCol = {IfARe, IfBRe, IfCRe};
static constexpr std::array<Int, 3> mIfImCol = {IfAIm, IfBIm, IfCIm};
static constexpr std::array<Int, 3> mUReCol = {0, 2, 4};
static constexpr std::array<Int, 3> mUImCol = {1, 3, 5};

/// Positive-sequence control states the bridge-voltage reference depends on.
static constexpr std::array<Int, 7> mOwnCol = {
Psi, PFiltered, QFiltered, PhiD, PhiQ, GammaD, GammaQ};

Real mLf = 0.0;
Real mCf = 0.0;
Real mRf = 0.0;
Real mRc = 0.0;

Real mOmegaN = 0.0;
Real mKpPLL = 0.0;
Real mKiPLL = 0.0;

Real mOmegaCutoff = 0.0;
Real mPRef = 0.0;
Real mQRef = 0.0;
Real mKpPowerCtrl = 0.0;
Real mKiPowerCtrl = 0.0;
Real mKpCurrCtrl = 0.0;
Real mKiCurrCtrl = 0.0;

/// Negative-sequence d-axis current reference; zero (default) = negative-sequence suppression.
Real mIRefNd = 0.0;
/// Negative-sequence q-axis current reference; zero (default) = negative-sequence suppression.
Real mIRefNq = 0.0;

const Attribute<Real>::Ptr mVcD;
const Attribute<Real>::Ptr mVcQ;
Expand All @@ -68,29 +89,74 @@ class AvVoltSourceInverterStateSpace final
const Attribute<Real>::Ptr mPInst;
const Attribute<Real>::Ptr mQInst;
const Attribute<Real>::Ptr mOmegaPLL;
const Attribute<Real>::Ptr mIrcNd;
const Attribute<Real>::Ptr mIrcNq;

/// Negative-sequence measurement, bridge-voltage reference and the conj(vc_p) coefficients of the measurement.
struct NegSeqTerms {
Complex ircNDQ{0.0, 0.0};
Complex vRefNDQ{0.0, 0.0};
Complex vRefNEnv0{0.0, 0.0};
Complex3 hIrcN{};
};

/// Sensitivities of the phase-a bridge-voltage reference envelopes to states and inputs.
struct RefSensitivities {
/// d(vRefEnv0)/d(state) for the mOwnCol states, in that order.
std::array<Complex, 7> posOwn{};
Complex3 posVcRe{};
Complex3 posVcIm{};
Complex3 posURe{};
Complex3 posUIm{};
Complex negPsi{0.0, 0.0};
Complex negGammaND{0.0, 0.0};
Complex negGammaNQ{0.0, 0.0};
Complex3 negVcRe{};
Complex3 negVcIm{};
Complex3 negURe{};
Complex3 negUIm{};
};

/// Builds the affine real model (A,B,C,D,E,F) around (x,u): RHS + analytic Jacobian, E = f(x,u) - A*x - B*u.
void buildStateSpaceModel(const Matrix &x, const Matrix &u, Matrix &A,
Matrix &B, Matrix &C, Matrix &D, Matrix &E,
Matrix &F) const;

/// Baseband negative-sequence measurement and PI control around the operating point.
NegSeqTerms computeNegSeqTerms(const Matrix &x, const Complex3 &vc,
const Complex3 &uEnv,
const Complex3 &redistFactor,
const Complex3 &projCoeff,
const Complex &expJPsi) const;

/// Filter-inductor Jacobian rows: own-phase terms plus the coupling through both control chains.
void buildInductorRows(const Complex3 &redistFactor,
const Complex3 &projCoeff,
const RefSensitivities &sens, Matrix &A,
Matrix &B) const;

protected:
Bool updateComponentParameters() override final;
void updateLogAttributes(const Matrix &u) const override final;

public:
using SharedFactory<AvVoltSourceInverterStateSpace>::make;

/// @param enableNegSeqControl adds the baseband negative-sequence current-control loop (+2 states); positive-sequence-only when false.
AvVoltSourceInverterStateSpace(String uid, String name,
Logger::Level logLevel = Logger::Level::off);
Logger::Level logLevel = Logger::Level::off,
Bool enableNegSeqControl = false);
AvVoltSourceInverterStateSpace(String name,
Logger::Level logLevel = Logger::Level::off)
: AvVoltSourceInverterStateSpace(name, name, logLevel) {}
Logger::Level logLevel = Logger::Level::off,
Bool enableNegSeqControl = false)
: AvVoltSourceInverterStateSpace(name, name, logLevel,
enableNegSeqControl) {}

void setParameters(Real lf, Real cf, Real rf, Real rc, Real omegaN,
Comment thread
leonardocarreras marked this conversation as resolved.
Real kpPLL, Real kiPLL, Real omegaCutoff, Real pRef,
Real qRef, Real kpPowerCtrl, Real kiPowerCtrl,
Real kpCurrCtrl, Real kiCurrCtrl);
Real kpCurrCtrl, Real kiCurrCtrl, Real iRefNd = 0.0,
Comment thread
leonardocarreras marked this conversation as resolved.
Comment thread
leonardocarreras marked this conversation as resolved.
Comment thread
leonardocarreras marked this conversation as resolved.
Comment thread
leonardocarreras marked this conversation as resolved.
Comment thread
leonardocarreras marked this conversation as resolved.
Comment thread
leonardocarreras marked this conversation as resolved.
Comment thread
leonardocarreras marked this conversation as resolved.
Comment thread
leonardocarreras marked this conversation as resolved.
Comment thread
leonardocarreras marked this conversation as resolved.
Real iRefNq = 0.0);

void initializeFromNodesAndTerminals(Real frequency) override;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ class MixedVTypeVariableSSNComp : public MNASimPowerComp<Complex>,
MatrixComp mW;
MatrixComp mYHist;

/// Packed real state: [realStates..., Re(cplxState0), Im(cplxState0), ...].
/// Packed real state of size stateSize(), each envelope taking a [Re,Im]
/// pair. Never sliced here: the derived component chooses the ordering.
const Attribute<Matrix>::Ptr mX;

MixedVTypeVariableSSNComp(String uid, String name, Int realStateCount,
Expand Down
Loading
Loading