deltaFlow
PSSE.C
Go to the documentation of this file.
1/*
2 * Copyright (c) 2024 Saud Zahir
3 *
4 * This file is part of deltaFlow.
5 *
6 * deltaFlow is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public
8 * License as published by the Free Software Foundation; either
9 * version 3 of the License, or (at your option) any later version.
10 *
11 * deltaFlow is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public
17 * License along with deltaFlow. If not, see
18 * <https://www.gnu.org/licenses/>.
19 */
20
26#include <algorithm>
27#include <cmath>
28#include <fstream>
29#include <map>
30#include <sstream>
31
32#include "Logger.H"
33#include "PSSE.H"
34#include "Utils.H"
35
38
39static std::vector<std::string> splitFields(const std::string& line) {
40 std::vector<std::string> fields;
41 std::string data = line;
42 auto slashPos = data.find('/');
43 if (slashPos != std::string::npos)
44 data = data.substr(0, slashPos);
45 std::stringstream ss(data);
46 std::string field;
47 while (std::getline(ss, field, ',')) {
48 fields.push_back(strip(field));
49 }
50 return fields;
51}
52
53static bool isSectionEnd(const std::string& line) {
54 std::string s = strip(line);
55 if (s.empty()) return false;
56 return s == "0" || s == "Q"
57 || (s.size() >= 2 && s[0] == '0' && (s[1] == ' ' || s[1] == '/' || s[1] == ','));
58}
59
60void PSSERawFormat::read(const std::string& filename) {
61 std::ifstream file(filename);
62 if (!file.is_open()) {
63 LOG_ERROR("Cannot open input file: {}", filename);
64 return;
65 }
66
67 LOG_DEBUG("Reading PSS/E Raw Format: {}", filename);
68
69 std::string line;
70
71 std::getline(file, line);
72 auto hdr = splitFields(line);
73
74 double sbase = 100.0;
75 int version = 33;
76
77 if (hdr.size() >= 2) sbase = std::stod(hdr[1]);
78 if (hdr.size() >= 3) version = std::stoi(hdr[2]);
79
80 LOG_DEBUG("PSS/E RAW format version {} detected (SBASE = {:.2f} MVA)", version, sbase);
81
82 if (version != 32 && version != 33) {
83 LOG_WARN("PSS/E version {} is not explicitly supported. Attempting to parse as v{}.",
84 version, (version >= 33 ? 33 : 32));
85 }
86
87 // Title lines (2 lines)
88 std::string title1, title2;
89 std::getline(file, title1);
90 std::getline(file, title2);
91 LOG_DEBUG("PSS/E case: {}", strip(title1));
92
93 // Bus data
94 // v32: I,'NAME',BASKV,IDE,AREA,ZONE,OWNER,VM,VA
95 // v33: I,'NAME',BASKV,IDE,AREA,ZONE,OWNER,VM,VA,NVHI,NVLO,EVHI,EVLO
96 std::vector<int> busID;
97 std::vector<std::string> busName;
98 std::vector<int> busType;
99 std::vector<double> V, delta_vec;
100
101 std::map<int, int> busIndex;
102 int busCount = 0;
103
104 while (std::getline(file, line)) {
105 if (isSectionEnd(line)) break;
106
107 auto f = splitFields(line);
108 if (f.size() < 9) continue;
109
110 busCount++;
111 int origID = std::stoi(f[0]);
112 busIndex[origID] = busCount;
113
114 busID.push_back(busCount);
115 busName.push_back(stripQuotes(f[1]));
116
117 int ide = std::stoi(f[3]);
118 int type;
119 switch (ide) {
120 case 3: type = 1; break; // Slack
121 case 2: type = 2; break; // PV
122 default: type = 3; break; // PQ (including isolated IDE=4)
123 }
124 busType.push_back(type);
125
126 double vmag = std::stod(f[7]);
127 V.push_back(vmag > 0.0 ? vmag : 1.0);
128 delta_vec.push_back(0.0);
129 }
130
131 int nBus = busCount;
132 LOG_DEBUG(" {} buses read", nBus);
133
134 // Per-bus accumulators
135 std::vector<double> Pl(nBus, 0.0), Ql(nBus, 0.0);
136 std::vector<double> Pg(nBus, 0.0), Qg(nBus, 0.0);
137 std::vector<double> Qgmax(nBus, 0.0), Qgmin(nBus, 0.0);
138 std::vector<double> Gs(nBus, 0.0), Bs(nBus, 0.0);
139
140 // Load data
141 // I,'ID',STATUS,AREA,ZONE,PL,QL,IP,IQ,YP,YQ,OWNER,SCALE[,INTRPT]
142 while (std::getline(file, line)) {
143 if (isSectionEnd(line)) break;
144
145 auto f = splitFields(line);
146 if (f.size() < 8) continue;
147
148 int origBus = std::stoi(f[0]);
149 int status = std::stoi(f[2]);
150 if (status == 0) continue;
151
152 int idx = busIndex[origBus] - 1;
153 Pl[idx] += std::stod(f[5]) / sbase;
154 Ql[idx] += std::stod(f[6]) / sbase;
155 }
156
157 // Fixed shunt data: I,STATUS,GL,BL
158 while (std::getline(file, line)) {
159 if (isSectionEnd(line)) break;
160
161 auto f = splitFields(line);
162 if (f.size() < 4) continue;
163
164 int origBus = std::stoi(f[0]);
165 int status = std::stoi(f[1]);
166 if (status == 0) continue;
167
168 int idx = busIndex[origBus] - 1;
169 Gs[idx] += std::stod(f[2]) / sbase;
170 Bs[idx] += std::stod(f[3]) / sbase;
171 }
172
173 // Generator data
174 // I,'ID',PG,QG,QT,QB,VS,IREG,MBASE,...
175 while (std::getline(file, line)) {
176 if (isSectionEnd(line)) break;
177
178 auto f = splitFields(line);
179 if (f.size() < 10) continue;
180
181 int origBus = std::stoi(f[0]);
182 int idx = busIndex[origBus] - 1;
183
184 Pg[idx] += std::stod(f[2]) / sbase;
185 Qg[idx] += std::stod(f[3]) / sbase;
186 Qgmax[idx] += std::stod(f[4]) / sbase; // QT
187 Qgmin[idx] += std::stod(f[5]) / sbase; // QB
188
189 // Use generator voltage setpoint for PV/Slack buses
190 double vs = std::stod(f[6]);
191 if (busType[idx] == 1 || busType[idx] == 2) {
192 V[idx] = vs;
193 }
194 }
195
196 // Branch data
197 // I,J,'CKT',R,X,B,RATEA,RATEB,RATEC,...
198 std::vector<int> fromBus, toBus;
199 std::vector<double> R_vec, X_vec, G_vec, B_vec, tap_vec;
200
201 while (std::getline(file, line)) {
202 if (isSectionEnd(line)) break;
203
204 auto f = splitFields(line);
205 if (f.size() < 6) continue;
206
207 int from = std::stoi(f[0]);
208 int to = std::abs(std::stoi(f[1]));
209
210 fromBus.push_back(busIndex[from]);
211 toBus.push_back(busIndex[to]);
212 R_vec.push_back(std::stod(f[3]));
213 X_vec.push_back(std::stod(f[4]));
214 G_vec.push_back(0.0);
215 B_vec.push_back(std::stod(f[5]));
216 tap_vec.push_back(1.0);
217 }
218
219 // Transformer data (2-winding: K==0, 4-line records)
220 //
221 // Line 1: I,J,K,'CKT',CW,CZ,CM,MAG1,MAG2,NMETR,'NAME',STAT,...
222 // Line 2: R1-2, X1-2, SBASE1-2
223 // Line 3: WINDV1, NOMV1, ANG1, RATA1, RATB1, RATC1, ...
224 // Line 4: WINDV2, NOMV2
225 // (3-winding adds a 5th line)
226 while (std::getline(file, line)) {
227 if (isSectionEnd(line)) break;
228
229 auto f1 = splitFields(line);
230 if (f1.size() < 5) continue;
231
232 int I = std::stoi(f1[0]);
233 int J = std::stoi(f1[1]);
234 int K = std::stoi(f1[2]);
235 // f1[3] = CKT, f1[4] = CW, f1[5] = CZ
236 int cz = (f1.size() >= 6) ? std::stoi(f1[5]) : 1;
237
238 // Line 2: R1-2, X1-2, SBASE1-2
239 std::getline(file, line);
240 auto f2 = splitFields(line);
241
242 double r12 = (f2.size() >= 1) ? std::stod(f2[0]) : 0.0;
243 double x12 = (f2.size() >= 2) ? std::stod(f2[1]) : 0.0;
244 double sbase12 = (f2.size() >= 3) ? std::stod(f2[2]) : sbase;
245
246 // CZ=2: convert from winding base to system base
247 if (cz == 2 && sbase12 > 0.0) {
248 r12 *= sbase / sbase12;
249 x12 *= sbase / sbase12;
250 }
251
252 // Line 3: WINDV1 (tap ratio) ...
253 std::getline(file, line);
254 auto f3 = splitFields(line);
255 double windv1 = (f3.size() >= 1) ? std::stod(f3[0]) : 1.0;
256
257 // Line 4: WINDV2, NOMV2
258 std::getline(file, line);
259
260 if (K != 0) {
261 // 3-winding: skip extra winding line (line 5)
262 std::getline(file, line);
263 LOG_WARN("3-winding transformer ({}-{}-{}) encountered, skipping.", I, J, K);
264 continue;
265 }
266
267 fromBus.push_back(busIndex[I]);
268 toBus.push_back(busIndex[J]);
269 R_vec.push_back(r12);
270 X_vec.push_back(x12);
271 G_vec.push_back(0.0);
272 B_vec.push_back(0.0);
273 double tapVal = (windv1 == 0.0) ? 1.0 : windv1;
274 tap_vec.push_back(tapVal);
275 }
276
277 busData.ID = Eigen::Map<Eigen::VectorXi>(busID.data(), nBus);
278 busData.Type = Eigen::Map<Eigen::VectorXi>(busType.data(), nBus);
279 busData.V = Eigen::Map<Eigen::VectorXd>(V.data(), nBus);
280 busData.delta = Eigen::Map<Eigen::VectorXd>(delta_vec.data(), nBus);
281 busData.Pg = Eigen::Map<Eigen::VectorXd>(Pg.data(), nBus);
282 busData.Qg = Eigen::Map<Eigen::VectorXd>(Qg.data(), nBus);
283 busData.Pl = Eigen::Map<Eigen::VectorXd>(Pl.data(), nBus);
284 busData.Ql = Eigen::Map<Eigen::VectorXd>(Ql.data(), nBus);
285 busData.Qgmax = Eigen::Map<Eigen::VectorXd>(Qgmax.data(), nBus);
286 busData.Qgmin = Eigen::Map<Eigen::VectorXd>(Qgmin.data(), nBus);
287 busData.Gs = Eigen::Map<Eigen::VectorXd>(Gs.data(), nBus);
288 busData.Bs = Eigen::Map<Eigen::VectorXd>(Bs.data(), nBus);
289 busData.Name = busName;
290
291 int nBranch = static_cast<int>(fromBus.size());
292 branchData.From = Eigen::Map<Eigen::VectorXi>(fromBus.data(), nBranch);
293 branchData.To = Eigen::Map<Eigen::VectorXi>(toBus.data(), nBranch);
294 branchData.R = Eigen::Map<Eigen::VectorXd>(R_vec.data(), nBranch);
295 branchData.X = Eigen::Map<Eigen::VectorXd>(X_vec.data(), nBranch);
296 branchData.G = Eigen::Map<Eigen::VectorXd>(G_vec.data(), nBranch);
297 branchData.B = Eigen::Map<Eigen::VectorXd>(B_vec.data(), nBranch);
298 branchData.tapRatio = Eigen::Map<Eigen::VectorXd>(tap_vec.data(), nBranch);
299
300 LOG_DEBUG("PSS/E v{} file parsed: {} buses, {} branches (incl. transformers)",
301 version, nBus, nBranch);
302}
Logger utilities for deltaFlow, providing logging macros and a singleton Logger class.
#define LOG_WARN(msg,...)
Macro for logging a warning-level message.
Definition Logger.H:87
#define LOG_DEBUG(msg,...)
Macro for logging a debug-level message.
Definition Logger.H:85
#define LOG_ERROR(msg,...)
Macro for logging an error-level message.
Definition Logger.H:88
static std::vector< std::string > splitFields(const std::string &line)
Definition PSSE.C:39
static bool isSectionEnd(const std::string &line)
Definition PSSE.C:53
PSS/E Raw Data Format reader for deltaFlow.
Utility functions and helpers for deltaFlow.
void read(const std::string &filename) override
Parse a PSS/E .raw file.
Definition PSSE.C:60
BranchData branchData
Parsed branch data.
Definition Reader.H:65
BusData busData
Parsed bus data.
Definition Reader.H:64
std::string stripQuotes(const std::string &s)
Strip surrounding single quotes and whitespace from a string.
Definition Utils.H:74
std::string strip(const std::string &s)
Strip leading and trailing whitespace from a string.
Definition Utils.H:62
Eigen::VectorXd tapRatio
Transformer tap ratio ($$ a $$)
Definition Data.H:90
Eigen::VectorXd B
Line susceptance ($$ B $$) [p.u.].
Definition Data.H:89
Eigen::VectorXd X
Reactance ($$ X $$) [p.u.].
Definition Data.H:87
Eigen::VectorXi From
From bus indices.
Definition Data.H:84
Eigen::VectorXd R
Resistance ($$ R $$) [p.u.].
Definition Data.H:86
Eigen::VectorXi To
To bus indices.
Definition Data.H:85
Eigen::VectorXd G
Line conductance ($$ G $$) [p.u.].
Definition Data.H:88
Eigen::VectorXd Ql
Reactive power load [MVAr or p.u.].
Definition Data.H:65
Eigen::VectorXd Qgmax
Max reactive power generation [MVAr or p.u.].
Definition Data.H:66
Eigen::VectorXi ID
Bus numbers.
Definition Data.H:56
Eigen::VectorXd V
Voltage magnitude [p.u.].
Definition Data.H:60
Eigen::VectorXd Pg
Active power generation [MW or p.u.].
Definition Data.H:62
std::vector< std::string > Name
Bus names.
Definition Data.H:57
Eigen::VectorXd Qgmin
Min reactive power generation [MVAr or p.u.].
Definition Data.H:67
Eigen::VectorXd Gs
Shunt conductance ($$ G_{sh} $$) [p.u.].
Definition Data.H:68
Eigen::VectorXd delta
Voltage angle [rad or deg].
Definition Data.H:61
Eigen::VectorXd Bs
Shunt susceptance ($$ B_{sh} $$) [p.u.].
Definition Data.H:69
Eigen::VectorXd Pl
Active power load [MW or p.u.].
Definition Data.H:64
Eigen::VectorXd Qg
Reactive power generation [MVAr or p.u.].
Definition Data.H:63
Eigen::VectorXi Type
Bus type (1=Slack, 2=PV, 3=PQ)
Definition Data.H:58