From 40bb369432158f950b26cd7ef701f005c4f4c8d0 Mon Sep 17 00:00:00 2001 From: lukelowry Date: Thu, 28 May 2026 18:53:48 -0500 Subject: [PATCH] Add REPCA phasor dynamics model --- CHANGELOG.md | 1 + GridKit/CommonMath.hpp | 30 +- GridKit/CommonMath.md | 16 +- GridKit/Model/PhasorDynamics/CMakeLists.txt | 1 + .../Model/PhasorDynamics/ComponentLibrary.hpp | 1 + .../PhasorDynamics/Converter/CMakeLists.txt | 6 + .../Model/PhasorDynamics/Converter/README.md | 2 +- .../Converter/REPCA/CMakeLists.txt | 54 ++ .../PhasorDynamics/Converter/REPCA/README.md | 368 +++++++++ .../PhasorDynamics/Converter/REPCA/Repca.cpp | 27 + .../PhasorDynamics/Converter/REPCA/Repca.hpp | 192 +++++ .../Converter/REPCA/RepcaData.hpp | 101 +++ .../REPCA/RepcaDependencyTracking.cpp | 27 + .../Converter/REPCA/RepcaEnzyme.cpp | 92 +++ .../Converter/REPCA/RepcaImpl.hpp | 779 ++++++++++++++++++ GridKit/Model/PhasorDynamics/INPUT_FORMAT.md | 7 +- GridKit/Model/PhasorDynamics/SystemModel.hpp | 84 ++ .../Model/PhasorDynamics/SystemModelData.hpp | 3 + .../SystemModelDataJSONParser.hpp | 6 + docs/Figures/PhasorDynamics_REPCA_Diagram.png | Bin 0 -> 91838 bytes .../Math/SmoothnessIndicatorTests.hpp | 50 +- .../Math/runSmoothnessIndicatorTests.cpp | 3 +- tests/UnitTests/PhasorDynamics/CMakeLists.txt | 10 + .../PhasorDynamics/ConverterRepcaTests.hpp | 573 +++++++++++++ .../PhasorDynamics/runConverterRepcaTests.cpp | 21 + 25 files changed, 2430 insertions(+), 24 deletions(-) create mode 100644 GridKit/Model/PhasorDynamics/Converter/CMakeLists.txt create mode 100644 GridKit/Model/PhasorDynamics/Converter/REPCA/CMakeLists.txt create mode 100644 GridKit/Model/PhasorDynamics/Converter/REPCA/README.md create mode 100644 GridKit/Model/PhasorDynamics/Converter/REPCA/Repca.cpp create mode 100644 GridKit/Model/PhasorDynamics/Converter/REPCA/Repca.hpp create mode 100644 GridKit/Model/PhasorDynamics/Converter/REPCA/RepcaData.hpp create mode 100644 GridKit/Model/PhasorDynamics/Converter/REPCA/RepcaDependencyTracking.cpp create mode 100644 GridKit/Model/PhasorDynamics/Converter/REPCA/RepcaEnzyme.cpp create mode 100644 GridKit/Model/PhasorDynamics/Converter/REPCA/RepcaImpl.hpp create mode 100644 docs/Figures/PhasorDynamics_REPCA_Diagram.png create mode 100644 tests/UnitTests/PhasorDynamics/ConverterRepcaTests.hpp create mode 100644 tests/UnitTests/PhasorDynamics/runConverterRepcaTests.cpp diff --git a/CHANGELOG.md b/CHANGELOG.md index 066786e09..387105fb5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -62,6 +62,7 @@ - Added cmake-format hooks, including in pre-commit. - Added off-nominal tap ratio and phase shift support to the PhasorDynamics `Branch` model. - Added portable Vector class to GridKit +- Added `REPCA` converter model implementation for PhasorDynamics. ## v0.1 diff --git a/GridKit/CommonMath.hpp b/GridKit/CommonMath.hpp index 9bb3a5c3c..daaa11e94 100644 --- a/GridKit/CommonMath.hpp +++ b/GridKit/CommonMath.hpp @@ -153,7 +153,31 @@ namespace GridKit } /** - * @brief Smooth two-sided deadband function + * @brief Smooth Type 1 no-offset two-sided deadband function + * + * Smooth approximation to a deadband that returns zero inside the band and + * passes the input through unchanged outside the band. + * + * @tparam ScalarT - scalar data type + * @tparam RealT - Real data type (see GridKit::ScalarTraits::RealT) + * + * @param[in] x - Input signal + * @param[in] lower - Lower breakpoint + * @param[in] upper - Upper breakpoint + * @return Smooth no-offset deadbanded value + */ + template + __attribute__((always_inline)) inline ScalarT deadband1( + const ScalarT x, + const RealT lower, + const RealT upper) + { + assert(lower <= upper); + return x * (sigmoid(lower - x) + sigmoid(x - upper)); + } + + /** + * @brief Smooth Type 2 offset two-sided deadband function * * Smooth approximation to x - min(max(x, lower), upper), composed from the * smooth ramp function. @@ -164,10 +188,10 @@ namespace GridKit * @param[in] x - Input signal * @param[in] lower - Lower breakpoint * @param[in] upper - Upper breakpoint - * @return Smooth deadbanded value + * @return Smooth offset deadbanded value */ template - __attribute__((always_inline)) inline ScalarT deadband( + __attribute__((always_inline)) inline ScalarT deadband2( const ScalarT x, const RealT lower, const RealT upper) diff --git a/GridKit/CommonMath.md b/GridKit/CommonMath.md index a654487a2..29a9a7901 100644 --- a/GridKit/CommonMath.md +++ b/GridKit/CommonMath.md @@ -49,7 +49,8 @@ The scale $\mu=4\cdot f_{\text{sync}}=240$ is chosen so $\sigma$ behaves like a | `max` | Smooth binary maximum | `REECA` | | `min` | Smooth binary minimum | `REECA` | | `clamp` | Bounded saturation | `IEEEST` | -| `deadband` | Signed two-sided deadband | `REECA` | +| `deadband1` | Type 1 no-offset signed two-sided deadband | - | +| `deadband2` | Type 2 offset signed two-sided deadband | `REECA` | | `slew` | Symmetric slew-rate limiter | - | | `linseg` | Saturated linear segment contribution | `REGCA`, `REECA` | | `above` | Above-lower-limit indicator | - | @@ -84,7 +85,15 @@ x & \ell \le x \le u \\ u & x > u \end{cases} \\ -\text{deadband}(x;\ell,u) +\text{deadband1}(x;\ell,u) +&= +\begin{cases} +x & x < \ell \\ +0 & \ell \le x \le u \\ +x & x > u +\end{cases} +\\ +\text{deadband2}(x;\ell,u) &= \begin{cases} x-\ell & x < \ell \\ @@ -156,7 +165,8 @@ f & x \ge u \land f < 0 \\ \text{max}(x,y) &= y+\rho(x-y) \\ \text{min}(x,y) &= x-\rho(x-y) \\ \text{clamp}(x;\ell,u) &= \ell+\rho(x-\ell)-\rho(x-u) \\ -\text{deadband}(x;\ell,u) &= \rho(x-u)-\rho(\ell-x) \\ +\text{deadband1}(x;\ell,u) &= x\left[\sigma(\ell-x)+\sigma(x-u)\right] \\ +\text{deadband2}(x;\ell,u) &= \rho(x-u)-\rho(\ell-x) \\ \text{slew}(f;r) &= -r+\rho(f+r)-\rho(f-r) \\ \text{linseg}(x;a,b,h) &= \dfrac{h}{b-a}\left[\rho(x-a)-\rho(x-b)\right] \\ \text{above}(x;\ell) &= \sigma(x-\ell) \\ diff --git a/GridKit/Model/PhasorDynamics/CMakeLists.txt b/GridKit/Model/PhasorDynamics/CMakeLists.txt index f76e58c0f..1c7d3eb5b 100644 --- a/GridKit/Model/PhasorDynamics/CMakeLists.txt +++ b/GridKit/Model/PhasorDynamics/CMakeLists.txt @@ -40,6 +40,7 @@ add_subdirectory(Branch) add_subdirectory(Bus) add_subdirectory(BusFault) add_subdirectory(BusToSignalAdapter) +add_subdirectory(Converter) add_subdirectory(Exciter) add_subdirectory(Governor) add_subdirectory(Load) diff --git a/GridKit/Model/PhasorDynamics/ComponentLibrary.hpp b/GridKit/Model/PhasorDynamics/ComponentLibrary.hpp index e48a8a9c5..d737178d7 100644 --- a/GridKit/Model/PhasorDynamics/ComponentLibrary.hpp +++ b/GridKit/Model/PhasorDynamics/ComponentLibrary.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include diff --git a/GridKit/Model/PhasorDynamics/Converter/CMakeLists.txt b/GridKit/Model/PhasorDynamics/Converter/CMakeLists.txt new file mode 100644 index 000000000..be3287ed4 --- /dev/null +++ b/GridKit/Model/PhasorDynamics/Converter/CMakeLists.txt @@ -0,0 +1,6 @@ +# [[ +# Author(s): +# - Luke Lowery +# ]] + +add_subdirectory(REPCA) diff --git a/GridKit/Model/PhasorDynamics/Converter/README.md b/GridKit/Model/PhasorDynamics/Converter/README.md index ad38ba19a..e45c070d1 100644 --- a/GridKit/Model/PhasorDynamics/Converter/README.md +++ b/GridKit/Model/PhasorDynamics/Converter/README.md @@ -9,6 +9,6 @@ models and the bus equations, typically through commanded active and reactive cu The GridKit converter documentation includes: -- Renewable Energy Generator/Converter Model REGCA (See [REGCA](REGCA/README.md)) - Renewable Energy Generator/Converter Model REGCB (See [REGCB](REGCB/README.md)) - Renewable Energy Electrical Control Model REECA (See [REECA](REECA/README.md)) +- Renewable Energy Plant Control Model REPCA (See [REPCA](REPCA/README.md)) diff --git a/GridKit/Model/PhasorDynamics/Converter/REPCA/CMakeLists.txt b/GridKit/Model/PhasorDynamics/Converter/REPCA/CMakeLists.txt new file mode 100644 index 000000000..c315b9c8d --- /dev/null +++ b/GridKit/Model/PhasorDynamics/Converter/REPCA/CMakeLists.txt @@ -0,0 +1,54 @@ +# [[ +# Author(s): +# - Luke Lowery +# ]] + +set(_install_headers Repca.hpp RepcaData.hpp) + +if(GRIDKIT_ENABLE_ENZYME) + gridkit_add_library( + phasor_dynamics_converter_repca + SOURCES RepcaEnzyme.cpp + HEADERS ${_install_headers} + INCLUDE_DIRECTORIES PRIVATE ${GRIDKIT_THIRD_PARTY_DIR}/magic-enum/include + LINK_LIBRARIES + PUBLIC + GridKit::phasor_dynamics_core + PUBLIC + GridKit::phasor_dynamics_signal + PRIVATE + ClangEnzymeFlags + COMPILE_OPTIONS + PRIVATE + -mllvm + -enzyme-auto-sparsity=1 + -fno-math-errno) +else() + gridkit_add_library( + phasor_dynamics_converter_repca + SOURCES Repca.cpp + HEADERS ${_install_headers} + INCLUDE_DIRECTORIES PRIVATE ${GRIDKIT_THIRD_PARTY_DIR}/magic-enum/include + LINK_LIBRARIES + PUBLIC + GridKit::phasor_dynamics_core + PUBLIC + GridKit::phasor_dynamics_signal) +endif() + +gridkit_add_library( + phasor_dynamics_converter_repca_dependency_tracking + SOURCES RepcaDependencyTracking.cpp + INCLUDE_DIRECTORIES PRIVATE ${GRIDKIT_THIRD_PARTY_DIR}/magic-enum/include + LINK_LIBRARIES + PUBLIC + GridKit::phasor_dynamics_core + PUBLIC + GridKit::phasor_dynamics_signal_dependency_tracking) + +target_link_libraries( + phasor_dynamics_components + INTERFACE GridKit::phasor_dynamics_converter_repca) +target_link_libraries( + phasor_dynamics_components_dependency_tracking + INTERFACE GridKit::phasor_dynamics_converter_repca_dependency_tracking) diff --git a/GridKit/Model/PhasorDynamics/Converter/REPCA/README.md b/GridKit/Model/PhasorDynamics/Converter/REPCA/README.md new file mode 100644 index 000000000..d690496f6 --- /dev/null +++ b/GridKit/Model/PhasorDynamics/Converter/REPCA/README.md @@ -0,0 +1,368 @@ +# **Renewable Energy Plant Control Model (REPCA)** + +REPCA is a WECC renewable energy plant control model for inverter-coupled +resources. In GridKit it is represented as a plant-level signal-control model +that computes active- and reactive-power commands for downstream electrical +control models. + +## Block Diagram + +Standard REPCA block diagram. + +
+ + + Figure 1: REPCA block diagram. Figure courtesy of [PowerWorld](https://www.powerworld.com/WebHelp/) +
+ +## Model Parameters + +Symbol | Units | JSON | Description | Typical Value | Note +------------------------------------|----------|-------------|---------------------------------------------------------|---------------|------ +$S^{\mathrm{base}}$ | [MVA] | `mva` | REPCA component power base | 0.0 | Block name: `MVABase`; zero uses system base +$s_{\mathrm{comp}}$ | [binary] | `VcompFlag` | Voltage-compensation mode flag | 1.0 | 1 = line-drop compensation, 0 = reactive-droop compensation +$s_{\mathrm{ref}}$ | [binary] | `RefFlag` | Reactive-loop reference flag | 1.0 | 1 = voltage control, 0 = reactive-power control +$s_{\mathrm{freq}}$ | [binary] | `Freqflag` | Active-power control output flag | 0.0 | 1 = use active-power loop output, 0 = output zero +$T_{\mathrm{fltr}}$ | [sec] | `Tfltr` | Voltage and reactive-power measurement filter time constant | 0.05 | If zero, $V_{\mathrm{meas}}$ and $Q_{\mathrm{meas}}$ are algebraic +$T_{\mathrm{ft}}$ | [sec] | `Tft` | Reactive-command lead time constant | 0.0 | +$T_{\mathrm{fv}}$ | [sec] | `Tfv` | Reactive-command lag time constant | 3.0 | +$T_{\mathrm{p}}$ | [sec] | `Tp` | Active-power measurement filter time constant | 0.0 | If zero, $P_{\mathrm{meas}}$ is algebraic +$T_{\mathrm{lag}}$ | [sec] | `Tlag` | Active-power command lag time constant | 3.0 | If zero, $P_{\mathrm{ref}}$ is algebraic +$V_{\mathrm{frz}}$ | [p.u.] | `Vfrz` | Regulated-voltage threshold below which the reactive PI state freezes | 0.7 | +$R_c$ | [p.u.] | `Rc` | Line-drop compensation resistance | 0.0 | +$X_c$ | [p.u.] | `Xc` | Line-drop compensation reactance | 0.0 | +$K_c$ | [p.u.] | `Kc` | Reactive-current compensation gain | 1.0 | +$D_{\mathrm{bd1}}$ | [p.u.] | `dbdlow` | Lower reactive-loop deadband threshold | 0.0 | REPCA1/REPCTA1 key; negative of `dbd` for symmetric REPC_A data +$D_{\mathrm{bd2}}$ | [p.u.] | `dbdupper` | Upper reactive-loop deadband threshold | 0.0 | REPCA1/REPCTA1 key; `dbd` for symmetric REPC_A data +$e^{\max}$ | [p.u.] | `emax` | Maximum reactive-loop error limit | 1.0 | +$e^{\min}$ | [p.u.] | `emin` | Minimum reactive-loop error limit | -1.0 | +$K_{\mathrm{p}}$ | [p.u.] | `Kp` | Reactive-power controller proportional gain | 10.0 | +$K_{\mathrm{i}}$ | [p.u./s] | `Ki` | Reactive-power controller integral gain | 10.0 | +$Q^{\max}$ | [p.u.] | `Qmax` | Maximum reactive-power command | 1.0 | +$Q^{\min}$ | [p.u.] | `Qmin` | Minimum reactive-power command | -1.0 | +$D_{f,\mathrm{bd1}}$ | [p.u.] | `fdbd1` | Lower frequency-error deadband threshold | 0.0 | +$D_{f,\mathrm{bd2}}$ | [p.u.] | `fdbd2` | Upper frequency-error deadband threshold | 0.0 | +$D_{\mathrm{dn}}$ | [p.u.] | `Ddn` | Down-frequency droop response gain | 20.0 | +$D_{\mathrm{up}}$ | [p.u.] | `Dup` | Up-frequency droop response gain | 0.0 | +$e_P^{\max}$ | [p.u.] | `femax` | Maximum active-power error limit | 1.0 | +$e_P^{\min}$ | [p.u.] | `femin` | Minimum active-power error limit | -1.0 | +$K_{\mathrm{pg}}$ | [p.u.] | `Kpg` | Active-power controller proportional gain | 10.0 | +$K_{\mathrm{ig}}$ | [p.u./s] | `Kig` | Active-power controller integral gain | 10.0 | +$P^{\max}$ | [p.u.] | `Pmax` | Maximum active-power command | 2.0 | +$P^{\min}$ | [p.u.] | `Pmin` | Minimum active-power command | 0.0 | + +### Parameter Validation + +Invalid REPCA parameter sets are rejected by the following checks. + +The required checks are: + +```math +\begin{aligned} + &S^{\mathrm{base}} \ge 0 \\ + &s_{\mathrm{comp}}, s_{\mathrm{ref}}, s_{\mathrm{freq}} \in \{0,1\} \\ + &T_{\mathrm{fltr}}, T_{\mathrm{p}}, T_{\mathrm{lag}} \ge 0 \\ + &T_{\mathrm{ft}} \ge 0,\quad T_{\mathrm{fv}} > 0 \\ + &V_{\mathrm{frz}} \ge 0 \\ + &D_{\mathrm{bd1}} \le 0 \le D_{\mathrm{bd2}} \\ + &e^{\min} \le 0 \le e^{\max} \\ + &Q^{\min} \le Q^{\max} \\ + &D_{f,\mathrm{bd1}} \le 0 \le D_{f,\mathrm{bd2}} \\ + &D_{\mathrm{dn}}, D_{\mathrm{up}} \ge 0 \\ + &e_P^{\min} \le 0 \le e_P^{\max} \\ + &P^{\min} \le P^{\max} +\end{aligned} +``` + +### Model Derived Parameters + +The off-mode flag complements are: + +```math +\begin{aligned} + s_{\mathrm{comp}}^{\mathrm{off}} &= 1 - s_{\mathrm{comp}} \\ + s_{\mathrm{ref}}^{\mathrm{off}} &= 1 - s_{\mathrm{ref}} +\end{aligned} +``` + +The branch-power base conversion factor is: + +```math +\begin{aligned} + k_{\mathrm{base}} &= + \begin{cases} + 1, & S^{\mathrm{base}} = 0 \\ + \dfrac{S^{\mathrm{sys}}}{S^{\mathrm{base}}}, & S^{\mathrm{base}} > 0 + \end{cases} +\end{aligned} +``` + +Here $S^{\mathrm{sys}}$ is the network system power base. Branch power inputs +are on system base and are converted to component base with +$k_{\mathrm{base}}$. Branch current inputs are already on component base. + +## Model Variables + +### Internal Variables + +#### Differential + +Symbol | Units | Description | Note +------------------------|--------|-------------------------------------|------ +$V_{\mathrm{meas}}$ | [p.u.] | Filtered regulated voltage | State 1 in Fig. 1; source label: `Vmeas`; algebraic when $T_{\mathrm{fltr}} = 0$ +$Q_{\mathrm{meas}}$ | [p.u.] | Filtered reactive-power signal | State 2 in Fig. 1; source label: `Qmeas`; algebraic when $T_{\mathrm{fltr}} = 0$ +$x_Q$ | [p.u.] | Reactive PI controller state | State 3 in Fig. 1; source label: `Reactive PI` +$x_{\mathrm{Qext}}$ | [p.u.] | Reactive-command lead-lag state | State 4 in Fig. 1; source label: `Qext` +$P_{\mathrm{meas}}$ | [p.u.] | Filtered active-power signal | State 5 in Fig. 1; source label: `Pmeas`; algebraic when $T_{\mathrm{p}} = 0$ +$x_P$ | [p.u.] | Active-power PI controller state | State 6 in Fig. 1; source label: `Power PI` +$P_{\mathrm{ref}}$ | [p.u.] | Active-power command lag state | State 7 in Fig. 1; source label: `Pref`; algebraic when $T_{\mathrm{lag}} = 0$ + +#### Algebraic + +Symbol | Units | Description | Note +--------------------------------|--------|-------------------------------------|------ +$V_{\mathrm{reg}}$ | [p.u.] | Regulated-bus voltage magnitude | From regulated-bus phasor +$V_{\mathrm{ldc}}$ | [p.u.] | Line-drop compensated voltage magnitude | Selected when $s_{\mathrm{comp}}=1$ +$V_{\mathrm{droop}}$ | [p.u.] | Reactive-droop-compensated voltage | Selected when $s_{\mathrm{comp}}=0$ +$V_{\mathrm{ctrl}}$ | [p.u.] | Selected voltage-measurement input | Input to $V_{\mathrm{meas}}$ filter +$s_{\mathrm{frz}}$ | [binary] | Reactive-PI voltage-enable indicator | 1 when $V_{\mathrm{reg}} > V_{\mathrm{frz}}$ +$e_{\mathrm{RQ}}$ | [p.u.] | Selected reactive-loop error | Chosen by $s_{\mathrm{ref}}$ +$e_{\mathrm{RQ}}^{\mathrm{db}}$ | [p.u.] | Deadbanded reactive-loop error | Defined by CommonMath `deadband2` +$e_{\mathrm{RQ}}^{\mathrm{lim}}$ | [p.u.] | Limited reactive-loop error | Feeds reactive PI +$Q_{\mathrm{PI}}$ | [p.u.] | Reactive PI output | Limited by $Q^{\min}$ and $Q^{\max}$ +$Q_{\mathrm{ext}}$ | [p.u.] | Reactive-power command output | Sent to downstream electrical control +$e_f$ | [p.u.] | Frequency error after deadband | From $f_{\mathrm{ref}} - f$ +$e_P$ | [p.u.] | Active-power control error | Plant reference minus $P_{\mathrm{meas}}$ plus frequency droop correction +$e_P^{\mathrm{lim}}$ | [p.u.] | Limited active-power control error | Feeds active-power PI +$P_{\mathrm{PI}}$ | [p.u.] | Active-power PI output | Limited by $P^{\min}$ and $P^{\max}$ +$P_{\mathrm{ext}}$ | [p.u.] | Active-power command output | Sent to downstream electrical control when $s_{\mathrm{freq}}=1$ + +### External Variables + +#### Differential +None. + +#### Algebraic + +Symbol | Units | Description | Note +-------------------------------------|--------|-----------------------------------|------ +$V_{\mathrm{reg,r}}$ | [p.u.] | Regulated-bus voltage, real component | Source label: `Vreg` +$V_{\mathrm{reg,i}}$ | [p.u.] | Regulated-bus voltage, imaginary component | Source label: `Vreg` +$I_{\mathrm{br,r}}$ | [p.u.] | Branch current real component | Component base; source label: `Ibranch`; required +$I_{\mathrm{br,i}}$ | [p.u.] | Branch current imaginary component | Component base; source label: `Ibranch`; required +$P_{\mathrm{br}}$ | [p.u.] | Branch active power | System base; required +$Q_{\mathrm{br}}$ | [p.u.] | Branch reactive power | System base; required +$V_{\mathrm{ref}}$ | [p.u.] | Voltage-control reference | Optional, defaults to initialized constant +$Q_{\mathrm{ref}}$ | [p.u.] | Reactive-power reference | Component base; optional, defaults to initialized constant +$P_{\mathrm{plant}}^{\mathrm{ref}}$ | [p.u.] | Plant active-power reference | Component base; optional, defaults to initialized constant +$f$ | [p.u.] | Frequency input | Source label: `Freq`; optional, defaults to zero +$f_{\mathrm{ref}}$ | [p.u.] | Frequency reference | Source label: `Freq_ref`; optional, defaults to zero + +## Model Equations + +### Differential Equations + +The measurement filters and active-power output lag are written in descriptor form; when $T_{\mathrm{fltr}} = 0$, $T_{\mathrm{p}} = 0$, or $T_{\mathrm{lag}} = 0$, the corresponding residual becomes algebraic. The reactive-command lead-lag denominator requires $T_{\mathrm{fv}} > 0$. + +```math +\begin{aligned} + 0 &= -T_{\mathrm{fltr}}\dot V_{\mathrm{meas}} - V_{\mathrm{meas}} + V_{\mathrm{ctrl}} \\ + 0 &= -T_{\mathrm{fltr}}\dot Q_{\mathrm{meas}} - Q_{\mathrm{meas}} + k_{\mathrm{base}}Q_{\mathrm{br}} \\ + 0 &= + -\dot x_Q + + s_{\mathrm{frz}} + \text{antiwindup}\!\left( + Q_{\mathrm{PI}}, + K_{\mathrm{i}}e_{\mathrm{RQ}}^{\mathrm{lim}}, + Q^{\min}, + Q^{\max} + \right) \\ + 0 &= -T_{\mathrm{fv}}\dot x_{\mathrm{Qext}} - x_{\mathrm{Qext}} + Q_{\mathrm{PI}} \\ + 0 &= -T_{\mathrm{p}}\dot P_{\mathrm{meas}} - P_{\mathrm{meas}} + k_{\mathrm{base}}P_{\mathrm{br}} \\ + 0 &= + -\dot x_P + + \text{antiwindup}\!\left( + P_{\mathrm{PI}}, + K_{\mathrm{ig}}e_P^{\mathrm{lim}}, + P^{\min}, + P^{\max} + \right) \\ + 0 &= -T_{\mathrm{lag}}\dot P_{\mathrm{ref}} - P_{\mathrm{ref}} + P_{\mathrm{PI}} +\end{aligned} +``` + +CommonMath defines the [Anti-Windup](../../../../CommonMath.md#anti-windup-indicator) target and smooth approximation. + +### Algebraic Equations + +The algebraic targets use CommonMath helper notation where applicable: + +```math +\begin{aligned} + 0 &= -V_{\mathrm{reg}}^2 + V_{\mathrm{reg,r}}^2 + V_{\mathrm{reg,i}}^2 \\ + 0 &= + -V_{\mathrm{ldc}}^2 + + (V_{\mathrm{reg,r}} - R_c I_{\mathrm{br,r}} + X_c I_{\mathrm{br,i}})^2 + + (V_{\mathrm{reg,i}} - R_c I_{\mathrm{br,i}} - X_c I_{\mathrm{br,r}})^2 \\ + 0 &= -V_{\mathrm{droop}} + V_{\mathrm{reg}} + K_c k_{\mathrm{base}}Q_{\mathrm{br}} \\ + 0 &= -V_{\mathrm{ctrl}} + s_{\mathrm{comp}}V_{\mathrm{ldc}} + s_{\mathrm{comp}}^{\mathrm{off}}V_{\mathrm{droop}} \\ + 0 &= -s_{\mathrm{frz}} + \text{above}(V_{\mathrm{reg}}, V_{\mathrm{frz}}) \\[0.5ex] + 0 &= -e_{\mathrm{RQ}} + + s_{\mathrm{ref}}\left(V_{\mathrm{ref}} - V_{\mathrm{meas}}\right) + + s_{\mathrm{ref}}^{\mathrm{off}}\left(Q_{\mathrm{ref}} - Q_{\mathrm{meas}}\right) \\ + 0 &= -e_{\mathrm{RQ}}^{\mathrm{db}} + + \text{deadband2}(e_{\mathrm{RQ}}, D_{\mathrm{bd1}}, D_{\mathrm{bd2}}) \\ + 0 &= -e_{\mathrm{RQ}}^{\mathrm{lim}} + + \text{clamp}(e_{\mathrm{RQ}}^{\mathrm{db}}, e^{\min}, e^{\max}) \\ + 0 &= -Q_{\mathrm{PI}} + \text{clamp}(K_{\mathrm{p}} e_{\mathrm{RQ}}^{\mathrm{lim}} + x_Q, Q^{\min}, Q^{\max}) \\ + 0 &= -T_{\mathrm{fv}}(Q_{\mathrm{ext}} - x_{\mathrm{Qext}}) + + T_{\mathrm{ft}}(Q_{\mathrm{PI}} - x_{\mathrm{Qext}}) \\[0.5ex] + 0 &= -e_f + \text{deadband2}(f_{\mathrm{ref}} - f, D_{f,\mathrm{bd1}}, D_{f,\mathrm{bd2}}) \\ + 0 &= -e_P + + P_{\mathrm{plant}}^{\mathrm{ref}} + - P_{\mathrm{meas}} + + D_{\mathrm{dn}}\rho(e_f) + - D_{\mathrm{up}}\rho(-e_f) \\ + 0 &= -e_P^{\mathrm{lim}} + \text{clamp}(e_P, e_P^{\min}, e_P^{\max}) \\ + 0 &= -P_{\mathrm{PI}} + \text{clamp}(K_{\mathrm{pg}} e_P^{\mathrm{lim}} + x_P, P^{\min}, P^{\max}) \\ + 0 &= -P_{\mathrm{ext}} + s_{\mathrm{freq}}P_{\mathrm{ref}} +\end{aligned} +``` + +The $V_{\mathrm{reg}}$ and $V_{\mathrm{ldc}}$ variables use nonnegative branches of squared algebraic residuals. The exact frequency-droop target is $D_{\mathrm{dn}}e_f$ for positive deadbanded frequency error, $D_{\mathrm{up}}e_f$ for negative deadbanded frequency error, and zero in the deadband. The $\rho$ form above is the smooth CommonMath representation of that split. + +CommonMath defines the helper targets and smooth approximations for [above, clamp, and deadband2](../../../../CommonMath.md#derived-functions). The frequency split uses the primitive [ramp](../../../../CommonMath.md#primitives) $\rho$. + +## Initialization + +Initialization is performed by evaluating the steady-state residuals in dependency order. Let subscript $0$ denote initial values and set all internal derivatives to zero. REPCA reads branch power and branch current from connected signal ports: + +```math +\begin{aligned} + P_{\mathrm{br},0} &= pbranch_0 \\ + Q_{\mathrm{br},0} &= qbranch_0 \\ + I_{\mathrm{br,r},0} &= ibranchr_0 \\ + I_{\mathrm{br,i},0} &= ibranchi_0 +\end{aligned} +``` + +Then convert branch power to component base and evaluate the voltage-compensation signals: + +```math +\begin{aligned} + V_{\mathrm{reg},0} &= \sqrt{V_{\mathrm{reg,r},0}^2 + V_{\mathrm{reg,i},0}^2} \\ + V_{\mathrm{ldc},0} + &= \sqrt{ + (V_{\mathrm{reg,r},0} - R_c I_{\mathrm{br,r},0} + X_c I_{\mathrm{br,i},0})^2 + + (V_{\mathrm{reg,i},0} - R_c I_{\mathrm{br,i},0} - X_c I_{\mathrm{br,r},0})^2 + } \\ + V_{\mathrm{droop},0} &= V_{\mathrm{reg},0} + K_c k_{\mathrm{base}}Q_{\mathrm{br},0} \\ + V_{\mathrm{ctrl},0} &= s_{\mathrm{comp}}V_{\mathrm{ldc},0} + s_{\mathrm{comp}}^{\mathrm{off}}V_{\mathrm{droop},0} +\end{aligned} +``` + +If optional reference inputs are not connected, use steady-state constants that +make the selected control errors zero. Omitted frequency inputs default to zero: + +```math +\begin{aligned} + f_0 &= 0 \\ + f_{\mathrm{ref},0} &= 0 \\ + e_{f,0} &= \text{deadband2}(f_{\mathrm{ref},0} - f_0, D_{f,\mathrm{bd1}}, D_{f,\mathrm{bd2}}) \\ + P_{\mathrm{freq},0} &= D_{\mathrm{dn}}\rho(e_{f,0}) - D_{\mathrm{up}}\rho(-e_{f,0}) \\ + V_{\mathrm{ref},0} &= V_{\mathrm{ctrl},0} \\ + Q_{\mathrm{ref},0} &= k_{\mathrm{base}}Q_{\mathrm{br},0} \\ + P_{\mathrm{plant},0}^{\mathrm{ref}} &= k_{\mathrm{base}}P_{\mathrm{br},0} - P_{\mathrm{freq},0} +\end{aligned} +``` + +Connected optional references use their supplied initial values and must satisfy the same steady-state residuals. + +Initialize the measurement variables from the descriptor-form filter residuals: + +```math +\begin{aligned} + V_{\mathrm{meas},0} &= V_{\mathrm{ctrl},0} \\ + Q_{\mathrm{meas},0} &= k_{\mathrm{base}}Q_{\mathrm{br},0} \\ + P_{\mathrm{meas},0} &= k_{\mathrm{base}}P_{\mathrm{br},0} +\end{aligned} +``` + +Then evaluate the reactive-control algebraic chain: + +```math +\begin{aligned} + s_{\mathrm{frz},0} &= \text{above}(V_{\mathrm{reg},0}, V_{\mathrm{frz}}) \\ + e_{\mathrm{RQ},0} + &= s_{\mathrm{ref}}\left(V_{\mathrm{ref},0} - V_{\mathrm{meas},0}\right) + + s_{\mathrm{ref}}^{\mathrm{off}}\left(Q_{\mathrm{ref},0} - Q_{\mathrm{meas},0}\right) \\ + e_{\mathrm{RQ},0}^{\mathrm{db}} + &= \text{deadband2}(e_{\mathrm{RQ},0}, D_{\mathrm{bd1}}, D_{\mathrm{bd2}}) \\ + e_{\mathrm{RQ},0}^{\mathrm{lim}} + &= \text{clamp}(e_{\mathrm{RQ},0}^{\mathrm{db}}, e^{\min}, e^{\max}) +\end{aligned} +``` + +Choose the initial reactive-command output and PI states as: + +```math +\begin{aligned} + Q_{\mathrm{ext},0} &= \text{clamp}(k_{\mathrm{base}}Q_{\mathrm{br},0}, Q^{\min}, Q^{\max}) \\ + Q_{\mathrm{PI},0} &= Q_{\mathrm{ext},0} \\ + x_{\mathrm{Qext},0} &= Q_{\mathrm{PI},0} \\ + x_{Q,0} &= Q_{\mathrm{ext},0} - K_{\mathrm{p}}e_{\mathrm{RQ},0}^{\mathrm{lim}} +\end{aligned} +``` + +This satisfies the lead-lag algebraic output and produces a zero lead-lag +state derivative. The reactive PI state derivative must also be zero after +antiwindup is applied. + +If the `qext` output signal already carries a seeded value, REPCA uses that +value for $Q_{\mathrm{ext},0}$ and back-solves the same PI states from it. + +Evaluate the active-power control chain: + +```math +\begin{aligned} + e_{P,0} + &= P_{\mathrm{plant},0}^{\mathrm{ref}} + - P_{\mathrm{meas},0} + + P_{\mathrm{freq},0} \\ + e_{P,0}^{\mathrm{lim}} &= \text{clamp}(e_{P,0}, e_P^{\min}, e_P^{\max}) +\end{aligned} +``` + +Choose: + +```math +\begin{aligned} + P_{\mathrm{ref},0} &= \text{clamp}(k_{\mathrm{base}}P_{\mathrm{br},0}, P^{\min}, P^{\max}) \\ + P_{\mathrm{PI},0} &= P_{\mathrm{ref},0} \\ + x_{P,0} &= P_{\mathrm{ref},0} - K_{\mathrm{pg}}e_{P,0}^{\mathrm{lim}} \\ + P_{\mathrm{ext},0} &= s_{\mathrm{freq}}P_{\mathrm{ref},0} +\end{aligned} +``` + +If the `pext` output signal already carries a seeded value and +$s_{\mathrm{freq}} \ne 0$, REPCA uses +$P_{\mathrm{ref},0}=P_{\mathrm{ext},0}/s_{\mathrm{freq}}$. + +The initialized derivative vector is zero. Initialization rejects the case if +the reactive-power or active-power PI antiwindup rate is nonzero. + +## Model Outputs + +Output | Units | Description | Note +----------------|--------|-------------------------------------|------ +`qext` | [p.u.] | Reactive-power command output | Component base; sent to downstream electrical control +`pext` | [p.u.] | Active-power command output | Component base; sent to downstream electrical control when `Freqflag = 1` +`vmeas` | [p.u.] | Filtered regulated voltage | +`qmeas` | [p.u.] | Filtered reactive-power signal | +`pmeas` | [p.u.] | Filtered active-power signal | +`pref` | [p.u.] | Active-power command lag state | +`vctrl` | [p.u.] | Selected voltage-measurement input | +`sfrz` | [binary] | Reactive-PI voltage-enable indicator | +`qpi` | [p.u.] | Reactive PI output | +`pfreq` | [p.u.] | Computed frequency droop active-power correction | +`ppi` | [p.u.] | Active-power PI output | diff --git a/GridKit/Model/PhasorDynamics/Converter/REPCA/Repca.cpp b/GridKit/Model/PhasorDynamics/Converter/REPCA/Repca.cpp new file mode 100644 index 000000000..7a47d9271 --- /dev/null +++ b/GridKit/Model/PhasorDynamics/Converter/REPCA/Repca.cpp @@ -0,0 +1,27 @@ +/** + * @file Repca.cpp + * @author Luke Lowery (lukel@tamu.edu) + * @brief Non-Enzyme instantiation for the REPCA plant-control model. + */ + +#include "RepcaImpl.hpp" + +namespace GridKit +{ + namespace PhasorDynamics + { + namespace Converter + { + template + int Repca::evaluateJacobian() + { + Log::misc() << "Evaluate Jacobian for Repca..." << std::endl; + Log::misc() << "Jacobian evaluation is not implemented!" << std::endl; + return 0; + } + + template class Repca; + template class Repca; + } // namespace Converter + } // namespace PhasorDynamics +} // namespace GridKit diff --git a/GridKit/Model/PhasorDynamics/Converter/REPCA/Repca.hpp b/GridKit/Model/PhasorDynamics/Converter/REPCA/Repca.hpp new file mode 100644 index 000000000..0be462b57 --- /dev/null +++ b/GridKit/Model/PhasorDynamics/Converter/REPCA/Repca.hpp @@ -0,0 +1,192 @@ +/** + * @file Repca.hpp + * @author Luke Lowery (lukel@tamu.edu) + * @brief Declaration of the REPCA plant-control model. + */ + +#pragma once + +#include +#include +#include + +#include +#include +#include +#include + +namespace GridKit +{ + namespace PhasorDynamics + { + template + class BusBase; + + template + class SignalNode; + + namespace Converter + { + /// Internal variables of a `Repca`. + enum class RepcaInternalVariables : size_t + { + VMEAS, ///< Filtered regulated voltage + QMEAS, ///< Filtered reactive-power signal + XQ, ///< Reactive PI state + XQEXT, ///< Reactive-command lead-lag state + PMEAS, ///< Filtered active-power signal + XP, ///< Active-power PI state + PREF, ///< Active-power command lag state + VREG, ///< Regulated-bus voltage magnitude + VLDC, ///< Line-drop compensated voltage magnitude + VDROOP, ///< Reactive-droop-compensated voltage + VCTRL, ///< Selected voltage-measurement input + SFRZ, ///< Reactive PI voltage-enable indicator + ERQ, ///< Selected reactive-loop error + ERQDB, ///< Deadbanded reactive-loop error + ERQLIM, ///< Limited reactive-loop error + QPI, ///< Reactive PI output + QEXT, ///< Reactive-power command output + EF, ///< Frequency error after deadband + EP, ///< Active-power control error + EPLIM, ///< Limited active-power control error + PPI, ///< Active-power PI output + PEXT, ///< Active-power command output + MAXIMUM, + }; + + /// External variables of a `Repca`. + enum class RepcaExternalVariables : size_t + { + IBRANCHR, ///< Branch current real component on component base + IBRANCHI, ///< Branch current imaginary component on component base + QBRANCH, ///< Branch reactive-power signal on system base + PBRANCH, ///< Branch active-power signal on system base + VREF, ///< Voltage reference + QREF, ///< Reactive-power reference + PPLANTREF, ///< Plant active-power reference + FREQ, ///< Optional frequency input + FREQREF, ///< Optional frequency reference + MAXIMUM, + }; + + template + class Repca : public Component + { + using Component::alpha_; + using Component::f_; + using Component::gridkit_component_id_; + using Component::J_; + using Component::J_cols_buffer_; + using Component::J_rows_buffer_; + using Component::J_vals_buffer_; + using Component::residual_indices_; + using Component::size_; + using Component::tag_; + using Component::va_system_base_; + using Component::variable_indices_; + using Component::wb_; + using Component::y_; + using Component::yp_; + + public: + using ScalarT = scalar_type; + using IdxT = index_type; + using RealT = typename Component::RealT; + using BusT = BusBase; + using SignalT = SignalNode; + using ModelDataT = RepcaData; + using MonitorT = Model::VariableMonitor; + + Repca(BusT* bus); + Repca(BusT* bus, const ModelDataT& data); + ~Repca(); + + int setGridKitComponentID(IdxT) override final; + int allocate() override final; + int verify() const override final; + int initialize() override final; + int tagDifferentiable() override final; + int evaluateResidual() override final; + int evaluateJacobian() override final; + + auto getSignals() + -> ComponentSignals& + { + return signals_; + } + + const Model::VariableMonitorBase* getMonitor() const override; + + __attribute__((always_inline)) inline int evaluateInternalResidual( + ScalarT*, ScalarT*, ScalarT*, ScalarT*, ScalarT*); + + private: + void initModelParams(const ModelDataT& data); + void initializeMonitor(); + + int verifyBranchSignalPorts() const; + ScalarT readExternalOrDefault(RepcaExternalVariables variable, ScalarT default_value) const; + void setDerivedParameters(); + ScalarT toComponentBase(ScalarT value) const; + + ScalarT& Vr(); + ScalarT& Vi(); + + BusT* bus_{nullptr}; + + RealT mva_base_{0}; + RealT VcompFlag_{0}; + RealT RefFlag_{0}; + RealT Freqflag_{0}; + RealT Tfltr_{0}; + RealT Tft_{0}; + RealT Tfv_{0}; + RealT Tp_{0}; + RealT Tlag_{0}; + RealT Vfrz_{0}; + RealT Rc_{0}; + RealT Xc_{0}; + RealT Kc_{0}; + RealT dbdlow_{0}; + RealT dbdupper_{0}; + RealT emax_{0}; + RealT emin_{0}; + RealT Kp_{0}; + RealT Ki_{0}; + RealT Qmax_{0}; + RealT Qmin_{0}; + RealT fdbd1_{0}; + RealT fdbd2_{0}; + RealT Ddn_{0}; + RealT Dup_{0}; + RealT femax_{0}; + RealT femin_{0}; + RealT Kpg_{0}; + RealT Kig_{0}; + RealT Pmax_{0}; + RealT Pmin_{0}; + + IdxT parameter_error_count_{0}; + RealT system_to_component_base_{1}; + RealT vcomp_off_{1}; + RealT ref_off_{1}; + + ScalarT vref_set_{0}; + ScalarT qref_set_{0}; + ScalarT pplantref_set_{0}; + ScalarT freq_set_{0}; + ScalarT freqref_set_{0}; + + ComponentSignals signals_; + std::unique_ptr monitor_; + + std::vector ws_; + std::vector ws_indices_; + }; + } // namespace Converter + } // namespace PhasorDynamics +} // namespace GridKit diff --git a/GridKit/Model/PhasorDynamics/Converter/REPCA/RepcaData.hpp b/GridKit/Model/PhasorDynamics/Converter/REPCA/RepcaData.hpp new file mode 100644 index 000000000..018e6fe46 --- /dev/null +++ b/GridKit/Model/PhasorDynamics/Converter/REPCA/RepcaData.hpp @@ -0,0 +1,101 @@ +/** + * @file RepcaData.hpp + * @author Luke Lowery (lukel@tamu.edu) + * @brief Modeling data for the REPCA plant-control model. + */ + +#pragma once + +#include + +namespace GridKit +{ + namespace PhasorDynamics + { + namespace Converter + { + /// Parameter keys for the REPCA plant-control model. + enum class RepcaParameters + { + mva, ///< Component MVA base; zero uses system base + VcompFlag, ///< Voltage-compensation mode flag + RefFlag, ///< Reactive-loop reference flag + Freqflag, ///< Active-power output flag + Tfltr, ///< Voltage and reactive-power filter time constant + Tft, ///< Reactive-command lead time constant + Tfv, ///< Reactive-command lag time constant + Tp, ///< Active-power measurement filter time constant + Tlag, ///< Active-power command lag time constant + Vfrz, ///< Reactive PI freeze voltage threshold + Rc, ///< Line-drop compensation resistance + Xc, ///< Line-drop compensation reactance + Kc, ///< Reactive-current compensation gain + dbdlow, ///< Lower reactive-loop deadband threshold + dbdupper, ///< Upper reactive-loop deadband threshold + emax, ///< Maximum reactive-loop error limit + emin, ///< Minimum reactive-loop error limit + Kp, ///< Reactive controller proportional gain + Ki, ///< Reactive controller integral gain + Qmax, ///< Maximum reactive-power command + Qmin, ///< Minimum reactive-power command + fdbd1, ///< Lower frequency-error deadband threshold + fdbd2, ///< Upper frequency-error deadband threshold + Ddn, ///< Down-frequency droop response gain + Dup, ///< Up-frequency droop response gain + femax, ///< Maximum active-power error limit + femin, ///< Minimum active-power error limit + Kpg, ///< Active-power proportional gain + Kig, ///< Active-power integral gain + Pmax, ///< Maximum active-power command + Pmin ///< Minimum active-power command + }; + + /// Ports for the REPCA plant-control model. + enum class RepcaPorts + { + bus, ///< Regulated bus ID + ibranchr, ///< Branch current real signal ID + ibranchi, ///< Branch current imaginary signal ID + qbranch, ///< Branch reactive-power signal ID + pbranch, ///< Branch active-power signal ID + vref, ///< Optional voltage reference signal ID + qref, ///< Optional reactive-power reference signal ID + pplantref, ///< Optional plant active-power reference signal ID + freq, ///< Optional frequency input signal ID + freqref, ///< Optional frequency reference signal ID + qext, ///< Reactive-power command output signal ID + pext ///< Active-power command output signal ID + }; + + /// Variables available through the monitor interface. + enum class RepcaMonitorableVariables + { + qext, ///< Reactive-power command output + pext, ///< Active-power command output + vmeas, ///< Filtered regulated voltage + qmeas, ///< Filtered reactive-power signal + pmeas, ///< Filtered active-power signal + pref, ///< Active-power command lag state + vctrl, ///< Selected voltage-measurement input + sfrz, ///< Reactive PI voltage-enable indicator + qpi, ///< Reactive PI output + pfreq, ///< Frequency droop active-power correction + ppi ///< Active-power PI output + }; + + template + struct RepcaData : public ComponentData + { + RepcaData() = default; + + using Parameters = RepcaParameters; + using Ports = RepcaPorts; + using MonitorableVariables = RepcaMonitorableVariables; + }; + } // namespace Converter + } // namespace PhasorDynamics +} // namespace GridKit diff --git a/GridKit/Model/PhasorDynamics/Converter/REPCA/RepcaDependencyTracking.cpp b/GridKit/Model/PhasorDynamics/Converter/REPCA/RepcaDependencyTracking.cpp new file mode 100644 index 000000000..7b8365122 --- /dev/null +++ b/GridKit/Model/PhasorDynamics/Converter/REPCA/RepcaDependencyTracking.cpp @@ -0,0 +1,27 @@ +/** + * @file RepcaDependencyTracking.cpp + * @author Luke Lowery (lukel@tamu.edu) + * @brief Dependency-tracking instantiations for the REPCA plant-control model. + */ + +#include "RepcaImpl.hpp" + +namespace GridKit +{ + namespace PhasorDynamics + { + namespace Converter + { + template + int Repca::evaluateJacobian() + { + Log::misc() << "Evaluate Jacobian for Repca..." << std::endl; + Log::misc() << "Jacobian evaluation is not implemented!" << std::endl; + return 0; + } + + template class Repca; + template class Repca; + } // namespace Converter + } // namespace PhasorDynamics +} // namespace GridKit diff --git a/GridKit/Model/PhasorDynamics/Converter/REPCA/RepcaEnzyme.cpp b/GridKit/Model/PhasorDynamics/Converter/REPCA/RepcaEnzyme.cpp new file mode 100644 index 000000000..a694d6d03 --- /dev/null +++ b/GridKit/Model/PhasorDynamics/Converter/REPCA/RepcaEnzyme.cpp @@ -0,0 +1,92 @@ +/** + * @file RepcaEnzyme.cpp + * @author Luke Lowery (lukel@tamu.edu) + * @brief Enzyme sparse Jacobian for the REPCA plant-control model. + */ + +#include + +#include "RepcaImpl.hpp" + +namespace GridKit +{ + namespace PhasorDynamics + { + namespace Converter + { + template + int Repca::evaluateJacobian() + { + Log::misc() << "Evaluate Jacobian for Repca..." << std::endl; + Log::misc() << "Jacobian evaluation is experimental!" << std::endl; + + J_.zeroMatrix(); + if (J_rows_buffer_ == nullptr) + { + J_rows_buffer_ = new IdxT[static_cast(size_) * static_cast(size_)]; + J_cols_buffer_ = new IdxT[static_cast(size_) * static_cast(size_)]; + J_vals_buffer_ = new RealT[static_cast(size_) * static_cast(size_)]; + } + + using ModelT = GridKit::PhasorDynamics::Converter::Repca; + using Fn = GridKit::Enzyme::Sparse::MemberFunctions; + + GridKit::Enzyme::Sparse::DfDy::eval(this, + f_.size(), + y_.size(), + (this->getResidualIndices()).data(), + (this->getVariableIndices()).data(), + y_.data(), + yp_.data(), + wb_.data(), + ws_.data(), + alpha_, + J_rows_buffer_, + J_cols_buffer_, + J_vals_buffer_, + J_); + + GridKit::Enzyme::Sparse::DfDwb::eval(this, + f_.size(), + static_cast(bus_->size()), + (this->getResidualIndices()).data(), + (bus_->getVariableIndices()).data(), + y_.data(), + yp_.data(), + wb_.data(), + ws_.data(), + J_rows_buffer_, + J_cols_buffer_, + J_vals_buffer_, + J_); + + GridKit::Enzyme::Sparse::DfDws::eval(this, + f_.size(), + ws_.size(), + (this->getResidualIndices()).data(), + ws_indices_.data(), + y_.data(), + yp_.data(), + wb_.data(), + ws_.data(), + J_rows_buffer_, + J_cols_buffer_, + J_vals_buffer_, + J_); + return 0; + } + + template class Repca; + template class Repca; + } // namespace Converter + } // namespace PhasorDynamics +} // namespace GridKit diff --git a/GridKit/Model/PhasorDynamics/Converter/REPCA/RepcaImpl.hpp b/GridKit/Model/PhasorDynamics/Converter/REPCA/RepcaImpl.hpp new file mode 100644 index 000000000..ffb344f6b --- /dev/null +++ b/GridKit/Model/PhasorDynamics/Converter/REPCA/RepcaImpl.hpp @@ -0,0 +1,779 @@ +/** + * @file RepcaImpl.hpp + * @author Luke Lowery (lukel@tamu.edu) + * @brief Definition of the REPCA plant-control model. + */ + +#pragma once + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace GridKit +{ + namespace PhasorDynamics + { + namespace Converter + { + using Log = ::GridKit::Utilities::Logger; + + template + Repca::Repca(BusT* bus) + : bus_(bus) + { + size_ = static_cast(RepcaInternalVariables::MAXIMUM); + } + + template + Repca::Repca(BusT* bus, const ModelDataT& data) + : bus_(bus), + monitor_(std::make_unique(data)) + { + initModelParams(data); + initializeMonitor(); + size_ = static_cast(RepcaInternalVariables::MAXIMUM); + } + + template + Repca::~Repca() + { + } + + template + scalar_type& Repca::Vr() + { + return bus_->Vr(); + } + + template + scalar_type& Repca::Vi() + { + return bus_->Vi(); + } + + template + void Repca::setDerivedParameters() + { + system_to_component_base_ = ONE; + if (mva_base_ > ZERO) + { + const RealT va_component_base = mva_base_ * static_cast(1.0e6); + system_to_component_base_ = va_system_base_ / va_component_base; + } + vcomp_off_ = ONE - VcompFlag_; + ref_off_ = ONE - RefFlag_; + } + + template + scalar_type Repca::toComponentBase(scalar_type value) const + { + return value * system_to_component_base_; + } + + template + int Repca::verifyBranchSignalPorts() const + { + int ret = 0; + + const bool has_pbranch = signals_.template isAttached(); + const bool has_qbranch = signals_.template isAttached(); + const bool has_ibranchr = signals_.template isAttached(); + const bool has_ibranchi = signals_.template isAttached(); + + if (!has_pbranch || !has_qbranch) + { + Log::error() << "Repca: pbranch and qbranch must be connected\n"; + ret += 1; + } + + if (!has_ibranchr || !has_ibranchi) + { + Log::error() << "Repca: ibranchr and ibranchi must be connected\n"; + ret += 1; + } + + return ret; + } + + template + void Repca::initModelParams(const ModelDataT& data) + { + using Params = typename ModelDataT::Parameters; + + parameter_error_count_ = 0; + + auto load_required_real = [&](auto key, RealT& target, const char* name) + { + if (!data.parameters.contains(key)) + { + Log::error() << "Repca: missing required parameter '" << name << "'\n"; + ++parameter_error_count_; + return; + } + + const auto& value = data.parameters.at(key); + if (const auto* real_value = std::get_if(&value)) + { + target = *real_value; + } + else if (const auto* index_value = std::get_if(&value)) + { + target = static_cast(*index_value); + } + else + { + Log::error() << "Repca: parameter '" << name << "' must be numeric\n"; + ++parameter_error_count_; + } + }; + + auto load_required_switch = [&](auto key, RealT& target, const char* name) + { + if (!data.parameters.contains(key)) + { + Log::error() << "Repca: missing required parameter '" << name << "'\n"; + ++parameter_error_count_; + return; + } + + const auto& value = data.parameters.at(key); + if (const auto* bool_value = std::get_if(&value)) + { + target = ZERO; + if (*bool_value) + { + target = ONE; + } + } + else if (const auto* index_value = std::get_if(&value); + index_value && (*index_value == 0 || *index_value == 1)) + { + target = static_cast(*index_value); + } + else if (const auto* real_value = std::get_if(&value); + real_value && (*real_value == ZERO || *real_value == ONE) ) + { + target = *real_value; + } + else + { + Log::error() << "Repca: parameter '" << name << "' must be bool or 0/1\n"; + ++parameter_error_count_; + } + }; + + load_required_real(Params::mva, mva_base_, "mva"); + load_required_switch(Params::VcompFlag, VcompFlag_, "VcompFlag"); + load_required_switch(Params::RefFlag, RefFlag_, "RefFlag"); + load_required_switch(Params::Freqflag, Freqflag_, "Freqflag"); + load_required_real(Params::Tfltr, Tfltr_, "Tfltr"); + load_required_real(Params::Tft, Tft_, "Tft"); + load_required_real(Params::Tfv, Tfv_, "Tfv"); + load_required_real(Params::Tp, Tp_, "Tp"); + load_required_real(Params::Tlag, Tlag_, "Tlag"); + load_required_real(Params::Vfrz, Vfrz_, "Vfrz"); + load_required_real(Params::Rc, Rc_, "Rc"); + load_required_real(Params::Xc, Xc_, "Xc"); + load_required_real(Params::Kc, Kc_, "Kc"); + load_required_real(Params::dbdlow, dbdlow_, "dbdlow"); + load_required_real(Params::dbdupper, dbdupper_, "dbdupper"); + load_required_real(Params::emax, emax_, "emax"); + load_required_real(Params::emin, emin_, "emin"); + load_required_real(Params::Kp, Kp_, "Kp"); + load_required_real(Params::Ki, Ki_, "Ki"); + load_required_real(Params::Qmax, Qmax_, "Qmax"); + load_required_real(Params::Qmin, Qmin_, "Qmin"); + load_required_real(Params::fdbd1, fdbd1_, "fdbd1"); + load_required_real(Params::fdbd2, fdbd2_, "fdbd2"); + load_required_real(Params::Ddn, Ddn_, "Ddn"); + load_required_real(Params::Dup, Dup_, "Dup"); + load_required_real(Params::femax, femax_, "femax"); + load_required_real(Params::femin, femin_, "femin"); + load_required_real(Params::Kpg, Kpg_, "Kpg"); + load_required_real(Params::Kig, Kig_, "Kig"); + load_required_real(Params::Pmax, Pmax_, "Pmax"); + load_required_real(Params::Pmin, Pmin_, "Pmin"); + + setDerivedParameters(); + } + + template + const Model::VariableMonitorBase* Repca::getMonitor() const + { + return monitor_.get(); + } + + template + void Repca::initializeMonitor() + { + using Variable = typename ModelDataT::MonitorableVariables; + auto index = [](RepcaInternalVariables variable) + { + return static_cast(variable); + }; + + monitor_->set(Variable::qext, [this, index] + { return y_[index(RepcaInternalVariables::QEXT)]; }); + monitor_->set(Variable::pext, [this, index] + { return y_[index(RepcaInternalVariables::PEXT)]; }); + monitor_->set(Variable::vmeas, [this, index] + { return y_[index(RepcaInternalVariables::VMEAS)]; }); + monitor_->set(Variable::qmeas, [this, index] + { return y_[index(RepcaInternalVariables::QMEAS)]; }); + monitor_->set(Variable::pmeas, [this, index] + { return y_[index(RepcaInternalVariables::PMEAS)]; }); + monitor_->set(Variable::pref, [this, index] + { return y_[index(RepcaInternalVariables::PREF)]; }); + monitor_->set(Variable::vctrl, [this, index] + { return y_[index(RepcaInternalVariables::VCTRL)]; }); + monitor_->set(Variable::sfrz, [this, index] + { return y_[index(RepcaInternalVariables::SFRZ)]; }); + monitor_->set(Variable::qpi, [this, index] + { return y_[index(RepcaInternalVariables::QPI)]; }); + monitor_->set(Variable::pfreq, [this, index] + { + const auto EF = index(RepcaInternalVariables::EF); + return Ddn_ * Math::ramp(y_[EF]) - Dup_ * Math::ramp(-y_[EF]); }); + monitor_->set(Variable::ppi, [this, index] + { return y_[index(RepcaInternalVariables::PPI)]; }); + } + + template + int Repca::setGridKitComponentID(IdxT component_id) + { + gridkit_component_id_ = component_id; + return 0; + } + + template + int Repca::allocate() + { + setDerivedParameters(); + + size_ = static_cast(RepcaInternalVariables::MAXIMUM); + auto size = static_cast(size_); + + f_.assign(size, ScalarT{0}); + y_.assign(size, ScalarT{0}); + yp_.assign(size, ScalarT{0}); + tag_.assign(size, false); + variable_indices_.resize(size); + residual_indices_.resize(size); + + wb_.assign(2, ScalarT{0}); + + auto signal_size = static_cast(RepcaExternalVariables::MAXIMUM); + ws_.assign(signal_size, ScalarT{0}); + ws_indices_.assign(signal_size, INVALID_INDEX); + + for (IdxT j = 0; j < size_; ++j) + { + this->setVariableIndex(j, j); + this->setResidualIndex(j, j); + } + + if (signals_.template isAssigned()) + { + signals_.template getSignalNode()->set( + &y_[static_cast(RepcaInternalVariables::QEXT)], + &(this->getVariableIndex(static_cast(RepcaInternalVariables::QEXT)))); + } + + if (signals_.template isAssigned()) + { + signals_.template getSignalNode()->set( + &y_[static_cast(RepcaInternalVariables::PEXT)], + &(this->getVariableIndex(static_cast(RepcaInternalVariables::PEXT)))); + } + + return 0; + } + + template + int Repca::verify() const + { + int ret = static_cast(parameter_error_count_); + + auto check = [&](bool condition, const char* message) + { + if (!condition) + { + Log::error() << "Repca: " << message << '\n'; + ret += 1; + } + }; + + if (bus_ == nullptr) + { + Log::error() << "Repca: bus pointer is null\n"; + ret += 1; + } + + check(mva_base_ >= ZERO, "mva must be non-negative"); + check(VcompFlag_ == ZERO || VcompFlag_ == ONE, + "VcompFlag must be 0 or 1"); + check(RefFlag_ == ZERO || RefFlag_ == ONE, "RefFlag must be 0 or 1"); + check(Freqflag_ == ZERO || Freqflag_ == ONE, + "Freqflag must be 0 or 1"); + check(Tfltr_ >= ZERO, "Tfltr must be non-negative"); + check(Tp_ >= ZERO, "Tp must be non-negative"); + check(Tlag_ >= ZERO, "Tlag must be non-negative"); + check(Tft_ >= ZERO, "Tft must be non-negative"); + check(Tfv_ > ZERO, "Tfv must be positive"); + check(Vfrz_ >= ZERO, "Vfrz must be non-negative"); + check(dbdlow_ <= ZERO && ZERO <= dbdupper_, + "dbdlow <= 0 <= dbdupper is required"); + check(emin_ <= ZERO && ZERO <= emax_, "emin <= 0 <= emax is required"); + check(Qmin_ <= Qmax_, "Qmin must be less than or equal to Qmax"); + check(fdbd1_ <= ZERO && ZERO <= fdbd2_, + "fdbd1 <= 0 <= fdbd2 is required"); + check(Ddn_ >= ZERO, "Ddn must be non-negative"); + check(Dup_ >= ZERO, "Dup must be non-negative"); + check(femin_ <= ZERO && ZERO <= femax_, "femin <= 0 <= femax is required"); + check(Pmin_ <= Pmax_, "Pmin must be less than or equal to Pmax"); + ret += verifyBranchSignalPorts(); + + if (signals_.template isAttached() + && !signals_.template isLinked()) + { + Log::error() << "Repca: ibranchr signal attached with no linked source\n"; + ret += 1; + } + if (signals_.template isAttached() + && !signals_.template isLinked()) + { + Log::error() << "Repca: ibranchi signal attached with no linked source\n"; + ret += 1; + } + if (signals_.template isAttached() + && !signals_.template isLinked()) + { + Log::error() << "Repca: qbranch signal attached with no linked source\n"; + ret += 1; + } + if (signals_.template isAttached() + && !signals_.template isLinked()) + { + Log::error() << "Repca: pbranch signal attached with no linked source\n"; + ret += 1; + } + if (signals_.template isAttached() + && !signals_.template isLinked()) + { + Log::error() << "Repca: vref signal attached with no linked source\n"; + ret += 1; + } + if (signals_.template isAttached() + && !signals_.template isLinked()) + { + Log::error() << "Repca: qref signal attached with no linked source\n"; + ret += 1; + } + if (signals_.template isAttached() + && !signals_.template isLinked()) + { + Log::error() << "Repca: pplantref signal attached with no linked source\n"; + ret += 1; + } + if (signals_.template isAttached() + && !signals_.template isLinked()) + { + Log::error() << "Repca: freq signal attached with no linked source\n"; + ret += 1; + } + if (signals_.template isAttached() + && !signals_.template isLinked()) + { + Log::error() << "Repca: freqref signal attached with no linked source\n"; + ret += 1; + } + + return ret; + } + + template + scalar_type Repca::readExternalOrDefault( + RepcaExternalVariables variable, + scalar_type default_value) const + { + switch (variable) + { + case RepcaExternalVariables::IBRANCHR: + if (signals_.template isAttached()) + return signals_.template readExternalVariable(); + break; + case RepcaExternalVariables::IBRANCHI: + if (signals_.template isAttached()) + return signals_.template readExternalVariable(); + break; + case RepcaExternalVariables::QBRANCH: + if (signals_.template isAttached()) + return signals_.template readExternalVariable(); + break; + case RepcaExternalVariables::PBRANCH: + if (signals_.template isAttached()) + return signals_.template readExternalVariable(); + break; + case RepcaExternalVariables::VREF: + if (signals_.template isAttached()) + return signals_.template readExternalVariable(); + break; + case RepcaExternalVariables::QREF: + if (signals_.template isAttached()) + return signals_.template readExternalVariable(); + break; + case RepcaExternalVariables::PPLANTREF: + if (signals_.template isAttached()) + return signals_.template readExternalVariable(); + break; + case RepcaExternalVariables::FREQ: + if (signals_.template isAttached()) + return signals_.template readExternalVariable(); + break; + case RepcaExternalVariables::FREQREF: + if (signals_.template isAttached()) + return signals_.template readExternalVariable(); + break; + case RepcaExternalVariables::MAXIMUM: + break; + } + return default_value; + } + + template + int Repca::initialize() + { + if (parameter_error_count_ > 0 || verify() > 0) + { + Log::error() << "Repca: cannot initialize with invalid configuration\n"; + return 1; + } + + setDerivedParameters(); + + const auto VMEAS = static_cast(RepcaInternalVariables::VMEAS); + const auto QMEAS = static_cast(RepcaInternalVariables::QMEAS); + const auto XQ = static_cast(RepcaInternalVariables::XQ); + const auto XQEXT = static_cast(RepcaInternalVariables::XQEXT); + const auto PMEAS = static_cast(RepcaInternalVariables::PMEAS); + const auto XP = static_cast(RepcaInternalVariables::XP); + const auto PREF = static_cast(RepcaInternalVariables::PREF); + const auto VREG = static_cast(RepcaInternalVariables::VREG); + const auto VLDC = static_cast(RepcaInternalVariables::VLDC); + const auto VDROOP = static_cast(RepcaInternalVariables::VDROOP); + const auto VCTRL = static_cast(RepcaInternalVariables::VCTRL); + const auto SFRZ = static_cast(RepcaInternalVariables::SFRZ); + const auto ERQ = static_cast(RepcaInternalVariables::ERQ); + const auto ERQDB = static_cast(RepcaInternalVariables::ERQDB); + const auto ERQLIM = static_cast(RepcaInternalVariables::ERQLIM); + const auto QPI = static_cast(RepcaInternalVariables::QPI); + const auto QEXT = static_cast(RepcaInternalVariables::QEXT); + const auto EF = static_cast(RepcaInternalVariables::EF); + const auto EP = static_cast(RepcaInternalVariables::EP); + const auto EPLIM = static_cast(RepcaInternalVariables::EPLIM); + const auto PPI = static_cast(RepcaInternalVariables::PPI); + const auto PEXT = static_cast(RepcaInternalVariables::PEXT); + + const ScalarT vr = Vr(); + const ScalarT vi = Vi(); + const ScalarT vt_sq = vr * vr + vi * vi; + + if (static_cast(vt_sq) <= ZERO) + { + Log::error() + << "Repca: regulated-bus voltage magnitude must be positive at initialization\n"; + return 1; + } + + const ScalarT pbr0_system = + signals_.template readExternalVariable(); + const ScalarT qbr0_system = + signals_.template readExternalVariable(); + const ScalarT pbr0 = toComponentBase(pbr0_system); + const ScalarT qbr0 = toComponentBase(qbr0_system); + const ScalarT ibrr0 = + signals_.template readExternalVariable(); + const ScalarT ibri0 = + signals_.template readExternalVariable(); + + y_[VREG] = std::sqrt(vr * vr + vi * vi); + y_[VLDC] = std::sqrt((vr - Rc_ * ibrr0 + Xc_ * ibri0) + * (vr - Rc_ * ibrr0 + Xc_ * ibri0) + + (vi - Rc_ * ibri0 - Xc_ * ibrr0) + * (vi - Rc_ * ibri0 - Xc_ * ibrr0)); + y_[VDROOP] = y_[VREG] + Kc_ * qbr0; + y_[VCTRL] = VcompFlag_ * y_[VLDC] + vcomp_off_ * y_[VDROOP]; + + y_[VMEAS] = y_[VCTRL]; + y_[QMEAS] = qbr0; + y_[PMEAS] = pbr0; + + freq_set_ = readExternalOrDefault(RepcaExternalVariables::FREQ, ScalarT{ZERO}); + freqref_set_ = readExternalOrDefault(RepcaExternalVariables::FREQREF, ScalarT{ZERO}); + + y_[EF] = Math::deadband2(freqref_set_ - freq_set_, fdbd1_, fdbd2_); + const ScalarT pfreq0 = Ddn_ * Math::ramp(y_[EF]) - Dup_ * Math::ramp(-y_[EF]); + + vref_set_ = readExternalOrDefault(RepcaExternalVariables::VREF, y_[VMEAS]); + qref_set_ = readExternalOrDefault(RepcaExternalVariables::QREF, y_[QMEAS]); + pplantref_set_ = + readExternalOrDefault(RepcaExternalVariables::PPLANTREF, y_[PMEAS] - pfreq0); + + y_[SFRZ] = Math::above(y_[VREG], Vfrz_); + y_[ERQ] = RefFlag_ * (vref_set_ - y_[VMEAS]) + ref_off_ * (qref_set_ - y_[QMEAS]); + y_[ERQDB] = Math::deadband2(y_[ERQ], dbdlow_, dbdupper_); + y_[ERQLIM] = Math::clamp(y_[ERQDB], emin_, emax_); + + ScalarT qext0 = Math::clamp(qbr0, Qmin_, Qmax_); + if (signals_.template isAssigned()) + { + qext0 = y_[QEXT]; + } + + y_[QEXT] = qext0; + y_[QPI] = y_[QEXT]; + y_[XQEXT] = y_[QEXT]; + y_[XQ] = y_[QEXT] - Kp_ * y_[ERQLIM]; + + y_[EP] = pplantref_set_ - y_[PMEAS] + pfreq0; + y_[EPLIM] = Math::clamp(y_[EP], femin_, femax_); + + ScalarT pref0 = Math::clamp(pbr0, Pmin_, Pmax_); + if (signals_.template isAssigned() + && Freqflag_ != ZERO) + { + pref0 = y_[PEXT] / Freqflag_; + } + + y_[PREF] = pref0; + y_[PPI] = y_[PREF]; + y_[XP] = y_[PREF] - Kpg_ * y_[EPLIM]; + y_[PEXT] = Freqflag_ * y_[PREF]; + + static constexpr RealT init_tol = static_cast(1.0e-10); + + std::fill(yp_.begin(), yp_.end(), ZERO); + + const ScalarT q_rate = + y_[SFRZ] * Math::antiwindup(y_[QPI], Ki_ * y_[ERQLIM], Qmin_, Qmax_); + + const ScalarT p_rate = + Math::antiwindup(y_[PPI], Kig_ * y_[EPLIM], Pmin_, Pmax_); + + if (std::abs(static_cast(q_rate)) > init_tol) + { + Log::error() << "Repca: reactive controller initial condition is not steady state\n"; + return 1; + } + + if (std::abs(static_cast(p_rate)) > init_tol) + { + Log::error() << "Repca: active-power controller initial condition is not steady state\n"; + return 1; + } + + return 0; + } + + template + int Repca::tagDifferentiable() + { + std::fill(tag_.begin(), tag_.end(), false); + tag_[static_cast(RepcaInternalVariables::VMEAS)] = (Tfltr_ > ZERO); + tag_[static_cast(RepcaInternalVariables::QMEAS)] = (Tfltr_ > ZERO); + tag_[static_cast(RepcaInternalVariables::XQ)] = true; + tag_[static_cast(RepcaInternalVariables::XQEXT)] = true; + tag_[static_cast(RepcaInternalVariables::PMEAS)] = (Tp_ > ZERO); + tag_[static_cast(RepcaInternalVariables::XP)] = true; + tag_[static_cast(RepcaInternalVariables::PREF)] = (Tlag_ > ZERO); + return 0; + } + + template + __attribute__((always_inline)) inline int + Repca::evaluateInternalResidual( + ScalarT* y, + ScalarT* yp, + ScalarT* wb, + ScalarT* ws, + ScalarT* f) + { + const auto VMEAS = static_cast(RepcaInternalVariables::VMEAS); + const auto QMEAS = static_cast(RepcaInternalVariables::QMEAS); + const auto XQ = static_cast(RepcaInternalVariables::XQ); + const auto XQEXT = static_cast(RepcaInternalVariables::XQEXT); + const auto PMEAS = static_cast(RepcaInternalVariables::PMEAS); + const auto XP = static_cast(RepcaInternalVariables::XP); + const auto PREF = static_cast(RepcaInternalVariables::PREF); + const auto VREG = static_cast(RepcaInternalVariables::VREG); + const auto VLDC = static_cast(RepcaInternalVariables::VLDC); + const auto VDROOP = static_cast(RepcaInternalVariables::VDROOP); + const auto VCTRL = static_cast(RepcaInternalVariables::VCTRL); + const auto SFRZ = static_cast(RepcaInternalVariables::SFRZ); + const auto ERQ = static_cast(RepcaInternalVariables::ERQ); + const auto ERQDB = static_cast(RepcaInternalVariables::ERQDB); + const auto ERQLIM = static_cast(RepcaInternalVariables::ERQLIM); + const auto QPI = static_cast(RepcaInternalVariables::QPI); + const auto QEXT = static_cast(RepcaInternalVariables::QEXT); + const auto EF = static_cast(RepcaInternalVariables::EF); + const auto EP = static_cast(RepcaInternalVariables::EP); + const auto EPLIM = static_cast(RepcaInternalVariables::EPLIM); + const auto PPI = static_cast(RepcaInternalVariables::PPI); + const auto PEXT = static_cast(RepcaInternalVariables::PEXT); + + const auto IBRANCHR = static_cast(RepcaExternalVariables::IBRANCHR); + const auto IBRANCHI = static_cast(RepcaExternalVariables::IBRANCHI); + const auto QBRANCH = static_cast(RepcaExternalVariables::QBRANCH); + const auto PBRANCH = static_cast(RepcaExternalVariables::PBRANCH); + const auto VREF = static_cast(RepcaExternalVariables::VREF); + const auto QREF = static_cast(RepcaExternalVariables::QREF); + const auto PPLANTREF = static_cast(RepcaExternalVariables::PPLANTREF); + const auto FREQ = static_cast(RepcaExternalVariables::FREQ); + const auto FREQREF = static_cast(RepcaExternalVariables::FREQREF); + + const ScalarT vr = wb[0]; + const ScalarT vi = wb[1]; + const ScalarT pbr = toComponentBase(ws[PBRANCH]); + const ScalarT qbr = toComponentBase(ws[QBRANCH]); + const ScalarT ibrr = ws[IBRANCHR]; + const ScalarT ibri = ws[IBRANCHI]; + + f[VMEAS] = -Tfltr_ * yp[VMEAS] - y[VMEAS] + y[VCTRL]; + f[QMEAS] = -Tfltr_ * yp[QMEAS] - y[QMEAS] + qbr; + f[XQ] = -yp[XQ] + + y[SFRZ] * Math::antiwindup(y[QPI], Ki_ * y[ERQLIM], Qmin_, Qmax_); + f[XQEXT] = -Tfv_ * yp[XQEXT] - y[XQEXT] + y[QPI]; + f[PMEAS] = -Tp_ * yp[PMEAS] - y[PMEAS] + pbr; + f[XP] = -yp[XP] + Math::antiwindup(y[PPI], Kig_ * y[EPLIM], Pmin_, Pmax_); + f[PREF] = -Tlag_ * yp[PREF] - y[PREF] + y[PPI]; + + f[VREG] = -y[VREG] * y[VREG] + vr * vr + vi * vi; + f[VLDC] = -y[VLDC] * y[VLDC] + + (vr - Rc_ * ibrr + Xc_ * ibri) + * (vr - Rc_ * ibrr + Xc_ * ibri) + + (vi - Rc_ * ibri - Xc_ * ibrr) + * (vi - Rc_ * ibri - Xc_ * ibrr); + f[VDROOP] = -y[VDROOP] + y[VREG] + Kc_ * qbr; + f[VCTRL] = -y[VCTRL] + VcompFlag_ * y[VLDC] + vcomp_off_ * y[VDROOP]; + f[SFRZ] = -y[SFRZ] + Math::above(y[VREG], Vfrz_); + f[ERQ] = -y[ERQ] + RefFlag_ * (ws[VREF] - y[VMEAS]) + + ref_off_ * (ws[QREF] - y[QMEAS]); + f[ERQDB] = -y[ERQDB] + Math::deadband2(y[ERQ], dbdlow_, dbdupper_); + f[ERQLIM] = -y[ERQLIM] + Math::clamp(y[ERQDB], emin_, emax_); + f[QPI] = -y[QPI] + Math::clamp(Kp_ * y[ERQLIM] + y[XQ], Qmin_, Qmax_); + f[QEXT] = -Tfv_ * (y[QEXT] - y[XQEXT]) + Tft_ * (y[QPI] - y[XQEXT]); + f[EF] = -y[EF] + Math::deadband2(ws[FREQREF] - ws[FREQ], fdbd1_, fdbd2_); + const ScalarT pfreq = Ddn_ * Math::ramp(y[EF]) - Dup_ * Math::ramp(-y[EF]); + f[EP] = -y[EP] + ws[PPLANTREF] - y[PMEAS] + pfreq; + f[EPLIM] = -y[EPLIM] + Math::clamp(y[EP], femin_, femax_); + f[PPI] = -y[PPI] + Math::clamp(Kpg_ * y[EPLIM] + y[XP], Pmin_, Pmax_); + f[PEXT] = -y[PEXT] + Freqflag_ * y[PREF]; + + return 0; + } + + template + int Repca::evaluateResidual() + { + setDerivedParameters(); + + const auto IBRANCHR = static_cast(RepcaExternalVariables::IBRANCHR); + const auto IBRANCHI = static_cast(RepcaExternalVariables::IBRANCHI); + const auto QBRANCH = static_cast(RepcaExternalVariables::QBRANCH); + const auto PBRANCH = static_cast(RepcaExternalVariables::PBRANCH); + const auto VREF = static_cast(RepcaExternalVariables::VREF); + const auto QREF = static_cast(RepcaExternalVariables::QREF); + const auto PPLANTREF = static_cast(RepcaExternalVariables::PPLANTREF); + const auto FREQ = static_cast(RepcaExternalVariables::FREQ); + const auto FREQREF = static_cast(RepcaExternalVariables::FREQREF); + + const ScalarT vr = Vr(); + const ScalarT vi = Vi(); + + ws_[IBRANCHR] = ZERO; + ws_[IBRANCHI] = ZERO; + ws_[QBRANCH] = ZERO; + ws_[PBRANCH] = ZERO; + ws_[VREF] = vref_set_; + ws_[QREF] = qref_set_; + ws_[PPLANTREF] = pplantref_set_; + ws_[FREQ] = freq_set_; + ws_[FREQREF] = freqref_set_; + std::fill(ws_indices_.begin(), ws_indices_.end(), INVALID_INDEX); + + if (signals_.template isAttached()) + { + ws_[IBRANCHR] = + signals_.template readExternalVariable(); + ws_indices_[IBRANCHR] = + signals_.template readExternalVariableIndex(); + } + if (signals_.template isAttached()) + { + ws_[IBRANCHI] = + signals_.template readExternalVariable(); + ws_indices_[IBRANCHI] = + signals_.template readExternalVariableIndex(); + } + if (signals_.template isAttached()) + { + ws_[QBRANCH] = signals_.template readExternalVariable(); + ws_indices_[QBRANCH] = + signals_.template readExternalVariableIndex(); + } + if (signals_.template isAttached()) + { + ws_[PBRANCH] = signals_.template readExternalVariable(); + ws_indices_[PBRANCH] = + signals_.template readExternalVariableIndex(); + } + + if (signals_.template isAttached()) + { + ws_[VREF] = signals_.template readExternalVariable(); + ws_indices_[VREF] = + signals_.template readExternalVariableIndex(); + } + if (signals_.template isAttached()) + { + ws_[QREF] = signals_.template readExternalVariable(); + ws_indices_[QREF] = + signals_.template readExternalVariableIndex(); + } + if (signals_.template isAttached()) + { + ws_[PPLANTREF] = + signals_.template readExternalVariable(); + ws_indices_[PPLANTREF] = + signals_.template readExternalVariableIndex(); + } + if (signals_.template isAttached()) + { + ws_[FREQ] = signals_.template readExternalVariable(); + ws_indices_[FREQ] = + signals_.template readExternalVariableIndex(); + } + if (signals_.template isAttached()) + { + ws_[FREQREF] = signals_.template readExternalVariable(); + ws_indices_[FREQREF] = + signals_.template readExternalVariableIndex(); + } + + wb_[0] = vr; + wb_[1] = vi; + + evaluateInternalResidual(y_.data(), yp_.data(), wb_.data(), ws_.data(), f_.data()); + return 0; + } + } // namespace Converter + } // namespace PhasorDynamics +} // namespace GridKit diff --git a/GridKit/Model/PhasorDynamics/INPUT_FORMAT.md b/GridKit/Model/PhasorDynamics/INPUT_FORMAT.md index 32df36cdc..f78d814bc 100644 --- a/GridKit/Model/PhasorDynamics/INPUT_FORMAT.md +++ b/GridKit/Model/PhasorDynamics/INPUT_FORMAT.md @@ -144,16 +144,17 @@ are specified: `Genrou` | 6th order machine model | `bus`, `pmech`\*, `speed`\*, `efd`\* | `p0`, `q0`, `H`, `D`, `Ra`, `Tdop`, `Tdopp`, `Tqop`, `Tqopp`, `Xd`, `Xdp`, `Xdpp`, `Xq`, `Xqp`, `Xqpp`, `Xl`, `S10`, `S12`, `mva` | `ir`, `ii`, `p`, `q`, `delta`, `omega`, `speed` `Gensal` | 5th order salient-pole machine model | `bus`, `pmech`\*, `speed`\*, `efd`\* | `p0`, `q0`, `H`, `D`, `Ra`, `Tdop`, `Tdopp`, `Tqopp`, `Xd`, `Xdp`, `Xdpp`, `Xq`, `Xl`, `S10`, `S12`, `mva` | `ir`, `ii`, `p`, `q`, `delta`, `omega`, `speed`, `Eqp`, `psidp`, `psiqpp`, `psidpp`, `vd`, `vq`, `te`, `id`, `iq` `GenClassical` | the classical machine model | `bus`, `pmech`\*, `speed`\*, `efd`\* | `p0`, `q0`, `H`, `D`, `Ra`, `Xdp`, `mva` | `ir`, `ii`, `p`, `q`, `delta`, `omega` + `Repca` | the REPCA renewable plant-control model | `bus`, `ibranchr`, `ibranchi`, `qbranch`, `pbranch`, `vref`\*, `qref`\*, `pplantref`\*, `freq`\*, `freqref`\*, `qext`, `pext` | `mva`, `VcompFlag`, `RefFlag`, `Freqflag`, `Tfltr`, `Tft`, `Tfv`, `Tp`, `Tlag`, `Vfrz`, `Rc`, `Xc`, `Kc`, `dbdlow`, `dbdupper`, `emax`, `emin`, `Kp`, `Ki`, `Qmax`, `Qmin`, `fdbd1`, `fdbd2`, `Ddn`, `Dup`, `femax`, `femin`, `Kpg`, `Kig`, `Pmax`, `Pmin` | `qext`, `pext`, `vmeas`, `qmeas`, `pmeas`, `pref`, `vctrl`, `sfrz`, `qpi`, `pfreq`, `ppi` `Tgov1 ` | the TGOV1 governor model | `pmech`, `speed` | `R`, `T1`, `T2`, `T3`, `Pvmax`, `Pvmin`, `Dt` | `none` `Ieeet1` | the IEEET1 exciter model | `bus`, `speed`, `efd`, `vs`\* | `Tr`, `Ka`, `Ta`, `Ke`, `Te`, `Kf`, `Tf`, `Vrmin`, `Vrmax`, `E1`, `E2`, `Se1`, `Se2`, `Ispdlim` | `efd`, `ksat` `SexsPti` | the SEXS-PTI simplified exciter model | `bus`, `efd`, `vs`\* | `Ta`, `Tb`, `Te`, `K`, `Efdmax`, `Efdmin` | `efd` - `Ieeest` | the IEEEST stabilizer model | `input`, `output` | `A1`, `A2`, `A3`, `A4`, `A5`, `A6`, `T1`, `T2`, `T3`, `T4`, `T5`, `T6`, `Ks`, `Lsmin`, `Lsmax`, `Vcl`, `Vcu`, `Tdelay` | `vss` + `Ieeest` | the IEEEST stabilizer model | `input`, `output` | `A1`, `A2`, `A3`, `A4`, `A5`, `A6`, `T1`, `T2`, `T3`, `T4`, `T5`, `T6`, `Ks`, `Lsmin`, `Lsmax`, `Vcl`, `Vcu`, `Tdelay` | `vss` `BusFault` | simple impedance-based fault at a bus | `bus`, `status`\* | `state0`, `R`, `X` | `state`, `ir`, `ii` `BusToSignalAdapter` | signal adapter component for a bus | `bus`, `vr`, `vi`, `ir`, `ii` | | Ports marked with \* are optional and, if missing, will be assumed to be -connected to a constant value. This list is subject to change. - +connected to a constant value. +This list is subject to change. For `Branch`, `tap` and `phase` are optional parameters. If omitted, `tap` defaults to `1.0` and `phase` defaults to `0.0` radians. Bus `bus1` is the tap diff --git a/GridKit/Model/PhasorDynamics/SystemModel.hpp b/GridKit/Model/PhasorDynamics/SystemModel.hpp index d7b4cc9d7..bbafd69f4 100644 --- a/GridKit/Model/PhasorDynamics/SystemModel.hpp +++ b/GridKit/Model/PhasorDynamics/SystemModel.hpp @@ -84,6 +84,7 @@ namespace GridKit using namespace Governor; using namespace Exciter; using namespace Stabilizer; + using namespace Converter; // Set system model tolerances rel_tol_ = 1e-7; @@ -153,6 +154,89 @@ namespace GridKit addComponent(adapter); } + // Add REPCA plant controllers + for (const auto& repcadata : data.repca) + { + using DataT = typename SystemModelData::RepcaDataT; + + IdxT bus_index = 0; + if (repcadata.ports.contains(DataT::Ports::bus)) + { + bus_index = repcadata.ports.at(DataT::Ports::bus); + } + + auto* repca = new Repca(getBus(bus_index), repcadata); + + if (repcadata.ports.contains(DataT::Ports::ibranchr)) + { + const IdxT signal = repcadata.ports.at(DataT::Ports::ibranchr); + repca->getSignals().template attachSignalNode( + getSignal(signal)); + } + if (repcadata.ports.contains(DataT::Ports::ibranchi)) + { + const IdxT signal = repcadata.ports.at(DataT::Ports::ibranchi); + repca->getSignals().template attachSignalNode( + getSignal(signal)); + } + if (repcadata.ports.contains(DataT::Ports::qbranch)) + { + const IdxT signal = repcadata.ports.at(DataT::Ports::qbranch); + repca->getSignals().template attachSignalNode( + getSignal(signal)); + } + if (repcadata.ports.contains(DataT::Ports::pbranch)) + { + const IdxT signal = repcadata.ports.at(DataT::Ports::pbranch); + repca->getSignals().template attachSignalNode( + getSignal(signal)); + } + if (repcadata.ports.contains(DataT::Ports::vref)) + { + const IdxT signal = repcadata.ports.at(DataT::Ports::vref); + repca->getSignals().template attachSignalNode( + getSignal(signal)); + } + if (repcadata.ports.contains(DataT::Ports::qref)) + { + const IdxT signal = repcadata.ports.at(DataT::Ports::qref); + repca->getSignals().template attachSignalNode( + getSignal(signal)); + } + if (repcadata.ports.contains(DataT::Ports::pplantref)) + { + const IdxT signal = repcadata.ports.at(DataT::Ports::pplantref); + repca->getSignals().template attachSignalNode( + getSignal(signal)); + } + if (repcadata.ports.contains(DataT::Ports::freq)) + { + const IdxT signal = repcadata.ports.at(DataT::Ports::freq); + repca->getSignals().template attachSignalNode( + getSignal(signal)); + } + if (repcadata.ports.contains(DataT::Ports::freqref)) + { + const IdxT signal = repcadata.ports.at(DataT::Ports::freqref); + repca->getSignals().template attachSignalNode( + getSignal(signal)); + } + if (repcadata.ports.contains(DataT::Ports::qext)) + { + const IdxT signal = repcadata.ports.at(DataT::Ports::qext); + repca->getSignals().template assignSignalNode( + getSignal(signal)); + } + if (repcadata.ports.contains(DataT::Ports::pext)) + { + const IdxT signal = repcadata.ports.at(DataT::Ports::pext); + repca->getSignals().template assignSignalNode( + getSignal(signal)); + } + + addComponent(repca); + } + // Add branches for (const auto& branchdata : data.branch) { diff --git a/GridKit/Model/PhasorDynamics/SystemModelData.hpp b/GridKit/Model/PhasorDynamics/SystemModelData.hpp index d4deef66e..eb7cc8fd8 100644 --- a/GridKit/Model/PhasorDynamics/SystemModelData.hpp +++ b/GridKit/Model/PhasorDynamics/SystemModelData.hpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -40,6 +41,7 @@ namespace GridKit using BusDataT = BusData; using BusToSignalAdapterDataT = BusToSignalAdapterData; using BusFaultDataT = BusFaultData; + using RepcaDataT = Converter::RepcaData; using Tgov1DataT = Governor::Tgov1Data; using Ieeet1DataT = Exciter::Ieeet1Data; using SexsPtiDataT = Exciter::SexsPtiData; @@ -93,6 +95,7 @@ namespace GridKit std::vector adapter; ///< bus-to-signal adapters within the model std::vector branch; ///< Branches within the model std::vector bus_fault; ///< Bus faults within the model + std::vector repca; ///< REPCA plant controllers within the model std::vector genrou; ///< GENROU instances within the model std::vector gensal; ///< GENSAL instances within the model std::vector genclassical; ///< Classical generator instances within the model diff --git a/GridKit/Model/PhasorDynamics/SystemModelDataJSONParser.hpp b/GridKit/Model/PhasorDynamics/SystemModelDataJSONParser.hpp index 00a6278c2..a0e52c153 100644 --- a/GridKit/Model/PhasorDynamics/SystemModelDataJSONParser.hpp +++ b/GridKit/Model/PhasorDynamics/SystemModelDataJSONParser.hpp @@ -136,6 +136,12 @@ namespace GridKit raw_component.get_to(loadzip); sm.loadzip.push_back(loadzip); } + else if (kind == "Repca") + { + typename SystemModelData::RepcaDataT repca; + raw_component.get_to(repca); + sm.repca.push_back(repca); + } else if (kind == "Tgov1") { typename SystemModelData::Tgov1DataT gov; diff --git a/docs/Figures/PhasorDynamics_REPCA_Diagram.png b/docs/Figures/PhasorDynamics_REPCA_Diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..08fef3c155f65641aa9c581283f3322fc208d367 GIT binary patch literal 91838 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 scalar(0.0)); @@ -82,7 +106,7 @@ namespace GridKit success *= (std::abs(upper_breakpoint) < tol); const ScalarT x = scalar(-0.4); - success *= (std::abs(Math::deadband(x, lower, upper) + success *= (std::abs(Math::deadband2(x, lower, upper) - (x - Math::clamp(x, lower, upper))) < scalar(kRoundoffTolerance)); @@ -91,17 +115,17 @@ namespace GridKit const ScalarT expected_far_above = far_above_input - upper; const ScalarT expected_far_below = far_below_input - lower; - success *= std::isfinite(Math::deadband(far_above_input, lower, upper)); - success *= within(Math::deadband(far_above_input, lower, upper), expected_far_above, tol); - success *= std::isfinite(Math::deadband(far_below_input, lower, upper)); - success *= within(Math::deadband(far_below_input, lower, upper), expected_far_below, tol); + success *= std::isfinite(Math::deadband2(far_above_input, lower, upper)); + success *= within(Math::deadband2(far_above_input, lower, upper), expected_far_above, tol); + success *= std::isfinite(Math::deadband2(far_below_input, lower, upper)); + success *= within(Math::deadband2(far_below_input, lower, upper), expected_far_below, tol); const ScalarT point = scalar(0.25); const ScalarT above_point = scalar(0.75); const ScalarT below_point = scalar(-0.25); - success *= (std::abs(Math::deadband(above_point, point, point) - (above_point - point)) + success *= (std::abs(Math::deadband2(above_point, point, point) - (above_point - point)) < scalar(kRoundoffTolerance)); - success *= (std::abs(Math::deadband(below_point, point, point) - (below_point - point)) + success *= (std::abs(Math::deadband2(below_point, point, point) - (below_point - point)) < scalar(kRoundoffTolerance)); return success.report(__func__); diff --git a/tests/UnitTests/Math/runSmoothnessIndicatorTests.cpp b/tests/UnitTests/Math/runSmoothnessIndicatorTests.cpp index 233c36ddd..aecdc7ae8 100644 --- a/tests/UnitTests/Math/runSmoothnessIndicatorTests.cpp +++ b/tests/UnitTests/Math/runSmoothnessIndicatorTests.cpp @@ -7,7 +7,8 @@ int main() GridKit::Testing::SmoothnessIndicatorTests test; result += test.clamp(); - result += test.deadband(); + result += test.deadband1(); + result += test.deadband2(); result += test.limitIndicators(); result += test.slew(); result += test.linseg(); diff --git a/tests/UnitTests/PhasorDynamics/CMakeLists.txt b/tests/UnitTests/PhasorDynamics/CMakeLists.txt index 932cb13b8..571d4c33b 100644 --- a/tests/UnitTests/PhasorDynamics/CMakeLists.txt +++ b/tests/UnitTests/PhasorDynamics/CMakeLists.txt @@ -94,6 +94,14 @@ target_link_libraries( GridKit::phasor_dynamics_components_dependency_tracking GridKit::testing) +add_executable(test_phasor_converter_repca runConverterRepcaTests.cpp) +target_link_libraries( + test_phasor_converter_repca + GridKit::definitions + GridKit::phasor_dynamics_components + GridKit::phasor_dynamics_components_dependency_tracking + GridKit::testing) + add_executable(test_phasor_stabilizer_ieeest runStabilizerIeeestTests.cpp) target_link_libraries( test_phasor_stabilizer_ieeest @@ -138,6 +146,7 @@ add_test(NAME PhasorDynamicsGovernorTgov1Test COMMAND test_phasor_governor_tgov1 add_test(NAME PhasorDynamicsExciterIeeet1Test COMMAND test_phasor_exciter_ieeet1) add_test(NAME PhasorDynamicsGensalTest COMMAND test_phasor_gensal) add_test(NAME PhasorDynamicsExciterSexsPtiTest COMMAND test_phasor_exciter_sexspti) +add_test(NAME PhasorDynamicsConverterRepcaTest COMMAND test_phasor_converter_repca) add_test(NAME PhasorDynamicsStabilizerIeeestTest COMMAND test_phasor_stabilizer_ieeest) add_test(NAME PhasorDynamicsGenClassicalTest COMMAND test_phasor_gen_classical) add_test(NAME PhasorDynamicsLoadTest COMMAND test_phasor_load) @@ -159,6 +168,7 @@ install( test_phasor_exciter_ieeet1 test_phasor_gensal test_phasor_exciter_sexspti + test_phasor_converter_repca test_phasor_stabilizer_ieeest test_phasor_gen_classical test_phasor_system diff --git a/tests/UnitTests/PhasorDynamics/ConverterRepcaTests.hpp b/tests/UnitTests/PhasorDynamics/ConverterRepcaTests.hpp new file mode 100644 index 000000000..fe88961ca --- /dev/null +++ b/tests/UnitTests/PhasorDynamics/ConverterRepcaTests.hpp @@ -0,0 +1,573 @@ +#pragma once + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace GridKit +{ + namespace Testing + { + template + class ConverterRepcaTests + { + public: + using ScalarT = scalar_type; + using IdxT = index_type; + using RealT = typename PhasorDynamics::Component::RealT; + + static constexpr ScalarT kTol = static_cast(1.0e-8); + + TestOutcome constructionAndValidation() + { + TestStatus success = true; + + PhasorDynamics::Bus bus(1.0, 0.0); + + PhasorDynamics::Converter::Repca repca(&bus, makeRepcaData()); + success *= (repca.size() + == static_cast( + PhasorDynamics::Converter::RepcaInternalVariables::MAXIMUM)); + success *= (repca.getMonitor() != nullptr); + BranchSignalSet branch_signals; + branch_signals.attachTo(repca); + success *= (repca.verify() == 0); + + auto bad_repca = makeRepcaData(); + bad_repca.parameters[PhasorDynamics::Converter::RepcaParameters::Tfv] = + static_cast(0.0); + PhasorDynamics::Converter::Repca bad_repca_model(&bus, bad_repca); + branch_signals.attachTo(bad_repca_model); + success *= (bad_repca_model.verify() > 0); + + return success.report(__func__); + } + + TestOutcome repcaSignalsInitializationAndResidual() + { + using Var = PhasorDynamics::Converter::RepcaInternalVariables; + using Ext = PhasorDynamics::Converter::RepcaExternalVariables; + + TestStatus success = true; + + PhasorDynamics::Bus bus(1.0, 0.0); + bus.allocate(); + bus.initialize(); + + ScalarT ibranchr_value{0.6}; + ScalarT ibranchi_value{-0.2}; + ScalarT pbranch_value{0.6}; + ScalarT qbranch_value{0.2}; + ScalarT qext_value{0.0}; + ScalarT pext_value{0.0}; + IdxT ibranchr_index = 20; + IdxT ibranchi_index = 21; + IdxT pbranch_index = 22; + IdxT qbranch_index = 23; + IdxT qext_index = 24; + IdxT pext_index = 25; + + PhasorDynamics::SignalNode ibranchr_node; + PhasorDynamics::SignalNode ibranchi_node; + PhasorDynamics::SignalNode pbranch_node; + PhasorDynamics::SignalNode qbranch_node; + PhasorDynamics::SignalNode qext_node; + PhasorDynamics::SignalNode pext_node; + ibranchr_node.set(&ibranchr_value, &ibranchr_index); + ibranchi_node.set(&ibranchi_value, &ibranchi_index); + pbranch_node.set(&pbranch_value, &pbranch_index); + qbranch_node.set(&qbranch_value, &qbranch_index); + qext_node.set(&qext_value, &qext_index); + pext_node.set(&pext_value, &pext_index); + + auto data = makeRepcaData(); + PhasorDynamics::Converter::Repca repca(&bus, data); + repca.getSignals().template attachSignalNode(&ibranchr_node); + repca.getSignals().template attachSignalNode(&ibranchi_node); + repca.getSignals().template attachSignalNode(&pbranch_node); + repca.getSignals().template attachSignalNode(&qbranch_node); + repca.getSignals().template assignSignalNode(&qext_node); + repca.getSignals().template assignSignalNode(&pext_node); + + success *= (repca.allocate() == 0); + qext_node.init(static_cast(0.2)); + pext_node.init(static_cast(0.6)); + success *= (repca.verify() == 0); + success *= (repca.initialize() == 0); + success *= (repca.tagDifferentiable() == 0); + success *= (repca.evaluateResidual() == 0); + + success *= isEqual(repca.y()[index(Var::PMEAS)], static_cast(0.6), kTol); + success *= isEqual(repca.y()[index(Var::QMEAS)], static_cast(0.2), kTol); + success *= isEqual(repca.y()[index(Var::QEXT)], static_cast(0.2), kTol); + success *= isEqual(repca.y()[index(Var::PEXT)], static_cast(0.6), kTol); + success *= isEqual(qext_node.read(), repca.y()[index(Var::QEXT)], kTol); + success *= isEqual(pext_node.read(), repca.y()[index(Var::PEXT)], kTol); + success *= (repca.tag()[index(Var::VMEAS)] == true); + success *= (repca.tag()[index(Var::PREF)] == false); + + for (size_t i = 0; i < repca.getResidual().size(); ++i) + { + success *= isEqual(repca.getResidual()[i], static_cast(0.0), kTol); + success *= isEqual(repca.yp()[i], static_cast(0.0), kTol); + } + + return success.report(__func__); + } + + TestOutcome rejectsHalfConnectedBranchSignals() + { + using Ext = PhasorDynamics::Converter::RepcaExternalVariables; + + TestStatus success = true; + + PhasorDynamics::Bus bus(1.0, 0.0); + + ScalarT signal_value{0.6}; + IdxT signal_index = 30; + + PhasorDynamics::SignalNode signal_node; + signal_node.set(&signal_value, &signal_index); + + { + PhasorDynamics::Converter::Repca repca(&bus, makeRepcaData()); + repca.getSignals().template attachSignalNode(&signal_node); + success *= (repca.verify() > 0); + } + + { + PhasorDynamics::Converter::Repca repca(&bus, makeRepcaData()); + repca.getSignals().template attachSignalNode(&signal_node); + success *= (repca.verify() > 0); + } + + { + PhasorDynamics::Converter::Repca repca(&bus, makeRepcaData()); + repca.getSignals().template attachSignalNode(&signal_node); + success *= (repca.verify() > 0); + } + + { + PhasorDynamics::Converter::Repca repca(&bus, makeRepcaData()); + repca.getSignals().template attachSignalNode(&signal_node); + success *= (repca.verify() > 0); + } + + return success.report(__func__); + } + + TestOutcome rejectsLineDropCompensationWithoutCurrentSignals() + { + using Params = PhasorDynamics::Converter::RepcaParameters; + + TestStatus success = true; + + PhasorDynamics::Bus bus(1.0, 0.0); + + auto data = makeRepcaData(); + data.parameters[Params::VcompFlag] = static_cast(1); + PhasorDynamics::Converter::Repca repca(&bus, data); + BranchSignalSet branch_signals; + branch_signals.attachPowerTo(repca); + + success *= (repca.verify() > 0); + + return success.report(__func__); + } + + TestOutcome convertsSystemBaseBranchPowerToComponentBase() + { + using Var = PhasorDynamics::Converter::RepcaInternalVariables; + using Params = PhasorDynamics::Converter::RepcaParameters; + + TestStatus success = true; + + PhasorDynamics::Bus bus(1.0, 0.0); + bus.allocate(); + bus.initialize(); + + BranchSignalSet branch_signals(static_cast(0.3), + static_cast(0.1), + static_cast(0.3), + static_cast(-0.1), + 31); + + auto data = makeRepcaData(); + data.parameters[Params::mva] = static_cast(50.0); + PhasorDynamics::Converter::Repca repca(&bus, data); + repca.setSystemBase(static_cast(60.0), static_cast(100.0e6)); + branch_signals.attachTo(repca); + + success *= (repca.allocate() == 0); + success *= (repca.verify() == 0); + success *= (repca.initialize() == 0); + success *= isEqual(repca.y()[index(Var::PMEAS)], static_cast(0.6), kTol); + success *= isEqual(repca.y()[index(Var::QMEAS)], static_cast(0.2), kTol); + success *= isEqual(repca.y()[index(Var::PEXT)], static_cast(0.6), kTol); + success *= isEqual(repca.y()[index(Var::QEXT)], static_cast(0.2), kTol); + + return success.report(__func__); + } + + TestOutcome mvaZeroUsesSystemBase() + { + using Var = PhasorDynamics::Converter::RepcaInternalVariables; + using Params = PhasorDynamics::Converter::RepcaParameters; + + TestStatus success = true; + + PhasorDynamics::Bus bus(1.0, 0.0); + bus.allocate(); + bus.initialize(); + + BranchSignalSet branch_signals(static_cast(0.3), + static_cast(0.1), + static_cast(0.3), + static_cast(-0.1), + 33); + + auto data = makeRepcaData(); + data.parameters[Params::mva] = static_cast(0.0); + + PhasorDynamics::Converter::Repca repca(&bus, data); + repca.setSystemBase(static_cast(60.0), static_cast(100.0e6)); + branch_signals.attachTo(repca); + + success *= (repca.allocate() == 0); + success *= (repca.verify() == 0); + success *= (repca.initialize() == 0); + success *= isEqual(repca.y()[index(Var::PMEAS)], static_cast(0.3), kTol); + success *= isEqual(repca.y()[index(Var::QMEAS)], static_cast(0.1), kTol); + success *= isEqual(repca.y()[index(Var::PEXT)], static_cast(0.3), kTol); + success *= isEqual(repca.y()[index(Var::QEXT)], static_cast(0.1), kTol); + + return success.report(__func__); + } + + TestOutcome rejectsNonSteadyConnectedReferences() + { + using Params = PhasorDynamics::Converter::RepcaParameters; + using Ext = PhasorDynamics::Converter::RepcaExternalVariables; + + TestStatus success = true; + + PhasorDynamics::Bus bus(1.0, 0.0); + bus.allocate(); + bus.initialize(); + + { + auto data = makeRepcaData(); + data.parameters[Params::Ki] = static_cast(1.0); + + ScalarT qref_value{0.5}; + IdxT qref_index = 35; + + PhasorDynamics::SignalNode qref_node; + qref_node.set(&qref_value, &qref_index); + + PhasorDynamics::Converter::Repca repca(&bus, data); + BranchSignalSet branch_signals; + branch_signals.attachTo(repca); + repca.getSignals().template attachSignalNode(&qref_node); + + success *= (repca.allocate() == 0); + success *= (repca.verify() == 0); + success *= (repca.initialize() > 0); + } + + { + auto data = makeRepcaData(); + data.parameters[Params::Kig] = static_cast(1.0); + + ScalarT pplantref_value{0.5}; + IdxT pplantref_index = 36; + + PhasorDynamics::SignalNode pplantref_node; + pplantref_node.set(&pplantref_value, &pplantref_index); + + PhasorDynamics::Converter::Repca repca(&bus, data); + BranchSignalSet branch_signals; + branch_signals.attachTo(repca); + repca.getSignals().template attachSignalNode(&pplantref_node); + + success *= (repca.allocate() == 0); + success *= (repca.verify() == 0); + success *= (repca.initialize() > 0); + } + + return success.report(__func__); + } + + TestOutcome initializationUsesBranchSignals() + { + using Var = PhasorDynamics::Converter::RepcaInternalVariables; + + TestStatus success = true; + + PhasorDynamics::Bus bus(1.0, 0.0); + bus.allocate(); + bus.initialize(); + + BranchSignalSet branch_signals(static_cast(0.1), + static_cast(-0.4), + static_cast(0.1), + static_cast(0.4), + 37); + + PhasorDynamics::Converter::Repca repca(&bus, makeRepcaData()); + branch_signals.attachTo(repca); + + success *= (repca.allocate() == 0); + success *= (repca.verify() == 0); + success *= (repca.initialize() == 0); + + success *= isEqual(repca.y()[index(Var::PMEAS)], static_cast(0.1), kTol); + success *= isEqual(repca.y()[index(Var::QMEAS)], static_cast(-0.4), kTol); + success *= isEqual(repca.y()[index(Var::PEXT)], static_cast(0.1), kTol); + success *= isEqual(repca.y()[index(Var::QEXT)], static_cast(-0.4), kTol); + + success *= (repca.evaluateResidual() == 0); + + for (size_t i = 0; i < repca.getResidual().size(); ++i) + { + success *= isEqual(repca.getResidual()[i], static_cast(0.0), kTol); + success *= isEqual(repca.yp()[i], static_cast(0.0), kTol); + } + + return success.report(__func__); + } + + TestOutcome omittedFrequencyPortsDefaultToZero() + { + using Var = PhasorDynamics::Converter::RepcaInternalVariables; + using Params = PhasorDynamics::Converter::RepcaParameters; + + TestStatus success = true; + + PhasorDynamics::Bus bus(1.0, 0.0); + bus.allocate(); + bus.initialize(); + + auto data = makeRepcaData(); + data.parameters[Params::Ddn] = static_cast(5.0); + data.parameters[Params::Dup] = static_cast(5.0); + data.parameters[Params::Kig] = static_cast(1.0); + + PhasorDynamics::Converter::Repca repca(&bus, data); + BranchSignalSet branch_signals; + branch_signals.attachTo(repca); + + success *= (repca.allocate() == 0); + success *= (repca.verify() == 0); + success *= (repca.initialize() == 0); + success *= (repca.evaluateResidual() == 0); + + const ScalarT pfreq = static_cast(5.0) * Math::ramp(repca.y()[index(Var::EF)]) + - static_cast(5.0) * Math::ramp(-repca.y()[index(Var::EF)]); + + success *= isEqual(repca.y()[index(Var::EF)], static_cast(0.0), kTol); + success *= isEqual(pfreq, static_cast(0.0), kTol); + + for (size_t i = 0; i < repca.getResidual().size(); ++i) + { + success *= isEqual(repca.getResidual()[i], static_cast(0.0), kTol); + success *= isEqual(repca.yp()[i], static_cast(0.0), kTol); + } + + return success.report(__func__); + } + + TestOutcome jsonParse() + { + TestStatus success = true; + + std::istringstream input(R"json( +{ + "header": { + "format_version": 0, + "format_revision": 1, + "case_name": "renewable plant control", + "case_description": "REPCA parser test", + "case_comments": "", + "freq_base": 60.0, + "va_base": 100000000.0 + }, + "buses": [ + { + "number": 1, + "class": "bus", + "name": "Bus 1", + "init": { "Vr": 1.0, "Vi": 0.0 }, + "v_base": 1.0 + } + ], + "signals": [ + { "signal_id": 10, "name": "Qext" }, + { "signal_id": 11, "name": "Pext" }, + { "signal_id": 12, "name": "Ibranchr" }, + { "signal_id": 13, "name": "Ibranchi" }, + { "signal_id": 14, "name": "Pbranch" }, + { "signal_id": 15, "name": "Qbranch" } + ], + "devices": [ + { + "class": "Repca", + "ports": { + "bus": 1, + "ibranchr": 12, + "ibranchi": 13, + "pbranch": 14, + "qbranch": 15, + "qext": 10, + "pext": 11 + }, + "id": "REP1", + "params": { + "mva": 100.0, "VcompFlag": 0, "RefFlag": 0, "Freqflag": 1, + "Tfltr": 0.02, "Tft": 0.0, "Tfv": 0.02, "Tp": 0.02, "Tlag": 0.0, + "Vfrz": 0.7, "Rc": 0.0, "Xc": 0.0, "Kc": 0.0, + "dbdlow": -0.01, "dbdupper": 0.01, "emax": 0.1, "emin": -0.1, + "Kp": 1.0, "Ki": 0.0, "Qmax": 1.0, "Qmin": -1.0, + "fdbd1": -0.01, "fdbd2": 0.01, "Ddn": 0.0, "Dup": 0.0, + "femax": 0.1, "femin": -0.1, "Kpg": 1.0, "Kig": 0.0, + "Pmax": 1.0, "Pmin": 0.0 + } + } + ] +} +)json"); + + auto data = PhasorDynamics::parseSystemModelData(input); + success *= (data.repca.size() == 1); + success *= (data.repca[0].ports.at(PhasorDynamics::Converter::RepcaPorts::pbranch) + == 14); + success *= (data.repca[0].ports.at(PhasorDynamics::Converter::RepcaPorts::qbranch) + == 15); + const auto mva_param = + data.repca[0].parameters.at(PhasorDynamics::Converter::RepcaParameters::mva); + success *= (std::get(mva_param) == static_cast(100.0)); + + return success.report(__func__); + } + + private: + static size_t index(PhasorDynamics::Converter::RepcaInternalVariables variable) + { + return static_cast(variable); + } + + struct BranchSignalSet + { + ScalarT ibranchr_value; + ScalarT ibranchi_value; + ScalarT pbranch_value; + ScalarT qbranch_value; + IdxT ibranchr_index; + IdxT ibranchi_index; + IdxT pbranch_index; + IdxT qbranch_index; + + PhasorDynamics::SignalNode ibranchr_node; + PhasorDynamics::SignalNode ibranchi_node; + PhasorDynamics::SignalNode pbranch_node; + PhasorDynamics::SignalNode qbranch_node; + + BranchSignalSet(ScalarT pbranch = static_cast(0.6), + ScalarT qbranch = static_cast(0.2), + ScalarT ibranchr = static_cast(0.6), + ScalarT ibranchi = static_cast(-0.2), + IdxT first_index = 20) + : ibranchr_value(ibranchr), + ibranchi_value(ibranchi), + pbranch_value(pbranch), + qbranch_value(qbranch), + ibranchr_index(first_index), + ibranchi_index(first_index + 1), + pbranch_index(first_index + 2), + qbranch_index(first_index + 3) + { + ibranchr_node.set(&ibranchr_value, &ibranchr_index); + ibranchi_node.set(&ibranchi_value, &ibranchi_index); + pbranch_node.set(&pbranch_value, &pbranch_index); + qbranch_node.set(&qbranch_value, &qbranch_index); + } + + template + void attachTo(RepcaT& repca) + { + using Ext = PhasorDynamics::Converter::RepcaExternalVariables; + + repca.getSignals().template attachSignalNode(&ibranchr_node); + repca.getSignals().template attachSignalNode(&ibranchi_node); + repca.getSignals().template attachSignalNode(&pbranch_node); + repca.getSignals().template attachSignalNode(&qbranch_node); + } + + template + void attachPowerTo(RepcaT& repca) + { + using Ext = PhasorDynamics::Converter::RepcaExternalVariables; + + repca.getSignals().template attachSignalNode(&pbranch_node); + repca.getSignals().template attachSignalNode(&qbranch_node); + } + }; + + auto makeRepcaData() -> PhasorDynamics::Converter::RepcaData + { + using Params = PhasorDynamics::Converter::RepcaParameters; + using Mon = PhasorDynamics::Converter::RepcaMonitorableVariables; + + PhasorDynamics::Converter::RepcaData data; + data.device_class = "Repca"; + data.disambiguation_string = "repca_test"; + data.monitored_variables.insert(Mon::qext); + data.monitored_variables.insert(Mon::pext); + + data.parameters[Params::mva] = static_cast(100.0); + data.parameters[Params::VcompFlag] = static_cast(0); + data.parameters[Params::RefFlag] = static_cast(0); + data.parameters[Params::Freqflag] = static_cast(1); + data.parameters[Params::Tfltr] = static_cast(0.02); + data.parameters[Params::Tft] = static_cast(0.0); + data.parameters[Params::Tfv] = static_cast(0.02); + data.parameters[Params::Tp] = static_cast(0.02); + data.parameters[Params::Tlag] = static_cast(0.0); + data.parameters[Params::Vfrz] = static_cast(0.7); + data.parameters[Params::Rc] = static_cast(0.0); + data.parameters[Params::Xc] = static_cast(0.0); + data.parameters[Params::Kc] = static_cast(0.0); + data.parameters[Params::dbdlow] = static_cast(-0.01); + data.parameters[Params::dbdupper] = static_cast(0.01); + data.parameters[Params::emax] = static_cast(0.1); + data.parameters[Params::emin] = static_cast(-0.1); + data.parameters[Params::Kp] = static_cast(1.0); + data.parameters[Params::Ki] = static_cast(0.0); + data.parameters[Params::Qmax] = static_cast(1.0); + data.parameters[Params::Qmin] = static_cast(-1.0); + data.parameters[Params::fdbd1] = static_cast(-0.01); + data.parameters[Params::fdbd2] = static_cast(0.01); + data.parameters[Params::Ddn] = static_cast(0.0); + data.parameters[Params::Dup] = static_cast(0.0); + data.parameters[Params::femax] = static_cast(0.1); + data.parameters[Params::femin] = static_cast(-0.1); + data.parameters[Params::Kpg] = static_cast(1.0); + data.parameters[Params::Kig] = static_cast(0.0); + data.parameters[Params::Pmax] = static_cast(1.0); + data.parameters[Params::Pmin] = static_cast(0.0); + + return data; + } + }; + } // namespace Testing +} // namespace GridKit diff --git a/tests/UnitTests/PhasorDynamics/runConverterRepcaTests.cpp b/tests/UnitTests/PhasorDynamics/runConverterRepcaTests.cpp new file mode 100644 index 000000000..a6f21fe35 --- /dev/null +++ b/tests/UnitTests/PhasorDynamics/runConverterRepcaTests.cpp @@ -0,0 +1,21 @@ +#include "ConverterRepcaTests.hpp" + +int main() +{ + GridKit::Testing::TestingResults result; + + GridKit::Testing::ConverterRepcaTests test; + + result += test.constructionAndValidation(); + result += test.repcaSignalsInitializationAndResidual(); + result += test.rejectsHalfConnectedBranchSignals(); + result += test.rejectsLineDropCompensationWithoutCurrentSignals(); + result += test.convertsSystemBaseBranchPowerToComponentBase(); + result += test.mvaZeroUsesSystemBase(); + result += test.rejectsNonSteadyConnectedReferences(); + result += test.initializationUsesBranchSignals(); + result += test.omittedFrequencyPortsDefaultToZero(); + result += test.jsonParse(); + + return result.summary(); +}