Skip to content

Commit 3ec4650

Browse files
committed
minimal corrections
1 parent 9f84a03 commit 3ec4650

28 files changed

Lines changed: 640 additions & 516 deletions

‎GridKit/Model/PhasorDynamics/Converter/REGCA/README.md‎

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,9 @@ $Q_0$ | [p.u.] | `q0` | Initial reactive power
3636
$S^\mathrm{base}$ | [MVA] | `mva` | REGCA component power base | 100.0 |
3737
$T_\mathrm{g}$ | [sec] | `Tg` | Converter current-control lag time constant | 0.02 | Block name: `Tg`
3838
$T_M$ | [sec] | `TM` | Terminal voltage sensor time constant | 0.02 | Block name: `Tfltr`
39-
$R_q^{\max}$ | [p.u./s] | `Rqmax` | Reactive-current recovery positive rate limit | 999.0 | Block name: `Iqrmax`
40-
$R_q^{\min}$ | [p.u./s] | `Rqmin` | Reactive-current recovery negative rate limit | -999.0 | Block name: `Iqrmin`
41-
$R_p^{\max}$ | [p.u./s] | `Rpmax` | Active-current magnitude recovery rate limit | 999.0 | Block name: `rrpwr`
39+
$R_q^{\max}$ | [p.u./s] | `Rqmax` | Reactive-current recovery positive rate limit | 999.0 | Block name: `Iqrmax`; disabled when $R_q^{\max}\le 0$
40+
$R_q^{\min}$ | [p.u./s] | `Rqmin` | Reactive-current recovery negative rate limit | -999.0 | Block name: `Iqrmin`; disabled when $R_q^{\min}\ge 0$
41+
$R_p^{\max}$ | [p.u./s] | `Rpmax` | Active-current magnitude recovery rate limit | 999.0 | Block name: `rrpwr`; must be nonnegative
4242
$s_L$ | [binary] | `sL` | LVPL switch | 1 | Block name: `LPVLSW`
4343
$I_{L1}$ | [p.u.] | `IL1` | LVPL upper-current ceiling | 1.1 | Block name: `LVPL1`
4444
$V_{L0}$ | [p.u.] | `VL0` | LVPL zero-crossing voltage | 0.4 | Block name: `zerox`
@@ -65,13 +65,11 @@ every other condition is a configuration error.
6565
S^\mathrm{base}
6666
&> 0 \\
6767
R_p^{\max}
68-
&> 0 \\
69-
R_q^{\min}
70-
&< 0 < R_q^{\max} \\
71-
s_L
72-
&\in \{0,1\} \\
68+
&\ge 0 \\
7369
I_{L1}
7470
&\ge 0 \\
71+
s_L
72+
&\in \{0,1\} \\
7573
0
7674
&\le V_{L0} < V_{L1} \\
7775
0
@@ -171,16 +169,19 @@ f_\mathrm{p}^{\lim}
171169

172170
### Differential Equations
173171

174-
The $I_q$ limiter branch is selected by the initial reactive power $Q_0$.
172+
The $I_q$ limiter branch is selected by the initial reactive power $Q_0$ and
173+
the sign that enables the corresponding limit.
175174

176175
```math
177176
\begin{aligned}
178177
0 &= -\dot V_M + \dfrac{1}{T_M} (V_T - V_M) \\
179178
0 &= -\dot I_q +
180179
\begin{cases}
181-
\text{min}(f_\mathrm{q}, R_q^{\max}) & Q_0 > 0 \\
182-
f_\mathrm{q} & Q_0 = 0 \\
183-
\text{max}(f_\mathrm{q}, R_q^{\min}) & Q_0 < 0
180+
\text{min}(f_\mathrm{q}, R_q^{\max})
181+
& Q_0 > 0 \land R_q^{\max} > 0 \\
182+
\text{max}(f_\mathrm{q}, R_q^{\min})
183+
& Q_0 < 0 \land R_q^{\min} < 0 \\
184+
f_\mathrm{q} & \text{otherwise}
184185
\end{cases} \\
185186
0 &= -\dot I_p +
186187
\begin{cases}

‎GridKit/Model/PhasorDynamics/Converter/REGCA/RegcaImpl.hpp‎

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -89,11 +89,11 @@ namespace GridKit
8989

9090
use_rqmax_ = ZERO<RealT>;
9191
use_rqmin_ = ZERO<RealT>;
92-
if (q0_ > ZERO<RealT>)
92+
if (q0_ > ZERO<RealT> && Rqmax_ > ZERO<RealT>)
9393
{
9494
use_rqmax_ = ONE<RealT>;
9595
}
96-
else if (q0_ < ZERO<RealT>)
96+
else if (q0_ < ZERO<RealT> && Rqmin_ < ZERO<RealT>)
9797
{
9898
use_rqmin_ = ONE<RealT>;
9999
}
@@ -436,8 +436,7 @@ namespace GridKit
436436
}
437437

438438
check(mva_base_ > ZERO<RealT>, "mva must be positive");
439-
check(Rpmax_ > ZERO<RealT>, "Rpmax must be positive");
440-
check(Rqmin_ < ZERO<RealT> && ZERO<RealT> < Rqmax_, "Rqmin < 0 < Rqmax is required");
439+
check(Rpmax_ >= ZERO<RealT>, "Rpmax must be non-negative");
441440
check(IL1_ >= ZERO<RealT>, "IL1 must be non-negative");
442441
check(KL_ > ZERO<RealT>, "LVPL release slope must be positive");
443442
check(ZERO<RealT> <= VL0_ && VL0_ < VL1_, "VL0/VL1 must satisfy 0 <= VL0 < VL1");

‎GridKit/Model/PhasorDynamics/Exciter/ESDC1A/Esdc1a.hpp‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ namespace GridKit
3838
EV, ///< \f$e_V\f$ Algebraic voltage-error summing output [p.u.]
3939
VLL, ///< \f$V_{\mathrm{LL}}\f$ Algebraic input lead-lag output [p.u.]
4040
VHV, ///< \f$V_{\mathrm{HV}}\f$ Algebraic high-value gate output [p.u.]
41-
SE, ///< \f$S_E\f$ Algebraic exciter saturation coefficient [p.u.]
41+
SE, ///< \f$E_{\mathrm{fd}}'S_E(E_{\mathrm{fd}}')\f$ Scaled-quadratic saturation contribution [p.u.]
4242
VFE, ///< \f$V_{\mathrm{FE}}\f$ Algebraic exciter feedback drive [p.u.]
4343
EFD, ///< \f$E_{\mathrm{fd}}\f$ Algebraic field-voltage output [p.u.]
4444
MAXIMUM, ///< Number of ESDC1A internal variables

‎GridKit/Model/PhasorDynamics/Exciter/ESDC1A/Esdc1aData.hpp‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ namespace GridKit
6969
vc, ///< \f$V_C\f$ Filtered terminal-voltage magnitude [p.u.]
7070
vr, ///< \f$V_R\f$ Voltage-regulator output [p.u.]
7171
vf, ///< \f$V_F\f$ Stabilizing feedback state [p.u.]
72-
se, ///< \f$S_E\f$ Exciter saturation coefficient [p.u.]
72+
se, ///< \f$E_{\mathrm{fd}}'S_E(E_{\mathrm{fd}}')\f$ Scaled-quadratic saturation contribution [p.u.]
7373
vfe ///< \f$V_{\mathrm{FE}}\f$ Exciter feedback drive [p.u.]
7474
};
7575

‎GridKit/Model/PhasorDynamics/Exciter/ESDC1A/Esdc1aImpl.hpp‎

Lines changed: 43 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,6 @@ namespace GridKit
162162
}
163163

164164
check(Ka_ > ZERO<RealT>, "Ka must be positive");
165-
check(Tc_ >= ZERO<RealT>, "Tc must be non-negative");
166165
check(Vrmin_ <= Vrmax_, "Vrmin must be less than or equal to Vrmax");
167166
check(UEL_ >= static_cast<IdxT>(0) && UEL_ <= static_cast<IdxT>(3),
168167
"UEL must be 0, 1, 2, or 3");
@@ -176,14 +175,11 @@ namespace GridKit
176175
{
177176
check(E1_ > ZERO<RealT>, "E1 must be positive when saturation is enabled");
178177
check(E2_ > ZERO<RealT>, "E2 must be positive when saturation is enabled");
179-
check(Se1_ > ZERO<RealT>, "Se1 must be positive when saturation is enabled");
180-
check(Se2_ > ZERO<RealT>, "Se2 must be positive when saturation is enabled");
181-
182-
const bool saturation_points_are_ordered =
183-
(E2_ > E1_ && Se2_ > Se1_)
184-
|| (E2_ < E1_ && Se2_ < Se1_);
185-
check(saturation_points_are_ordered,
186-
"E1/E2 and Se1/Se2 must be ordered consistently");
178+
check(Se1_ >= ZERO<RealT>, "Se1 must be non-negative when saturation is enabled");
179+
check(Se2_ >= ZERO<RealT>, "Se2 must be non-negative when saturation is enabled");
180+
181+
const bool sat_ordered = (E2_ > E1_ && Se2_ > Se1_) || (E2_ < E1_ && Se2_ < Se1_);
182+
check(sat_ordered, "E1/E2 and Se1/Se2 must be ordered consistently");
187183
}
188184

189185
if (!signals_.template isAssigned<Esdc1aInternalVariables::EFD>())
@@ -314,8 +310,15 @@ namespace GridKit
314310
return 1;
315311
}
316312

317-
const ScalarT se0 = SB_ * Math::qramp(efdp0 - SA_);
318-
const ScalarT vfe0 = (Ke_ + se0) * efdp0;
313+
const ScalarT se0 = SB_ * Math::qramp(efdp0 - SA_);
314+
315+
RealT ke0 = Ke_;
316+
if (ke0 == ZERO<RealT>)
317+
{
318+
ke0 = (Vrmax_ / 10.0 - static_cast<RealT>(se0)) / static_cast<RealT>(efdp0);
319+
}
320+
321+
const ScalarT vfe0 = ke0 * efdp0 + se0;
319322
const ScalarT vr0 = vfe0;
320323
const ScalarT vhv0 = vr0 / Ka_;
321324

@@ -344,15 +347,16 @@ namespace GridKit
344347
vll0 = vuel0 + inverseRamp(gate_margin0);
345348
}
346349

347-
const ScalarT vf0 = ScalarT{ZERO<RealT>};
348350
const ScalarT ev0 = vll0;
349351
const ScalarT xll0 = ev0;
350-
const ScalarT vref0 = ev0 + vc0 + vf0 - vs0 - uel_on_ * vuel0;
352+
const ScalarT vref0 = ev0 + vc0 - vs0 - uel_on_ * vuel0;
353+
354+
Ke_ = ke0;
351355

352356
y[EFDP] = efdp0;
353357
y[VC] = vc0;
354358
y[VR] = vr0;
355-
y[VF] = vf0;
359+
y[VF] = ZERO<RealT>;
356360
y[XLL] = xll0;
357361
y[EV] = ev0;
358362
y[VLL] = vll0;
@@ -560,7 +564,7 @@ namespace GridKit
560564

561565
const ScalarT ec = std::sqrt(wb[0] * wb[0] + wb[1] * wb[1]);
562566
const ScalarT ev_target = vref + vs + uel_on_ * vuel - vc - vf;
563-
const ScalarT vfe_target = (Ke_ + se) * efdp;
567+
const ScalarT vfe_target = Ke_ * efdp + se;
564568
const ScalarT efdp_rate = (vr - vfe) / Te_;
565569
const ScalarT limited_efdp_rate = awmin(efdp, efdp_rate, ZERO<RealT>);
566570

@@ -749,7 +753,7 @@ namespace GridKit
749753
* @brief Resolve the parameter-derived constants and selector masks
750754
*
751755
* Raises the transducer, regulator, lead-lag, exciter, and feedback
752-
* lags to the well-posedness floor, fits the quadratic saturation
756+
* lags to the well-posedness floor, fits the scaled-quadratic saturation
753757
* curve, and turns the three selectors into multiplicative masks. The
754758
* masks let the residual select signal routing without
755759
* parameter-dependent control flow, which keeps its structure fixed for
@@ -814,23 +818,37 @@ namespace GridKit
814818
// The disabled test matches the verify() predicate exactly.
815819
const bool saturation_disabled =
816820
Se1_ == ZERO<RealT> && Se2_ == ZERO<RealT>;
817-
const bool saturation_points_are_ordered =
818-
(E2_ > E1_ && Se2_ > Se1_)
819-
|| (E2_ < E1_ && Se2_ < Se1_);
821+
const bool sat_ordered = (E2_ > E1_ && Se2_ > Se1_) || (E2_ < E1_ && Se2_ < Se1_);
820822
const bool saturation_consistent =
821823
E1_ > ZERO<RealT> && E2_ > ZERO<RealT>
822-
&& Se1_ > ZERO<RealT> && Se2_ > ZERO<RealT>
823-
&& saturation_points_are_ordered;
824+
&& Se1_ >= ZERO<RealT> && Se2_ >= ZERO<RealT>
825+
&& sat_ordered;
824826
if (saturation_disabled || !saturation_consistent)
825827
{
826828
SA_ = ZERO<RealT>;
827829
SB_ = ZERO<RealT>;
828830
return;
829831
}
830832

831-
const RealT C = std::sqrt(Se2_ / Se1_);
833+
if (Se1_ == ZERO<RealT>)
834+
{
835+
const RealT dE = E2_ - E1_;
836+
SA_ = E1_;
837+
SB_ = Se2_ * E2_ / (dE * dE);
838+
return;
839+
}
840+
841+
if (Se2_ == ZERO<RealT>)
842+
{
843+
const RealT dE = E1_ - E2_;
844+
SA_ = E2_;
845+
SB_ = Se1_ * E1_ / (dE * dE);
846+
return;
847+
}
848+
849+
const RealT C = std::sqrt(Se2_ * E2_ / (Se1_ * E1_));
832850
SA_ = (C * E1_ - E2_) / (C - ONE<RealT>);
833-
SB_ = Se1_ / ((E1_ - SA_) * (E1_ - SA_));
851+
SB_ = Se1_ * E1_ / ((E1_ - SA_) * (E1_ - SA_));
834852
}
835853

836854
/**
@@ -840,30 +858,17 @@ namespace GridKit
840858
* *input*, so the residual reproduces the requested output through the
841859
* same smooth ramp it evaluates.
842860
*
843-
* For large positive values, the ramp is effectively equal to the input, so the
844-
* inverse is effectively the output. In that regime this function returns `ramp_output` directly.
845-
* This branching is numerically more robust.
846-
*
847861
* @param[in] ramp_output Strictly positive requested ramp output.
848862
* @return The input the smooth ramp maps to the requested output.
849863
*
850864
* @pre @p ramp_output is finite and strictly positive.
851-
* @warning This function contains conditional branching and may be used
852-
* during initialization, but not during residual or Jacobian
853-
* evaluation.
854865
*/
855866
template <typename scalar_type, typename index_type>
856867
typename Esdc1a<scalar_type, index_type>::RealT
857868
Esdc1a<scalar_type, index_type>::inverseRamp(RealT ramp_output) const
858869
{
859-
static constexpr RealT SOFTPLUS_WIDTH = static_cast<RealT>(50.0);
860-
861-
const RealT scaled_output = Math::MU<RealT> * ramp_output;
862-
if (scaled_output > SOFTPLUS_WIDTH)
863-
{
864-
return ramp_output;
865-
}
866-
return std::log(std::expm1(scaled_output)) / Math::MU<RealT>;
870+
const RealT mu = Math::MU<RealT>;
871+
return ramp_output + std::log(-std::expm1(-mu * ramp_output)) / mu;
867872
}
868873

869874
/**

‎GridKit/Model/PhasorDynamics/Exciter/ESDC1A/README.md‎

Lines changed: 40 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ Invalid ESDC1A parameter sets are rejected by the following checks:
5454
\begin{aligned}
5555
K_A
5656
&> 0 \\
57-
T_R, T_A, T_B, T_C, T_E, T_{F1}
57+
T_R, T_A, T_B, T_E, T_{F1}
5858
&\ge 0 \\
5959
V_R^{\min}
6060
&\le V_R^{\max} \\
@@ -71,11 +71,12 @@ The saturation points are either disabled together,
7171
S_E(E_1) = S_E(E_2) = 0,
7272
```
7373

74-
or define a valid two-point quadratic fit:
74+
or define a valid two-point scaled-quadratic fit:
7575

7676
```math
7777
\begin{aligned}
78-
E_1, E_2, S_E(E_1), S_E(E_2) &> 0 \\
78+
E_1, E_2 &> 0 \\
79+
S_E(E_1), S_E(E_2) &\ge 0 \\
7980
\left(E_2-E_1\right)
8081
\left[S_E(E_2)-S_E(E_1)\right] &> 0
8182
\end{aligned}
@@ -99,14 +100,30 @@ raised to that floor in place, so every equation below uses the raised value:
99100
\end{cases}
100101
\end{aligned}
101102
```
102-
103103
When saturation is disabled, $S_A = 0$ and $S_B = 0$. Otherwise,
104104

105+
```math
106+
E S_E(E) = S_B q(E-S_A).
107+
```
108+
109+
When one saturation value is zero,
110+
111+
```math
112+
\begin{aligned}
113+
S_E(E_1)=0 &: \quad S_A=E_1,\qquad
114+
S_B=\dfrac{E_2S_E(E_2)}{(E_2-E_1)^2} \\
115+
S_E(E_2)=0 &: \quad S_A=E_2,\qquad
116+
S_B=\dfrac{E_1S_E(E_1)}{(E_1-E_2)^2}.
117+
\end{aligned}
118+
```
119+
120+
and when both saturation values are positive,
121+
105122
```math
106123
\begin{aligned}
107-
C &= \sqrt{\dfrac{S_E(E_2)}{S_E(E_1)}} \\
124+
C &= \sqrt{\dfrac{E_2S_E(E_2)}{E_1S_E(E_1)}} \\
108125
S_A &= \dfrac{C E_1 - E_2}{C - 1} \\
109-
S_B &= \dfrac{S_E(E_1)}{(E_1 - S_A)^2}
126+
S_B &= \dfrac{E_1S_E(E_1)}{(E_1 - S_A)^2}
110127
\end{aligned}
111128
```
112129

@@ -149,7 +166,7 @@ Symbol | Units | Description
149166
$e_V$ | [p.u.] | Voltage-error summing output |
150167
$V_{\mathrm{LL}}$ | [p.u.] | Input lead-lag output |
151168
$V_{\mathrm{HV}}$ | [p.u.] | High-value gate output |
152-
$S_E$ | [p.u.] | Exciter saturation coefficient | Evaluated at $E_{\mathrm{fd}}'$
169+
$E_{\mathrm{fd}}'S_E(E_{\mathrm{fd}}')$ | [p.u.] | Scaled-quadratic saturation contribution |
153170
$V_{\mathrm{FE}}$ | [p.u.] | Exciter feedback drive |
154171
$E_{\mathrm{fd}}$ | [p.u.] | Field-voltage output | Published through `efd`
155172

@@ -246,11 +263,12 @@ of [Appendix A](#appendix-a-awmin).
246263
& s_{\mathrm{UEL}} = 1
247264
\end{cases} \\
248265
0 &=
249-
-S_E
266+
-E_{\mathrm{fd}}'S_E(E_{\mathrm{fd}}')
250267
+ S_B q\left(E_{\mathrm{fd}}' - S_A\right) \\
251268
0 &=
252269
-V_{\mathrm{FE}}
253-
+ \left(K_E + S_E\right)E_{\mathrm{fd}}' \\
270+
+ K_E E_{\mathrm{fd}}'
271+
+ E_{\mathrm{fd}}'S_E(E_{\mathrm{fd}}') \\
254272
0 &=
255273
-E_{\mathrm{fd}}
256274
+ \left(1 + s_{\mathrm{spd}}\omega\right)E_{\mathrm{fd}}'
@@ -297,10 +315,17 @@ routed through the gate:
297315
E_{\mathrm{fd}}'
298316
&\leftarrow
299317
\dfrac{E_{\mathrm{fd}}}{1 + s_{\mathrm{spd}}\omega} \\
300-
S_E
318+
E_{\mathrm{fd}}'S_E(E_{\mathrm{fd}}')
301319
&\leftarrow S_B q\left(E_{\mathrm{fd}}' - S_A\right) \\
320+
K_E
321+
&\leftarrow
322+
\begin{cases}
323+
\dfrac{V_R^{\max}/10-E_{\mathrm{fd}}'S_E(E_{\mathrm{fd}}')}{E_{\mathrm{fd}}'} & K_E=0 \\
324+
K_E & K_E\ne 0
325+
\end{cases} \\
302326
V_{\mathrm{FE}}
303-
&\leftarrow \left(K_E + S_E\right)E_{\mathrm{fd}}' \\
327+
&\leftarrow K_E E_{\mathrm{fd}}'
328+
+ E_{\mathrm{fd}}'S_E(E_{\mathrm{fd}}') \\
304329
V_R
305330
&\leftarrow V_{\mathrm{FE}} \\
306331
V_{\mathrm{HV}}
@@ -327,9 +352,9 @@ routed through the gate:
327352
Initialization rejects a non-finite or zero bus-voltage magnitude, a
328353
non-finite field-voltage seed, non-finite Known signal inputs, a nonpositive
329354
speed multiplier $1 + s_{\mathrm{spd}}\omega$, $E_{\mathrm{fd}}'<0$ while
330-
$s_{\mathrm{lim}}=1$, $V_R$ outside
331-
$[V_R^{\min},V_R^{\max}]$, and high-value-gate active starts with
332-
$s_{\mathrm{UEL}} = 0$ and
355+
$s_{\mathrm{lim}}=1$, initial $V_R$ outside $[V_R^{\min},V_R^{\max}]$,
356+
and high-value-gate active
357+
starts with $s_{\mathrm{UEL}} = 0$ and
333358
$V_{\mathrm{HV}}\le V_{\mathrm{UEL}}$.
334359

335360
Every check resolves before any storage is written, so a rejected
@@ -361,7 +386,7 @@ Output | Units | Description | Note
361386
`vc` | [p.u.] | Filtered terminal-voltage magnitude | $V_C$
362387
`vr` | [p.u.] | Voltage-regulator output | $V_R$
363388
`vf` | [p.u.] | Stabilizing feedback state | $V_F$
364-
`se` | [p.u.] | Exciter saturation coefficient | $S_E$
389+
`se` | [p.u.] | Scaled-quadratic saturation contribution | $E_{\mathrm{fd}}'S_E(E_{\mathrm{fd}}')$
365390
`vfe` | [p.u.] | Exciter feedback drive | $V_{\mathrm{FE}}$
366391

367392
## Testing

‎GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1.hpp‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ namespace GridKit
5050
VF, ///< Feedback voltage
5151
VE, ///< Exciter control voltage
5252
EFD, ///< Efd
53-
KSAT, ///< Saturation
53+
KSAT, ///< \f$E_{\mathrm{fd}}'S(E_{\mathrm{fd}}')\f$ Scaled-quadratic saturation contribution
5454
MAXIMUM,
5555
};
5656

0 commit comments

Comments
 (0)