diff --git a/docs/user_manual/sphinx/Section9-ApplicationModules.rst b/docs/user_manual/sphinx/Section9-ApplicationModules.rst
index f24c2554b..d7fbe3645 100644
--- a/docs/user_manual/sphinx/Section9-ApplicationModules.rst
+++ b/docs/user_manual/sphinx/Section9-ApplicationModules.rst
@@ -71,20 +71,36 @@ shown below:
IEEE14.raw
50
1.0e-6
+ 1.0
+ warm
true
3
+ 0.1
+ false
+ false
+ false
+ 10
+ json
+ pf_result
- -ksp_view
- -ksp_type richardson
+ -ksp_type preonly
-pc_type lu
-pc_factor_mat_solver_type superlu_dist
- -ksp_max_it 1
+The ``networkConfiguration`` field accepts PSS/E RAW files in any
+supported version (v23, v33, v34, v35, v36). For v30+ files, the parser
+automatically detects the version from the third field of the first line
+in the RAW file header. Version-specific tags
+(``networkConfiguration_v33``, ``networkConfiguration_v34``, etc.) are
+still supported for backward compatibility and take precedence over
+auto-detection. PSS/E v23 files (which lack the version field) are
+handled as the default when auto-detection finds no version indicator.
+
This example specifies the input network configuration, the maximum number of
iterations in the non-linear Newton-Raphson solver, the solution tolerance
and the properties of the linear solver. The ``qlim`` parameter enables
@@ -92,8 +108,80 @@ reactive power limit enforcement (default ``true``). When enabled, PV
buses whose generator reactive power output exceeds the specified
``Qmax`` or ``Qmin`` limits are switched to PQ buses and the power
flow is re-solved. The ``maxQlimIterations`` parameter (default 3)
-controls the maximum number of outer PV-to-PQ switching iterations. This
-is a minimal example and more options for the solver can be used.
+controls the maximum number of outer PV-to-PQ switching iterations.
+The ``qlimDeadband`` parameter (default 0.1 Mvar) sets a tolerance around
+the reactive power limits: a PV bus is only converted to PQ when its
+required Q exceeds a limit by more than this amount, preventing spurious
+switching due to small numerical imbalances near the limit.
+
+The ``SwitchedShunt`` parameter (default ``false``) enables automatic
+switched shunt voltage control. When enabled, buses with switched shunt
+data (MODSW=1 for discrete or MODSW=2 for continuous control) are
+monitored after each Newton-Raphson convergence. If the controlled bus
+voltage falls outside the ``[VSWLO, VSWHI]`` deadband, the shunt
+susceptance is adjusted: discrete mode switches one capacitor or reactor
+bank step per iteration, while continuous mode adjusts B smoothly toward
+the deadband midpoint. Cycle detection locks shunts that oscillate
+between two states. Shunts with ``SWREM`` set to a nonzero bus number
+regulate voltage at that remote bus instead of the local bus.
+
+The ``LTC`` parameter (default ``false``) enables load tap changer
+(LTC) control on transformers. Transformers with COD1=1 in their PSS/E
+data (or a nonzero CONT bus in v23 transformer adjustment records)
+automatically adjust their tap ratios to regulate voltage at the
+controlled bus within the ``[VMI, VMA]`` deadband. Tap ratios are
+adjusted by one discrete step per controller iteration, bounded by
+``[RMI, RMA]``. The step size is computed from the tap range and
+number of tap positions (NTP), or read directly from the STEP field
+when available. Cycle detection prevents tap hunting.
+
+The ``AreaInterchange`` parameter (default ``false``) enables area
+interchange control. When enabled, after the inner controller loop
+converges, the solver computes actual MW exports for each area by
+summing tie-line flows (branches crossing area boundaries). If the
+export deviates from the desired value (``PDES`` from the area
+interchange data) by more than the tolerance (``PTOL``), the solver
+adjusts real power generation at the area slack bus (``ISW``) and
+re-solves. This outer loop runs up to 10 iterations.
+
+The ``maxControllerIterations`` parameter (default 10) sets the maximum
+number of inner controller iterations. This limit is shared by all
+active controls (Q-limits, switched shunts, LTC, and IREG remote
+voltage regulation). When only Q-limits are active, the
+``maxQlimIterations`` parameter is used for backward compatibility.
+
+The ``dampingFactor`` parameter (default 1.0) scales each Newton-Raphson
+correction vector before it is applied to the bus voltages and angles.
+A value of 1.0 gives standard (undamped) Newton-Raphson. Values between
+0 and 1 reduce the step size, which can improve convergence stability for
+ill-conditioned systems such as heavily loaded networks or cases where the
+warm-start initialization is far from the solution. For well-initialized
+cases (RAW files with converged VM/VA stored) a value of 1.0 is
+recommended.
+
+The ``initStart`` parameter (default ``warm``) controls how bus voltages
+and angles are initialized before the first Newton-Raphson iteration.
+``warm`` reads voltage magnitude and angle from the PSS/E RAW file bus
+section (columns VM and VA), placing all buses at the operating point
+stored in the network file. ``flat`` initializes all buses to 1.0 pu and
+0 degrees regardless of the RAW file values, which can be useful for
+cases where the stored voltages are unreliable or absent.
+
+The ``outputFormat`` and ``outputFile`` parameters control structured
+output after a successful solve. When ``outputFormat`` is set to
+``json``, the solver writes per-bus voltages, angles, and power
+injections to ``_buses.json`` and per-branch flows to
+``_branches.json``. When set to ``csv``, the results are
+written to ``_buses.csv`` and ``_branches.csv``.
+If these parameters are omitted no structured output file is written.
+
+The recommended ``LinearSolver`` configuration for the hand-coded
+Newton-Raphson ``solve()`` routine is ``-ksp_type preonly`` with direct
+LU factorization (``-pc_type lu``). This applies the factorization once
+per iteration without an iterative Krylov loop and avoids spurious
+divergence checks that can terminate otherwise-converging solutions.
+
+This is a minimal example and more options for the solver can be used.
Readers are encouraged to look at the examples that are copied into the
powerflow directory as part of the build.
@@ -408,6 +496,34 @@ can be reversed by calling
void clearQlimViolations()
+Switched shunt voltage violations can be checked and adjusted using
+
+::
+
+ bool checkSwitchedShuntViolations()
+
+This adjusts shunt susceptance on buses where the controlled voltage is
+outside the deadband. After unsetting a contingency, shunt adjustments can
+be reset to their initial BINIT values by calling
+
+::
+
+ void clearSwitchedShunts()
+
+LTC transformer tap ratio violations can be checked and adjusted using
+
+::
+
+ bool checkLTCViolations()
+
+This adjusts tap ratios on LTC-controlled transformers where the controlled
+bus voltage is outside the deadband. After unsetting a contingency, tap
+ratios can be reset to their initial values by calling
+
+::
+
+ void clearLTCControls()
+
Finally, the internal voltage variables that are used as the solution
variables in the power flow calculation can be reset to their original
values (specified in the network configuration file) by calling the
diff --git a/src/CHANGELOG.md b/src/CHANGELOG.md
index 745b409b8..66ad3decd 100644
--- a/src/CHANGELOG.md
+++ b/src/CHANGELOG.md
@@ -11,6 +11,41 @@ model](https://www.atlassian.com/git/tutorials/comparing-workflows/gitflow-workf
The Unreleased section will be empty for tagged releases. Unreleased
functionality appears in the develop branch.
+- Added
+ - Power Flow
+ - Switched shunt control (discrete MODSW=1 and continuous MODSW=2 modes)
+ with per-block N/B stepping, cycle detection, and lockout
+ - LTC (load tap changer) transformer control with tap direction
+ dependent on controlled bus side (from-bus vs to-bus)
+ - Area interchange control for power flow
+ - Auto-detect PSS/E version from RAW file header
+ - IREG remote voltage regulation via PV bus swap: for generators with
+ IREG != 0, the remote bus becomes PV (V = VS) and the generator bus
+ becomes PQ, matching PSS/E and PowerWorld behavior
+ - Q-limit deadband (qlimDeadband XML parameter) to prevent PV/PQ
+ cycling at marginal Q violations
+ - NR step damping for improved convergence robustness
+ - Test cases for switched shunt, LTC, and area interchange controls
+ - Documentation
+ - User manual updated with switched shunt and LTC control sections
+- Fixed
+ - Power Flow
+ - Star bus output filtering: synthetic buses from 3-winding transformers
+ are excluded from power flow output
+ - SHUNT_SWREM/SWREG name mismatch: v34 parser stored remote regulation
+ bus under SWREG but PF component read SWREM; added fallback
+ - LTC tap direction: reversed tap adjustment for to-bus controlled
+ transformers (COD1=1, CONT1=to-bus)
+ - 3-winding transformer ratings in v34/v35 parsers: winding 2 and 3
+ ratings were written to data1 instead of data2/data3
+ - 3-winding transformer star bus voltage initialization from
+ VMSTAR/ANSTAR fields
+ - Parser
+ - PSS/E v34 switched shunt parser: PTI34 incorrectly used v35 parser
+ (SwitchedShuntParser35) which assumed ID and NREG fields not present
+ in v34 format, causing BINIT to read B1 instead (e.g., 100 vs 400
+ Mvar), producing large voltage errors at switched shunt buses
+
## [3.6]
- Added
- Docker Support
@@ -39,7 +74,6 @@ functionality appears in the develop branch.
- ZIP load model with voltage-dependent load representation
- Added Pinj, Qinj to power flow screen outputs
- Per-iteration max mismatch reporting in Newton-Raphson solve
- - IREG remote voltage regulation support from PSS/E generator data
- JSON and CSV export of power flow results via ResultsExporter
- Contingency Analysis Enhancements
- N-1 auto-generation feature for contingency analysis
@@ -51,6 +85,8 @@ functionality appears in the develop branch.
- Dynamic Simulation Enhancements
- GENROU PSS integration
- Iterative equilibrium initialization (XML: equilibriumInit, default false)
+ - Support dynamic models (GENROU, GENSAL, Classical) for generators with
+ negative PG.
- Added build trigger when releasing a new version of GridPACK
- Changed
- User manual updated for v3.6 including power flow, state estimation,
diff --git a/src/applications/components/pf_matrix/pf_components.cpp b/src/applications/components/pf_matrix/pf_components.cpp
index 6ff316c2a..df8efd8be 100755
--- a/src/applications/components/pf_matrix/pf_components.cpp
+++ b/src/applications/components/pf_matrix/pf_components.cpp
@@ -32,6 +32,20 @@
* - Implemented voltage-dependent ZIP load model
* @date 2026-02-19
*
+ * @updated Yousu Chen
+ * - Added switched shunt control (MODSW=1 discrete, MODSW=2 continuous)
+ * @date 2026-02-24
+ *
+ * @updated Yousu Chen
+ * - Added LTC (load tap changer) control on PFBranch
+ * @date 2026-03-28
+ *
+ * @updated Yousu Chen
+ * - IREG PV bus swap for remote voltage regulation
+ * - Star bus filtering for 3-winding transformer output
+ * - LTC tap direction fix for to-bus controlled transformers
+ * @date 2026-04-04
+ *
* @brief Methods used in power flow application
*
*
@@ -101,6 +115,8 @@ gridpack::powerflow::PFBus::PFBus(void)
p_theta = 0.0;
p_angle = 0.0;
p_voltage = 0.0;
+ p_vmin = 0.0;
+ p_vmax = 0.0;
/*p_pl = 0.0;
p_ql = 0.0;
p_ip = 0.0;
@@ -113,9 +129,29 @@ gridpack::powerflow::PFBus::PFBus(void)
p_ngen = 0;
p_data = NULL;
p_ignore = false;
+ p_isStarBus = false;
+ p_isIREG_PV = false;
+ p_ireg_vs = 0.0;
+ p_ireg_remote_bus = 0;
+ p_ireg_remote_v_ptr = NULL;
p_vMag_ptr = NULL;
p_vAng_ptr = NULL;
p_PV_ptr = NULL;
+ // Switched shunt defaults
+ p_hasSwitchedShunt = false;
+ p_swshunt_modsw = 0;
+ p_swshunt_vswhi = 1.0;
+ p_swshunt_vswlo = 1.0;
+ p_swshunt_swrem = 0;
+ p_swshunt_binit = 0.0;
+ p_swshunt_adjm = 0;
+ p_swshunt_nblocks = 0;
+ p_swshunt_bcurrent = 0.0;
+ p_swshunt_bmin = 0.0;
+ p_swshunt_bmax = 0.0;
+ p_swshunt_locked = false;
+ p_swshunt_adj_count = 0;
+ p_swshunt_bprev = 0.0;
}
/**
@@ -141,7 +177,7 @@ bool gridpack::powerflow::PFBus::matrixDiagSize(int *isize, int *jsize) const
#else
if (getReferenceBus()) {
return false;
- } else if (p_isPV) {
+ } else if (p_isPV && !p_isIREG_PV) {
*isize = 1;
*jsize = 1;
return true;
@@ -212,7 +248,7 @@ bool gridpack::powerflow::PFBus::vectorSize(int *size) const
#else
if (getReferenceBus()) {
return false;
- } else if (p_isPV) {
+ } else if (p_isPV && !p_isIREG_PV) {
*size = 1;
} else {
*size = 2;
@@ -292,7 +328,7 @@ bool gridpack::powerflow::PFBus::vectorValues(RealType *values)
* @return false: violations exist (Q limits hit, bus converted to PQ)
* @return true: no violations (all generators within limits)
*/
-bool gridpack::powerflow::PFBus::chkQlim(void)
+bool gridpack::powerflow::PFBus::chkQlim(double q_deadband)
{
if (!p_isPV) {
if (p_PV_ptr) *p_PV_ptr = p_isPV;
@@ -414,10 +450,13 @@ bool gridpack::powerflow::PFBus::chkQlim(void)
}
}
- // Check if Q requirement can be met
+ // Check if Q requirement can be met.
+ // q_deadband avoids switching buses that are only marginally over their Q
+ // limit due to floating-point differences. Configurable via XML qlimDeadband
+ // (default 0.1 Mvar, matching PW's 0.1 MVA convergence tolerance).
bool need_pv_to_pq = false;
char warnBuf[256];
- if (Q_required > Q_max_total) {
+ if (Q_required > Q_max_total + q_deadband) {
// Exceeds total Qmax - need to convert to PQ
snprintf(warnBuf, sizeof(warnBuf),
"\nWarning: Bus %d Q requirement (%8.3f) exceeds total QMAX (%8.3f), converting to PQ\n",
@@ -431,7 +470,7 @@ bool gridpack::powerflow::PFBus::chkQlim(void)
q_assigned[i] = p_qmax[i];
}
}
- } else if (Q_required < Q_min_total) {
+ } else if (Q_required < Q_min_total - q_deadband) {
// Below total Qmin - need to convert to PQ
snprintf(warnBuf, sizeof(warnBuf),
"\nWarning: Bus %d Q requirement (%8.3f) below total QMIN (%8.3f), converting to PQ\n",
@@ -520,10 +559,196 @@ void gridpack::powerflow::PFBus::clearQlim()
if (p_PV_ptr) *p_PV_ptr = p_isPV;
}
+/**
+ * Adjust switched shunt B to bring controlled voltage within deadband.
+ * @param v_controlled voltage at controlled bus (local or remote)
+ * @return true if an adjustment was made
+ */
+bool gridpack::powerflow::PFBus::adjustSwitchedShunt(double v_controlled)
+{
+ if (!p_hasSwitchedShunt || p_swshunt_locked) return false;
+
+ // Skip adjustment for generator buses
+ // Shunt control only becomes active after PV->PQ conversion
+ if (p_isPV || getReferenceBus()) return false;
+
+ // Check if voltage is within deadband.
+ // When VSWHI == VSWLO (zero-width deadband), add a minimum tolerance to
+ // prevent cycling due to floating-point representation.
+ double vswlo = p_swshunt_vswlo;
+ double vswhi = p_swshunt_vswhi;
+ if (vswhi - vswlo < 0.002) {
+ double vmid = (vswhi + vswlo) / 2.0;
+ vswlo = vmid - 0.001;
+ vswhi = vmid + 0.001;
+ }
+ if (v_controlled >= vswlo && v_controlled <= vswhi) {
+ return false; // No action needed
+ }
+
+ double delta_b = 0.0;
+
+ if (p_swshunt_modsw == 1) {
+ // MODSW=1: Discrete adjustment - one step per controller iteration
+ if (v_controlled < p_swshunt_vswlo) {
+ // Voltage too low - increase B (add capacitor step or remove reactor step)
+ bool adjusted = false;
+ // Try adding a capacitor step (positive B blocks, left to right)
+ for (int ib = 0; ib < p_swshunt_nblocks; ib++) {
+ if (p_swshunt_b[ib] > 0.0 && p_swshunt_steps_on[ib] < p_swshunt_n[ib]) {
+ p_swshunt_steps_on[ib]++;
+ delta_b = p_swshunt_b[ib];
+ adjusted = true;
+ break;
+ }
+ }
+ // If no capacitor step available, try removing a reactor step (negative B blocks)
+ if (!adjusted) {
+ for (int ib = p_swshunt_nblocks - 1; ib >= 0; ib--) {
+ if (p_swshunt_b[ib] < 0.0 && p_swshunt_steps_on[ib] > 0) {
+ p_swshunt_steps_on[ib]--;
+ delta_b = -p_swshunt_b[ib]; // Removing reactor increases B
+ adjusted = true;
+ break;
+ }
+ }
+ }
+ if (!adjusted) return false;
+ } else {
+ // Voltage too high - decrease B (remove capacitor step or add reactor step)
+ bool adjusted = false;
+ // Try removing a capacitor step (positive B blocks, right to left)
+ for (int ib = p_swshunt_nblocks - 1; ib >= 0; ib--) {
+ if (p_swshunt_b[ib] > 0.0 && p_swshunt_steps_on[ib] > 0) {
+ p_swshunt_steps_on[ib]--;
+ delta_b = -p_swshunt_b[ib];
+ adjusted = true;
+ break;
+ }
+ }
+ // If no capacitor step to remove, try adding a reactor step (negative B blocks)
+ if (!adjusted) {
+ for (int ib = 0; ib < p_swshunt_nblocks; ib++) {
+ if (p_swshunt_b[ib] < 0.0 && p_swshunt_steps_on[ib] < p_swshunt_n[ib]) {
+ p_swshunt_steps_on[ib]++;
+ delta_b = p_swshunt_b[ib]; // Adding reactor decreases B
+ adjusted = true;
+ break;
+ }
+ }
+ }
+ if (!adjusted) return false;
+ }
+ } else if (p_swshunt_modsw == 2) {
+ // MODSW=2: Continuous adjustment toward deadband midpoint
+ double v_target = (p_swshunt_vswhi + p_swshunt_vswlo) / 2.0;
+ double k = p_swshunt_bmax - p_swshunt_bmin;
+ if (k <= 0.0) k = 1.0; // Fallback
+ double b_target = p_swshunt_bcurrent + k * (v_target - v_controlled);
+ // Clamp to [Bmin, Bmax]
+ if (b_target < p_swshunt_bmin) b_target = p_swshunt_bmin;
+ if (b_target > p_swshunt_bmax) b_target = p_swshunt_bmax;
+ delta_b = b_target - p_swshunt_bcurrent;
+ if (fabs(delta_b) < 1.0e-6) return false; // No meaningful change
+ } else {
+ return false;
+ }
+
+ // Cycle detection: if this adjustment would return to previous B, we are
+ // oscillating between two discrete steps that straddle the deadband.
+ // Choose the better of the two states (less deviated from the deadband)
+ // before locking. If the current voltage is noticeably outside the deadband,
+ // apply the reverting step so we lock at the less-deviated setting.
+ double b_new = p_swshunt_bcurrent + delta_b;
+ if (p_swshunt_adj_count > 0 && fabs(b_new - p_swshunt_bprev) < 1.0e-6) {
+ double outside = (v_controlled > vswhi) ? (v_controlled - vswhi)
+ : (vswlo - v_controlled);
+ if (outside > 0.01) {
+ // Current state is far outside deadband; accept the reverting step
+ // (which moves toward the deadband) and then lock.
+ p_swshunt_bprev = p_swshunt_bcurrent;
+ p_swshunt_bcurrent = b_new;
+ addShuntValues(0.0, delta_b / p_sbase);
+ p_swshunt_locked = true;
+ return true;
+ }
+ p_swshunt_locked = true;
+ return false;
+ }
+
+ // Apply the change to YMBus::p_shunt_bs via addShuntValues()
+ p_swshunt_bprev = p_swshunt_bcurrent;
+ p_swshunt_bcurrent = b_new;
+ addShuntValues(0.0, delta_b / p_sbase);
+
+ // Increment adjustment count and lock out after 10 adjustments
+ p_swshunt_adj_count++;
+ if (p_swshunt_adj_count >= 10) {
+ p_swshunt_locked = true;
+ }
+
+ return true;
+}
+
+/**
+ * Reset switched shunt to BINIT state
+ */
+void gridpack::powerflow::PFBus::resetSwitchedShunt()
+{
+ if (!p_hasSwitchedShunt) return;
+
+ // Remove current B from shunt, restore to BINIT
+ double delta = p_swshunt_binit - p_swshunt_bcurrent;
+ addShuntValues(0.0, delta / p_sbase);
+ p_swshunt_bcurrent = p_swshunt_binit;
+
+ // Re-decompose BINIT into step counts
+ double b_remaining = p_swshunt_binit;
+ for (int ib = 0; ib < p_swshunt_nblocks; ib++) {
+ if (p_swshunt_b[ib] == 0.0) {
+ p_swshunt_steps_on[ib] = 0;
+ continue;
+ }
+ int steps = static_cast(b_remaining / p_swshunt_b[ib]);
+ if (steps < 0) steps = 0;
+ if (steps > p_swshunt_n[ib]) steps = p_swshunt_n[ib];
+ p_swshunt_steps_on[ib] = steps;
+ b_remaining -= steps * p_swshunt_b[ib];
+ }
+
+ p_swshunt_locked = false;
+ p_swshunt_adj_count = 0;
+ p_swshunt_bprev = p_swshunt_binit;
+}
+
+/**
+ * Check if bus has a switched shunt
+ */
+bool gridpack::powerflow::PFBus::hasSwitchedShunt() const
+{
+ return p_hasSwitchedShunt;
+}
+
+/**
+ * Get remote bus number for switched shunt control
+ */
+int gridpack::powerflow::PFBus::getSwitchedShuntRemoteBus() const
+{
+ return p_swshunt_swrem;
+}
+
+/**
+ * Get current switched shunt susceptance
+ */
+double gridpack::powerflow::PFBus::getSwitchedShuntB() const
+{
+ return p_swshunt_bcurrent;
+}
+
/**
* Set the internal values of the voltage magnitude and phase angle. Need this
- * function to push values from vectors back onto buses
+ * function to push values from vectors back onto buses
* @param values array containing voltage magnitude and angle
*/
void gridpack::powerflow::PFBus::setValues(gridpack::ComplexType *values)
@@ -534,7 +759,7 @@ void gridpack::powerflow::PFBus::setValues(gridpack::ComplexType *values)
#ifdef LARGE_MATRIX
p_v -= real(values[1]);
#else
- if (!p_isPV) {
+ if (!p_isPV || p_isIREG_PV) {
p_v -= real(values[1]);
}
#endif
@@ -648,6 +873,11 @@ void gridpack::powerflow::PFBus::load(
bool ok = data->getValue(CASE_SBASE, &p_sbase);
data->getValue(BUS_VOLTAGE_ANG, &p_angle);
data->getValue(BUS_VOLTAGE_MAG, &p_voltage);
+ // Load bus voltage limits (NVHI/NVLO from RAW) for IREG clamping
+ p_vmax = 1.1; // default
+ p_vmin = 0.9; // default
+ data->getValue(BUS_VOLTAGE_MAX, &p_vmax);
+ data->getValue(BUS_VOLTAGE_MIN, &p_vmin);
double pi = 4.0*atan(1.0);
// Apply initial start mode
@@ -674,6 +904,14 @@ void gridpack::powerflow::PFBus::load(
} else {
p_original_isolated = false;
}
+ // Detect synthetic star buses from 3-winding transformers
+ std::string busName;
+ p_isStarBus = false;
+ if (data->getValue(BUS_NAME, &busName)) {
+ if (busName.substr(0, 9) == "DUMMY_BUS") {
+ p_isStarBus = true;
+ }
+ }
p_area = 1;
data->getValue(BUS_AREA, &p_area);
p_zone = 1;
@@ -769,6 +1007,35 @@ void gridpack::powerflow::PFBus::load(
p_ngen++;
}
}
+ // Detect IREG PV buses: if ALL online generators have remote IREG,
+ // use augmented Jacobian (control V at remote bus instead of local bus)
+ if (p_isPV && p_ngen > 0) {
+ bool all_remote = true;
+ int ireg_bus_found = 0;
+ double ireg_vs_found = 0.0;
+ int orig_idx = getOriginalIndex();
+ for (int ig = 0; ig < p_ngen; ig++) {
+ if (p_gstatus[ig] == 1) {
+ int ireg = p_ireg[ig];
+ if (ireg == 0 || ireg == orig_idx) {
+ all_remote = false;
+ break;
+ }
+ if (ireg_bus_found == 0) {
+ ireg_bus_found = ireg;
+ ireg_vs_found = p_vs[ig];
+ }
+ }
+ }
+ if (all_remote && ireg_bus_found != 0) {
+ // Store IREG info but don't enable augmented Jacobian yet
+ // The factory will handle PV swap after all buses are loaded
+ p_ireg_remote_bus = ireg_bus_found;
+ p_ireg_vs = ireg_vs_found;
+ // p_isIREG_PV is set by factory after PV swap setup
+ }
+ }
+
// Normalize p_pFac (P-based factor for P distribution)
if (pcaptot != 0.0 && p_ngen > 1) {
for (i=0; i= total_qmax - Q_init_tol || total_qg <= total_qmin + Q_init_tol) {
+ p_isPV = false;
+ }
+ }
+
p_saveisPV = p_isPV;
p_save2isPV = p_isPV; // Initialize for Q limit handling - ensures valid value if chkQlim() never called
@@ -857,6 +1144,82 @@ void gridpack::powerflow::PFBus::load(
}
}
}
+ // Load switched shunt data
+ p_hasSwitchedShunt = false;
+ p_swshunt_n.clear();
+ p_swshunt_b.clear();
+ p_swshunt_steps_on.clear();
+ p_swshunt_nblocks = 0;
+ p_swshunt_locked = false;
+ p_swshunt_adj_count = 0;
+
+ int swshunt_modsw = 0;
+ int swshunt_stat = 1; // Default in-service (v23 format has no STATUS field)
+ if (data->getValue(SHUNT_MODSW, &swshunt_modsw)) {
+ data->getValue(SHUNT_SWCH_STAT, &swshunt_stat); // Override if present (v33+)
+ if (swshunt_stat == 1 && (swshunt_modsw == 1 || swshunt_modsw == 2)) {
+ p_swshunt_modsw = swshunt_modsw;
+ p_swshunt_vswhi = 1.0;
+ p_swshunt_vswlo = 1.0;
+ p_swshunt_swrem = 0;
+ p_swshunt_binit = 0.0;
+ p_swshunt_adjm = 0;
+ data->getValue(SHUNT_VSWHI, &p_swshunt_vswhi);
+ data->getValue(SHUNT_VSWLO, &p_swshunt_vswlo);
+ if (!data->getValue(SHUNT_SWREM, &p_swshunt_swrem)) {
+ data->getValue(SHUNT_SWREG, &p_swshunt_swrem); // v34+ uses SWREG
+ }
+ data->getValue(SHUNT_BINIT, &p_swshunt_binit);
+ data->getValue(SHUNT_ADJM, &p_swshunt_adjm);
+
+ // Read N1-N8, B1-B8 block data
+ const char *n_keys[] = {SHUNT_N1, SHUNT_N2, SHUNT_N3, SHUNT_N4,
+ SHUNT_N5, SHUNT_N6, SHUNT_N7, SHUNT_N8};
+ const char *b_keys[] = {SHUNT_B1, SHUNT_B2, SHUNT_B3, SHUNT_B4,
+ SHUNT_B5, SHUNT_B6, SHUNT_B7, SHUNT_B8};
+ for (int ib = 0; ib < 8; ib++) {
+ int ni = 0;
+ double bi = 0.0;
+ data->getValue(n_keys[ib], &ni);
+ data->getValue(b_keys[ib], &bi);
+ if (ni == 0 && bi == 0.0) break; // End of blocks
+ p_swshunt_n.push_back(ni);
+ p_swshunt_b.push_back(bi);
+ p_swshunt_nblocks++;
+ }
+
+ if (p_swshunt_nblocks > 0) {
+ // Compute Bmin and Bmax from block data
+ p_swshunt_bmin = 0.0;
+ p_swshunt_bmax = 0.0;
+ for (int ib = 0; ib < p_swshunt_nblocks; ib++) {
+ double block_b = p_swshunt_n[ib] * p_swshunt_b[ib];
+ if (block_b > 0.0) {
+ p_swshunt_bmax += block_b; // Capacitor block
+ } else {
+ p_swshunt_bmin += block_b; // Reactor block
+ }
+ }
+
+ // Decompose BINIT into initial step counts per block
+ p_swshunt_steps_on.resize(p_swshunt_nblocks, 0);
+ double b_remaining = p_swshunt_binit;
+ for (int ib = 0; ib < p_swshunt_nblocks; ib++) {
+ if (p_swshunt_b[ib] == 0.0) continue;
+ int steps = static_cast(b_remaining / p_swshunt_b[ib]);
+ if (steps < 0) steps = 0;
+ if (steps > p_swshunt_n[ib]) steps = p_swshunt_n[ib];
+ p_swshunt_steps_on[ib] = steps;
+ b_remaining -= steps * p_swshunt_b[ib];
+ }
+
+ p_swshunt_bcurrent = p_swshunt_binit;
+ p_swshunt_bprev = p_swshunt_binit;
+ p_hasSwitchedShunt = true;
+ }
+ }
+ }
+
// If this is being called a second time, then update pointers
if (p_vMag_ptr) *p_vMag_ptr = p_v;
if (p_vAng_ptr) {
@@ -1249,6 +1612,11 @@ void gridpack::powerflow::PFBus::adjustVoltageForRemoteReg(double dv)
{
p_v += dv;
p_voltage += dv;
+ // Clamp to bus voltage limits to prevent unbounded accumulation over many
+ // outer iterations (each call adds dv; without clamping p_voltage drifts
+ // to multi-pu values causing impossible Q requirements)
+ if (p_vmax > 0.0 && p_voltage > p_vmax) { p_v = p_vmax; p_voltage = p_vmax; }
+ if (p_vmin > 0.0 && p_voltage < p_vmin) { p_v = p_vmin; p_voltage = p_vmin; }
if (p_vMag_ptr) *p_vMag_ptr = p_v;
}
@@ -1423,7 +1791,7 @@ bool gridpack::powerflow::PFBus::serialWrite(char *string, const int bufsize,
Q += p_v*p_v*(-p_ybusi);
p_Pinj = P;
p_Qinj = Q;
- if (!isIsolated()) {
+ if (!isIsolated() && !p_isStarBus) {
sprintf(string, " %6d %12.6f %12.6f %12.6f %12.6f\n",
getOriginalIndex(),angle,p_v,p_Pinj,p_Qinj);
}
@@ -1482,6 +1850,7 @@ bool gridpack::powerflow::PFBus::serialWrite(char *string, const int bufsize,
static_cast(branches.size()));
}
} else if (!strcmp(signal,"record")) {
+ if (p_isStarBus) return false;
char sbuf[128];
char *cptr = string;
int slen = 0;
@@ -1749,6 +2118,15 @@ bool gridpack::powerflow::PFBus::serialWrite(char *string, const int bufsize,
} else {
return false;
}
+ } else if (!strcmp(signal,"swshunt")) {
+ if (p_hasSwitchedShunt) {
+ sprintf(string, " %6d MODSW=%d Binit=%8.2f Bcurrent=%8.2f V=%8.4f [%6.4f, %6.4f]\n",
+ getOriginalIndex(), p_swshunt_modsw,
+ p_swshunt_binit, p_swshunt_bcurrent,
+ p_v, p_swshunt_vswlo, p_swshunt_vswhi);
+ return true;
+ }
+ return false;
}
return true;
}
@@ -2160,6 +2538,7 @@ int gridpack::powerflow::PFBus::diagonalJacobianValues(double *rvals)
}
#else
if (!getReferenceBus() && !p_isPV) {
+ // Standard PQ
rvals[0] = -p_Qinj - p_ybusi * p_v *p_v;
rvals[1] = p_Pinj - p_ybusr * p_v *p_v;
rvals[2] = p_Pinj / p_v + p_ybusr * p_v;
@@ -2168,7 +2547,16 @@ int gridpack::powerflow::PFBus::diagonalJacobianValues(double *rvals)
rvals[2] += dpzip_dV / p_sbase;
rvals[3] += dqzip_dV / p_sbase;
return 4;
+ } else if (!getReferenceBus() && p_isPV && p_isIREG_PV) {
+ // IREG PV: equations [ΔP, V_remote-VS], variables [θ, V]
+ rvals[0] = -p_Qinj - p_ybusi * p_v * p_v; // ∂ΔP/∂θ
+ rvals[1] = 0.0; // ∂(V_remote-VS)/∂θ
+ rvals[2] = p_Pinj / p_v + p_ybusr * p_v; // ∂ΔP/∂V
+ rvals[2] += dpzip_dV / p_sbase;
+ rvals[3] = 1.0e-10; // small pivot helper
+ return 4;
} else if (!getReferenceBus() && p_isPV) {
+ // Standard PV
rvals[0] = -p_Qinj - p_ybusi * p_v *p_v;
return 1;
} else {
@@ -2239,6 +2627,11 @@ int gridpack::powerflow::PFBus::rhsValues(double *rvals)
if (!p_isPV) {
rvals[1] = Q;
nvals = 2;
+ } else if (p_isIREG_PV) {
+ // V_remote - VS constraint
+ double v_remote = (p_ireg_remote_v_ptr) ? *p_ireg_remote_v_ptr : p_ireg_vs;
+ rvals[1] = v_remote - p_ireg_vs;
+ nvals = 2;
}
#endif
return nvals;
@@ -2567,6 +2960,22 @@ gridpack::powerflow::PFBranch::PFBranch(void)
p_theta = 0.0;
p_sbase = 0.0;
p_mode = YBus;
+ // LTC defaults
+ p_hasLTC = false;
+ p_ltc_elem = -1;
+ p_ltc_code = 0;
+ p_ltc_cont = 0;
+ p_ltc_cont_is_to = false;
+ p_ltc_rma = 1.1;
+ p_ltc_rmi = 0.9;
+ p_ltc_vma = 1.1;
+ p_ltc_vmi = 0.9;
+ p_ltc_ntp = 33;
+ p_ltc_step = 0.0;
+ p_ltc_tap_init = 0.0;
+ p_ltc_tap_prev = 0.0;
+ p_ltc_locked = false;
+ p_ltc_adj_count = 0;
}
/**
@@ -2600,8 +3009,9 @@ bool gridpack::powerflow::PFBranch::matrixForwardSize(int *isize, int *jsize) co
*jsize = 2;
return true;
#else
- bool bus1PV = bus1->isPV();
- bool bus2PV = bus2->isPV();
+ // IREG PV buses have 2 vars/eqs (like PQ), not 1 (like standard PV)
+ bool bus1PV = bus1->isPV() && !bus1->isIREG_PV();
+ bool bus2PV = bus2->isPV() && !bus2->isIREG_PV();
if (bus1PV && bus2PV) {
*isize = 1;
*jsize = 1;
@@ -2646,8 +3056,9 @@ bool gridpack::powerflow::PFBranch::matrixReverseSize(int *isize, int *jsize) co
*jsize = 2;
return true;
#else
- bool bus1PV = bus1->isPV();
- bool bus2PV = bus2->isPV();
+ // IREG PV buses have 2 vars/eqs (like PQ), not 1 (like standard PV)
+ bool bus1PV = bus1->isPV() && !bus1->isIREG_PV();
+ bool bus2PV = bus2->isPV() && !bus2->isIREG_PV();
if (bus1PV && bus2PV) {
*isize = 1;
*jsize = 1;
@@ -2847,6 +3258,67 @@ void gridpack::powerflow::PFBranch::load(
p_rateC.push_back(rvar);
p_ignore.push_back(false);
}
+
+ // Load LTC control data — scan elements for COD1=1 (voltage control)
+ p_hasLTC = false;
+ p_ltc_elem = -1;
+ p_ltc_locked = false;
+ p_ltc_adj_count = 0;
+ for (int idx = 0; idx < p_elems; idx++) {
+ int code1 = 0;
+ int cont1 = 0;
+ // v33+: TRANSFORMER_CODE1 and TRANSFORMER_CONT1
+ // v23: TRANSFORMER_CONTROL stores controlled bus (nonzero implies voltage control)
+ bool has_ltc_data = false;
+ if (data->getValue(TRANSFORMER_CODE1, &code1, idx) && code1 == 1) {
+ if (!data->getValue(TRANSFORMER_CONT1, &cont1, idx)) {
+ data->getValue(TRANSFORMER_CONTROL, &cont1, idx);
+ }
+ has_ltc_data = (cont1 != 0);
+ } else if (data->getValue(TRANSFORMER_CONTROL, &cont1, idx) && cont1 != 0) {
+ // v23 format: TRANSFORMER_CONTROL present with nonzero bus implies voltage control
+ code1 = 1;
+ has_ltc_data = true;
+ }
+ if (has_ltc_data) {
+
+ double rma = 1.1, rmi = 0.9, vma = 1.1, vmi = 0.9;
+ int ntp = 33;
+ data->getValue(TRANSFORMER_RMA, &rma, idx);
+ data->getValue(TRANSFORMER_RMI, &rmi, idx);
+ data->getValue(TRANSFORMER_VMA, &vma, idx);
+ data->getValue(TRANSFORMER_VMI, &vmi, idx);
+ data->getValue(TRANSFORMER_NTP, &ntp, idx);
+
+ // Compute step size: use TRANSFORMER_STEP if available (v23), else from tap range
+ double step = 0.0;
+ if (!data->getValue(TRANSFORMER_STEP, &step, idx) || step < 1.0e-6) {
+ if (ntp > 1) {
+ step = (rma - rmi) / (ntp - 1);
+ }
+ }
+ if (step < 1.0e-6) step = 0.00625; // Default 0.625% step
+
+ p_hasLTC = true;
+ p_ltc_elem = idx;
+ p_ltc_code = code1;
+ p_ltc_cont = cont1;
+ // Determine if controlled bus is the to-bus (tap direction reversal needed)
+ int frombus = 0, tobus = 0;
+ data->getValue(BRANCH_FROMBUS, &frombus);
+ data->getValue(BRANCH_TOBUS, &tobus);
+ p_ltc_cont_is_to = (cont1 == tobus);
+ p_ltc_rma = rma;
+ p_ltc_rmi = rmi;
+ p_ltc_vma = vma;
+ p_ltc_vmi = vmi;
+ p_ltc_ntp = ntp;
+ p_ltc_step = step;
+ p_ltc_tap_init = p_tap_ratio[idx];
+ p_ltc_tap_prev = p_tap_ratio[idx];
+ break; // Only support one LTC per branch
+ }
+ }
}
/**
@@ -2854,6 +3326,113 @@ void gridpack::powerflow::PFBranch::load(
* the mapper
* @param mode: enumerated constant for different modes
*/
+/**
+ * Adjust LTC tap ratio to bring controlled voltage within deadband.
+ * @param v_controlled voltage at controlled bus
+ * @return true if a tap adjustment was made
+ */
+bool gridpack::powerflow::PFBranch::adjustLTC(double v_controlled)
+{
+ if (!p_hasLTC || p_ltc_locked) return false;
+ int idx = p_ltc_elem;
+
+ // Check if voltage is within deadband
+ double vmi = p_ltc_vmi;
+ double vma = p_ltc_vma;
+ if (vma - vmi < 0.002) {
+ double vmid = (vma + vmi) / 2.0;
+ vmi = vmid - 0.001;
+ vma = vmid + 0.001;
+ }
+ if (v_controlled >= vmi && v_controlled <= vma) {
+ return false; // No action needed
+ }
+
+ double tap_current = p_tap_ratio[idx];
+ double tap_new = tap_current;
+
+ // Tap direction depends on which bus is controlled:
+ // - Tap on from-bus side: increasing tap INCREASES from-bus V, DECREASES to-bus V
+ // - If controlling to-bus: low V → decrease tap; high V → increase tap
+ // - If controlling from-bus: low V → increase tap; high V → decrease tap
+ double step = p_ltc_step;
+ if (p_ltc_cont_is_to) step = -step; // Reverse direction for to-bus control
+
+ if (v_controlled < p_ltc_vmi) {
+ // Voltage too low
+ tap_new = tap_current + step;
+ } else {
+ // Voltage too high
+ tap_new = tap_current - step;
+ }
+
+ // Clamp to [RMI, RMA]
+ if (tap_new > p_ltc_rma) tap_new = p_ltc_rma;
+ if (tap_new < p_ltc_rmi) tap_new = p_ltc_rmi;
+
+ // No change possible (already at limit)
+ if (fabs(tap_new - tap_current) < 1.0e-8) {
+ p_ltc_locked = true;
+ return false;
+ }
+
+ // Cycle detection: if this adjustment would return to previous tap, lock
+ if (p_ltc_adj_count > 0 && fabs(tap_new - p_ltc_tap_prev) < 1.0e-8) {
+ p_ltc_locked = true;
+ return false;
+ }
+
+ // Apply the tap change to both PFBranch and YMBranch tap ratio vectors
+ p_ltc_tap_prev = tap_current;
+ p_tap_ratio[idx] = tap_new;
+ YMBranch::setParam(BRANCH_TAP, tap_new, idx);
+
+ p_ltc_adj_count++;
+ if (p_ltc_adj_count >= 10) {
+ p_ltc_locked = true;
+ }
+
+ return true;
+}
+
+/**
+ * Reset LTC to initial tap ratio
+ */
+void gridpack::powerflow::PFBranch::resetLTC()
+{
+ if (!p_hasLTC) return;
+ int idx = p_ltc_elem;
+ p_tap_ratio[idx] = p_ltc_tap_init;
+ YMBranch::setParam(BRANCH_TAP, p_ltc_tap_init, idx);
+ p_ltc_tap_prev = p_ltc_tap_init;
+ p_ltc_locked = false;
+ p_ltc_adj_count = 0;
+}
+
+/**
+ * Check if branch has LTC control
+ */
+bool gridpack::powerflow::PFBranch::hasLTC() const
+{
+ return p_hasLTC;
+}
+
+/**
+ * Get controlled bus number for LTC
+ */
+int gridpack::powerflow::PFBranch::getLTCControlledBus() const
+{
+ return p_ltc_cont;
+}
+
+/**
+ * Get index of the LTC-controlled element within this branch
+ */
+int gridpack::powerflow::PFBranch::getLTCElementIndex() const
+{
+ return p_ltc_elem;
+}
+
void gridpack::powerflow::PFBranch::setMode(int mode)
{
if (mode == YBus) {
@@ -3279,8 +3858,9 @@ int gridpack::powerflow::PFBranch::forwardJacobianValues(double *rvals)
double t11, t12, t21, t22;
double cs = cos(p_theta);
double sn = sin(p_theta);
- bool bus1PV = bus1->isPV();
- bool bus2PV = bus2->isPV();
+ // IREG PV buses have 2 vars/eqs (like PQ), not 1 (like standard PV)
+ bool bus1PV = bus1->isPV() && !bus1->isIREG_PV();
+ bool bus2PV = bus2->isPV() && !bus2->isIREG_PV();
#ifdef LARGE_MATRIX
rvals[0] = (p_ybusr_frwd*sn - p_ybusi_frwd*cs);
rvals[1] = (p_ybusr_frwd*cs + p_ybusi_frwd*sn);
@@ -3330,8 +3910,17 @@ int gridpack::powerflow::PFBranch::forwardJacobianValues(double *rvals)
rvals[2] *= bus1->getVoltage();
rvals[3] *= bus1->getVoltage();
nvals = 4;
- }
+ }
#endif
+ // For IREG PV bus1: replace Q-row with V_remote constraint
+ if (bus1->isIREG_PV()) {
+ int remote = bus1->getIREGRemoteBus();
+ int bus2_idx = bus2->getOriginalIndex();
+ if (nvals >= 2) rvals[1] = 0.0; // ∂(V_remote)/∂θ_bus2 = 0
+ if (nvals >= 4) {
+ rvals[3] = (bus2_idx == remote) ? 1.0 : 0.0;
+ }
+ }
return nvals;
} else {
return 0;
@@ -3354,8 +3943,9 @@ int gridpack::powerflow::PFBranch::reverseJacobianValues(double *rvals)
double t11, t12, t21, t22;
double cs = cos(-p_theta);
double sn = sin(-p_theta);
- bool bus1PV = bus1->isPV();
- bool bus2PV = bus2->isPV();
+ // IREG PV buses have 2 vars/eqs (like PQ), not 1 (like standard PV)
+ bool bus1PV = bus1->isPV() && !bus1->isIREG_PV();
+ bool bus2PV = bus2->isPV() && !bus2->isIREG_PV();
#ifdef LARGE_MATRIX
rvals[0] = (p_ybusr_rvrs*sn - p_ybusi_rvrs*cs);
rvals[1] = (p_ybusr_rvrs*cs + p_ybusi_rvrs*sn);
diff --git a/src/applications/components/pf_matrix/pf_components.hpp b/src/applications/components/pf_matrix/pf_components.hpp
index a11cf0068..72d250b8c 100644
--- a/src/applications/components/pf_matrix/pf_components.hpp
+++ b/src/applications/components/pf_matrix/pf_components.hpp
@@ -32,6 +32,21 @@
* - Generators with IREG != 0 regulate voltage at a remote bus
* @date 2026-03-03
*
+ * @updated Yousu Chen
+ * - Added switched shunt control (MODSW=1 discrete, MODSW=2 continuous)
+ * - Member variables, control methods, and serialize support
+ * @date 2026-02-24
+ *
+ * @updated Yousu Chen
+ * - Added LTC (load tap changer) control on PFBranch
+ * @date 2026-03-28
+ *
+ * @updated Yousu Chen
+ * - IREG augmented via PV bus swap (remote bus becomes PV at VS)
+ * - Star bus filtering for 3-winding transformer output
+ * - LTC tap direction fix for to-bus controlled transformers
+ * @date 2026-04-04
+ *
* @brief
*
*
@@ -313,6 +328,23 @@ class PFBus
*/
bool isPV(void);
+ bool isStarBus() const { return p_isStarBus; }
+ bool isIREG_PV() const { return p_isIREG_PV; }
+ int getIREGRemoteBus() const { return p_ireg_remote_bus; }
+ double getIREGVS() const { return p_ireg_vs; }
+ void setIREGRemoteVoltagePtr(double *ptr) { p_ireg_remote_v_ptr = ptr; }
+ double* getVoltagePtr() { return p_vMag_ptr; }
+ void setVoltageMag(double v) {
+ p_v = v; p_voltage = v;
+ if (p_vMag_ptr) *p_vMag_ptr = v;
+ }
+ void saveIsPVState() { p_saveisPV = p_isPV; p_save2isPV = p_isPV; }
+ void setVoltageForIREG(double v) {
+ p_v = v; p_voltage = v;
+ p_type = 2; p_save_type = 2; // Mark as PV bus
+ if (p_vMag_ptr) *p_vMag_ptr = v;
+ }
+
/**
* Set voltage value
*/
@@ -370,7 +402,7 @@ class PFBus
* chkQlim
check QLIM violations
*/
- bool chkQlim(void);
+ bool chkQlim(double q_deadband = 0.1);
/**
* Clear changes that were made for Q limit violations and reset
@@ -625,6 +657,36 @@ class PFBus
*/
void adjustVoltageForRemoteReg(double dv);
+ /**
+ * Check if this bus has a switched shunt and adjust if needed
+ * @param v_controlled voltage at controlled bus (local or remote)
+ * @return true if an adjustment was made
+ */
+ bool adjustSwitchedShunt(double v_controlled);
+
+ /**
+ * Reset switched shunt to BINIT state
+ */
+ void resetSwitchedShunt();
+
+ /**
+ * Check if bus has a switched shunt
+ * @return true if bus has a switched shunt
+ */
+ bool hasSwitchedShunt() const;
+
+ /**
+ * Get remote bus number for switched shunt control (SWREM)
+ * @return SWREM bus number, or 0 if locally controlled
+ */
+ int getSwitchedShuntRemoteBus() const;
+
+ /**
+ * Get current switched shunt susceptance
+ * @return current B value in Mvar at unity voltage
+ */
+ double getSwitchedShuntB() const;
+
private:
static std::vector p_qlimWarnings;
static InitStartMode p_initStartMode;
@@ -679,6 +741,32 @@ class PFBus
bool p_sink;
double p_rtpr_scale;
bool p_original_isolated;
+ bool p_isStarBus; // true for synthetic star buses from 3-winding transformers
+
+ // IREG augmented Jacobian variables
+ bool p_isIREG_PV; // true if PV bus with all gens remote-regulating
+ double p_ireg_vs; // VS target for the remote bus
+ int p_ireg_remote_bus; // remote bus number (for factory to set up pointer)
+ double *p_ireg_remote_v_ptr; // pointer to remote bus voltage magnitude
+
+ // Switched shunt control variables
+ bool p_hasSwitchedShunt; // true if this bus has an active switched shunt
+ int p_swshunt_modsw; // control mode (1=discrete, 2=continuous)
+ double p_swshunt_vswhi; // controlled voltage upper limit (pu)
+ double p_swshunt_vswlo; // controlled voltage lower limit (pu)
+ int p_swshunt_swrem; // remote controlled bus number (0=local)
+ double p_swshunt_binit; // initial B value (Mvar at 1.0 pu V)
+ int p_swshunt_adjm; // adjustment method (0=input order, 1=optimal)
+ std::vector p_swshunt_n; // number of steps per block (up to 8 blocks)
+ std::vector p_swshunt_b; // B increment per step per block (Mvar)
+ int p_swshunt_nblocks; // number of valid blocks
+ std::vector p_swshunt_steps_on; // current steps switched on per block
+ double p_swshunt_bcurrent; // current total B (Mvar)
+ double p_swshunt_bmin; // minimum achievable B
+ double p_swshunt_bmax; // maximum achievable B
+ bool p_swshunt_locked; // lock out after max adjustments
+ int p_swshunt_adj_count; // number of adjustments made
+ double p_swshunt_bprev; // previous B value for cycle detection
/**
* Variables that are exchanged between buses
@@ -728,8 +816,20 @@ class PFBus
& p_ngen & p_type & p_save_type & p_nload
& p_area & p_zone
& p_source & p_sink
- & p_rtpr_scale;
- }
+ & p_rtpr_scale
+ & p_hasSwitchedShunt
+ & p_swshunt_modsw
+ & p_swshunt_vswhi & p_swshunt_vswlo
+ & p_swshunt_swrem
+ & p_swshunt_binit & p_swshunt_adjm
+ & p_swshunt_n & p_swshunt_b
+ & p_swshunt_nblocks
+ & p_swshunt_steps_on
+ & p_swshunt_bcurrent
+ & p_swshunt_bmin & p_swshunt_bmax
+ & p_swshunt_locked & p_swshunt_adj_count
+ & p_swshunt_bprev;
+ }
};
@@ -899,6 +999,36 @@ class PFBranch
int forwardJacobianValues(double *rvals);
int reverseJacobianValues(double *rvals);
+ /**
+ * Check if this branch has LTC control and adjust tap if needed
+ * @param v_controlled voltage at controlled bus
+ * @return true if a tap adjustment was made
+ */
+ bool adjustLTC(double v_controlled);
+
+ /**
+ * Reset LTC to initial tap ratio
+ */
+ void resetLTC();
+
+ /**
+ * Check if branch has LTC control
+ * @return true if branch has an LTC-controlled transformer
+ */
+ bool hasLTC() const;
+
+ /**
+ * Get controlled bus number for LTC
+ * @return CONT1 bus number (0 if not LTC-controlled)
+ */
+ int getLTCControlledBus() const;
+
+ /**
+ * Get index of the LTC-controlled element within this branch
+ * @return element index, or -1 if no LTC
+ */
+ int getLTCElementIndex() const;
+
private:
std::vector p_ignore;
std::vector p_reactance;
@@ -924,6 +1054,23 @@ class PFBranch
int p_elems;
bool p_active;
+ // LTC (Load Tap Changer) control variables
+ bool p_hasLTC; // true if this branch has an LTC-controlled transformer
+ int p_ltc_elem; // index of the LTC element within this branch
+ int p_ltc_code; // control mode (1=voltage, 0=off)
+ int p_ltc_cont; // controlled bus number
+ bool p_ltc_cont_is_to; // true if controlled bus is the to-bus (tap direction reversal)
+ double p_ltc_rma; // upper tap limit
+ double p_ltc_rmi; // lower tap limit
+ double p_ltc_vma; // controlled voltage upper limit (pu)
+ double p_ltc_vmi; // controlled voltage lower limit (pu)
+ int p_ltc_ntp; // number of tap positions
+ double p_ltc_step; // tap step size (computed from RMA, RMI, NTP)
+ double p_ltc_tap_init; // initial tap ratio
+ double p_ltc_tap_prev; // previous tap for cycle detection
+ bool p_ltc_locked; // lock out after cycling or max adjustments
+ int p_ltc_adj_count; // number of adjustments made
+
private:
@@ -953,8 +1100,13 @@ class PFBranch
& p_theta
& p_sbase
& p_elems
- & p_active;
- }
+ & p_active
+ & p_hasLTC & p_ltc_elem & p_ltc_code & p_ltc_cont
+ & p_ltc_rma & p_ltc_rmi & p_ltc_vma & p_ltc_vmi
+ & p_ltc_ntp & p_ltc_step
+ & p_ltc_tap_init & p_ltc_tap_prev
+ & p_ltc_locked & p_ltc_adj_count;
+ }
};
diff --git a/src/applications/contingency_analysis/ca_driver.cpp b/src/applications/contingency_analysis/ca_driver.cpp
index 6b60f0d78..88d385201 100644
--- a/src/applications/contingency_analysis/ca_driver.cpp
+++ b/src/applications/contingency_analysis/ca_driver.cpp
@@ -809,10 +809,23 @@ void gridpack::contingency_analysis::CADriver::execute(int argc, char** argv)
#endif
// Skip power flow if contingency setup failed (no valid slack) or islanding detected
bool slackCapacityOk = true; // Will be checked after solve
- if (contingencyFound && !islandDetected && pf_app.solve()) {
- if (check_Qlim && !pf_app.checkQlimViolations()) {
- pf_app.solve();
+ bool solveOk = false;
+ if (contingencyFound && !islandDetected) {
+ try {
+ solveOk = pf_app.solve();
+ if (solveOk && check_Qlim && !pf_app.checkQlimViolations()) {
+ pf_app.solve();
+ }
+ } catch (const std::exception& e) {
+ printf("p[%d] hit exception: %s\n", world.rank(), e.what());
+ printf("Solver failure\n");
+ solveOk = false;
+ } catch (...) {
+ printf("p[%d] hit unknown exception during solve\n", world.rank());
+ solveOk = false;
}
+ }
+ if (solveOk) {
// Write PV->PQ conversion warnings to output file
if (print_calcs && check_Qlim) {
std::vector& warnings = gridpack::powerflow::PFBus::getQlimWarnings();
diff --git a/src/applications/data_sets/input/powerflow/input_14_ltc.xml b/src/applications/data_sets/input/powerflow/input_14_ltc.xml
new file mode 100644
index 000000000..676ec49de
--- /dev/null
+++ b/src/applications/data_sets/input/powerflow/input_14_ltc.xml
@@ -0,0 +1,20 @@
+
+
+
+ IEEE14_ltc.raw
+ 50
+ 1.0e-6
+ true
+ true
+ true
+ 10
+
+
+ -ksp_type richardson
+ -pc_type lu
+ -pc_factor_mat_solver_type superlu_dist
+ -ksp_max_it 1
+
+
+
+
diff --git a/src/applications/data_sets/input/powerflow/input_14_swshunt.xml b/src/applications/data_sets/input/powerflow/input_14_swshunt.xml
new file mode 100644
index 000000000..d511f92dc
--- /dev/null
+++ b/src/applications/data_sets/input/powerflow/input_14_swshunt.xml
@@ -0,0 +1,19 @@
+
+
+
+ IEEE14_swshunt.raw
+ 50
+ 1.0e-6
+ true
+ true
+ 10
+
+
+ -ksp_type richardson
+ -pc_type lu
+ -pc_factor_mat_solver_type superlu_dist
+ -ksp_max_it 1
+
+
+
+
diff --git a/src/applications/data_sets/input/powerflow/input_european_swshunt.xml b/src/applications/data_sets/input/powerflow/input_european_swshunt.xml
new file mode 100644
index 000000000..3244c68ce
--- /dev/null
+++ b/src/applications/data_sets/input/powerflow/input_european_swshunt.xml
@@ -0,0 +1,20 @@
+
+
+
+ EuropeanOpenModel_v23.raw
+ 50
+ 1.0e-6
+ true
+ true
+ 10
+
+
+ -ksp_view
+ -ksp_type richardson
+ -pc_type lu
+ -pc_factor_mat_solver_type superlu_dist
+ -ksp_max_it 1
+
+
+
+
diff --git a/src/applications/data_sets/input/powerflow/input_kundur_pf_controls.xml b/src/applications/data_sets/input/powerflow/input_kundur_pf_controls.xml
new file mode 100644
index 000000000..8c5f67373
--- /dev/null
+++ b/src/applications/data_sets/input/powerflow/input_kundur_pf_controls.xml
@@ -0,0 +1,23 @@
+
+
+
+ kundur-twoarea_pf_controls.raw
+ 50
+ 1.0e-6
+ true
+ true
+ true
+ true
+ 10
+ json
+ pf_kundur
+
+
+ -ksp_type richardson
+ -pc_type lu
+ -pc_factor_mat_solver_type superlu_dist
+ -ksp_max_it 1
+
+
+
+
diff --git a/src/applications/data_sets/raw/IEEE14_ltc.raw b/src/applications/data_sets/raw/IEEE14_ltc.raw
new file mode 100755
index 000000000..82f4d6cca
--- /dev/null
+++ b/src/applications/data_sets/raw/IEEE14_ltc.raw
@@ -0,0 +1,62 @@
+0 100.000
+
+
+ 1, 3, 0.000, 0.000, 0.000, 0.000, 1,1.06000, 0.0000,'BUS-1 ',100.0000, 2
+ 2, 2, 21.700, 12.700, 0.000, 0.000, 1,1.04500, -4.9800,'BUS-2 ',100.0000, 2
+ 3, 2, 94.200, 19.000, 0.000, 0.000, 1,1.01000, -12.7200,'BUS-3 ',100.0000, 2
+ 4, 1, 47.800, -3.900, 0.000, 0.000, 1,1.01900, -10.3300,'BUS-4 ',100.0000, 2
+ 5, 1, 7.600, 1.600, 0.000, 0.000, 1,1.02000, -8.7800,'BUS-5 ',100.0000, 2
+ 6, 2, 11.200, 7.500, 0.000, 0.000, 1,1.07000, -14.2200,'BUS-6 ',100.0000, 2
+ 7, 1, 0.000, 0.000, 0.000, 0.000, 1,1.06200, -13.3700,'BUS-7 ',100.0000, 2
+ 8, 2, 0.000, 0.000, 0.000, 0.000, 1,1.09000, -13.3600,'BUS-8 ',100.0000, 2
+ 9, 1, 29.500, 16.600, 0.000, 19.000, 1,1.05600, -14.9400,'BUS-9 ',100.0000, 2
+ 10, 1, 9.000, 5.800, 0.000, 0.000, 1,1.05100, -15.1000,'BUS-10 ',100.0000, 2
+ 11, 1, 3.500, 1.800, 0.000, 0.000, 1,1.05700, -14.7900,'BUS-11 ',100.0000, 2
+ 12, 1, 6.100, 1.600, 0.000, 0.000, 1,1.05500, -15.0700,'BUS-12 ',100.0000, 2
+ 13, 1, 13.500, 5.800, 0.000, 0.000, 1,1.05000, -15.1600,'BUS-13 ',100.0000, 2
+ 14, 1, 14.900, 5.000, 0.000, 0.000, 1,1.03600, -16.0400,'BUS-14 ',100.0000, 2
+0
+ 1,'1 ', 232.400, -16.900, 99990.000, -9999.000,1.06000, 0, 100.000, 0.00000, 1.00000, 0.00000, 0.00000, 1.00000,1, 100.0, 0.000, 0.000
+ 2,'1 ', 40.000, 42.400, 50.000, -40.000,1.04500, 0, 100.000, 0.00000, 1.00000, 0.00000, 0.00000, 1.00000,1, 100.0, 0.000, 0.000
+ 3,'1 ', 0.000, 23.400, 40.000, 0.000,1.01000, 0, 100.000, 0.00000, 1.00000, 0.00000, 0.00000, 1.00000,1, 100.0, 0.000, 0.000
+ 6,'1 ', 0.000, 12.200, 24.000, -6.000,1.07000, 0, 100.000, 0.00000, 1.00000, 0.00000, 0.00000, 1.00000,1, 100.0, 0.000, 0.000
+ 8,'1 ', 0.000, 17.400, 24.000, -6.000,1.09000, 0, 100.000, 0.00000, 1.00000, 0.00000, 0.00000, 1.00000,1, 100.0, 0.000, 0.000
+0 / END OF GENERATOR DATA, BEGIN BRANCH DATA
+ 1, 2,'BL', 0.01938, 0.05917, 0.05280, 0.00, 0.00, 0.00,0.00000,000.000, 0.00000, 0.00000, 0.00000, 0.00000, 1
+ 1, 5,'BL', 0.05403, 0.22304, 0.04920, 0.00, 0.00, 0.00,0.00000,000.000, 0.00000, 0.00000, 0.00000, 0.00000, 1
+ 2, 3,'BL', 0.04699, 0.19797, 0.04380, 0.00, 0.00, 0.00,0.00000,000.000, 0.00000, 0.00000, 0.00000, 0.00000, 1
+ 2, 4,'BL', 0.05811, 0.17632, 0.03400, 0.00, 0.00, 0.00,0.00000,000.000, 0.00000, 0.00000, 0.00000, 0.00000, 1
+ 2, 5,'BL', 0.05695, 0.17388, 0.03460, 0.00, 0.00, 0.00,0.00000,000.000, 0.00000, 0.00000, 0.00000, 0.00000, 1
+ 3, 4,'BL', 0.06701, 0.17103, 0.01280, 0.00, 0.00, 0.00,0.00000,000.000, 0.00000, 0.00000, 0.00000, 0.00000, 1
+ 4, 5,'BL', 0.01335, 0.04211, 0.00000, 0.00, 0.00, 0.00,0.00000,000.000, 0.00000, 0.00000, 0.00000, 0.00000, 1
+ 4, 7,'BL', 0.00000, 0.20912, 0.00000, 0.00, 0.00, 0.00,0.97800, 0.000, 0.00000, 0.00000, 0.00000, 0.00000, 1
+ 4, 9,'BL', 0.00000, 0.55618, 0.00000, 0.00, 0.00, 0.00,0.96900, 0.000, 0.00000, 0.00000, 0.00000, 0.00000, 1
+ 5, 6,'BL', 0.00000, 0.25202, 0.00000, 0.00, 0.00, 0.00,0.93200, 0.000, 0.00000, 0.00000, 0.00000, 0.00000, 1
+ 6, 11,'BL', 0.09498, 0.19890, 0.00000, 0.00, 0.00, 0.00,0.00000,000.000, 0.00000, 0.00000, 0.00000, 0.00000, 1
+ 6, 12,'BL', 0.12291, 0.25581, 0.00000, 0.00, 0.00, 0.00,0.00000,000.000, 0.00000, 0.00000, 0.00000, 0.00000, 1
+ 6, 13,'BL', 0.06615, 0.13027, 0.00000, 0.00, 0.00, 0.00,0.00000,000.000, 0.00000, 0.00000, 0.00000, 0.00000, 1
+ 7, 8,'BL', 0.00000, 0.17615, 0.00000, 0.00, 0.00, 0.00,0.00000,000.000, 0.00000, 0.00000, 0.00000, 0.00000, 1
+ 7, 9,'BL', 0.00000, 0.11001, 0.00000, 0.00, 0.00, 0.00,0.00000,000.000, 0.00000, 0.00000, 0.00000, 0.00000, 1
+ 9, 10,'BL', 0.03181, 0.08450, 0.00000, 0.00, 0.00, 0.00,0.00000,000.000, 0.00000, 0.00000, 0.00000, 0.00000, 1
+ 9, 14,'BL', 0.12711, 0.27038, 0.00000, 0.00, 0.00, 0.00,0.00000,000.000, 0.00000, 0.00000, 0.00000, 0.00000, 1
+ 10, 11,'BL', 0.08205, 0.19207, 0.00000, 0.00, 0.00, 0.00,0.00000,000.000, 0.00000, 0.00000, 0.00000, 0.00000, 1
+ 12, 13,'BL', 0.22092, 0.19988, 0.00000, 0.00, 0.00, 0.00,0.00000,000.000, 0.00000, 0.00000, 0.00000, 0.00000, 1
+ 13, 14,'BL', 0.17093, 0.34802, 0.00000, 0.00, 0.00, 0.00,0.00000,000.000, 0.00000, 0.00000, 0.00000, 0.00000, 1
+0 / END OF BRANCH DATA, BEGIN TRANSFORMER ADJUSTMENT DATA
+ 4, 9,'BL', 9, 1.10000, 0.90000, 1.05000, 1.06000, 0.00625, 0
+0 / END OF TRANSFORMER ADJUSTMENT DATA, BEGIN AREA DATA
+ 1, 0, 0.0, 3.000,' '
+0 / END OF AREA DATA, BEGIN TWO-TERMINAL DC DATA
+0 / END OF TWO-TERMINAL DC DATA, BEGIN SWITCHED SHUNT DATA
+ 14, 1, 1.04000, 1.05000, 0, 0.00, 3, 5.00
+ 9, 1, 1.05000, 1.06000, 0, 0.00, 2, 10.00
+0 / END OF SWITCHED SHUNT DATA, BEGIN IMPEDANCE CORRECTION DATA
+0 / END OF IMPEDANCE CORRECTION DATA, BEGIN MULTI-TERMINAL DC DATA
+0 / END OF MULTI-TERMINAL DC DATA, BEGIN MULTI-SECTION LINE DATA
+0 / END OF MULTI-SECTION LINE DATA, BEGIN ZONE DATA
+ 2,'ZONE_2 '
+0 / END OF ZONE DATA, BEGIN INTER-AREA TRANSFER DATA
+ 2, 1,'1 ', 0.00
+0 / END OF INTER-AREA TRANSFER DATA, BEGIN OWNER DATA
+ 1,'OWNER_1 '
+0 / END OF OWNER DATA, BEGIN FACTS DEVICE DATA
diff --git a/src/applications/data_sets/raw/IEEE14_swshunt.raw b/src/applications/data_sets/raw/IEEE14_swshunt.raw
new file mode 100755
index 000000000..d978d73b3
--- /dev/null
+++ b/src/applications/data_sets/raw/IEEE14_swshunt.raw
@@ -0,0 +1,61 @@
+0 100.000
+
+
+ 1, 3, 0.000, 0.000, 0.000, 0.000, 1,1.06000, 0.0000,'BUS-1 ',100.0000, 2
+ 2, 2, 21.700, 12.700, 0.000, 0.000, 1,1.04500, -4.9800,'BUS-2 ',100.0000, 2
+ 3, 2, 94.200, 19.000, 0.000, 0.000, 1,1.01000, -12.7200,'BUS-3 ',100.0000, 2
+ 4, 1, 47.800, -3.900, 0.000, 0.000, 1,1.01900, -10.3300,'BUS-4 ',100.0000, 2
+ 5, 1, 7.600, 1.600, 0.000, 0.000, 1,1.02000, -8.7800,'BUS-5 ',100.0000, 2
+ 6, 2, 11.200, 7.500, 0.000, 0.000, 1,1.07000, -14.2200,'BUS-6 ',100.0000, 2
+ 7, 1, 0.000, 0.000, 0.000, 0.000, 1,1.06200, -13.3700,'BUS-7 ',100.0000, 2
+ 8, 2, 0.000, 0.000, 0.000, 0.000, 1,1.09000, -13.3600,'BUS-8 ',100.0000, 2
+ 9, 1, 29.500, 16.600, 0.000, 19.000, 1,1.05600, -14.9400,'BUS-9 ',100.0000, 2
+ 10, 1, 9.000, 5.800, 0.000, 0.000, 1,1.05100, -15.1000,'BUS-10 ',100.0000, 2
+ 11, 1, 3.500, 1.800, 0.000, 0.000, 1,1.05700, -14.7900,'BUS-11 ',100.0000, 2
+ 12, 1, 6.100, 1.600, 0.000, 0.000, 1,1.05500, -15.0700,'BUS-12 ',100.0000, 2
+ 13, 1, 13.500, 5.800, 0.000, 0.000, 1,1.05000, -15.1600,'BUS-13 ',100.0000, 2
+ 14, 1, 14.900, 5.000, 0.000, 0.000, 1,1.03600, -16.0400,'BUS-14 ',100.0000, 2
+0
+ 1,'1 ', 232.400, -16.900, 99990.000, -9999.000,1.06000, 0, 100.000, 0.00000, 1.00000, 0.00000, 0.00000, 1.00000,1, 100.0, 0.000, 0.000
+ 2,'1 ', 40.000, 42.400, 50.000, -40.000,1.04500, 0, 100.000, 0.00000, 1.00000, 0.00000, 0.00000, 1.00000,1, 100.0, 0.000, 0.000
+ 3,'1 ', 0.000, 23.400, 40.000, 0.000,1.01000, 0, 100.000, 0.00000, 1.00000, 0.00000, 0.00000, 1.00000,1, 100.0, 0.000, 0.000
+ 6,'1 ', 0.000, 12.200, 24.000, -6.000,1.07000, 0, 100.000, 0.00000, 1.00000, 0.00000, 0.00000, 1.00000,1, 100.0, 0.000, 0.000
+ 8,'1 ', 0.000, 17.400, 24.000, -6.000,1.09000, 0, 100.000, 0.00000, 1.00000, 0.00000, 0.00000, 1.00000,1, 100.0, 0.000, 0.000
+0 / END OF GENERATOR DATA, BEGIN BRANCH DATA
+ 1, 2,'BL', 0.01938, 0.05917, 0.05280, 0.00, 0.00, 0.00,0.00000,000.000, 0.00000, 0.00000, 0.00000, 0.00000, 1
+ 1, 5,'BL', 0.05403, 0.22304, 0.04920, 0.00, 0.00, 0.00,0.00000,000.000, 0.00000, 0.00000, 0.00000, 0.00000, 1
+ 2, 3,'BL', 0.04699, 0.19797, 0.04380, 0.00, 0.00, 0.00,0.00000,000.000, 0.00000, 0.00000, 0.00000, 0.00000, 1
+ 2, 4,'BL', 0.05811, 0.17632, 0.03400, 0.00, 0.00, 0.00,0.00000,000.000, 0.00000, 0.00000, 0.00000, 0.00000, 1
+ 2, 5,'BL', 0.05695, 0.17388, 0.03460, 0.00, 0.00, 0.00,0.00000,000.000, 0.00000, 0.00000, 0.00000, 0.00000, 1
+ 3, 4,'BL', 0.06701, 0.17103, 0.01280, 0.00, 0.00, 0.00,0.00000,000.000, 0.00000, 0.00000, 0.00000, 0.00000, 1
+ 4, 5,'BL', 0.01335, 0.04211, 0.00000, 0.00, 0.00, 0.00,0.00000,000.000, 0.00000, 0.00000, 0.00000, 0.00000, 1
+ 4, 7,'BL', 0.00000, 0.20912, 0.00000, 0.00, 0.00, 0.00,0.97800, 0.000, 0.00000, 0.00000, 0.00000, 0.00000, 1
+ 4, 9,'BL', 0.00000, 0.55618, 0.00000, 0.00, 0.00, 0.00,0.96900, 0.000, 0.00000, 0.00000, 0.00000, 0.00000, 1
+ 5, 6,'BL', 0.00000, 0.25202, 0.00000, 0.00, 0.00, 0.00,0.93200, 0.000, 0.00000, 0.00000, 0.00000, 0.00000, 1
+ 6, 11,'BL', 0.09498, 0.19890, 0.00000, 0.00, 0.00, 0.00,0.00000,000.000, 0.00000, 0.00000, 0.00000, 0.00000, 1
+ 6, 12,'BL', 0.12291, 0.25581, 0.00000, 0.00, 0.00, 0.00,0.00000,000.000, 0.00000, 0.00000, 0.00000, 0.00000, 1
+ 6, 13,'BL', 0.06615, 0.13027, 0.00000, 0.00, 0.00, 0.00,0.00000,000.000, 0.00000, 0.00000, 0.00000, 0.00000, 1
+ 7, 8,'BL', 0.00000, 0.17615, 0.00000, 0.00, 0.00, 0.00,0.00000,000.000, 0.00000, 0.00000, 0.00000, 0.00000, 1
+ 7, 9,'BL', 0.00000, 0.11001, 0.00000, 0.00, 0.00, 0.00,0.00000,000.000, 0.00000, 0.00000, 0.00000, 0.00000, 1
+ 9, 10,'BL', 0.03181, 0.08450, 0.00000, 0.00, 0.00, 0.00,0.00000,000.000, 0.00000, 0.00000, 0.00000, 0.00000, 1
+ 9, 14,'BL', 0.12711, 0.27038, 0.00000, 0.00, 0.00, 0.00,0.00000,000.000, 0.00000, 0.00000, 0.00000, 0.00000, 1
+ 10, 11,'BL', 0.08205, 0.19207, 0.00000, 0.00, 0.00, 0.00,0.00000,000.000, 0.00000, 0.00000, 0.00000, 0.00000, 1
+ 12, 13,'BL', 0.22092, 0.19988, 0.00000, 0.00, 0.00, 0.00,0.00000,000.000, 0.00000, 0.00000, 0.00000, 0.00000, 1
+ 13, 14,'BL', 0.17093, 0.34802, 0.00000, 0.00, 0.00, 0.00,0.00000,000.000, 0.00000, 0.00000, 0.00000, 0.00000, 1
+0 / END OF BRANCH DATA, BEGIN TRANSFORMER ADJUSTMENT DATA
+0 / END OF TRANSFORMER ADJUSTMENT DATA, BEGIN AREA DATA
+ 1, 0, 0.0, 3.000,' '
+0 / END OF AREA DATA, BEGIN TWO-TERMINAL DC DATA
+0 / END OF TWO-TERMINAL DC DATA, BEGIN SWITCHED SHUNT DATA
+ 14, 1, 1.04000, 1.05000, 0, 0.00, 3, 5.00
+ 9, 1, 1.05000, 1.06000, 0, 0.00, 2, 10.00
+0 / END OF SWITCHED SHUNT DATA, BEGIN IMPEDANCE CORRECTION DATA
+0 / END OF IMPEDANCE CORRECTION DATA, BEGIN MULTI-TERMINAL DC DATA
+0 / END OF MULTI-TERMINAL DC DATA, BEGIN MULTI-SECTION LINE DATA
+0 / END OF MULTI-SECTION LINE DATA, BEGIN ZONE DATA
+ 2,'ZONE_2 '
+0 / END OF ZONE DATA, BEGIN INTER-AREA TRANSFER DATA
+ 2, 1,'1 ', 0.00
+0 / END OF INTER-AREA TRANSFER DATA, BEGIN OWNER DATA
+ 1,'OWNER_1 '
+0 / END OF OWNER DATA, BEGIN FACTS DEVICE DATA
diff --git a/src/applications/data_sets/raw/kundur-twoarea_pf_controls.raw b/src/applications/data_sets/raw/kundur-twoarea_pf_controls.raw
new file mode 100644
index 000000000..e84b9c4d8
--- /dev/null
+++ b/src/applications/data_sets/raw/kundur-twoarea_pf_controls.raw
@@ -0,0 +1,78 @@
+0, 100.00, 33, 0, 1, 60.00 / PSS(R)E 33 RAW - Kundur two-area with PF controls
+KUNDUR TWO-AREA - SWITCHED SHUNT + LTC + AREA INTERCHANGE TEST
+Power flow controls test case for GridPACK
+ 1,'GEN G1 ', 20.0000,3, 1, 1, 1,1.03000, 0.0000,1.10000,0.90000,1.10000,0.90000
+ 2,'GEN G2 ', 20.0000,2, 1, 1, 1,1.01000, -9.7670,1.10000,0.90000,1.10000,0.90000
+ 3,'GEN G3 ', 20.0000,2, 2, 1, 1,1.03000, -27.0845,1.10000,0.90000,1.10000,0.90000
+ 4,'GEN G4 ', 20.0000,2, 2, 1, 1,1.01000, -37.2743,1.10000,0.90000,1.10000,0.90000
+ 5,'G1 ', 230.0000,1, 1, 1, 1,1.00645, -6.4629,1.10000,0.90000,1.10000,0.90000
+ 6,'G2 ', 230.0000,1, 1, 1, 1,0.97812, -16.5492,1.10000,0.90000,1.10000,0.90000
+ 7,'LOAD A ', 230.0000,1, 1, 1, 1,0.96100, -24.9592,1.10000,0.90000,1.10000,0.90000
+ 8,'MID POINT ', 230.0000,1, 1, 1, 1,0.94859, -38.8331,1.10000,0.90000,1.10000,0.90000
+ 9,'LOAD B ', 230.0000,1, 2, 1, 1,0.97136, -52.4342,1.10000,0.90000,1.10000,0.90000
+ 10,'G4 ', 230.0000,1, 2, 1, 1,0.98346, -44.0195,1.10000,0.90000,1.10000,0.90000
+ 11,'G3 ', 230.0000,1, 2, 1, 1,1.00826, -33.7107,1.10000,0.90000,1.10000,0.90000
+ 0 /End of Bus data, Begin Load data
+ 7,'1 ',1, 1, 1, 967.000, 100.000, 0.000, 0.000, 0.000, 0.000, 1,1,0
+ 8,'1 ',0, 1, 1, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 1,1,0
+ 9,'1 ',1, 2, 1, 1767.000, 100.000, 0.000, 0.000, 0.000, 0.000, 1,1,0
+ 0 /End of Load data, Begin Fixed shunt data
+ 7,'1 ',1, 0.000, 200.000
+ 9,'1 ',1, 0.000, 350.000
+ 0 /End of Fixed shunt data, Begin Generator data
+ 1,'1 ', 700.105, 185.067, 474.000, -200.000,1.03000, 0, 900.000, 0.00000E+0, 2.50000E-1, 0.00000E+0, 0.00000E+0,1.00000,1, 100.0, 765.000, 0.000, 1,1.0000
+ 2,'1 ', 700.000, 234.676, 474.000, -200.000,1.01000, 0, 900.000, 0.00000E+0, 2.50000E-1, 0.00000E+0, 0.00000E+0,1.00000,1, 100.0, 765.000, 0.000, 1,1.0000
+ 3,'1 ', 719.000, 175.986, 474.000, -200.000,1.03000, 0, 900.000, 0.00000E+0, 2.50000E-1, 0.00000E+0, 0.00000E+0,1.00000,1, 100.0, 765.000, 0.000, 1,1.0000
+ 4,'1 ', 700.000, 202.071, 474.000, -200.000,1.01000, 0, 900.000, 0.00000E+0, 2.50000E-1, 0.00000E+0, 0.00000E+0,1.00000,1, 100.0, 765.000, 0.000, 1,1.0000
+ 0 /End of Generator data, Begin Branch data
+ 5, 6,'1 ', 2.50000E-3, 2.50000E-2, 0.04375, 750.00, 750.00, 0.00, 0.00000, 0.00000, 0.00000, 0.00000,1,1, 25.00, 1,1.0000
+ 6, 7,'1 ', 1.00000E-3, 1.00000E-2, 0.01750, 700.00, 700.00, 0.00, 0.00000, 0.00000, 0.00000, 0.00000,1,1, 10.00, 1,1.0000
+ 7, 8,'1 ', 1.10000E-2, 1.10000E-1, 0.19250, 400.00, 400.00, 0.00, 0.00000, 0.00000, 0.00000, 0.00000,1,1, 110.00, 1,1.0000
+ 7, 8,'2 ', 1.10000E-2, 1.10000E-1, 0.19250, 400.00, 400.00, 0.00, 0.00000, 0.00000, 0.00000, 0.00000,1,1, 110.00, 1,1.0000
+ 8, 9,'1 ', 1.10000E-2, 1.10000E-1, 0.19250, 400.00, 400.00, 0.00, 0.00000, 0.00000, 0.00000, 0.00000,1,1, 110.00, 1,1.0000
+ 8, 9,'2 ', 1.10000E-2, 1.10000E-1, 0.19250, 400.00, 400.00, 0.00, 0.00000, 0.00000, 0.00000, 0.00000,1,1, 110.00, 1,1.0000
+ 9, 10,'2 ', 1.00000E-3, 1.00000E-2, 0.01750, 700.00, 700.00, 0.00, 0.00000, 0.00000, 0.00000, 0.00000,1,1, 10.00, 1,1.0000
+ 10, 11,'1 ', 2.50000E-3, 2.50000E-2, 0.04375, 750.00, 750.00, 0.00, 0.00000, 0.00000, 0.00000, 0.00000,1,1, 25.00, 1,1.0000
+ 0 /End of Branch data, Begin Transformer data
+@! I,J,K,CKT,CW,CZ,CM,MAG1,MAG2,NMETR,NAME,STAT,O1,F1,O2,F2,O3,F3,O4,F4,VECGRP
+@! R1-2,X1-2,SBASE1-2
+@! WINDV1,NOMV1,ANG1,RATA1,RATB1,RATC1,COD1,CONT1,RMA1,RMI1,VMA1,VMI1,NTP1,TAB1,CR1,CX1,CNXA1
+@! WINDV2,NOMV2
+ 5, 1, 0,'1 ',1,2,1, 0.00000E+0, 0.00000E+0,1,'G1 STEP UP ',1, 1,1.0000, 0,1.0000, 0,1.0000, 0,1.0000,' '
+ 0.00000E+0, 1.50000E-1, 900.00
+1.00000, 230.000, 0.000, 900.00, 0.00, 0.00, 0, 0, 1.10000, 0.90000, 1.10000, 0.90000, 33, 0, 0.00000, 0.00000, 0.000
+1.00000, 20.000
+ 6, 2, 0,'1 ',1,2,1, 0.00000E+0, 0.00000E+0,1,'G2 STEP UP ',1, 1,1.0000, 0,1.0000, 0,1.0000, 0,1.0000,' '
+ 0.00000E+0, 1.50000E-1, 900.00
+1.00000, 230.000, 0.000, 900.00, 0.00, 0.00, 1, 6, 1.10000, 0.90000, 0.99500, 0.98500, 33, 0, 0.00000, 0.00000, 0.000
+1.00000, 20.000
+ 11, 3, 0,'1 ',1,2,1, 0.00000E+0, 0.00000E+0,1,'G3 STEP UP ',1, 1,1.0000, 0,1.0000, 0,1.0000, 0,1.0000,' '
+ 0.00000E+0, 1.50000E-1, 900.00
+1.00000, 230.000, 0.000, 900.00, 0.00, 0.00, 0, 0, 1.10000, 0.90000, 1.10000, 0.90000, 33, 0, 0.00000, 0.00000, 0.000
+1.00000, 20.000
+ 10, 4, 0,'1 ',1,2,1, 0.00000E+0, 0.00000E+0,1,'G4 STEP UP ',1, 1,1.0000, 0,1.0000, 0,1.0000, 0,1.0000,' '
+ 0.00000E+0, 1.50000E-1, 900.00
+1.00000, 230.000, 0.000, 900.00, 0.00, 0.00, 0, 0, 1.10000, 0.90000, 1.10000, 0.90000, 33, 0, 0.00000, 0.00000, 0.000
+1.00000, 20.000
+ 0 /End of Transformer data, Begin Area interchange data
+ 1, 1, 400.000, 5.000,'LEFT '
+ 2, 3, -400.000, 5.000,'RIGHT '
+ 0 /End of Area interchange data, Begin Two-terminal dc line data
+ 0 /End of Two-terminal dc line data, Begin VSC dc line data
+ 0 /End of VSC dc line data, Begin Impedance correction table data
+ 0 /End of Impedance correction table data, Begin Multi-terminal dc line data
+ 0 /End of Multi-terminal dc line data, Begin Multi-section line data
+ 0 /End of Multi-section line data, Begin Zone data
+ 1,'1 '
+ 0 /End of Zone data, Begin Inter-area transfer data
+ 0 /End of Inter-area transfer data, Begin Owner data
+ 1,'1 '
+ 0 /End of Owner data, Begin FACTS device data
+ 0 /End of FACTS device data, Begin Switched shunt data
+@! I,MODSW,ADJM,ST, VSWHI, VSWLO, SWREG, RMPCT, 'RMIDNT', BINIT, N1, B1
+ 8,1,0,1, 0.96000, 0.95000, 0, 100.0,' ', 0.00, 5, 50.00
+ 9,1,0,1, 0.98000, 0.97000, 0, 100.0,' ', 0.00, 4, 100.00
+ 0 /End of Switched shunt data, Begin GNE device data
+ 0 /End of GNE device data, Begin Induction machine data
+ 0 /End of Induction machine data
+Q
diff --git a/src/applications/modules/powerflow/pf_app_module.cpp b/src/applications/modules/powerflow/pf_app_module.cpp
index 3cf352dcd..bd33584da 100644
--- a/src/applications/modules/powerflow/pf_app_module.cpp
+++ b/src/applications/modules/powerflow/pf_app_module.cpp
@@ -13,6 +13,22 @@
* - Added setInitStartMode for power flow initialization (warm/flat start)
* @date 2026-02-02
*
+ * @updated Yousu Chen
+ * - Added switched shunt control configuration and controller loop solve architecture
+ * @date 2026-02-24
+ *
+ * @updated Yousu Chen
+ * - Added LTC (load tap changer) control configuration
+ * - Added area interchange MW control
+ * - Auto-detect PSS/E version from RAW file header
+ * @date 2026-03-28
+ *
+ * @updated Yousu Chen
+ * - Auto-detect: skip @! comment lines in v35/v36 RAW headers
+ * - IREG PV swap
+ * - 3-winding transformer star bus filtering for bus and branch output
+ * @date 2026-04-05
+ *
* @brief
*
*
@@ -37,6 +53,7 @@
#include "pf_helper.hpp"
#include "gridpack/utilities/string_utils.hpp"
#include
+#include
#define USE_REAL_VALUES
@@ -140,11 +157,65 @@ void gridpack::powerflow::PFAppModule::readNetwork(
printf("No network configuration file specified\n");
return;
}
+
+ // Auto-detect PSS/E version from RAW file header when using networkConfiguration
+ // PSS/E v30+ files have: IC, SBASE, REV, ... on line 1 (comma-delimited).
+ // PSS/E v23 files have: IC SBASE (space-delimited, no version field).
+ if (filetype == PTI23) {
+ gridpack::utility::StringUtils util;
+ std::string fname = util.trimQuotes(filename);
+ util.trim(fname);
+ std::ifstream testFile(fname.c_str());
+ if (testFile.good()) {
+ std::string line1;
+ // Skip comment lines (@! prefix) used in v35/v36 RAW files
+ do {
+ std::getline(testFile, line1);
+ } while (testFile.good() && line1.size() > 1 && line1[0] == '@');
+ testFile.close();
+ // Check if line contains commas (v30+ format)
+ if (line1.find(',') != std::string::npos) {
+ // Extract 3rd comma-delimited field (version number)
+ // Line format: IC, SBASE, REV, ...
+ size_t pos1 = line1.find(',');
+ size_t pos2 = (pos1 != std::string::npos) ? line1.find(',', pos1+1) : std::string::npos;
+ if (pos2 != std::string::npos) {
+ size_t pos3 = line1.find(',', pos2+1);
+ std::string verStr = line1.substr(pos2+1,
+ (pos3 != std::string::npos) ? pos3-pos2-1 : std::string::npos);
+ util.trim(verStr);
+ int ver = atoi(verStr.c_str());
+ if (ver == 33) {
+ filetype = PTI33;
+ } else if (ver == 34) {
+ filetype = PTI34;
+ } else if (ver == 35) {
+ filetype = PTI35;
+ } else if (ver >= 36) {
+ filetype = PTI36;
+ } else if (ver >= 30) {
+ filetype = PTI33; // Fallback: treat v30-v32 as v33
+ }
+ if (filetype != PTI23 && !p_no_print) {
+ char ioBuf2[128];
+ sprintf(ioBuf2, "Auto-detected PSS/E v%d format from RAW file header\n", ver);
+ printf("%s", ioBuf2);
+ }
+ }
+ }
+ }
+ }
// Convergence and iteration parameters
p_tolerance = cursor->get("tolerance",1.0e-6);
p_qlim = cursor->get("qlim",true);
p_max_iteration = cursor->get("maxIteration",50);
p_max_qlim_iterations = cursor->get("maxQlimIterations",3);
+ p_switchedShunt = cursor->get("SwitchedShunt",false);
+ p_ltc = cursor->get("LTC",false);
+ p_areaInterchange = cursor->get("AreaInterchange",false);
+ p_max_controller_iterations = cursor->get("maxControllerIterations",10);
+ p_qlim_deadband = cursor->get("qlimDeadband",0.1);
+ p_dampingFactor = cursor->get("dampingFactor",1.0);
ComplexType tol;
// Phase shift sign
double phaseShiftSign = cursor->get("phaseShiftSign",1.0);
@@ -318,6 +389,12 @@ void gridpack::powerflow::PFAppModule::initialize()
timer->start(t_updt);
p_network->initBusUpdate();
timer->stop(t_updt);
+
+ // Set up IREG PV bus swap (must be after setExchange and initBusUpdate)
+ p_factory->setupIREGPointers();
+ // Sync PV status changes to ghost buses across processes
+ p_network->updateBuses();
+
timer->stop(t_total);
}
@@ -347,23 +424,71 @@ bool gridpack::powerflow::PFAppModule::solve()
int t_total = timer->createCategory("Powerflow: Total Application");
timer->start(t_total);
p_factory->clearViolations();
- gridpack::ComplexType tol = 2.0*p_tolerance;
- int iter = 0;
- bool repeat = true;
- int int_repeat = 0;
- while (repeat) {
+ char ioBuf[128];
+
+ // Determine max controller iterations based on configuration
+ int max_ctrl_iter = p_qlim ? p_max_qlim_iterations : 1;
+ if (p_switchedShunt || p_ltc) {
+ max_ctrl_iter = p_max_controller_iterations;
+ }
+ max_ctrl_iter = std::max(max_ctrl_iter, 10);
+
+ // Load area interchange data if enabled
+ int numAreas = 0;
+ std::vector area_num, area_isw;
+ std::vector area_pdes, area_ptol;
+ if (p_areaInterchange) {
+ boost::shared_ptr netdata =
+ p_network->getNetworkData();
+ netdata->getValue(AREA_TOTAL, &numAreas);
+ for (int ia = 0; ia < numAreas; ia++) {
+ int anum = 0, aisw = 0;
+ double pdes = 0.0, ptol = 10.0;
+ netdata->getValue(AREAINTG_NUMBER, &anum, ia);
+ netdata->getValue(AREAINTG_ISW, &aisw, ia);
+ netdata->getValue(AREAINTG_PDES, &pdes, ia);
+ netdata->getValue(AREAINTG_PTOL, &ptol, ia);
+ area_num.push_back(anum);
+ area_isw.push_back(aisw);
+ area_pdes.push_back(pdes);
+ area_ptol.push_back(ptol);
+ }
+ }
+
+ // =========================================================================
+ // Area Interchange Loop (outer) — adjusts area slack bus generation
+ // =========================================================================
+ int ai_iter = 0;
+ int max_ai_iter = p_areaInterchange ? 10 : 1;
+ bool ai_repeat = true;
+
+ while (ai_repeat) {
+ ai_repeat = false;
+ ai_iter++;
+
+ // =========================================================================
+ // Controller Loop — handles qlim, switched shunts, IREG, LTC
+ // =========================================================================
+ int ctrl_iter = 0;
+ bool ctrl_repeat = true;
+
+ while (ctrl_repeat) {
ret = true; // Reset ret - if this iteration converges, we should return true
- iter = 0;
- tol = 2.0*p_tolerance;
- int_repeat ++;
+ ctrl_repeat = false;
+ ctrl_iter++;
bool qlim_handled_early = false; // Track if Q limits were handled during stagnation
- char ioBuf[128];
+
if (!p_no_print) {
- sprintf (ioBuf," repeat time = %d \n", int_repeat);
+ sprintf(ioBuf," Controller iteration = %d \n", ctrl_iter);
p_busIO->header(ioBuf);
}
+ // =====================================================================
+ // Inner NR Loop — solve power flow equations
+ // =====================================================================
+
// set YBus components so that you can create Y matrix
+ // (picks up modified p_shunt_bs from switched shunt adjustments)
int t_fact = timer->createCategory("Powerflow: Factory Operations");
timer->start(t_fact);
p_factory->setYBus();
@@ -371,32 +496,18 @@ bool gridpack::powerflow::PFAppModule::solve()
int t_cmap = timer->createCategory("Powerflow: Create Mappers");
timer->start(t_cmap);
- p_factory->setMode(YBus);
-
-#if 0
- gridpack::mapper::FullMatrixMap mMap(p_network);
-#endif
+ p_factory->setMode(YBus);
timer->stop(t_cmap);
int t_mmap = timer->createCategory("Powerflow: Map to Matrix");
- timer->start(t_mmap);
-#if 0
- gridpack::mapper::FullMatrixMap mMap(p_network);
- boost::shared_ptr Y = mMap.mapToMatrix();
- p_busIO->header("\nY-matrix values\n");
- // Y->print();
- Y->save("Ybus.m");
-#endif
- timer->stop(t_mmap);
// make Sbus components to create S vector
timer->start(t_fact);
p_factory->setSBus();
timer->stop(t_fact);
- // p_busIO->header("\nIteration 0\n");
// Set PQ
timer->start(t_cmap);
- p_factory->setMode(RHS);
+ p_factory->setMode(RHS);
gridpack::mapper::BusVectorMap vMap(p_network);
timer->stop(t_cmap);
int t_vmap = timer->createCategory("Powerflow: Map to Vector");
@@ -408,9 +519,9 @@ bool gridpack::powerflow::PFAppModule::solve()
boost::shared_ptr PQ = vMap.mapToVector();
#endif
timer->stop(t_vmap);
- gridpack::ComplexType tol_org = PQ->normInfinity();
+ gridpack::ComplexType tol_org = PQ->normInfinity();
if (!p_no_print) {
- sprintf (ioBuf,"\n----------test Iteration 0, before PF solve, Tol: %12.6e \n", real(tol_org));
+ sprintf(ioBuf,"\n----------test Iteration 0, before PF solve, Tol: %12.6e \n", real(tol_org));
p_busIO->header(ioBuf);
}
@@ -419,7 +530,6 @@ bool gridpack::powerflow::PFAppModule::solve()
p_convergence.converged = false;
p_convergence.iterations = 0;
- // PQ->print();
timer->start(t_cmap);
p_factory->setMode(Jacobian);
gridpack::mapper::FullMatrixMap jMap(p_network);
@@ -432,8 +542,6 @@ bool gridpack::powerflow::PFAppModule::solve()
boost::shared_ptr J = jMap.mapToMatrix();
#endif
timer->stop(t_mmap);
- // p_busIO->header("\nJacobian values\n");
- // J->print();
// Create X vector by cloning PQ
#ifdef USE_REAL_VALUES
@@ -455,16 +563,10 @@ bool gridpack::powerflow::PFAppModule::solve()
solver.configure(cursor);
timer->stop(t_csolv);
- // First iteration
- X->zero(); //might not need to do this
- //p_busIO->header("\nCalling solver\n");
+ // First NR iteration
+ X->zero();
int t_lsolv = timer->createCategory("Powerflow: Solve Linear Equation");
timer->start(t_lsolv);
- // char dbgfile[32];
- // sprintf(dbgfile,"j0.bin");
- // J->saveBinary(dbgfile);
- // sprintf(dbgfile,"pq0.bin");
- // PQ->saveBinary(dbgfile);
try {
solver.solve(*PQ, *X);
} catch (const gridpack::Exception e) {
@@ -478,14 +580,15 @@ bool gridpack::powerflow::PFAppModule::solve()
}
timer->stop(t_lsolv);
timer->stop(t_total);
-
return false;
}
+ if (p_dampingFactor < 1.0) X->scale(p_dampingFactor);
timer->stop(t_lsolv);
- tol = PQ->normInfinity();
- // Create timer for map to bus
+
+ gridpack::ComplexType tol = PQ->normInfinity();
int t_bmap = timer->createCategory("Powerflow: Map to Bus");
int t_updt = timer->createCategory("Powerflow: Bus Update");
+ int iter = 0;
if (!p_no_print) {
sprintf(ioBuf,"\nIteration %d Tol: %12.6e\n",iter+1,real(tol));
p_busIO->header(ioBuf);
@@ -494,22 +597,18 @@ bool gridpack::powerflow::PFAppModule::solve()
// Variables for early stagnation detection
gridpack::ComplexType tol_prev = tol;
int stagnant_count = 0;
- const int STAGNANT_THRESHOLD = 5; // Check Q limits after 5 stagnant iterations
- const double STAGNANT_TOL = 1.0e-10; // Tolerance for detecting stagnation
+ const int STAGNANT_THRESHOLD = 5;
+ const double STAGNANT_TOL = 1.0e-10;
while (real(tol) > p_tolerance && iter < p_max_iteration) {
// Push current values in X vector back into network components
- // Need to implement setValues method in PFBus class in order for this to
- // work
timer->start(t_bmap);
p_factory->setMode(RHS);
vMap.mapToBus(X);
timer->stop(t_bmap);
- // Exchange data between ghost buses (I don't think we need to exchange data
- // between branches)
+ // Exchange data between ghost buses
timer->start(t_updt);
- // p_factory->checkQlimViolations();
p_network->updateBuses();
timer->stop(t_updt);
@@ -520,8 +619,6 @@ bool gridpack::powerflow::PFAppModule::solve()
#else
vMap.mapToVector(PQ);
#endif
- // p_busIO->header("\nnew PQ vector at iter %d\n",iter);
- // PQ->print();
timer->stop(t_vmap);
timer->start(t_mmap);
p_factory->setMode(Jacobian);
@@ -532,13 +629,9 @@ bool gridpack::powerflow::PFAppModule::solve()
#endif
timer->stop(t_mmap);
- // Create linear solver
+ // Solve linear system
timer->start(t_lsolv);
- X->zero(); //might not need to do this
- // sprintf(dbgfile,"j%d.bin",iter+1);
- // J->saveBinary(dbgfile);
- // sprintf(dbgfile,"pq%d.bin",iter+1);
- // PQ->saveBinary(dbgfile);
+ X->zero();
try {
solver.solve(*PQ, *X);
} catch (const gridpack::Exception e) {
@@ -554,6 +647,7 @@ bool gridpack::powerflow::PFAppModule::solve()
timer->stop(t_total);
return false;
}
+ if (p_dampingFactor < 1.0) X->scale(p_dampingFactor);
timer->stop(t_lsolv);
tol = PQ->normInfinity();
@@ -631,8 +725,8 @@ bool gridpack::powerflow::PFAppModule::solve()
if (p_qlim && fabs(real(tol) - real(tol_prev)) < STAGNANT_TOL) {
stagnant_count++;
if (stagnant_count >= STAGNANT_THRESHOLD) {
+ p_factory->setQlimDeadband(p_qlim_deadband);
if (!p_factory->checkQlimViolations()) {
- // Q limit violations found - break to restart with PV->PQ changes
if (!p_no_print) {
sprintf(ioBuf,"Stagnation detected at iter %d, Qlim violations found\n", iter);
p_busIO->header(ioBuf);
@@ -640,19 +734,18 @@ bool gridpack::powerflow::PFAppModule::solve()
qlim_handled_early = true;
break;
} else {
- // No Q limit violations but still stagnant - likely numerical issue
- stagnant_count = 0; // Reset and continue trying
+ stagnant_count = 0;
}
}
} else {
- stagnant_count = 0; // Reset if tolerance is changing
+ stagnant_count = 0;
}
tol_prev = tol;
- if (real(tol)> 100.0*real(tol_org)){
+ if (real(tol) > 100.0*real(tol_org)) {
ret = false;
if (!p_no_print) {
- sprintf (ioBuf,"\n-------------current iteration tol bigger than 100 times of original tol, power flow diverge\n");
+ sprintf(ioBuf,"\n-------------current iteration tol bigger than 100 times of original tol, power flow diverge\n");
p_busIO->header(ioBuf);
}
break;
@@ -687,83 +780,155 @@ bool gridpack::powerflow::PFAppModule::solve()
}
}
- // IREG: Adjust local bus voltage for remote bus voltage regulation.
+ // =====================================================================
+ // Controller checks (after NR converges or exits)
+ // =====================================================================
+
+ // Skip controller adjustments if inner NR failed — voltages are invalid
+ // and adjustments based on bad voltages will make the next solve worse.
+ if (!ret) {
+ ctrl_repeat = false;
+ } else {
+
+ // 1. IREG: Adjust local bus voltage for remote bus voltage regulation.
// Must be done before Q-limit check since voltage adjustments affect Q.
bool ireg_ok = p_factory->adjustRemoteRegulation();
if (!ireg_ok && !p_no_print) {
- sprintf(ioBuf, "IREG: remote voltage regulation adjustments applied (outer iter %d)\n",
- int_repeat);
+ sprintf(ioBuf, "IREG: remote voltage regulation adjustments applied (ctrl iter %d)\n",
+ ctrl_iter);
p_busIO->header(ioBuf);
}
+ if (!ireg_ok && ctrl_iter < max_ctrl_iter) {
+ ctrl_repeat = true;
+ }
- // Use larger iteration limit when IREG is active (needs more outer iterations)
- int max_outer = std::max(p_max_qlim_iterations, 10);
-
- if (!p_qlim) {
- // No Q-limit enforcement; only IREG can cause repeat
- if (ireg_ok || int_repeat >= max_outer) {
- repeat = false;
- if (!ireg_ok && !p_no_print) {
- sprintf(ioBuf, "IREG: max outer iterations (%d) reached, accepting solution\n",
- max_outer);
+ // 2. Q-limit check (existing PV->PQ conversion)
+ if (p_qlim && !qlim_handled_early) {
+ p_factory->setQlimDeadband(p_qlim_deadband);
+ if (!p_factory->checkQlimViolations()) {
+ // Violations found, PV->PQ changes made — need to re-solve
+ if (!p_no_print) {
+ sprintf(ioBuf,"Qlim violations found at controller iter %d\n", ctrl_iter);
p_busIO->header(ioBuf);
}
+ if (ctrl_iter < max_ctrl_iter) {
+ ctrl_repeat = true;
+ }
}
- // else repeat stays true for IREG convergence
} else if (qlim_handled_early) {
- // Q limits were already handled during early stagnation detection
- // Check if we've reached max outer iterations
- if (int_repeat >= max_outer) {
+ // Q limits were already handled during stagnation detection
+ if (ctrl_iter < max_ctrl_iter) {
+ ctrl_repeat = true;
+ }
+ }
+
+ // 3. Switched shunt check (NEW)
+ if (p_switchedShunt) {
+ if (!p_factory->checkSwitchedShuntViolations()) {
if (!p_no_print) {
- sprintf(ioBuf,"Max outer iterations (%d) reached, accepting current solution\n",
- max_outer);
+ sprintf(ioBuf,"Switched shunt adjustments made at controller iter %d\n", ctrl_iter);
p_busIO->header(ioBuf);
}
- repeat = false;
+ if (ctrl_iter < max_ctrl_iter) {
+ ctrl_repeat = true;
+ }
}
- // Otherwise repeat stays true to restart with new PV/PQ configuration
- } else {
- bool qlim_ok = p_factory->checkQlimViolations();
- if (qlim_ok && ireg_ok) {
- repeat = false;
- } else {
- // Check if we've reached max outer iterations
- if (int_repeat >= max_outer) {
- if (!p_no_print) {
- sprintf(ioBuf,"Max outer iterations (%d) reached, accepting current solution\n",
- max_outer);
- p_busIO->header(ioBuf);
- }
- repeat = false;
- } else {
- if (!qlim_ok && !p_no_print) {
- sprintf (ioBuf,"There are Qlim violations at iter =%d\n", iter);
- p_busIO->header(ioBuf);
- }
+ }
+
+ // 4. LTC tap adjustment check
+ if (p_ltc) {
+ if (!p_factory->checkLTCViolations()) {
+ if (!p_no_print) {
+ sprintf(ioBuf,"LTC tap adjustments made at controller iter %d\n", ctrl_iter);
+ p_busIO->header(ioBuf);
+ }
+ if (ctrl_iter < max_ctrl_iter) {
+ ctrl_repeat = true;
}
}
}
- // Push final result back onto buses, but ONLY if no Q limit violations.
- // If there are Q limit violations, p_isPV changed for some buses after vMap
- // was created. The vMap indexing is based on old dimensions, but setValues()
- // uses the current p_isPV. This mismatch causes incorrect memory access.
- // When repeat=true, a new iteration will start with a fresh vMap anyway.
- if (!repeat) {
+
+ } // end if (ret) — controller checks only run when NR converged
+
+ // Check if max controller iterations reached
+ if (ctrl_repeat && ctrl_iter >= max_ctrl_iter) {
+ if (!p_no_print) {
+ sprintf(ioBuf,"Max controller iterations (%d) reached, accepting current solution\n", max_ctrl_iter);
+ p_busIO->header(ioBuf);
+ }
+ ctrl_repeat = false;
+ }
+
+ // Push final result back onto buses, but ONLY if no controller changes.
+ // If qlim changed PV->PQ, vMap dimensions are stale — a new iteration
+ // will start with a fresh vMap. Shunt changes are safe (same dimensions)
+ // but we re-solve anyway for consistency.
+ if (!ctrl_repeat) {
timer->start(t_bmap);
p_factory->setMode(RHS);
vMap.mapToBus(X);
timer->stop(t_bmap);
- // Make sure that ghost buses have up-to-date values before printing out
- // results
timer->start(t_updt);
p_network->updateBuses();
timer->stop(t_updt);
}
+ } // end controller loop
+
+ // =========================================================================
+ // Area Interchange check (after controller loop converges)
+ // =========================================================================
+ if (p_areaInterchange && ret && numAreas > 0) {
+ std::map areaExport;
+ p_factory->computeAreaExport(areaExport);
+
+ bool ai_ok = true;
+ for (int ia = 0; ia < numAreas; ia++) {
+ double actual = areaExport[area_num[ia]];
+ double error = actual - area_pdes[ia];
+ if (fabs(error) > area_ptol[ia]) {
+ ai_ok = false;
+ if (!p_no_print) {
+ sprintf(ioBuf, "Area %d: export=%.2f MW, desired=%.2f MW, error=%.2f MW (tol=%.2f)\n",
+ area_num[ia], actual, area_pdes[ia], error, area_ptol[ia]);
+ p_busIO->header(ioBuf);
+ }
+ // Adjust generation at area slack bus (ISW)
+ // Decrease Pg by the export error to bring actual export toward PDES
+ int isw = area_isw[ia];
+ std::vector lids = p_network->getLocalBusIndices(isw);
+ if (lids.size() > 0) {
+ gridpack::powerflow::PFBus *sbus =
+ dynamic_cast(
+ p_network->getBus(lids[0]).get());
+ // Adjust the first generator's Pg on the area slack bus
+ std::vector gens = sbus->getGenerators();
+ if (gens.size() > 0) {
+ double pg_old = sbus->getGenPOutput(gens[0]);
+ double pg_new = pg_old - error;
+ sbus->setGeneratorRealPower(gens[0], pg_new,
+ p_network->getBusData(lids[0]).get());
+ if (!p_no_print) {
+ sprintf(ioBuf, " Adjusting bus %d gen %s: Pg %.2f -> %.2f MW\n",
+ isw, gens[0].c_str(), pg_old, pg_new);
+ p_busIO->header(ioBuf);
+ }
+ }
+ }
+ }
+ }
+ if (!ai_ok && ai_iter < max_ai_iter) {
+ ai_repeat = true;
+ ctrl_iter = 0; // Reset controller iteration count for next pass
+ } else if (!ai_ok && !p_no_print) {
+ sprintf(ioBuf, "Max area interchange iterations (%d) reached\n", max_ai_iter);
+ p_busIO->header(ioBuf);
+ }
}
+
+ } // end area interchange loop
timer->stop(t_total);
return ret;
-
}
/**
* Execute the iterative solve portion of the application using a library
@@ -885,6 +1050,9 @@ gridpack::powerflow::PFAppModule::collectResults()
dynamic_cast(
p_network->getBus(i).get());
+ // Skip synthetic star buses from 3-winding transformers
+ if (bus->isStarBus()) continue;
+
gridpack::utility::BusResult br;
br.busId = bus->getOriginalIndex();
br.type = bus->getBusType();
@@ -953,6 +1121,15 @@ gridpack::powerflow::PFAppModule::collectResults()
dynamic_cast(
p_network->getBranch(i).get());
+ // Skip branches connected to star buses (3-winding transformer internals)
+ int idx1, idx2;
+ p_network->getBranchEndpoints(i, &idx1, &idx2);
+ gridpack::powerflow::PFBus *bbus1 =
+ dynamic_cast(p_network->getBus(idx1).get());
+ gridpack::powerflow::PFBus *bbus2 =
+ dynamic_cast(p_network->getBus(idx2).get());
+ if (bbus1->isStarBus() || bbus2->isStarBus()) continue;
+
std::vector cktIds = branch->getLineIDs();
int bus1Id, bus2Id;
p_network->getOriginalBranchEndpoints(i, &bus1Id, &bus2Id);
@@ -1356,6 +1533,8 @@ bool gridpack::powerflow::PFAppModule::unSetContingency(
p_factory->clearLoneBus();
p_factory->clearIslands();
p_factory->restoreSlack(); // Restore original slack bus if it was transferred
+ p_factory->clearSwitchedShunts(); // Reset switched shunts to BINIT state
+ p_factory->clearLTCControls(); // Reset LTC taps to initial values
bool ret = true;
if (event.p_type == Generator) {
int ngen = event.p_busid.size();
@@ -1489,6 +1668,40 @@ void gridpack::powerflow::PFAppModule::clearQlimViolations()
p_factory->clearQlimViolations();
}
+/**
+ * Check switched shunt violations and adjust B values
+ * @return true if no violations found
+ */
+bool gridpack::powerflow::PFAppModule::checkSwitchedShuntViolations()
+{
+ return p_factory->checkSwitchedShuntViolations();
+}
+
+/**
+ * Clear switched shunt adjustments and reset to BINIT state
+ */
+void gridpack::powerflow::PFAppModule::clearSwitchedShunts()
+{
+ p_factory->clearSwitchedShunts();
+}
+
+/**
+ * Check LTC violations and adjust transformer tap ratios
+ * @return true if no violations found
+ */
+bool gridpack::powerflow::PFAppModule::checkLTCViolations()
+{
+ return p_factory->checkLTCViolations();
+}
+
+/**
+ * Clear LTC adjustments and reset taps to initial values
+ */
+void gridpack::powerflow::PFAppModule::clearLTCControls()
+{
+ p_factory->clearLTCControls();
+}
+
/**
* Reset voltages to values in network configuration file
*/
diff --git a/src/applications/modules/powerflow/pf_app_module.hpp b/src/applications/modules/powerflow/pf_app_module.hpp
index 6f068482a..1582dbd92 100644
--- a/src/applications/modules/powerflow/pf_app_module.hpp
+++ b/src/applications/modules/powerflow/pf_app_module.hpp
@@ -13,6 +13,15 @@
* - Added setInitStartMode for power flow initialization (warm/flat start)
* @date 2026-02-02
*
+ * @updated Yousu Chen
+ * - Added switched shunt control configuration and 3-loop solve architecture
+ * @date 2026-02-24
+ *
+ * @updated Yousu Chen
+ * - Added LTC (load tap changer) control configuration
+ * - Added area interchange MW control
+ * @date 2026-03-28
+ *
* @brief
*
*
@@ -322,6 +331,28 @@ class PFAppModule
*/
void clearQlimViolations();
+ /**
+ * Check switched shunt violations and adjust B values
+ * @return true if no violations found
+ */
+ bool checkSwitchedShuntViolations();
+
+ /**
+ * Clear switched shunt adjustments and reset to BINIT state
+ */
+ void clearSwitchedShunts();
+
+ /**
+ * Check LTC violations and adjust transformer tap ratios
+ * @return true if no violations found
+ */
+ bool checkLTCViolations();
+
+ /**
+ * Clear LTC adjustments and reset taps to initial values
+ */
+ void clearLTCControls();
+
/**
* Reset voltages to values in network configuration file
*/
@@ -929,12 +960,30 @@ class PFAppModule
// convergence tolerance
double p_tolerance;
+ // Newton step damping factor (0 < omega <= 1.0; default 1.0 = no damping)
+ double p_dampingFactor;
+
// qlim enforce flag (true=enabled, false=disabled)
bool p_qlim;
// maximum number of Q-limit iterations
int p_max_qlim_iterations;
+ // switched shunt control enable flag
+ bool p_switchedShunt;
+
+ // LTC (load tap changer) control enable flag
+ bool p_ltc;
+
+ // area interchange control enable flag
+ bool p_areaInterchange;
+
+ // maximum number of controller loop iterations
+ int p_max_controller_iterations;
+
+ // Q deadband (Mvar) for PV->PQ switch
+ double p_qlim_deadband;
+
// pointer to bus IO module
boost::shared_ptr > p_busIO;
diff --git a/src/applications/modules/powerflow/pf_factory_module.cpp b/src/applications/modules/powerflow/pf_factory_module.cpp
index 3a4b3ddf3..d2597ec61 100644
--- a/src/applications/modules/powerflow/pf_factory_module.cpp
+++ b/src/applications/modules/powerflow/pf_factory_module.cpp
@@ -13,6 +13,22 @@
* - Added setInitStartMode for power flow initialization (warm/flat start)
* @date 2026-02-02
*
+ * @updated Yousu Chen
+ * - Added checkSwitchedShuntViolations() and clearSwitchedShunts()
+ * @date 2026-02-24
+ *
+ * @updated Yousu Chen
+ * - Added checkLTCViolations() and clearLTCControls()
+ * - Added computeAreaExport() for area interchange control
+ * @date 2026-03-28
+ *
+ * @updated Yousu Chen
+ * - Added setupIREGPointers() for IREG PV bus swap with MPI Allgatherv
+ * for multi-process consistency (active + ghost bus copies)
+ * - Slack bus remote IREG auto-correction with warning
+ * - Skip IREG outer loop for buses handled by PV swap
+ * @date 2026-04-04
+ *
* @brief
*
*
@@ -44,6 +60,7 @@ PFFactoryModule::PFFactoryModule(PFFactoryModule::NetworkPtr network)
p_rateB = false;
p_islandCount = 0;
p_hasLoneBus = false;
+ p_qlim_deadband = 0.1;
p_originalSlackBusIdx = -1;
p_currentSlackBusIdx = -1;
p_slackTransferred = false;
@@ -963,7 +980,9 @@ bool gridpack::powerflow::PFFactoryModule::checkQlimViolations()
gridpack::powerflow::PFBus *bus =
dynamic_cast
(p_network->getBus(i).get());
- if (bus->chkQlim()) bus_ok = false;
+ if (bus->chkQlim(p_qlim_deadband)) {
+ bus_ok = false;
+ }
}
}
p_network->updateBuses();
@@ -1029,9 +1048,25 @@ bool gridpack::powerflow::PFFactoryModule::adjustRemoteRegulation(double tol)
p_network->getBus(i).get());
if (!bus->isPV()) continue;
+ // Skip IREG PV buses — voltage regulation handled in augmented Jacobian
+ if (bus->isIREG_PV()) continue;
+
int orig_idx = bus->getOriginalIndex();
int ngen = bus->getNumGenerators();
+ // If any online generator has local regulation (IREG=0), local voltage
+ // control takes precedence. Do not override the bus terminal voltage via
+ // the remote-regulation outer loop; the remote-reg generator's Q
+ // contribution is handled implicitly by the NR Jacobian.
+ bool has_local_reg = false;
+ for (int j = 0; j < ngen; j++) {
+ if (bus->getGenStatusByIdx(j) == 1 && bus->getIREG(j) == 0) {
+ has_local_reg = true;
+ break;
+ }
+ }
+ if (has_local_reg) continue;
+
// Find first online generator with remote regulation
int ireg_bus = 0;
double vs_target = 0.0;
@@ -1056,6 +1091,13 @@ bool gridpack::powerflow::PFFactoryModule::adjustRemoteRegulation(double tol)
double dv = vs_target - v_remote;
if (fabs(dv) > tol) {
+ // Apply damping to prevent NR divergence when Q-limits interact
+ // with voltage adjustments at multiple generators simultaneously
+ // Limit step to prevent Q explosion when
+ // multiple PV buses lose voltage control and cause large voltage deviations
+ const double MAX_DV = 0.05;
+ if (dv > MAX_DV) dv = MAX_DV;
+ else if (dv < -MAX_DV) dv = -MAX_DV;
bus->adjustVoltageForRemoteReg(dv);
all_ok = false;
}
@@ -1064,6 +1106,104 @@ bool gridpack::powerflow::PFFactoryModule::adjustRemoteRegulation(double tol)
return checkTrue(all_ok);
}
+/**
+ * Set up IREG remote voltage regulation via PV swap.
+ * For each generator bus with IREG, make the remote bus PV (V=VS)
+ * and the generator bus PQ (V free, Q from initial dispatch).
+ * Must be called after load() and setExchange().
+ */
+void gridpack::powerflow::PFFactoryModule::setupIREGPointers()
+{
+ int numBus = p_network->numBuses();
+ MPI_Comm comm = static_cast(p_network->communicator());
+ int nprocs = p_network->communicator().size();
+
+ // Pass 1: Collect IREG swap requests from local active buses.
+ // Also handle slack bus auto-correction.
+ std::vector swap_gen, swap_remote;
+ std::vector swap_vs;
+
+ for (int i = 0; i < numBus; i++) {
+ if (!p_network->getActiveBus(i)) continue;
+ gridpack::powerflow::PFBus *bus =
+ dynamic_cast(p_network->getBus(i).get());
+
+ // Slack bus cannot regulate a remote bus — auto-correct to local
+ if (bus->getReferenceBus()) {
+ int ngen = bus->getNumGenerators();
+ int orig_idx = bus->getOriginalIndex();
+ for (int j = 0; j < ngen; j++) {
+ int ireg = bus->getIREG(j);
+ if (bus->getGenStatusByIdx(j) == 1 && ireg != 0 && ireg != orig_idx) {
+ printf("Warning: Slack bus %d has remote regulation of bus %d. "
+ "Slack bus can regulate itself only. Setting local regulation.\n",
+ orig_idx, ireg);
+ gridpack::component::DataCollection *data = p_network->getBusData(i).get();
+ double v_init = 1.0;
+ data->getValue(BUS_VOLTAGE_MAG, &v_init);
+ bus->setVoltageMag(v_init);
+ break;
+ }
+ }
+ continue;
+ }
+
+ if (!bus->isPV()) continue;
+ int remote_bus_num = bus->getIREGRemoteBus();
+ if (remote_bus_num == 0) continue;
+
+ swap_gen.push_back(bus->getOriginalIndex());
+ swap_remote.push_back(remote_bus_num);
+ swap_vs.push_back(bus->getIREGVS());
+ }
+
+ // Gather all swap requests across processes
+ int nlocal = swap_gen.size();
+ int ntotal = 0;
+ MPI_Allreduce(&nlocal, &ntotal, 1, MPI_INT, MPI_SUM, comm);
+
+ if (ntotal == 0) return;
+
+ std::vector all_gen(ntotal), all_remote(ntotal);
+ std::vector all_vs(ntotal);
+ std::vector counts(nprocs), displs(nprocs);
+ MPI_Allgather(&nlocal, 1, MPI_INT, counts.data(), 1, MPI_INT, comm);
+ displs[0] = 0;
+ for (int p = 1; p < nprocs; p++) displs[p] = displs[p-1] + counts[p-1];
+
+ MPI_Allgatherv(swap_gen.data(), nlocal, MPI_INT,
+ all_gen.data(), counts.data(), displs.data(), MPI_INT, comm);
+ MPI_Allgatherv(swap_remote.data(), nlocal, MPI_INT,
+ all_remote.data(), counts.data(), displs.data(), MPI_INT, comm);
+ MPI_Allgatherv(swap_vs.data(), nlocal, MPI_DOUBLE,
+ all_vs.data(), counts.data(), displs.data(), MPI_DOUBLE, comm);
+
+ // Pass 2: Apply swaps — each process handles buses it owns
+ for (int s = 0; s < ntotal; s++) {
+ // Set generator bus to PQ (active and ghost copies)
+ std::vector gindices = p_network->getLocalBusIndices(all_gen[s]);
+ for (size_t g = 0; g < gindices.size(); g++) {
+ gridpack::powerflow::PFBus *gbus =
+ dynamic_cast(
+ p_network->getBus(gindices[g]).get());
+ gbus->setIsPV(false);
+ gbus->saveIsPVState();
+ }
+
+ // Set remote bus to PV at VS (active and ghost copies)
+ std::vector rindices = p_network->getLocalBusIndices(all_remote[s]);
+ for (size_t r = 0; r < rindices.size(); r++) {
+ gridpack::powerflow::PFBus *rbus =
+ dynamic_cast(
+ p_network->getBus(rindices[r]).get());
+ if (rbus->isPV() || rbus->getReferenceBus()) continue;
+ rbus->setIsPV(true);
+ rbus->saveIsPVState();
+ rbus->setVoltageForIREG(all_vs[s]);
+ }
+ }
+}
+
/**
* Clear changes that were made for Q limit violations and reset
* system to its original state
@@ -1364,5 +1504,143 @@ void gridpack::powerflow::PFFactoryModule::useRateB(bool flag)
}
}
+/**
+ * Check switched shunt violations and adjust shunt B values.
+ * For buses with SWREM != 0, resolves remote bus voltage via getLocalBusIndices.
+ * @return true if no violations found (all voltages within deadband)
+ */
+bool PFFactoryModule::checkSwitchedShuntViolations()
+{
+ int numBus = p_network->numBuses();
+ int i;
+ bool bus_ok = true;
+ for (i = 0; i < numBus; i++) {
+ if (!p_network->getActiveBus(i)) continue;
+ gridpack::powerflow::PFBus *bus =
+ dynamic_cast(p_network->getBus(i).get());
+ if (!bus->hasSwitchedShunt()) continue;
+
+ // Determine controlled voltage
+ double v_controlled = bus->getVoltage(); // Default: local control
+ int swrem = bus->getSwitchedShuntRemoteBus();
+ if (swrem > 0) {
+ // Remote control: look up voltage at remote bus
+ std::vector rindices = p_network->getLocalBusIndices(swrem);
+ if (rindices.size() > 0) {
+ gridpack::powerflow::PFBus *rbus =
+ dynamic_cast(
+ p_network->getBus(rindices[0]).get());
+ v_controlled = rbus->getVoltage();
+ }
+ }
+
+ if (bus->adjustSwitchedShunt(v_controlled)) {
+ bus_ok = false;
+ }
+ }
+ return checkTrue(bus_ok);
+}
+
+/**
+ * Clear switched shunt adjustments and reset to BINIT state
+ */
+void PFFactoryModule::clearSwitchedShunts()
+{
+ int numBus = p_network->numBuses();
+ for (int i = 0; i < numBus; i++) {
+ if (!p_network->getActiveBus(i)) continue;
+ gridpack::powerflow::PFBus *bus =
+ dynamic_cast(p_network->getBus(i).get());
+ bus->resetSwitchedShunt();
+ }
+}
+
+/**
+ * Check LTC violations and adjust transformer tap ratios.
+ * Iterates over branches, finds LTC-controlled transformers,
+ * looks up controlled bus voltage, and adjusts tap if outside deadband.
+ * @return true if no violations found
+ */
+bool PFFactoryModule::checkLTCViolations()
+{
+ int numBranch = p_network->numBranches();
+ bool branch_ok = true;
+ for (int i = 0; i < numBranch; i++) {
+ if (!p_network->getActiveBranch(i)) continue;
+ gridpack::powerflow::PFBranch *branch =
+ dynamic_cast(p_network->getBranch(i).get());
+ if (!branch->hasLTC()) continue;
+
+ // Look up voltage at controlled bus
+ int cont = branch->getLTCControlledBus();
+ double v_controlled = 1.0;
+ std::vector rindices = p_network->getLocalBusIndices(cont);
+ if (rindices.size() > 0) {
+ gridpack::powerflow::PFBus *cbus =
+ dynamic_cast(
+ p_network->getBus(rindices[0]).get());
+ v_controlled = cbus->getVoltage();
+ }
+
+ if (branch->adjustLTC(v_controlled)) {
+ branch_ok = false;
+ }
+ }
+ return checkTrue(branch_ok);
+}
+
+/**
+ * Clear LTC adjustments and reset taps to initial values
+ */
+void PFFactoryModule::clearLTCControls()
+{
+ int numBranch = p_network->numBranches();
+ for (int i = 0; i < numBranch; i++) {
+ if (!p_network->getActiveBranch(i)) continue;
+ gridpack::powerflow::PFBranch *branch =
+ dynamic_cast(p_network->getBranch(i).get());
+ branch->resetLTC();
+ }
+}
+
+/**
+ * Compute net MW export for each area via tie-line flows.
+ * For each branch connecting buses in different areas, the real power
+ * flow from the "from" bus side is counted as export for that bus's area.
+ * @param areaExport map from area number to net MW export (positive = export)
+ */
+void PFFactoryModule::computeAreaExport(std::map &areaExport)
+{
+ areaExport.clear();
+ int numBranch = p_network->numBranches();
+ for (int i = 0; i < numBranch; i++) {
+ if (!p_network->getActiveBranch(i)) continue;
+ gridpack::powerflow::PFBranch *branch =
+ dynamic_cast(p_network->getBranch(i).get());
+
+ // Get endpoint buses
+ int idx1, idx2;
+ p_network->getBranchEndpoints(i, &idx1, &idx2);
+ gridpack::powerflow::PFBus *bus1 =
+ dynamic_cast(p_network->getBus(idx1).get());
+ gridpack::powerflow::PFBus *bus2 =
+ dynamic_cast(p_network->getBus(idx2).get());
+
+ int area1 = bus1->getArea();
+ int area2 = bus2->getArea();
+ if (area1 == area2) continue; // Not a tie-line
+
+ // Sum power flow for each line element on this branch
+ std::vector tags = branch->getLineIDs();
+ for (size_t j = 0; j < tags.size(); j++) {
+ if (!branch->getBranchStatus(tags[j])) continue;
+ gridpack::ComplexType s = branch->getComplexPower(tags[j]);
+ double p_mw = real(s); // MW flowing from bus1 to bus2
+ areaExport[area1] += p_mw; // Export from area1
+ areaExport[area2] -= p_mw; // Import to area2
+ }
+ }
+}
+
} // namespace powerflow
} // namespace gridpack
diff --git a/src/applications/modules/powerflow/pf_factory_module.hpp b/src/applications/modules/powerflow/pf_factory_module.hpp
index 747083253..880855175 100644
--- a/src/applications/modules/powerflow/pf_factory_module.hpp
+++ b/src/applications/modules/powerflow/pf_factory_module.hpp
@@ -13,6 +13,19 @@
* - Added setInitStartMode for power flow initialization (warm/flat start)
* @date 2026-02-02
*
+ * @updated Yousu Chen
+ * - Added checkSwitchedShuntViolations() and clearSwitchedShunts()
+ * @date 2026-02-24
+ *
+ * @updated Yousu Chen
+ * - Added checkLTCViolations() and clearLTCControls()
+ * - Added computeAreaExport() for area interchange control
+ * @date 2026-03-28
+ *
+ * @updated Yousu Chen
+ * - Added setupIREGPointers() for IREG PV bus swap with MPI support
+ * @date 2026-04-05
+ *
* @brief
*
*
@@ -163,6 +176,12 @@ class PFFactoryModule
bool checkVoltageViolations();
bool checkVoltageViolations(int area);
+ /**
+ * Set Q limit deadband (Mvar). PV->PQ switch only occurs when Q exceeds
+ * limit by more than this amount.
+ */
+ void setQlimDeadband(double db) { p_qlim_deadband = db; }
+
/**
* Check to see if there are any Q limit violations in the network
* @param area only check for Q limit violations in this area
@@ -180,12 +199,47 @@ class PFFactoryModule
*/
bool adjustRemoteRegulation(double tol = 1.0e-4);
+ /**
+ * Set up pointers for IREG PV buses to read remote bus voltage.
+ * Must be called after setExchange() and initBusUpdate().
+ */
+ void setupIREGPointers();
+
/**
* Clear changes that were made for Q limit violations and reset
* system to its original state
*/
void clearQlimViolations();
+ /**
+ * Check switched shunt violations and adjust shunt B values.
+ * For buses with SWREM != 0, resolves remote bus voltage.
+ * @return true if no violations found (all voltages within deadband)
+ */
+ bool checkSwitchedShuntViolations();
+
+ /**
+ * Clear switched shunt adjustments and reset to BINIT state
+ */
+ void clearSwitchedShunts();
+
+ /**
+ * Check LTC violations and adjust transformer tap ratios.
+ * @return true if no violations found (all controlled voltages within deadband)
+ */
+ bool checkLTCViolations();
+
+ /**
+ * Clear LTC adjustments and reset taps to initial values
+ */
+ void clearLTCControls();
+
+ /**
+ * Compute net MW export for each area via tie-line flows.
+ * @param areaExport map from area number to net MW export (positive = export)
+ */
+ void computeAreaExport(std::map &areaExport);
+
/**
* Set "ignore" parameter on all buses with violations so that subsequent
* checks are not counted as violations
@@ -326,6 +380,7 @@ class PFFactoryModule
std::vector p_violations;
bool p_rateB;
+ double p_qlim_deadband; // Q deadband (Mvar) for PV->PQ switch
};
} // powerflow
diff --git a/src/parser/PTI34_parser.hpp b/src/parser/PTI34_parser.hpp
index b74e3e488..248218f52 100644
--- a/src/parser/PTI34_parser.hpp
+++ b/src/parser/PTI34_parser.hpp
@@ -8,6 +8,11 @@
*
* Created on: December 10, 2014
* Author: Kevin Glass, Bruce Palmer
+ *
+ * @updated Yousu Chen
+ * - Fixed: use SwitchedShuntParser34 instead of Parser35 (v34 format
+ * has no ID/NREG fields; wrong parser shifted BINIT to B1)
+ * @date 2026-04-04
*/
#ifndef PTI34_PARSER_HPP_
@@ -47,7 +52,7 @@
#include "gridpack/parser/block_parsers/interarea_parser33.hpp"
#include "gridpack/parser/block_parsers/owner_parser33.hpp"
#include "gridpack/parser/block_parsers/facts_parser33.hpp"
-#include "gridpack/parser/block_parsers/switched_shunt_parser35.hpp"
+#include "gridpack/parser/block_parsers/switched_shunt_parser34.hpp"
#define TERM_CHAR '0'
// SOURCE: http://www.ee.washington.edu/research/pstca/formats/pti.txt
@@ -291,7 +296,7 @@ class PTI34_parser : public BasePTIParser<_network>
gridpack::parser::FACTSParser33 facts_parser(&p_busMap,
&p_nameMap, &p_branchMap);
facts_parser.parse(p_istream);
- gridpack::parser::SwitchedShuntParser35 switched_shunt_parser(&p_busMap,
+ gridpack::parser::SwitchedShuntParser34 switched_shunt_parser(&p_busMap,
&p_nameMap, &p_branchMap);
switched_shunt_parser.parse(p_istream,p_busData);
diff --git a/src/parser/block_parsers/transformer_parser33.cpp b/src/parser/block_parsers/transformer_parser33.cpp
index 611d03b95..fcde6f683 100644
--- a/src/parser/block_parsers/transformer_parser33.cpp
+++ b/src/parser/block_parsers/transformer_parser33.cpp
@@ -11,6 +11,8 @@
* Author: Yousu Chen
* Fixed CZ parameter handling for impedance conversion: Feb. 19th, 2026
* Author: Yousu Chen
+ * Added TRANSFORMER_CONT1 parsing for LTC control: Mar. 28th, 2026
+ * Author: Yousu Chen
*/
#include "transformer_parser33.hpp"
@@ -144,26 +146,10 @@ void gridpack::parser::TransformerParser33::parse(
data->addValue(BUS_AREA,ival);
p_busData[l_idx1]->getValue(BUS_OWNER,&ival);
data->addValue(BUS_OWNER, ival);
- double rval = 0.0;
- double rvol;
- p_busData[l_idx1]->getValue(BUS_VOLTAGE_MAG,&rvol);
- rval += rvol;
- p_busData[l_idx2]->getValue(BUS_VOLTAGE_MAG,&rvol);
- rval += rvol;
- p_busData[l_idx3]->getValue(BUS_VOLTAGE_MAG,&rvol);
- rval += rvol;
- rval = rval/3.0;
- rval = 1.0;
+ // Use VMSTAR/ANSTAR from RAW line 2 (fields [9],[10]) for star bus init
+ double rval = (split_line2.size() > 9) ? atof(split_line2[9].c_str()) : 1.0;
data->addValue(BUS_VOLTAGE_MAG,rval);
- rval = 0.0;
- p_busData[l_idx1]->getValue(BUS_VOLTAGE_ANG,&rvol);
- rval += rvol;
- p_busData[l_idx2]->getValue(BUS_VOLTAGE_ANG,&rvol);
- rval += rvol;
- p_busData[l_idx3]->getValue(BUS_VOLTAGE_ANG,&rvol);
- rval += rvol;
- rval = rval/3.0;
- rval = 0.0;
+ rval = (split_line2.size() > 10) ? atof(split_line2[10].c_str()) : 0.0;
data->addValue(BUS_VOLTAGE_ANG,rval);
// parse remainder of line 1
@@ -636,6 +622,13 @@ void gridpack::parser::TransformerParser33::parse(
p_branchData[l_idx]->addValue(TRANSFORMER_CODE1,
atoi(split_line3[6].c_str()),nelems);
+ /*
+ * type: integer
+ * TRANSFORMER_CONT1 - controlled bus number
+ */
+ p_branchData[l_idx]->addValue(TRANSFORMER_CONT1,
+ atoi(split_line3[7].c_str()),nelems);
+
/*
* type: float
* TRANSFORMER_RMA
diff --git a/src/parser/block_parsers/transformer_parser34.cpp b/src/parser/block_parsers/transformer_parser34.cpp
index 1da142e01..32d08a336 100644
--- a/src/parser/block_parsers/transformer_parser34.cpp
+++ b/src/parser/block_parsers/transformer_parser34.cpp
@@ -11,6 +11,10 @@
* Author: Yousu Chen
* Fixed CZ parameter handling for impedance conversion: Feb. 19th, 2026
* Author: Yousu Chen
+ * Added TRANSFORMER_CONT1 parsing for LTC control: Mar. 28th, 2026
+ * Author: Yousu Chen
+ * Fixed 3-winding ratings data1->data2/data3: Apr. 4th, 2026
+ * Author: Yousu Chen
*/
#include "transformer_parser34.hpp"
#include "ga.h"
@@ -145,26 +149,10 @@ void gridpack::parser::TransformerParser34::parse(
data->addValue(BUS_AREA,ival);
p_busData[l_idx1]->getValue(BUS_OWNER,&ival);
data->addValue(BUS_OWNER, ival);
- double rval = 0.0;
- double rvol;
- p_busData[l_idx1]->getValue(BUS_VOLTAGE_MAG,&rvol);
- rval += rvol;
- p_busData[l_idx2]->getValue(BUS_VOLTAGE_MAG,&rvol);
- rval += rvol;
- p_busData[l_idx3]->getValue(BUS_VOLTAGE_MAG,&rvol);
- rval += rvol;
- rval = rval/3.0;
- rval = 1.0;
+ // Use VMSTAR/ANSTAR from RAW line 2 (fields [9],[10]) for star bus init
+ double rval = (split_line2.size() > 9) ? atof(split_line2[9].c_str()) : 1.0;
data->addValue(BUS_VOLTAGE_MAG,rval);
- rval = 0.0;
- p_busData[l_idx1]->getValue(BUS_VOLTAGE_ANG,&rvol);
- rval += rvol;
- p_busData[l_idx2]->getValue(BUS_VOLTAGE_ANG,&rvol);
- rval += rvol;
- p_busData[l_idx3]->getValue(BUS_VOLTAGE_ANG,&rvol);
- rval += rvol;
- rval = rval/3.0;
- rval = 0.0;
+ rval = (split_line2.size() > 10) ? atof(split_line2[10].c_str()) : 0.0;
data->addValue(BUS_VOLTAGE_ANG,rval);
// parse remainder of line 1
@@ -284,21 +272,21 @@ void gridpack::parser::TransformerParser34::parse(
data2->addValue(BRANCH_R,r2,0);
data2->addValue(BRANCH_X,x2,0);
data2->addValue(BRANCH_B,b2,0);
- data1->addValue(BRANCH_RATING_A,rate[0],0);
- data1->addValue(BRANCH_RATING_B,rate[1],0);
- data1->addValue(BRANCH_RATING_C,rate[2],0);
- data1->addValue(BRANCH_RATE1,rate[0],0);
- data1->addValue(BRANCH_RATE2,rate[1],0);
- data1->addValue(BRANCH_RATE3,rate[2],0);
- data1->addValue(BRANCH_RATE4,rate[3],0);
- data1->addValue(BRANCH_RATE5,rate[4],0);
- data1->addValue(BRANCH_RATE6,rate[5],0);
- data1->addValue(BRANCH_RATE7,rate[6],0);
- data1->addValue(BRANCH_RATE8,rate[7],0);
- data1->addValue(BRANCH_RATE9,rate[8],0);
- data1->addValue(BRANCH_RATE10,rate[9],0);
- data1->addValue(BRANCH_RATE11,rate[10],0);
- data1->addValue(BRANCH_RATE12,rate[11],0);
+ data2->addValue(BRANCH_RATING_A,rate[0],0);
+ data2->addValue(BRANCH_RATING_B,rate[1],0);
+ data2->addValue(BRANCH_RATING_C,rate[2],0);
+ data2->addValue(BRANCH_RATE1,rate[0],0);
+ data2->addValue(BRANCH_RATE2,rate[1],0);
+ data2->addValue(BRANCH_RATE3,rate[2],0);
+ data2->addValue(BRANCH_RATE4,rate[3],0);
+ data2->addValue(BRANCH_RATE5,rate[4],0);
+ data2->addValue(BRANCH_RATE6,rate[5],0);
+ data2->addValue(BRANCH_RATE7,rate[6],0);
+ data2->addValue(BRANCH_RATE8,rate[7],0);
+ data2->addValue(BRANCH_RATE9,rate[8],0);
+ data2->addValue(BRANCH_RATE10,rate[9],0);
+ data2->addValue(BRANCH_RATE11,rate[10],0);
+ data2->addValue(BRANCH_RATE12,rate[11],0);
data2->addValue(BRANCH_TAP,windv,0);
data2->addValue(BRANCH_SHIFT,ang,0);
data2->addValue(BRANCH_SWITCHED,false,0);
@@ -334,21 +322,21 @@ void gridpack::parser::TransformerParser34::parse(
data3->addValue(BRANCH_R,r3,0);
data3->addValue(BRANCH_X,x3,0);
data3->addValue(BRANCH_B,b3,0);
- data1->addValue(BRANCH_RATING_A,rate[0],0);
- data1->addValue(BRANCH_RATING_B,rate[1],0);
- data1->addValue(BRANCH_RATING_C,rate[2],0);
- data1->addValue(BRANCH_RATE1,rate[0],0);
- data1->addValue(BRANCH_RATE2,rate[1],0);
- data1->addValue(BRANCH_RATE3,rate[2],0);
- data1->addValue(BRANCH_RATE4,rate[3],0);
- data1->addValue(BRANCH_RATE5,rate[4],0);
- data1->addValue(BRANCH_RATE6,rate[5],0);
- data1->addValue(BRANCH_RATE7,rate[6],0);
- data1->addValue(BRANCH_RATE8,rate[7],0);
- data1->addValue(BRANCH_RATE9,rate[8],0);
- data1->addValue(BRANCH_RATE10,rate[9],0);
- data1->addValue(BRANCH_RATE11,rate[10],0);
- data1->addValue(BRANCH_RATE12,rate[11],0);
+ data3->addValue(BRANCH_RATING_A,rate[0],0);
+ data3->addValue(BRANCH_RATING_B,rate[1],0);
+ data3->addValue(BRANCH_RATING_C,rate[2],0);
+ data3->addValue(BRANCH_RATE1,rate[0],0);
+ data3->addValue(BRANCH_RATE2,rate[1],0);
+ data3->addValue(BRANCH_RATE3,rate[2],0);
+ data3->addValue(BRANCH_RATE4,rate[3],0);
+ data3->addValue(BRANCH_RATE5,rate[4],0);
+ data3->addValue(BRANCH_RATE6,rate[5],0);
+ data3->addValue(BRANCH_RATE7,rate[6],0);
+ data3->addValue(BRANCH_RATE8,rate[7],0);
+ data3->addValue(BRANCH_RATE9,rate[8],0);
+ data3->addValue(BRANCH_RATE10,rate[9],0);
+ data3->addValue(BRANCH_RATE11,rate[10],0);
+ data3->addValue(BRANCH_RATE12,rate[11],0);
data3->addValue(BRANCH_TAP,windv,0);
data3->addValue(BRANCH_SHIFT,ang,0);
data3->addValue(BRANCH_SWITCHED,false,0);
@@ -679,6 +667,13 @@ void gridpack::parser::TransformerParser34::parse(
p_branchData[l_idx]->addValue(TRANSFORMER_CODE1,
atoi(split_line3[15].c_str()),nelems);
+ /*
+ * type: integer
+ * TRANSFORMER_CONT1 - controlled bus number
+ */
+ p_branchData[l_idx]->addValue(TRANSFORMER_CONT1,
+ atoi(split_line3[16].c_str()),nelems);
+
/*
* type: float
* TRANSFORMER_RMA
diff --git a/src/parser/block_parsers/transformer_parser35.cpp b/src/parser/block_parsers/transformer_parser35.cpp
index c60b908e8..514d69f81 100644
--- a/src/parser/block_parsers/transformer_parser35.cpp
+++ b/src/parser/block_parsers/transformer_parser35.cpp
@@ -11,6 +11,10 @@
* Author: Yousu Chen
* Fixed CZ parameter handling for impedance conversion: Feb. 19th, 2026
* Author: Yousu Chen
+ * Added TRANSFORMER_CONT1 parsing for LTC control: Mar. 28th, 2026
+ * Author: Yousu Chen
+ * Fixed 3-winding ratings data1->data2/data3: Apr. 4th, 2026
+ * Author: Yousu Chen
*/
#include "transformer_parser35.hpp"
@@ -144,26 +148,10 @@ void gridpack::parser::TransformerParser35::parse(
data->addValue(BUS_AREA,ival);
p_busData[l_idx1]->getValue(BUS_OWNER,&ival);
data->addValue(BUS_OWNER, ival);
- double rval = 0.0;
- double rvol;
- p_busData[l_idx1]->getValue(BUS_VOLTAGE_MAG,&rvol);
- rval += rvol;
- p_busData[l_idx2]->getValue(BUS_VOLTAGE_MAG,&rvol);
- rval += rvol;
- p_busData[l_idx3]->getValue(BUS_VOLTAGE_MAG,&rvol);
- rval += rvol;
- rval = rval/3.0;
- rval = 1.0;
+ // Use VMSTAR/ANSTAR from RAW line 2 (fields [9],[10]) for star bus init
+ double rval = (split_line2.size() > 9) ? atof(split_line2[9].c_str()) : 1.0;
data->addValue(BUS_VOLTAGE_MAG,rval);
- rval = 0.0;
- p_busData[l_idx1]->getValue(BUS_VOLTAGE_ANG,&rvol);
- rval += rvol;
- p_busData[l_idx2]->getValue(BUS_VOLTAGE_ANG,&rvol);
- rval += rvol;
- p_busData[l_idx3]->getValue(BUS_VOLTAGE_ANG,&rvol);
- rval += rvol;
- rval = rval/3.0;
- rval = 0.0;
+ rval = (split_line2.size() > 10) ? atof(split_line2[10].c_str()) : 0.0;
data->addValue(BUS_VOLTAGE_ANG,rval);
// parse remainder of line 1
@@ -283,21 +271,21 @@ void gridpack::parser::TransformerParser35::parse(
data2->addValue(BRANCH_R,r2,0);
data2->addValue(BRANCH_X,x2,0);
data2->addValue(BRANCH_B,b2,0);
- data1->addValue(BRANCH_RATING_A,rate[0],0);
- data1->addValue(BRANCH_RATING_B,rate[1],0);
- data1->addValue(BRANCH_RATING_C,rate[2],0);
- data1->addValue(BRANCH_RATE1,rate[0],0);
- data1->addValue(BRANCH_RATE2,rate[1],0);
- data1->addValue(BRANCH_RATE3,rate[2],0);
- data1->addValue(BRANCH_RATE4,rate[3],0);
- data1->addValue(BRANCH_RATE5,rate[4],0);
- data1->addValue(BRANCH_RATE6,rate[5],0);
- data1->addValue(BRANCH_RATE7,rate[6],0);
- data1->addValue(BRANCH_RATE8,rate[7],0);
- data1->addValue(BRANCH_RATE9,rate[8],0);
- data1->addValue(BRANCH_RATE10,rate[9],0);
- data1->addValue(BRANCH_RATE11,rate[10],0);
- data1->addValue(BRANCH_RATE12,rate[11],0);
+ data2->addValue(BRANCH_RATING_A,rate[0],0);
+ data2->addValue(BRANCH_RATING_B,rate[1],0);
+ data2->addValue(BRANCH_RATING_C,rate[2],0);
+ data2->addValue(BRANCH_RATE1,rate[0],0);
+ data2->addValue(BRANCH_RATE2,rate[1],0);
+ data2->addValue(BRANCH_RATE3,rate[2],0);
+ data2->addValue(BRANCH_RATE4,rate[3],0);
+ data2->addValue(BRANCH_RATE5,rate[4],0);
+ data2->addValue(BRANCH_RATE6,rate[5],0);
+ data2->addValue(BRANCH_RATE7,rate[6],0);
+ data2->addValue(BRANCH_RATE8,rate[7],0);
+ data2->addValue(BRANCH_RATE9,rate[8],0);
+ data2->addValue(BRANCH_RATE10,rate[9],0);
+ data2->addValue(BRANCH_RATE11,rate[10],0);
+ data2->addValue(BRANCH_RATE12,rate[11],0);
data2->addValue(BRANCH_TAP,windv,0);
data2->addValue(BRANCH_SHIFT,ang,0);
data2->addValue(BRANCH_SWITCHED,false,0);
@@ -333,21 +321,21 @@ void gridpack::parser::TransformerParser35::parse(
data3->addValue(BRANCH_R,r3,0);
data3->addValue(BRANCH_X,x3,0);
data3->addValue(BRANCH_B,b3,0);
- data1->addValue(BRANCH_RATING_A,rate[0],0);
- data1->addValue(BRANCH_RATING_B,rate[1],0);
- data1->addValue(BRANCH_RATING_C,rate[2],0);
- data1->addValue(BRANCH_RATE1,rate[0],0);
- data1->addValue(BRANCH_RATE2,rate[1],0);
- data1->addValue(BRANCH_RATE3,rate[2],0);
- data1->addValue(BRANCH_RATE4,rate[3],0);
- data1->addValue(BRANCH_RATE5,rate[4],0);
- data1->addValue(BRANCH_RATE6,rate[5],0);
- data1->addValue(BRANCH_RATE7,rate[6],0);
- data1->addValue(BRANCH_RATE8,rate[7],0);
- data1->addValue(BRANCH_RATE9,rate[8],0);
- data1->addValue(BRANCH_RATE10,rate[9],0);
- data1->addValue(BRANCH_RATE11,rate[10],0);
- data1->addValue(BRANCH_RATE12,rate[11],0);
+ data3->addValue(BRANCH_RATING_A,rate[0],0);
+ data3->addValue(BRANCH_RATING_B,rate[1],0);
+ data3->addValue(BRANCH_RATING_C,rate[2],0);
+ data3->addValue(BRANCH_RATE1,rate[0],0);
+ data3->addValue(BRANCH_RATE2,rate[1],0);
+ data3->addValue(BRANCH_RATE3,rate[2],0);
+ data3->addValue(BRANCH_RATE4,rate[3],0);
+ data3->addValue(BRANCH_RATE5,rate[4],0);
+ data3->addValue(BRANCH_RATE6,rate[5],0);
+ data3->addValue(BRANCH_RATE7,rate[6],0);
+ data3->addValue(BRANCH_RATE8,rate[7],0);
+ data3->addValue(BRANCH_RATE9,rate[8],0);
+ data3->addValue(BRANCH_RATE10,rate[9],0);
+ data3->addValue(BRANCH_RATE11,rate[10],0);
+ data3->addValue(BRANCH_RATE12,rate[11],0);
data3->addValue(BRANCH_TAP,windv,0);
data3->addValue(BRANCH_SHIFT,ang,0);
data3->addValue(BRANCH_SWITCHED,false,0);
@@ -680,6 +668,13 @@ void gridpack::parser::TransformerParser35::parse(
p_branchData[l_idx]->addValue(TRANSFORMER_CODE1,
atoi(split_line3[15].c_str()),nelems);
+ /*
+ * type: integer
+ * TRANSFORMER_CONT1 - controlled bus number
+ */
+ p_branchData[l_idx]->addValue(TRANSFORMER_CONT1,
+ atoi(split_line3[16].c_str()),nelems);
+
/*
* type: float
* TRANSFORMER_RMA
diff --git a/src/utilities/string_utils.hpp b/src/utilities/string_utils.hpp
index 771b94de4..058cafdeb 100644
--- a/src/utilities/string_utils.hpp
+++ b/src/utilities/string_utils.hpp
@@ -170,7 +170,7 @@ class StringUtils {
ntok1 = tag.find_first_not_of('\'',ntok1);
ntok2 = tag.find('\'',ntok1);
} else if (no_qt) {
- ntok2 = tag.find_last_not_of(' ',ntok1)+1;
+ ntok2 = tag.find_last_not_of(' ')+1;
} else {
ntok1 = tag.find_first_not_of('\"',ntok1);
ntok2 = tag.find('\"',ntok1);