Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
7a7aa32
Make StatBlock summary output a runtime opt-out via writeStats XML op…
chenyousu Jun 17, 2026
aa1bce9
Gate per-case printfs and dedup auto-generation banner to rank 0
chenyousu Jun 17, 2026
0024b3b
Communicator::divide: skip GA_Pgroup_create when result equals source
chenyousu Jun 19, 2026
8115932
Wire ZoneParser33 and OwnerParser33 to store zone/owner numbers and n…
chenyousu Jun 19, 2026
ed29a66
PFBus: add getBusName, getBaseKV, getOwner accessors
chenyousu Jun 19, 2026
268b0a1
Capture per-(branch,contingency) flat rows under outputFormat=csv_flat
chenyousu Jun 19, 2026
7154536
csv_flat: gather rows across ranks and write outputFile_flat.csv
chenyousu Jun 19, 2026
08f0938
csv_flat: stream rows to per-rank files; bound rank-0 memory
chenyousu Jun 19, 2026
95975d3
split bus metadata into sidecar <outputFile>_buses.csv
chenyousu Jun 19, 2026
c1b1ebb
Fix warm-start Q-limit clamp direction for inconsistent QG
chenyousu Jun 20, 2026
7e62c94
add qlimDeadband option, default = 0.1
chenyousu Jun 20, 2026
c0e117c
add universal _convergence.csv
chenyousu Jun 20, 2026
9c6321e
add csv_delta option for CA outputs
chenyousu Jun 21, 2026
60f83f3
add contingencyRating A|B|C option (default C) in xml and output
chenyousu Jun 21, 2026
578bd93
add filter feature to monitoring branches
chenyousu Jun 21, 2026
cff541f
Add filters feature, csv_delta outputs, with input examples for IEEE …
chenyousu Jun 22, 2026
f814caf
Fix phantom flow on out-of-service branches in getComplexPower
chenyousu Jul 18, 2026
38484d5
Populate BRANCH_RATING_A/B/C from RATE1-3 for 2W xfmrs in PTI34/PTI35
chenyousu Jul 18, 2026
458fedd
Unify contingencyRating across CA violation logic and JSON loading_pe…
chenyousu Jul 18, 2026
0ad5441
Add violation reporting
chenyousu Jul 18, 2026
71bb31e
Add PI ranking and contingency rosters to CA summary
chenyousu Jul 18, 2026
ae4a3f4
Populate _violations.csv and _summary.json in text output mode
chenyousu Jul 18, 2026
2785ee8
Reject unknown outputFormat values instead of silently producing empt…
chenyousu Jul 18, 2026
9361307
Add composite PI ranking; textbook voltage PI over all buses
chenyousu Jul 18, 2026
b92affa
Add top_severe_contingencies with violated-first hierarchy
chenyousu Jul 18, 2026
e4d643e
Slim _summary.json: drop derived fields, use null for unset worst_*
chenyousu Jul 18, 2026
f3f8d18
Merge worst voltage low/high into single unsigned-deviation field
chenyousu Jul 18, 2026
4294388
Unify CA loading% denominator across all outputs; default rating A
chenyousu Aug 26, 2026
5a6ed90
minor update on default rating and manual
chenyousu Aug 26, 2026
349f4b6
updated contingency output formats for better usability
chenyousu Sep 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion docs/user_manual/sphinx/Section10-Examples.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1305,7 +1305,10 @@ An example contingency application has been included in the contingency
analysis directory. This contingency analysis is simpler than the one
available under the ``applications`` directory and provides a
relatively compact demonstration of some of the advanced features of
GridPACK. This application is built entirely around the power flow
GridPACK. The production driver under ``src/applications/contingency_analysis``
adds CSV outputs, monitor filters, and PSS/E-aligned options; its
runtime configuration is documented in the *Contingency Analysis Module*
section of Section 9. This application is built entirely around the power flow
module, so it has no network component classes of its own. The main
functionality is located in the ``CADriver`` class that consists of
two methods (other than the constructor and destructor). One function is
Expand Down
259 changes: 259 additions & 0 deletions docs/user_manual/sphinx/Section9-ApplicationModules.rst

Large diffs are not rendered by default.

139 changes: 125 additions & 14 deletions src/applications/components/pf_matrix/pf_components.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
// Static member initialization
gridpack::powerflow::InitStartMode gridpack::powerflow::PFBus::p_initStartMode = INIT_START_WARM;
bool gridpack::powerflow::PFBus::p_qlim = true;
double gridpack::powerflow::PFBus::p_qlim_deadband = 0.1;
std::vector<std::string> gridpack::powerflow::PFBus::p_qlimWarnings;

/**
Expand All @@ -87,6 +88,11 @@ void gridpack::powerflow::PFBus::setQlim(bool qlim)
p_qlim = qlim;
}

void gridpack::powerflow::PFBus::setQlimDeadband(double db)
{
p_qlim_deadband = db;
}

/**
* Clear accumulated Q limit warning messages
*/
Expand Down Expand Up @@ -453,7 +459,7 @@ bool gridpack::powerflow::PFBus::chkQlim(double q_deadband)
// 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).
// (default 0.1 Mvar)
bool need_pv_to_pq = false;
char warnBuf[256];
if (Q_required > Q_max_total + q_deadband) {
Expand Down Expand Up @@ -1077,11 +1083,7 @@ void gridpack::powerflow::PFBus::load(
}
}
}
// Warm-start Q-limit pre-saturation: if scheduled QG is already at QMAX or QMIN
// (within 0.1 Mvar), start this bus as PQ immediately. This matches PW behavior
// where generators already at their limits in the input data are treated as PQ,
// preventing IREG from driving them to impossible Q requirements.
// Only applies for warm start with qlim enabled.
// Warm-start Q-limit pre-saturation: scheduled QG at QMAX/QMIN -> start as PQ.
if (p_isPV && p_qlim && p_initStartMode != INIT_START_FLAT) {
double total_qg = 0.0, total_qmax = 0.0, total_qmin = 0.0;
for (i = 0; i < p_ngen; i++) {
Expand All @@ -1091,9 +1093,23 @@ void gridpack::powerflow::PFBus::load(
total_qmin += p_qmin[i];
}
}
const double Q_init_tol = 0.1; // Mvar — matches PW convergence tolerance
if (total_qg >= total_qmax - Q_init_tol || total_qg <= total_qmin + Q_init_tol) {
if (total_qg >= total_qmax - p_qlim_deadband
|| total_qg <= total_qmin + p_qlim_deadband) {
p_isPV = false;
// Pick the saturated limit from V vs VS (V>VS => QMIN, V<VS => QMAX), not
// scheduled QG which can be inconsistent. Local regulation only.
if (p_ireg_remote_bus == 0) {
double vset = 0.0; int nv = 0;
for (i = 0; i < p_ngen; i++)
if (p_gstatus[i] == 1) { vset += p_vs[i]; nv++; }
if (nv > 0) vset /= nv;
const double V_init_tol = 1.0e-4;
if (p_voltage > vset + V_init_tol) {
for (i = 0; i < p_ngen; i++) if (p_gstatus[i] == 1) p_qg[i] = p_qmin[i];
} else if (p_voltage < vset - V_init_tol) {
for (i = 0; i < p_ngen; i++) if (p_gstatus[i] == 1) p_qg[i] = p_qmax[i];
}
}
}
}

Expand Down Expand Up @@ -2495,6 +2511,39 @@ int gridpack::powerflow::PFBus::getZone()
return p_zone;
}

/**
* Get owner number for bus
* @return bus owner number (0 if not set)
*/
int gridpack::powerflow::PFBus::getOwner()
{
int owner = 0;
if (p_data) p_data->getValue(BUS_OWNER, &owner);
return owner;
}

/**
* Get base voltage for bus in kV
* @return base kV (0.0 if not set)
*/
double gridpack::powerflow::PFBus::getBaseKV()
{
double basekv = 0.0;
if (p_data) p_data->getValue(BUS_BASEKV, &basekv);
return basekv;
}

/**
* Get bus name string
* @return bus name (empty if not set)
*/
std::string gridpack::powerflow::PFBus::getBusName()
{
std::string name;
if (p_data) p_data->getValue(BUS_NAME, &name);
return name;
}

/**
* Evaluate diagonal block of Jacobian for power flow calculation and
* return result as an array of real values
Expand Down Expand Up @@ -3522,7 +3571,16 @@ gridpack::ComplexType gridpack::powerflow::PFBranch::getComplexPower(
{
gridpack::ComplexType vi, vj, Yii, Yij, s;
s = ComplexType(0.0,0.0);
gridpack::powerflow::PFBus *bus1 =
// Out-of-service line: getLineElements does not gate on status, so
// return zero to avoid a phantom flow from stale admittance.
int bsize = p_branch_status.size();
for (int i=0; i<bsize; i++) {
if (tag == p_ckt[i]) {
if (!p_branch_status[i]) return s;
break;
}
}
gridpack::powerflow::PFBus *bus1 =
dynamic_cast<gridpack::powerflow::PFBus*>(getBus1().get());
vi = bus1->getComplexVoltage();
gridpack::powerflow::PFBus *bus2 =
Expand All @@ -3543,6 +3601,14 @@ gridpack::ComplexType gridpack::powerflow::PFBranch::getReversePower(
{
gridpack::ComplexType vi, vj, Yjj, Yji, s;
s = ComplexType(0.0,0.0);
// Out-of-service line: see getComplexPower.
int bsize = p_branch_status.size();
for (int i=0; i<bsize; i++) {
if (tag == p_ckt[i]) {
if (!p_branch_status[i]) return s;
break;
}
}
gridpack::powerflow::PFBus *bus1 =
dynamic_cast<gridpack::powerflow::PFBus*>(getBus1().get());
vi = bus1->getComplexVoltage();
Expand All @@ -3562,10 +3628,42 @@ gridpack::ComplexType gridpack::powerflow::PFBranch::getReversePower(
* routine what about kind of information to write
* @return true if branch is contributing string to output, false otherwise
*/
std::string gridpack::powerflow::PFBranch::s_contingencyRating = "A";

void gridpack::powerflow::PFBranch::setContingencyRating(
const std::string& rating)
{
if (rating == "A" || rating == "B" || rating == "C") {
s_contingencyRating = rating;
} else {
s_contingencyRating = "A";
}
}

std::string gridpack::powerflow::PFBranch::getContingencyRating()
{
return s_contingencyRating;
}

// A->B->C fallback when the picked tier is zero.
double gridpack::powerflow::PFBranch::pickBranchRating(int elemIdx) const
{
if (elemIdx < 0 || elemIdx >= static_cast<int>(p_rateA.size())) return 0.0;
double a = p_rateA[elemIdx];
double b = (elemIdx < static_cast<int>(p_rateB.size())) ? p_rateB[elemIdx] : 0.0;
double c = (elemIdx < static_cast<int>(p_rateC.size())) ? p_rateC[elemIdx] : 0.0;
if (s_contingencyRating == "A") return a;
if (s_contingencyRating == "B") return (b > 0.0) ? b : a;
// "C"
if (c > 0.0) return c;
if (b > 0.0) return b;
return a;
}

bool gridpack::powerflow::PFBranch::serialWrite(char *string, const int bufsize,
const char *signal)
{
char buf[128];
char buf[256];
gridpack::powerflow::PFBus *bus1
= dynamic_cast<gridpack::powerflow::PFBus*>(getBus1().get());
gridpack::powerflow::PFBus *bus2
Expand Down Expand Up @@ -3639,15 +3737,28 @@ bool gridpack::powerflow::PFBranch::serialWrite(char *string, const int bufsize,
s = getComplexPower(tags[i]);
double p = real(s);
double q = imag(s);
gridpack::ComplexType s2 = getReversePower(tags[i]);
double p2 = real(s2);
double q2 = imag(s2);
if (!p_branch_status[i]) p = 0.0;
if (!p_branch_status[i]) q = 0.0;
if (!p_branch_status[i]) p2 = 0.0;
if (!p_branch_status[i]) q2 = 0.0;
if (bus1->isIsolated() || bus2->isIsolated()) p=0.0;
if (bus1->isIsolated() || bus2->isIsolated()) q=0.0;
double S = sqrt(p*p+q*q);
if (S > p_rateA[i] && p_rateA[i] != 0.0){
sprintf(buf, " %6d %6d %s %12.6f %12.6f %8.2f %8.2f%s\n",
if (bus1->isIsolated() || bus2->isIsolated()) p2=0.0;
if (bus1->isIsolated() || bus2->isIsolated()) q2=0.0;
double Sfrom = sqrt(p*p+q*q);
double Sto = sqrt(p2*p2+q2*q2);
// One loading value, measured at the more heavily loaded end, as
// PowerWorld reports it and as _violations.csv/_branches.csv compute it.
double S = (Sfrom > Sto) ? Sfrom : Sto;
double rate = pickBranchRating(i);
if (S > rate && rate != 0.0){
sprintf(buf, "%10d%10d%6s%13.6f%13.6f%13.6f%13.6f%13.6f%13.6f"
"%11.2f%10.2f%s\n",
getBus1OriginalIndex(),getBus2OriginalIndex(),tags[i].c_str(),
p,q,p_rateA[i],S/p_rateA[i]*100,"%");
p,q,Sfrom,p2,q2,Sto,rate,S/rate*100,"%");
int len = strlen(buf);
if (ilen + len < bufsize) {
sprintf(string,"%s",buf);
Expand Down
28 changes: 28 additions & 0 deletions src/applications/components/pf_matrix/pf_components.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,24 @@ class PFBus
*/
int getZone();

/**
* Get owner number for bus
* @return bus owner number (0 if not set)
*/
int getOwner();

/**
* Get base voltage for bus in kV
* @return base kV (0.0 if not set)
*/
double getBaseKV();

/**
* Get bus name string
* @return bus name (empty if not set)
*/
std::string getBusName();

/**
* Evaluate diagonal block of Jacobian for power flow calculation and return
* result as an array of real values
Expand Down Expand Up @@ -610,6 +628,7 @@ class PFBus
*/
static void setInitStartMode(InitStartMode mode);
static void setQlim(bool qlim);
static void setQlimDeadband(double db);

/**
* Clear accumulated Q limit warning messages
Expand Down Expand Up @@ -691,6 +710,7 @@ class PFBus
static std::vector<std::string> p_qlimWarnings;
static InitStartMode p_initStartMode;
static bool p_qlim;
static double p_qlim_deadband;
double p_shunt_gs;
double p_shunt_bs;
bool p_shunt;
Expand Down Expand Up @@ -970,6 +990,12 @@ class PFBranch
*/
double getBranchRatingC(std::string tag);

// Contingency rating tier ("A"|"B"|"C") used by serialWrite("flow",...).
// Default "A" preserves non-CA behavior; A->B->C fallback if picked=0.
static void setContingencyRating(const std::string& rating);
static std::string getContingencyRating();
double pickBranchRating(int elemIdx) const;

/**
* Get list of line IDs
* @return list of line identifiers
Expand Down Expand Up @@ -1057,6 +1083,8 @@ class PFBranch
// 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
static std::string s_contingencyRating;

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)
Expand Down
Loading