From c149a7a768118966c80b135fb9cee3991c427bcf Mon Sep 17 00:00:00 2001 From: lukelowry Date: Thu, 21 May 2026 23:44:01 -0500 Subject: [PATCH 1/3] Optimize CSV monitor output --- GridKit/Model/VariableMonitor.hpp | 45 ++++++++++++++++ GridKit/Model/VariableMonitorController.hpp | 49 ++++++++++++++++++ GridKit/Model/VariableMonitorImpl.hpp | 57 ++++++++++++++++++++- 3 files changed, 150 insertions(+), 1 deletion(-) diff --git a/GridKit/Model/VariableMonitor.hpp b/GridKit/Model/VariableMonitor.hpp index 3aa064554..a1a1aa475 100644 --- a/GridKit/Model/VariableMonitor.hpp +++ b/GridKit/Model/VariableMonitor.hpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -11,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -24,6 +26,32 @@ namespace GridKit template class VariableMonitorController; + namespace VariableMonitorDetail + { + template + void appendCsvReal(std::string& out, RealT value) + { + std::array buffer{}; + constexpr auto precision = std::numeric_limits::digits10 + 1; + + auto [ptr, ec] = std::to_chars(buffer.data(), + buffer.data() + buffer.size(), + value, + std::chars_format::scientific, + precision); + if (ec == std::errc{}) + { + out.append(buffer.data(), static_cast(ptr - buffer.data())); + return; + } + + std::ostringstream os; + os.precision(precision); + os << std::scientific << value; + out += os.str(); + } + } // namespace VariableMonitorDetail + /** * @enum VariableMonitorFormat * Available formats for monitor output @@ -106,6 +134,13 @@ namespace GridKit { } + virtual void appendCsvHeader(std::string& out, Csv csv) const + { + std::ostringstream os; + printHeader(os, csv); + out += os.str(); + } + ///@} ///@{ @@ -116,6 +151,16 @@ namespace GridKit virtual void print(std::ostream&, Json) const = 0; virtual void print(std::ostream&, Yaml) const = 0; + virtual void appendCsv(std::string& out, Csv csv) const + { + std::ostringstream os; + constexpr auto max_prec = std::numeric_limits::digits10 + 1; + os.precision(max_prec); + os << std::scientific; + print(os, csv); + out += os.str(); + } + ///@} ///@{ diff --git a/GridKit/Model/VariableMonitorController.hpp b/GridKit/Model/VariableMonitorController.hpp index 79db154ca..5cd60ff88 100644 --- a/GridKit/Model/VariableMonitorController.hpp +++ b/GridKit/Model/VariableMonitorController.hpp @@ -142,6 +142,29 @@ namespace GridKit os << "[\n"; } + void appendCsvHeader(std::string& out, Csv csv) const override + { + out += "t"; + for (auto&& var : variables_) + { + out += csv.delim; + out += var.label; + } + + for (auto* mon : monitors_) + { + mon->appendCsvHeader(out, csv); + } + } + + void printFullHeader(std::ostream& os, Csv csv) const + { + csv_line_.clear(); + appendCsvHeader(csv_line_, csv); + csv_line_ += '\n'; + os.write(csv_line_.data(), static_cast(csv_line_.size())); + } + /** * @brief Organize header output for this and all submonitors */ @@ -183,6 +206,21 @@ namespace GridKit } } + void appendCsv(std::string& out, Csv csv) const override + { + VariableMonitorDetail::appendCsvReal(out, *time_); + for (auto&& var : variables_) + { + out += csv.delim; + VariableMonitorDetail::appendCsvReal(out, *var.value); + } + + for (auto* mon : monitors_) + { + mon->appendCsv(out, csv); + } + } + void print(std::ostream& os, Json json) const override { std::string indent = " "; @@ -231,6 +269,14 @@ namespace GridKit } } + void printFull(std::ostream& os, Csv csv) const + { + csv_line_.clear(); + appendCsv(csv_line_, csv); + csv_line_ += '\n'; + os.write(csv_line_.data(), static_cast(csv_line_.size())); + } + /** * @brief Organize variable output for this and all submonitors */ @@ -439,6 +485,9 @@ namespace GridKit /// Collection of extra top-level monitored variables std::vector variables_; + + /// Reused line buffer for CSV output + mutable std::string csv_line_; }; } // namespace Model } // namespace GridKit diff --git a/GridKit/Model/VariableMonitorImpl.hpp b/GridKit/Model/VariableMonitorImpl.hpp index eda73332e..0421d8b10 100644 --- a/GridKit/Model/VariableMonitorImpl.hpp +++ b/GridKit/Model/VariableMonitorImpl.hpp @@ -114,6 +114,22 @@ namespace GridKit { os << f(); } + + void appendCsv(std::string& out) const + { + using RetValueT = std::remove_cvref_t; + + if constexpr (std::is_floating_point_v) + { + VariableMonitorDetail::appendCsvReal(out, static_cast(f())); + } + else + { + std::ostringstream os; + os << f(); + out += os.str(); + } + } }; template @@ -125,6 +141,11 @@ namespace GridKit { os << static_cast(f()); } + + void appendCsv(std::string& out) const + { + VariableMonitorDetail::appendCsvReal(out, static_cast(f())); + } }; template @@ -138,12 +159,26 @@ namespace GridKit template ValuePrinter(FuncT f) - : impl_{ValuePrinterType(f)} { + auto printer = ValuePrinterType{f}; + impl_ = [printer](std::ostream& os) + { + printer(os); + }; + append_csv_ = [printer](std::string& out) + { + printer.appendCsv(out); + }; + } + + void appendCsv(std::string& out) const + { + append_csv_(out); } private: std::function impl_; + std::function append_csv_; friend std::ostream& operator<<(std::ostream& os, const ValuePrinter& p) { @@ -170,6 +205,26 @@ namespace GridKit } } + void appendCsvHeader(std::string& out, Csv csv) const override + { + for (auto v : variables_) + { + out += csv.delim; + out += label_; + out += '_'; + out += enum_name(v); + } + } + + void appendCsv(std::string& out, Csv csv) const override + { + for (auto v : variables_) + { + out += csv.delim; + f_[static_cast(enum_integer(v))].appendCsv(out); + } + } + /** * @brief Print single variable */ From f1c348dff39f0c34300ffb8039318a5471fdd01d Mon Sep 17 00:00:00 2001 From: lukelowry Date: Mon, 25 May 2026 04:53:48 -0500 Subject: [PATCH 2/3] unify variable monitor output buffer api --- GridKit/Model/VariableMonitor.hpp | 88 +++++-- GridKit/Model/VariableMonitorController.hpp | 221 +++++++++--------- GridKit/Model/VariableMonitorImpl.hpp | 78 +++---- .../UnitTests/PhasorDynamics/SystemTests.hpp | 90 +++++++ .../PhasorDynamics/runSystemTests.cpp | 1 + 5 files changed, 307 insertions(+), 171 deletions(-) diff --git a/GridKit/Model/VariableMonitor.hpp b/GridKit/Model/VariableMonitor.hpp index a1a1aa475..b97dbcb83 100644 --- a/GridKit/Model/VariableMonitor.hpp +++ b/GridKit/Model/VariableMonitor.hpp @@ -29,7 +29,7 @@ namespace GridKit namespace VariableMonitorDetail { template - void appendCsvReal(std::string& out, RealT value) + void appendReal(std::string& out, RealT value) { std::array buffer{}; constexpr auto precision = std::numeric_limits::digits10 + 1; @@ -124,21 +124,35 @@ namespace GridKit /** * @brief Print items relevant to the start of a file */ - virtual void printHeader(std::ostream&, Csv) const = 0; + virtual void printHeader(std::ostream& os, Csv csv) const + { + std::string out; + appendHeader(out, csv); + os.write(out.data(), static_cast(out.size())); + } - virtual void printHeader(std::ostream&, Json) const + virtual void printHeader(std::ostream& os, Json json) const { + std::string out; + appendHeader(out, json); + os.write(out.data(), static_cast(out.size())); } - virtual void printHeader(std::ostream&, Yaml) const + virtual void printHeader(std::ostream& os, Yaml yaml) const { + std::string out; + appendHeader(out, yaml); + os.write(out.data(), static_cast(out.size())); } - virtual void appendCsvHeader(std::string& out, Csv csv) const + virtual void appendHeader(std::string&, Csv) const = 0; + + virtual void appendHeader(std::string&, Json) const + { + } + + virtual void appendHeader(std::string&, Yaml) const { - std::ostringstream os; - printHeader(os, csv); - out += os.str(); } ///@} @@ -147,35 +161,67 @@ namespace GridKit /** * @brief Print monitored variables at current state */ - virtual void print(std::ostream&, Csv) const = 0; - virtual void print(std::ostream&, Json) const = 0; - virtual void print(std::ostream&, Yaml) const = 0; + virtual void print(std::ostream& os, Csv csv) const + { + std::string out; + append(out, csv); + os.write(out.data(), static_cast(out.size())); + } - virtual void appendCsv(std::string& out, Csv csv) const + virtual void print(std::ostream& os, Json json) const { - std::ostringstream os; - constexpr auto max_prec = std::numeric_limits::digits10 + 1; - os.precision(max_prec); - os << std::scientific; - print(os, csv); - out += os.str(); + std::string out; + append(out, json); + os.write(out.data(), static_cast(out.size())); } + virtual void print(std::ostream& os, Yaml yaml) const + { + std::string out; + append(out, yaml); + os.write(out.data(), static_cast(out.size())); + } + + virtual void append(std::string&, Csv) const = 0; + virtual void append(std::string&, Json) const = 0; + virtual void append(std::string&, Yaml) const = 0; + ///@} ///@{ /** * @brief Print items relevant to the end of a file */ - virtual void printFooter(std::ostream&, Csv) const + virtual void printFooter(std::ostream& os, Csv csv) const + { + std::string out; + appendFooter(out, csv); + os.write(out.data(), static_cast(out.size())); + } + + virtual void printFooter(std::ostream& os, Json json) const + { + std::string out; + appendFooter(out, json); + os.write(out.data(), static_cast(out.size())); + } + + virtual void printFooter(std::ostream& os, Yaml yaml) const + { + std::string out; + appendFooter(out, yaml); + os.write(out.data(), static_cast(out.size())); + } + + virtual void appendFooter(std::string&, Csv) const { } - virtual void printFooter(std::ostream&, Json) const + virtual void appendFooter(std::string&, Json) const { } - virtual void printFooter(std::ostream&, Yaml) const + virtual void appendFooter(std::string&, Yaml) const { } diff --git a/GridKit/Model/VariableMonitorController.hpp b/GridKit/Model/VariableMonitorController.hpp index 5cd60ff88..578df113c 100644 --- a/GridKit/Model/VariableMonitorController.hpp +++ b/GridKit/Model/VariableMonitorController.hpp @@ -128,101 +128,146 @@ namespace GridKit /// @copydoc VariableMonitorBase::printHeader using VariableMonitorBase::printHeader; - void printHeader(std::ostream& os, Csv csv) const override + /** + * @brief Organize header output for this and all submonitors + */ + template + void printFullHeader(std::ostream& os, FormatT fmt) const { - os << "t"; - for (auto&& var : variables_) + buffer_.clear(); + appendHeader(buffer_, fmt); + buffer_ += '\n'; + os.write(buffer_.data(), static_cast(buffer_.size())); + } + + /** + * @brief Print header for all sinks + */ + void printHeader() const + { + for (auto&& sink : sinks_) { - os << csv.delim << var.label; + std::visit([this](auto&& sink) + { this->printFullHeader(*sink.os, sink.format); }, + sink); } } - void printHeader(std::ostream& os, Json) const override + /// @copydoc VariableMonitorBase::print + using VariableMonitorBase::print; + + /** + * @brief Organize variable output for this and all submonitors + */ + template + void printFull(std::ostream& os, FormatT fmt) const { - os << "[\n"; + buffer_.clear(); + append(buffer_, fmt); + buffer_ += '\n'; + os.write(buffer_.data(), static_cast(buffer_.size())); } - void appendCsvHeader(std::string& out, Csv csv) const override + /** + * @brief Print variables to each sink + */ + void print() const { - out += "t"; - for (auto&& var : variables_) - { - out += csv.delim; - out += var.label; - } - - for (auto* mon : monitors_) + for (auto&& sink : sinks_) { - mon->appendCsvHeader(out, csv); + std::visit([this](auto&& sink) + { + this->printFull(*sink.os, sink.format); + using T = std::remove_cvref_t; + if constexpr (std::is_same_v>) + { + sink.format.after_first = true; + } }, + sink); } } - void printFullHeader(std::ostream& os, Csv csv) const - { - csv_line_.clear(); - appendCsvHeader(csv_line_, csv); - csv_line_ += '\n'; - os.write(csv_line_.data(), static_cast(csv_line_.size())); - } + /// @copydoc VariableMonitorBase::printFooter + using VariableMonitorBase::printFooter; /** - * @brief Organize header output for this and all submonitors + * @brief Organize footer output for this and all submonitors */ template - void printFullHeader(std::ostream& os, FormatT fmt) const + void printFullFooter(std::ostream& os, FormatT fmt) const { - this->printHeader(os, fmt); - for (auto* mon : monitors_) - { - mon->printHeader(os, fmt); - } - os << '\n'; + buffer_.clear(); + appendFooter(buffer_, fmt); + os.write(buffer_.data(), static_cast(buffer_.size())); } /** - * @brief Print header for all sinks + * @brief Print footer for all sinks */ - void printHeader() const + void printFooter() const { for (auto&& sink : sinks_) { std::visit([this](auto&& sink) - { this->printFullHeader(*sink.os, sink.format); }, + { this->printFullFooter(*sink.os, sink.format); }, sink); } } - void print(std::ostream& os, Csv csv) const override + protected: + void appendHeader(std::string& out, Csv csv) const override { - os << *time_; + out += "t"; for (auto&& var : variables_) { - os << csv.delim << *var.value; + out += csv.delim; + out += var.label; + } + + for (auto* mon : monitors_) + { + mon->appendHeader(out, csv); + } + } + + void appendHeader(std::string& out, Json json) const override + { + out += "[\n"; + for (auto* mon : monitors_) + { + mon->appendHeader(out, json); } + } + void appendHeader(std::string& out, Yaml yaml) const override + { for (auto* mon : monitors_) { - mon->print(os, csv); + mon->appendHeader(out, yaml); } } - void appendCsv(std::string& out, Csv csv) const override + void append(std::string& out, Csv csv) const override { - VariableMonitorDetail::appendCsvReal(out, *time_); + VariableMonitorDetail::appendReal(out, *time_); for (auto&& var : variables_) { out += csv.delim; - VariableMonitorDetail::appendCsvReal(out, *var.value); + VariableMonitorDetail::appendReal(out, *var.value); } for (auto* mon : monitors_) { - mon->appendCsv(out, csv); + mon->append(out, csv); } } - void print(std::ostream& os, Json json) const override + void append(std::string& out, Json json) const override { + std::ostringstream os; + os.precision(std::numeric_limits::digits10 + 1); + os << std::scientific; + std::string indent = " "; if (json.after_first) { @@ -244,17 +289,25 @@ namespace GridKit { os << ",\n"; } - mon->print(os, Json()); + std::string monitor_out; + mon->append(monitor_out, Json()); + os << monitor_out; after_first = true; } indent.erase(indent.size() - 2); os << '\n' << indent << "}"; + + out += os.str(); } - void print(std::ostream& os, Yaml) const override + void append(std::string& out, Yaml yaml) const override { + std::ostringstream os; + os.precision(std::numeric_limits::digits10 + 1); + os << std::scientific; + std::string indent = " "; os << indent << "- t: " << *time_ << '\n'; indent.append(2, ' '); @@ -265,84 +318,36 @@ namespace GridKit for (auto* mon : monitors_) { - mon->print(os, Yaml()); + std::string monitor_out; + mon->append(monitor_out, yaml); + os << monitor_out; } - } - void printFull(std::ostream& os, Csv csv) const - { - csv_line_.clear(); - appendCsv(csv_line_, csv); - csv_line_ += '\n'; - os.write(csv_line_.data(), static_cast(csv_line_.size())); + out += os.str(); } - /** - * @brief Organize variable output for this and all submonitors - */ - template - void printFull(std::ostream& os, FormatT fmt) const + void appendFooter(std::string& out, Csv csv) const override { - const auto orig_prec = os.precision(); - constexpr auto max_prec = std::numeric_limits::digits10 + 1; - os.precision(max_prec); - os << std::scientific; - this->print(os, fmt); - os << '\n'; - os << std::defaultfloat; - os.precision(orig_prec); - } - - /** - * @brief Print variables to each sink - */ - void print() const - { - for (auto&& sink : sinks_) + for (auto* mon : monitors_) { - std::visit([this](auto&& sink) - { - this->printFull(*sink.os, sink.format); - using T = std::remove_cvref_t; - if constexpr (std::is_same_v>) - { - sink.format.after_first = true; - } }, - sink); + mon->appendFooter(out, csv); } } - /// @copydoc VariableMonitorBase::printFooter - using VariableMonitorBase::printFooter; - - void printFooter(std::ostream& os, Json) const override - { - os << "\n]\n"; - } - - /** - * @brief Organize footer output for this and all submonitors - */ - template - void printFullFooter(std::ostream& os, FormatT format) const + void appendFooter(std::string& out, Json json) const override { for (auto* mon : monitors_) { - mon->printFooter(os, format); + mon->appendFooter(out, json); } - this->printFooter(os, format); + out += "\n]\n"; } - /** - * @brief Print footer for all sinks - */ - void printFooter() const + void appendFooter(std::string& out, Yaml yaml) const override { - for (auto&& sink : sinks_) + for (auto* mon : monitors_) { - std::visit([this](auto&& sink) - { this->printFullFooter(*sink.os, sink.format); }, - sink); + mon->appendFooter(out, yaml); } } @@ -486,8 +491,8 @@ namespace GridKit /// Collection of extra top-level monitored variables std::vector variables_; - /// Reused line buffer for CSV output - mutable std::string csv_line_; + /// Reused output buffer + mutable std::string buffer_; }; } // namespace Model } // namespace GridKit diff --git a/GridKit/Model/VariableMonitorImpl.hpp b/GridKit/Model/VariableMonitorImpl.hpp index 0421d8b10..b96fac674 100644 --- a/GridKit/Model/VariableMonitorImpl.hpp +++ b/GridKit/Model/VariableMonitorImpl.hpp @@ -19,11 +19,11 @@ namespace GridKit * @brief Manage printing variables associated with a specific component or * bus * - * Implementations of the print functions print under the assumption of a - * larger context. For example, the Csv version prints the delimiter and the - * value (or label for the header) for each monitored value without a line - * break. That way other monitors can print likewise on the same line, and - * the line can be ended by the control monitor. + * Append functions assume a larger output context. For example, the CSV + * version appends the delimiter and the value (or label for the header) for + * each monitored value without a line break. That way other monitors can + * append likewise on the same line, and the line can be ended by the control + * monitor. */ template ; if constexpr (std::is_floating_point_v) { - VariableMonitorDetail::appendCsvReal(out, static_cast(f())); + VariableMonitorDetail::appendReal(out, static_cast(f())); } else { @@ -142,9 +142,9 @@ namespace GridKit os << static_cast(f()); } - void appendCsv(std::string& out) const + void appendValue(std::string& out) const { - VariableMonitorDetail::appendCsvReal(out, static_cast(f())); + VariableMonitorDetail::appendReal(out, static_cast(f())); } }; @@ -165,20 +165,20 @@ namespace GridKit { printer(os); }; - append_csv_ = [printer](std::string& out) + append_value_ = [printer](std::string& out) { - printer.appendCsv(out); + printer.appendValue(out); }; } - void appendCsv(std::string& out) const + void appendValue(std::string& out) const { - append_csv_(out); + append_value_(out); } private: std::function impl_; - std::function append_csv_; + std::function append_value_; friend std::ostream& operator<<(std::ostream& os, const ValuePrinter& p) { @@ -189,23 +189,7 @@ namespace GridKit ///@} - void printHeader(std::ostream& os, Csv csv) const override - { - for (auto v : variables_) - { - os << csv.delim << label_ << '_' << enum_name(v); - } - } - - void print(std::ostream& os, Csv csv) const override - { - for (auto v : variables_) - { - os << csv.delim << f_[static_cast(enum_integer(v))]; - } - } - - void appendCsvHeader(std::string& out, Csv csv) const override + void appendHeader(std::string& out, Csv csv) const override { for (auto v : variables_) { @@ -216,71 +200,81 @@ namespace GridKit } } - void appendCsv(std::string& out, Csv csv) const override + void append(std::string& out, Csv csv) const override { for (auto v : variables_) { out += csv.delim; - f_[static_cast(enum_integer(v))].appendCsv(out); + f_[static_cast(enum_integer(v))].appendValue(out); } } /** * @brief Print single variable */ - void print(std::ostream& os, VariableEnum v, Json) const + void printVariable(std::ostream& os, VariableEnum v, Json) const { os << indent_ << std::quoted(enum_name(v)) << ": " << f_[static_cast(enum_integer(v))] << ",\n"; } - void print(std::ostream& os, Json) const override + void append(std::string& out, Json) const override { if (empty()) { return; } + + std::ostringstream os; + os.precision(std::numeric_limits::digits10 + 1); + os << std::scientific; + os << indent_ << std::quoted(label_) << ": {\n"; indent_.append(2, ' '); std::ostringstream v_os; v_os.copyfmt(os); for (auto v : variables_) { - print(v_os, v, Json()); + printVariable(v_os, v, Json()); } auto vars = v_os.view(); vars.remove_suffix(2); os << vars << '\n'; indent_.erase(indent_.size() - 2); os << indent_ << "}"; - } - void printHeader(std::ostream&, Yaml) const override - { + out += os.str(); } /** * @brief Print single variable */ - void print(std::ostream& os, VariableEnum v, Yaml) const + void printVariable(std::ostream& os, VariableEnum v, Yaml) const { os << indent_ << enum_name(v) << ": " << f_[static_cast(enum_integer(v))] << '\n'; } - void print(std::ostream& os, Yaml) const override + void append(std::string& out, Yaml) const override { if (empty()) { return; } + + std::ostringstream os; + os.precision(std::numeric_limits::digits10 + 1); + os << std::scientific; + os << indent_ << label_ << ":\n"; indent_.append(2, ' '); for (auto v : variables_) { - print(os, v, Yaml()); + printVariable(os, v, Yaml()); } indent_.erase(indent_.size() - 2); + + out += os.str(); } private: diff --git a/tests/UnitTests/PhasorDynamics/SystemTests.hpp b/tests/UnitTests/PhasorDynamics/SystemTests.hpp index 28d8f3299..2b3ab18e7 100644 --- a/tests/UnitTests/PhasorDynamics/SystemTests.hpp +++ b/tests/UnitTests/PhasorDynamics/SystemTests.hpp @@ -2,6 +2,8 @@ #include #include +#include +#include #include #include @@ -151,6 +153,94 @@ namespace GridKit return success.report(__func__); } + TestOutcome monitorOutputConsistency() + { + TestStatus success = true; + + using BusDataT = PhasorDynamics::BusData; + using Variable = typename BusDataT::MonitorableVariables; + + BusDataT data; + data.name = "test_bus"; + data.Vr0 = RealT{1.25}; + data.Vi0 = RealT{-2.5}; + data.monitored_variables.insert(Variable::Vr); + data.monitored_variables.insert(Variable::Vm); + + PhasorDynamics::Bus bus(data); + bus.allocate(); + bus.initialize(); + bus.evaluateResidual(); + + RealT time{0.5}; + RealT aux{-0.0}; + + Model::VariableMonitorController monitor(time); + monitor.addVariable("aux", &aux); + monitor.addMonitor(bus.getMonitor()); + + auto strip_final_newline = [](std::string value) + { + if (!value.empty() && value.back() == '\n') + { + value.pop_back(); + } + return value; + }; + + auto state_preserved = [](auto&& print) + { + std::ostringstream os; + os.precision(4); + os << std::fixed; + const auto precision = os.precision(); + const auto flags = os.flags(); + + print(os); + + return os.precision() == precision && os.flags() == flags; + }; + + auto check_format = [&](auto fmt) + { + std::ostringstream direct_header; + monitor.printHeader(direct_header, fmt); + std::ostringstream full_header; + monitor.printFullHeader(full_header, fmt); + success *= direct_header.str() == strip_final_newline(full_header.str()); + + std::ostringstream direct_record; + monitor.print(direct_record, fmt); + std::ostringstream full_record; + monitor.printFull(full_record, fmt); + success *= direct_record.str() == strip_final_newline(full_record.str()); + + std::ostringstream direct_footer; + monitor.printFooter(direct_footer, fmt); + std::ostringstream full_footer; + monitor.printFullFooter(full_footer, fmt); + success *= direct_footer.str() == full_footer.str(); + + success *= state_preserved([&](std::ostream& os) + { monitor.printHeader(os, fmt); }); + success *= state_preserved([&](std::ostream& os) + { monitor.print(os, fmt); }); + success *= state_preserved([&](std::ostream& os) + { monitor.printFooter(os, fmt); }); + }; + + check_format(Model::VariableMonitorBase::Csv{}); + + auto json = Model::VariableMonitorBase::Json{}; + check_format(json); + json.after_first = true; + check_format(json); + + check_format(Model::VariableMonitorBase::Yaml{}); + + return success.report(__func__); + } + /** * @brief Test for exception when signals are incorrectly configured */ diff --git a/tests/UnitTests/PhasorDynamics/runSystemTests.cpp b/tests/UnitTests/PhasorDynamics/runSystemTests.cpp index a8b432be2..099a08327 100644 --- a/tests/UnitTests/PhasorDynamics/runSystemTests.cpp +++ b/tests/UnitTests/PhasorDynamics/runSystemTests.cpp @@ -10,6 +10,7 @@ int main() result += test.constructor(); result += test.composer(); + result += test.monitorOutputConsistency(); #ifdef GRIDKIT_ENABLE_ENZYME result += test.jacobian(); #endif From cd8538a6295c8f332068c187055786dd298cf58f Mon Sep 17 00:00:00 2001 From: lukelowry Date: Thu, 28 May 2026 16:34:24 -0500 Subject: [PATCH 3/3] Fix BusBase monitor instantiation and drop redundant monitor test --- GridKit/Model/PhasorDynamics/Bus/Bus.cpp | 2 + .../Bus/BusDependencyTracking.cpp | 2 + .../Model/PhasorDynamics/Bus/BusEnzyme.cpp | 2 + .../UnitTests/PhasorDynamics/SystemTests.hpp | 88 ------------------- .../PhasorDynamics/runSystemTests.cpp | 1 - 5 files changed, 6 insertions(+), 89 deletions(-) diff --git a/GridKit/Model/PhasorDynamics/Bus/Bus.cpp b/GridKit/Model/PhasorDynamics/Bus/Bus.cpp index 0971e2504..ee6d3ecd8 100644 --- a/GridKit/Model/PhasorDynamics/Bus/Bus.cpp +++ b/GridKit/Model/PhasorDynamics/Bus/Bus.cpp @@ -22,6 +22,8 @@ namespace GridKit } // Available template instantiations + template class BusBase; + template class BusBase; template class Bus; template class Bus; diff --git a/GridKit/Model/PhasorDynamics/Bus/BusDependencyTracking.cpp b/GridKit/Model/PhasorDynamics/Bus/BusDependencyTracking.cpp index fe116cf7c..cd3867343 100644 --- a/GridKit/Model/PhasorDynamics/Bus/BusDependencyTracking.cpp +++ b/GridKit/Model/PhasorDynamics/Bus/BusDependencyTracking.cpp @@ -22,6 +22,8 @@ namespace GridKit } // Available template instantiations + template class BusBase; + template class BusBase; template class Bus; template class Bus; diff --git a/GridKit/Model/PhasorDynamics/Bus/BusEnzyme.cpp b/GridKit/Model/PhasorDynamics/Bus/BusEnzyme.cpp index a315622c2..06cb2054e 100644 --- a/GridKit/Model/PhasorDynamics/Bus/BusEnzyme.cpp +++ b/GridKit/Model/PhasorDynamics/Bus/BusEnzyme.cpp @@ -59,6 +59,8 @@ namespace GridKit } // Available template instantiations + template class BusBase; + template class BusBase; template class Bus; template class Bus; diff --git a/tests/UnitTests/PhasorDynamics/SystemTests.hpp b/tests/UnitTests/PhasorDynamics/SystemTests.hpp index 2b3ab18e7..94bd68412 100644 --- a/tests/UnitTests/PhasorDynamics/SystemTests.hpp +++ b/tests/UnitTests/PhasorDynamics/SystemTests.hpp @@ -153,94 +153,6 @@ namespace GridKit return success.report(__func__); } - TestOutcome monitorOutputConsistency() - { - TestStatus success = true; - - using BusDataT = PhasorDynamics::BusData; - using Variable = typename BusDataT::MonitorableVariables; - - BusDataT data; - data.name = "test_bus"; - data.Vr0 = RealT{1.25}; - data.Vi0 = RealT{-2.5}; - data.monitored_variables.insert(Variable::Vr); - data.monitored_variables.insert(Variable::Vm); - - PhasorDynamics::Bus bus(data); - bus.allocate(); - bus.initialize(); - bus.evaluateResidual(); - - RealT time{0.5}; - RealT aux{-0.0}; - - Model::VariableMonitorController monitor(time); - monitor.addVariable("aux", &aux); - monitor.addMonitor(bus.getMonitor()); - - auto strip_final_newline = [](std::string value) - { - if (!value.empty() && value.back() == '\n') - { - value.pop_back(); - } - return value; - }; - - auto state_preserved = [](auto&& print) - { - std::ostringstream os; - os.precision(4); - os << std::fixed; - const auto precision = os.precision(); - const auto flags = os.flags(); - - print(os); - - return os.precision() == precision && os.flags() == flags; - }; - - auto check_format = [&](auto fmt) - { - std::ostringstream direct_header; - monitor.printHeader(direct_header, fmt); - std::ostringstream full_header; - monitor.printFullHeader(full_header, fmt); - success *= direct_header.str() == strip_final_newline(full_header.str()); - - std::ostringstream direct_record; - monitor.print(direct_record, fmt); - std::ostringstream full_record; - monitor.printFull(full_record, fmt); - success *= direct_record.str() == strip_final_newline(full_record.str()); - - std::ostringstream direct_footer; - monitor.printFooter(direct_footer, fmt); - std::ostringstream full_footer; - monitor.printFullFooter(full_footer, fmt); - success *= direct_footer.str() == full_footer.str(); - - success *= state_preserved([&](std::ostream& os) - { monitor.printHeader(os, fmt); }); - success *= state_preserved([&](std::ostream& os) - { monitor.print(os, fmt); }); - success *= state_preserved([&](std::ostream& os) - { monitor.printFooter(os, fmt); }); - }; - - check_format(Model::VariableMonitorBase::Csv{}); - - auto json = Model::VariableMonitorBase::Json{}; - check_format(json); - json.after_first = true; - check_format(json); - - check_format(Model::VariableMonitorBase::Yaml{}); - - return success.report(__func__); - } - /** * @brief Test for exception when signals are incorrectly configured */ diff --git a/tests/UnitTests/PhasorDynamics/runSystemTests.cpp b/tests/UnitTests/PhasorDynamics/runSystemTests.cpp index 099a08327..a8b432be2 100644 --- a/tests/UnitTests/PhasorDynamics/runSystemTests.cpp +++ b/tests/UnitTests/PhasorDynamics/runSystemTests.cpp @@ -10,7 +10,6 @@ int main() result += test.constructor(); result += test.composer(); - result += test.monitorOutputConsistency(); #ifdef GRIDKIT_ENABLE_ENZYME result += test.jacobian(); #endif