Skip to content

Commit 7958a69

Browse files
committed
Add choke tests and more error checking/handling
1 parent 43ae6f4 commit 7958a69

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+1973
-40
lines changed

src/ps/psse.cpp

Lines changed: 115 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ template <typename T> bool Approx(T a, T b, T rtol = 1.0e-5) {
2727

2828
void Warn(const std::string &message) { ExaGOLog(EXAGO_LOG_WARN, message); }
2929

30+
std::string RemoveNulls(std::string str) {
31+
str.erase(std::remove(str.begin(), str.end(), '\0'), str.end());
32+
return str;
33+
}
34+
3035
std::string Strip(std::string str) {
3136
auto notspace = [](char c) { return !std::isspace(c); };
3237
str.erase(begin(str), std::find_if(begin(str), end(str), notspace));
@@ -48,12 +53,25 @@ std::string ReadLine(std::istream &is) {
4853
}
4954

5055
std::istream &SkipLeadingWhitespace(std::istream &is) {
51-
while (std::isspace(is.peek())) {
56+
while (std::isspace(is.peek()) && is.peek() != '\n') {
5257
is.get();
5358
}
5459
return is;
5560
}
5661

62+
bool IsQRecord(std::istream &is) {
63+
bool result = false;
64+
if (SkipLeadingWhitespace(is).peek() == 'Q') {
65+
is.get();
66+
auto eof = std::char_traits<char>::eof();
67+
if (is.peek() == eof || std::isspace(is.peek())) {
68+
result = true;
69+
}
70+
is.unget();
71+
}
72+
return result;
73+
}
74+
5775
class QuoteStringParse {
5876
public:
5977
operator std::string() const { return s_; }
@@ -67,11 +85,18 @@ class QuoteStringParse {
6785
if (!(qc == '\'' || qc == '\"')) {
6886
Error("First character expected to be single or double quote");
6987
}
88+
// Skip opening quote
7089
is.get();
7190

7291
// Get line to closing quote
7392
std::getline(is, qs.s_, qc);
7493

94+
// Check for eof
95+
if (is.peek() == std::char_traits<char>::eof()) {
96+
// Put stream in eof state
97+
is.get();
98+
}
99+
75100
return is;
76101
}
77102

@@ -148,26 +173,59 @@ class LineItemStream : public std::istream {
148173
LineItemStream(std::istream &is) : std::istream(is.rdbuf()), is_(&is) {
149174
do {
150175
NextLine();
151-
} while (StartsWith("@!"));
176+
} while (!Empty() && StartsWith("@!"));
152177
}
153178

154179
CheckedLineItemStream Checked() { return CheckedLineItemStream(*this); }
155180

156181
operator bool() const { return !q_.empty(); }
157182

183+
bool Empty() const noexcept { return q_.empty(); }
158184
std::size_t Size() const noexcept { return q_.size(); }
159185
const std::string &Raw() const noexcept { return line_; }
160186

161187
bool StartsWith(const std::string &sub) const {
188+
if (q_.empty()) {
189+
return false;
190+
}
162191
return q_.front().find(sub) == 0;
163192
}
164193

194+
bool IsSectionHeader() {
195+
if (Empty()) {
196+
return false;
197+
}
198+
if (Size() <= 2) {
199+
if (Strip(q_.front()).find("0") == 0) {
200+
auto remainder = Strip(q_.front().substr(1));
201+
if (remainder.empty() || remainder.find("/") == 0) {
202+
return true;
203+
}
204+
}
205+
}
206+
return false;
207+
}
208+
165209
LineItemStream &NextLine() {
166-
line_ = ReadLine(*is_);
167-
std::istringstream iss(line_);
168210
q_.clear();
211+
if (IsQRecord(*is_) || is_->eof()) {
212+
return *this;
213+
}
214+
line_ = ReadLine(*is_);
215+
if (line_.empty()) {
216+
return *this;
217+
}
218+
if (line_.find("@!") == 0 || line_.find("0 /") == 0) {
219+
q_.push_back(line_);
220+
return *this;
221+
}
222+
std::string noComment;
223+
std::getline(std::istringstream(line_), noComment, '/');
224+
std::istringstream iss(noComment);
169225
for (std::string item; std::getline(iss, item, ',');) {
170-
q_.push_back(Strip(item));
226+
item = RemoveNulls(Strip(item));
227+
CheckSingleItem(item);
228+
q_.push_back(item);
171229
}
172230
return *this;
173231
}
@@ -193,6 +251,25 @@ class LineItemStream : public std::istream {
193251
return out;
194252
}
195253

254+
void CheckSingleItem(const std::string &item) {
255+
if (item.empty()) {
256+
return;
257+
}
258+
std::istringstream iss(item);
259+
if (iss.peek() == '\'' || iss.peek() == '\"') {
260+
QuoteStringParse p{};
261+
while (iss.peek() == '\'' || iss.peek() == '\"') {
262+
iss >> p;
263+
}
264+
} else {
265+
std::string p;
266+
iss >> p;
267+
}
268+
if (!iss.eof()) {
269+
Error("PSS(R)E parser: Space delimiters not supported");
270+
}
271+
}
272+
196273
std::istream *is_{nullptr};
197274

198275
std::string line_;
@@ -209,29 +286,34 @@ inline CheckedLineItemStream &CheckedLineItemStream::operator>>(T &item) {
209286

210287
CaseID ParseCaseID(std::istream &is) {
211288
CaseID cid;
289+
LineItemStream firstLine(is);
212290
std::string record;
213-
std::getline(is, record, '/');
291+
auto recordLineStream = std::stringstream(firstLine.String());
292+
std::getline(recordLineStream, record, '/');
214293
std::istringstream rec_stream(record);
215294
LineItemStream lis(rec_stream);
216-
lis >> cid.ic >> cid.sbase >> cid.rev;
295+
auto clis = lis.Checked();
296+
clis >> cid.ic >> cid.sbase >> cid.rev;
217297
int supported[] = {32, 33, 34};
218298
if (std::find(std::begin(supported), std::end(supported), cid.rev) ==
219299
std::end(supported)) {
220300
Error("PSS(R)E Power Flow Raw Data Files are supported only for "
221301
"versions 32, 33 and 34. This file is tagged version " +
222302
std::to_string(cid.rev));
223303
}
224-
lis >> cid.xfrrat >> cid.nxfrat >> cid.basfrq;
304+
clis >> cid.xfrrat >> cid.nxfrat >> cid.basfrq;
225305

226-
cid.extra[0] = ReadLine(is);
227-
cid.extra[1] = ReadLine(is);
228-
cid.extra[2] = ReadLine(is);
306+
cid.extra[0] = ReadLine(recordLineStream);
307+
if (is && !IsQRecord(is) && !is.eof()) {
308+
cid.extra[1] = ReadLine(is);
309+
cid.extra[2] = ReadLine(is);
310+
}
229311
return cid;
230312
}
231313

232314
void SkipSystemWideData(std::istream &is) {
233315
LineItemStream lis(is);
234-
while (!lis.StartsWith("0 /")) {
316+
while (!lis.Empty() && !lis.StartsWith("0 /")) {
235317
lis.NextLine();
236318
}
237319
}
@@ -468,9 +550,9 @@ struct Parser {
468550

469551
template <typename T> std::vector<T> ParseRecords(std::istream &is) {
470552
std::vector<T> recs;
471-
while (is && is.peek() != 'Q') {
553+
while (is && !IsQRecord(is) && !is.eof()) {
472554
LineItemStream lis(is);
473-
if (lis.StartsWith("0 /")) {
555+
if (lis.IsSectionHeader()) {
474556
break;
475557
}
476558
auto &rec = recs.emplace_back();
@@ -516,14 +598,6 @@ std::size_t BusMapping::GetInternalIndex(const std::string &bus_name) const {
516598
return name_map_.at(bus_name);
517599
}
518600

519-
std::size_t BusMapping::GetBusNumber(const std::string &bus_name) const {
520-
return buses_[GetInternalIndex(bus_name)].i;
521-
}
522-
523-
const std::string &BusMapping::GetBusName(std::size_t bus_number) const {
524-
return buses_[GetInternalIndex(bus_number)].name;
525-
}
526-
527601
const Bus &BusMapping::GetBus(const std::string &bus_name) const {
528602
return buses_[GetInternalIndex(bus_name)];
529603
}
@@ -532,6 +606,14 @@ const Bus &BusMapping::GetBus(std::size_t bus_number) const {
532606
return buses_[GetInternalIndex(bus_number)];
533607
}
534608

609+
std::size_t BusMapping::GetBusNumber(const std::string &bus_name) const {
610+
return GetBus(bus_name).i;
611+
}
612+
613+
const std::string &BusMapping::GetBusName(std::size_t bus_number) const {
614+
return GetBus(bus_number).name;
615+
}
616+
535617
void BusMapping::Resolve(BusRef &busref, BusMapping::Optional optional) const {
536618
if (req_unique_names_) {
537619
if (busref.id == 0 && busref.name.empty()) {
@@ -565,6 +647,10 @@ void BusMapping::Resolve(BusRef &busref, BusMapping::Optional optional) const {
565647
}
566648
Error("PSS(R)E parser: Bus reference number must be positive, got 0");
567649
}
650+
if (!HasBus(busref.id)) {
651+
Error("PSS(R)E parser: Bus " + std::to_string(busref.id) +
652+
" does not exist");
653+
}
568654
busref.bus = &GetBus(busref.id);
569655
}
570656
}
@@ -724,11 +810,16 @@ Network ParseNetwork(std::istream &is) {
724810
}
725811

726812
Network ParseNetwork(const std::string &filename) {
727-
std::ifstream is(filename);
728813
auto quoted_filename = "\'" + filename + "\'";
814+
if (!std::filesystem::exists(filename)) {
815+
Error("PSS(R)E parser: File not found: " + quoted_filename);
816+
}
817+
818+
std::ifstream is(filename);
729819
if (!is) {
730-
Error("Failed to open file: " + quoted_filename);
820+
Error("PSS(R)E parser: Failed to open file: " + quoted_filename);
731821
}
822+
732823
ExaGOLog(EXAGO_LOG_INFO, "Parsing PSS(R)E raw data file: " + quoted_filename);
733824
auto nw = ParseNetwork(is);
734825
nw.file_name = filename;

src/utils/utils.cpp

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -61,15 +61,14 @@ namespace {
6161
template <typename T>
6262
std::string ExaGOFormatOption(ExaGOOption<T> const &opt, std::size_t indent = 0,
6363
std::string indentstr = "\t") {
64-
using std::is_same;
65-
using U =
66-
typename std::remove_reference<typename std::remove_cv<T>::type>::type;
64+
using std::is_same_v;
65+
using U = std::remove_reference_t<std::remove_cv_t<T>>;
6766
std::string typestr =
68-
(is_same<U, bool>::value or is_same<U, PetscBool>::value)
67+
(is_same_v<U, bool> or is_same_v<U, PetscBool>)
6968
? "bool"
70-
: is_same<U, double>::value
69+
: is_same_v<U, double>
7170
? "real"
72-
: is_same<U, int>::value ? "int" : "unknown_type";
71+
: is_same_v<U, int> ? "int" : "unknown_type";
7372
std::string tab = "";
7473
for (int i = 0; i < indent; i++)
7574
tab += "\t";

tests/unit/ps/CMakeLists.txt

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,8 @@
1-
configure_file(
2-
${CMAKE_CURRENT_SOURCE_DIR}/ieee9bus_v33.raw
3-
${CMAKE_CURRENT_BINARY_DIR}/ieee9bus_v33.raw COPYONLY
4-
)
5-
configure_file(
6-
${CMAKE_CURRENT_SOURCE_DIR}/ieee9bus_v34_shunts.raw
7-
${CMAKE_CURRENT_BINARY_DIR}/ieee9bus_v34_shunts.raw COPYONLY
8-
)
9-
101
add_executable(test_psse_parser test_psse_parser.cpp)
112
target_link_libraries(test_psse_parser ExaGO::PS)
12-
add_test(NAME psse_parser COMMAND test_psse_parser)
3+
execute_process(COMMAND ${CMAKE_COMMAND} -E copy_directory
4+
${CMAKE_CURRENT_SOURCE_DIR}/data ${CMAKE_CURRENT_BINARY_DIR}/data)
5+
add_test(NAME psse_parser
6+
COMMAND test_psse_parser
7+
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/data
8+
)

tests/unit/ps/data/case0.raw

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
,,32
2+
Header 2
3+
Header 3
4+
1 'Line 1 ' 1.1000 -.1 1.1e-2 '1' '1.1' A,, 'A' /, "comment 1"
5+
2,'Line, "2"', 2.2000 , -0.2 , 0.022, '2 ' ,' 2.2',B , , 'B' / comment, '2'
6+
3 "Line, '3'" 3.3000 -0.3 33E-3 ' 3 ' ' 3.3' C , , 'C' / 'comment 3'
7+
4 'Line 4' 44e-1 -0.4 0.44E-1 '4' '4.4' D , , 'D' ///comment,4
8+
0 / END OF TEST1 DATA, BEGIN TEST2 DATA
9+
1 'Line 1 ' 1.1000 -.1 1.1e-2 '1' '1.1' A,, 'A' /, "comment 1"
10+
2,'Line, "2"', 2.2000 , -0.2 , 0.022, '2 ' ,' 2.2',B , , 'B' / comment, '2'
11+
Q
12+
3 "Line, '3'" 3.3000 -0.3 33E-3 ' 3 ' ' 3.3' C , , 'C' / 'comment 3'
13+
4 'Line 4' 44e-1 -0.4 0.44E-1 '4' '4.4' D , , 'D' ///comment,4
14+
0 / END OF TEST2 DATA

0 commit comments

Comments
 (0)