diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c31a6e41..80eca209c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -61,6 +61,7 @@ - Added `BusToSignalAdapter` component for communicating bus voltages and injection currents. - Added cmake-format hooks, including in pre-commit. - Added off-nominal tap ratio and phase shift support to the PhasorDynamics `Branch` model. +- Added `closed` parameter to `Branch` for declaring out-of-service lines in case files. ## v0.1 diff --git a/GridKit/Model/PhasorDynamics/Branch/Branch.hpp b/GridKit/Model/PhasorDynamics/Branch/Branch.hpp index 298b0d0b5..9617d746d 100644 --- a/GridKit/Model/PhasorDynamics/Branch/Branch.hpp +++ b/GridKit/Model/PhasorDynamics/Branch/Branch.hpp @@ -197,6 +197,7 @@ namespace GridKit RealT B_{0.0}; RealT tap_{1.0}; RealT phase_{0.0}; + RealT closed_{1.0}; IdxT bus1_id_{0}; IdxT bus2_id_{0}; diff --git a/GridKit/Model/PhasorDynamics/Branch/BranchData.hpp b/GridKit/Model/PhasorDynamics/Branch/BranchData.hpp index 6b048fded..6b8c630c8 100644 --- a/GridKit/Model/PhasorDynamics/Branch/BranchData.hpp +++ b/GridKit/Model/PhasorDynamics/Branch/BranchData.hpp @@ -21,6 +21,7 @@ namespace GridKit B, ///< Total shunt susceptance tap, ///< Off-nominal tap magnitude on bus1 side phase, ///< Phase shift angle in radians + closed ///< In-service flag (true = closed, default true) }; /// Ports for a branch diff --git a/GridKit/Model/PhasorDynamics/Branch/BranchImpl.hpp b/GridKit/Model/PhasorDynamics/Branch/BranchImpl.hpp index 80f661071..9007c3b87 100644 --- a/GridKit/Model/PhasorDynamics/Branch/BranchImpl.hpp +++ b/GridKit/Model/PhasorDynamics/Branch/BranchImpl.hpp @@ -171,6 +171,7 @@ namespace GridKit check(std::isfinite(B_), "B must be finite"); check(std::isfinite(tap_), "tap must be finite"); check(std::isfinite(phase_), "phase must be finite"); + check(std::isfinite(closed_), "closed must be finite"); check(R_ * R_ + X_ * X_ > RealT{0.0}, "R and X cannot both be zero"); check(tap_ > RealT{0.0}, "tap must be positive"); @@ -321,6 +322,20 @@ namespace GridKit readRealParameter(data, model_data_type::Parameters::tap, tap_); readRealParameter(data, model_data_type::Parameters::phase, phase_); + if (data.parameters.contains(model_data_type::Parameters::closed)) + { + const auto& value = data.parameters.at(model_data_type::Parameters::closed); + if (const auto* bool_value = std::get_if(&value)) + { + closed_ = *bool_value ? RealT{1.0} : RealT{0.0}; + } + else + { + Log::error() << "Branch: parameter closed must be boolean\n"; + parameter_error_count_ += 1; + } + } + if (data.ports.contains(model_data_type::Ports::bus1)) { bus1_id_ = data.ports.at(model_data_type::Ports::bus1); @@ -464,17 +479,17 @@ namespace GridKit const RealT g_diag = -(g_br + RealT{0.5} * G_); const RealT b_diag = -(b_br + RealT{0.5} * B_); - g11_ = g_diag * inv_tap * inv_tap; - b11_ = b_diag * inv_tap * inv_tap; + g11_ = closed_ * g_diag * inv_tap * inv_tap; + b11_ = closed_ * b_diag * inv_tap * inv_tap; - g12_ = (g_br * cos_ph - b_br * sin_ph) * inv_tap; - b12_ = (b_br * cos_ph + g_br * sin_ph) * inv_tap; + g12_ = closed_ * (g_br * cos_ph - b_br * sin_ph) * inv_tap; + b12_ = closed_ * (b_br * cos_ph + g_br * sin_ph) * inv_tap; - g21_ = (g_br * cos_ph + b_br * sin_ph) * inv_tap; - b21_ = (b_br * cos_ph - g_br * sin_ph) * inv_tap; + g21_ = closed_ * (g_br * cos_ph + b_br * sin_ph) * inv_tap; + b21_ = closed_ * (b_br * cos_ph - g_br * sin_ph) * inv_tap; - g22_ = g_diag; - b22_ = b_diag; + g22_ = closed_ * g_diag; + b22_ = closed_ * b_diag; } } // namespace PhasorDynamics diff --git a/GridKit/Model/PhasorDynamics/INPUT_FORMAT.md b/GridKit/Model/PhasorDynamics/INPUT_FORMAT.md index 32df36cdc..721d4269e 100644 --- a/GridKit/Model/PhasorDynamics/INPUT_FORMAT.md +++ b/GridKit/Model/PhasorDynamics/INPUT_FORMAT.md @@ -139,7 +139,7 @@ are specified: Device class | Description | Ports | Initialization parameters | Variables available to monitor ---------------------|------------------------------------------------------|----------------------------------|---------------------------- | ------------------------- - `Branch` | algebraic pi model for a line or off-nominal transformer branch | `bus1`, `bus2` | `R`, `X`, `G`, `B`, `tap`, `phase` | `ir1`, `ii1`, `im1`, `p1`, `q1`, `ir2`, `ii2`, `im2`, `p2`, `q2` + `Branch` | algebraic pi model for a line or off-nominal transformer branch | `bus1`, `bus2` | `R`, `X`, `G`, `B`, `tap`, `phase`, `closed` | `ir1`, `ii1`, `im1`, `p1`, `q1`, `ir2`, `ii2`, `im2`, `p2`, `q2` `Load` | a basic static impedence load model | `bus` | `R`, `X` | `p`, `q` `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` @@ -155,9 +155,10 @@ Ports marked with \* are optional and, if missing, will be assumed to be 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 -side for off-nominal transformer branches. +For `Branch`, `tap`, `phase`, and `closed` are optional parameters. If omitted, +`tap` defaults to `1.0`, `phase` defaults to `0.0` radians, and `closed` +defaults to `true`. Bus `bus1` is the tap side for off-nominal transformer +branches. ## Example File for a 2-Bus System diff --git a/tests/UnitTests/PhasorDynamics/BranchTests.hpp b/tests/UnitTests/PhasorDynamics/BranchTests.hpp index 6281f36ab..2b48cbd59 100644 --- a/tests/UnitTests/PhasorDynamics/BranchTests.hpp +++ b/tests/UnitTests/PhasorDynamics/BranchTests.hpp @@ -230,6 +230,53 @@ namespace GridKit return success.report(__func__); } + TestOutcome openBranch() + { + TestStatus success = true; + + RealT R{2.0}; + RealT X{4.0}; + RealT G{0.2}; + RealT B{1.2}; + + DependencyTracking::Variable Vr1{10.0}; + DependencyTracking::Variable Vi1{20.0}; + DependencyTracking::Variable Vr2{30.0}; + DependencyTracking::Variable Vi2{40.0}; + + Vr1.setVariableNumber(0); + Vi1.setVariableNumber(1); + Vr2.setVariableNumber(2); + Vi2.setVariableNumber(3); + + PhasorDynamics::BusInfinite bus1(Vr1, Vi1); + PhasorDynamics::BusInfinite bus2(Vr2, Vi2); + + PhasorDynamics::BranchData data; + data.parameters[PhasorDynamics::BranchParameters::R] = R; + data.parameters[PhasorDynamics::BranchParameters::X] = X; + data.parameters[PhasorDynamics::BranchParameters::G] = G; + data.parameters[PhasorDynamics::BranchParameters::B] = B; + data.parameters[PhasorDynamics::BranchParameters::closed] = false; + + PhasorDynamics::Branch branch(&bus1, &bus2, data); + branch.allocate(); + branch.evaluateResidual(); + + const RealT zero{0.0}; + std::vector residuals{bus1.Ir(), bus1.Ii(), bus2.Ir(), bus2.Ii()}; + for (const auto& res : residuals) + { + success *= isEqual(res.getValue(), zero); + for (const auto& [var_id, coef] : res.getDependencies()) + { + success *= isEqual(coef, zero); + } + } + + return success.report(__func__); + } + TestOutcome accessors() { // Verifies tap and phase-shift residual derivatives. diff --git a/tests/UnitTests/PhasorDynamics/runBranchTests.cpp b/tests/UnitTests/PhasorDynamics/runBranchTests.cpp index c0fe130c8..b9593196e 100644 --- a/tests/UnitTests/PhasorDynamics/runBranchTests.cpp +++ b/tests/UnitTests/PhasorDynamics/runBranchTests.cpp @@ -16,6 +16,7 @@ int main() result += test.offNominalResidual(); result += test.jacobian(); result += test.offNominalJacobian(); + result += test.openBranch(); return result.summary(); }