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/GridKit/Model/PhasorDynamics/SystemModelDataJSONParser.hpp b/GridKit/Model/PhasorDynamics/SystemModelDataJSONParser.hpp index 29d86a300..00a6278c2 100644 --- a/GridKit/Model/PhasorDynamics/SystemModelDataJSONParser.hpp +++ b/GridKit/Model/PhasorDynamics/SystemModelDataJSONParser.hpp @@ -64,7 +64,7 @@ namespace GridKit auto delim = raw_mon.value("delim", std::string(",")); if (format.has_value()) { - sm.monitor_sink.emplace_back(file_name, format.value(), delim); + sm.monitor_sink.emplace_back(format.value(), file_name, delim); } else { diff --git a/GridKit/Model/VariableMonitor.hpp b/GridKit/Model/VariableMonitor.hpp index 3aa064554..64b1e5987 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 + std::string formatReal(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{}) + { + return std::string(buffer.data(), ptr); + } + + std::ostringstream os; + os.precision(precision); + os << std::scientific << value; + return os.str(); + } + } // namespace VariableMonitorDetail + /** * @enum VariableMonitorFormat * Available formats for monitor output @@ -74,10 +102,10 @@ namespace GridKit */ struct SinkSpec { - /// Output file name (empty for stdout) - std::string file_name; /// Output format Format format; + /// Output file name (empty for stdout) + std::string file_name{}; /// Delimiter (used only with CSV format currently) std::string delim{","}; }; @@ -96,13 +124,13 @@ namespace GridKit /** * @brief Print items relevant to the start of a file */ - virtual void printHeader(std::ostream&, Csv) const = 0; + virtual void appendHeader(std::string&, Csv) const = 0; - virtual void printHeader(std::ostream&, Json) const + virtual void appendHeader(std::string&, Json) const { } - virtual void printHeader(std::ostream&, Yaml) const + virtual void appendHeader(std::string&, Yaml) const { } @@ -112,9 +140,9 @@ 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 append(std::string&, Csv) const = 0; + virtual void append(std::string&, Json) const = 0; + virtual void append(std::string&, Yaml) const = 0; ///@} @@ -122,15 +150,15 @@ namespace GridKit /** * @brief Print items relevant to the end of a file */ - virtual void printFooter(std::ostream&, Csv) const + 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 79db154ca..c7fea39e1 100644 --- a/GridKit/Model/VariableMonitorController.hpp +++ b/GridKit/Model/VariableMonitorController.hpp @@ -72,11 +72,11 @@ namespace GridKit * * @param spec Specifies details for the sink. */ - void addSink(const SinkSpec& spec) + void addSink(const SinkSpec& spec, std::ostream& os = std::cout) { if (spec.file_name.empty()) { - sinks_.push_back(make_sink(spec, std::cout)); + sinks_.push_back(make_sink(spec, os)); } else { @@ -125,66 +125,140 @@ namespace GridKit } } - /// @copydoc VariableMonitorBase::printHeader - using VariableMonitorBase::printHeader; + /** + * @brief Organize header output for this and all submonitors + */ + template + void printFullHeader(std::ostream& os, FormatT fmt) const + { + buffer_.clear(); + appendHeader(buffer_, fmt); + buffer_ += '\n'; + os.write(buffer_.data(), static_cast(buffer_.size())); + } - void printHeader(std::ostream& os, Csv csv) const override + /** + * @brief Print header for all sinks + */ + void printHeader() const { - os << "t"; - for (auto&& var : variables_) + 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 + /** + * @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())); } /** - * @brief Organize header output for this and all submonitors + * @brief Print variables to each sink */ - template - void printFullHeader(std::ostream& os, FormatT fmt) const + void print() const { - this->printHeader(os, fmt); - for (auto* mon : monitors_) + for (auto&& sink : sinks_) { - mon->printHeader(os, fmt); + 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); } - os << '\n'; } /** - * @brief Print header for all sinks + * @brief Organize footer output for this and all submonitors */ - void printHeader() const + template + void printFullFooter(std::ostream& os, FormatT fmt) const + { + buffer_.clear(); + appendFooter(buffer_, fmt); + os.write(buffer_.data(), static_cast(buffer_.size())); + } + + /** + * @brief Print footer for all sinks + */ + 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 + { + out += "t"; + for (auto&& var : variables_) + { + 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 { - os << *time_; + for (auto* mon : monitors_) + { + mon->appendHeader(out, yaml); + } + } + + void append(std::string& out, Csv csv) const override + { + out += VariableMonitorDetail::formatReal(*time_); for (auto&& var : variables_) { - os << csv.delim << *var.value; + out += csv.delim; + out += VariableMonitorDetail::formatReal(*var.value); } for (auto* mon : monitors_) { - mon->print(os, 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) { @@ -206,17 +280,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, ' '); @@ -227,76 +309,36 @@ namespace GridKit for (auto* mon : monitors_) { - mon->print(os, Yaml()); + std::string monitor_out; + mon->append(monitor_out, yaml); + os << monitor_out; } - } - /** - * @brief Organize variable output for this and all submonitors - */ - template - void printFull(std::ostream& os, FormatT fmt) const - { - 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); + out += os.str(); } - /** - * @brief Print variables to each sink - */ - void print() const + void appendFooter(std::string& out, Csv csv) const override { - 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); } } @@ -439,6 +481,9 @@ namespace GridKit /// Collection of extra top-level monitored variables std::vector variables_; + + /// Reused output buffer + mutable std::string buffer_; }; } // namespace Model } // namespace GridKit diff --git a/GridKit/Model/VariableMonitorImpl.hpp b/GridKit/Model/VariableMonitorImpl.hpp index eda73332e..a31399b18 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 void set(VariableEnum v, FuncT f) { - f_[static_cast(enum_integer(v))] = ValuePrinter{f}; + f_[static_cast(enum_integer(v))] = ValueFormatter{f}; } private: @@ -106,124 +106,128 @@ namespace GridKit * @brief Functors to handle printing different types */ template - struct ValuePrinterImpl + struct ValueFormatterImpl { FuncT f; - void operator()(std::ostream& os) const + std::string operator()() const { - os << f(); + using RetValueT = std::remove_cvref_t; + + if constexpr (std::is_floating_point_v + || std::is_same_v) + { + return VariableMonitorDetail::formatReal(static_cast(f())); + } + else + { + return (std::ostringstream{} << f()).str(); + } } }; template - struct ValuePrinterImpl - { - FuncT f; + using ValueFormatterType = + ValueFormatterImpl>; - void operator()(std::ostream& os) const - { - os << static_cast(f()); - } - }; - - template - using ValuePrinterType = - ValuePrinterImpl>; - - class ValuePrinter + class ValueFormatter { public: - ValuePrinter() = default; + ValueFormatter() = default; template - ValuePrinter(FuncT f) - : impl_{ValuePrinterType(f)} + ValueFormatter(FuncT f) + : impl_{ValueFormatterType{f}} { } - private: - std::function impl_; - - friend std::ostream& operator<<(std::ostream& os, const ValuePrinter& p) + std::string operator()() const { - p.impl_(os); - return os; + return impl_(); } + + private: + std::function impl_; }; ///@} - void printHeader(std::ostream& os, Csv csv) const override + void appendHeader(std::string& out, Csv csv) const override { for (auto v : variables_) { - os << csv.delim << label_ << '_' << enum_name(v); + out += csv.delim; + out += label_; + out += '_'; + out.append(enum_name(v)); } } - void print(std::ostream& os, Csv csv) const override + void append(std::string& out, Csv csv) const override { for (auto v : variables_) { - os << csv.delim << f_[static_cast(enum_integer(v))]; + out += csv.delim; + out += f_[static_cast(enum_integer(v))](); } } /** - * @brief Print single variable + * @brief Append single variable */ - void print(std::ostream& os, VariableEnum v, Json) const + void appendVariable(std::string& out, VariableEnum v, Json) const { - os << indent_ << std::quoted(enum_name(v)) << ": " - << f_[static_cast(enum_integer(v))] << ",\n"; + out += indent_ + "\""; + out.append(enum_name(v)); + out += "\": "; + out += f_[static_cast(enum_integer(v))](); + out += ",\n"; } - void print(std::ostream& os, Json) const override + void append(std::string& out, Json) const override { if (empty()) { return; } - os << indent_ << std::quoted(label_) << ": {\n"; + + out += indent_ + "\"" + label_ + "\": {\n"; indent_.append(2, ' '); - std::ostringstream v_os; - v_os.copyfmt(os); for (auto v : variables_) { - print(v_os, v, Json()); + appendVariable(out, v, Json()); } - auto vars = v_os.view(); - vars.remove_suffix(2); - os << vars << '\n'; + // Remove comma after last entry + out.erase(out.size() - 2); + out += '\n'; indent_.erase(indent_.size() - 2); - os << indent_ << "}"; - } - - void printHeader(std::ostream&, Yaml) const override - { + out += indent_ + "}"; } /** - * @brief Print single variable + * @brief Append single variable */ - void print(std::ostream& os, VariableEnum v, Yaml) const + void appendVariable(std::string& out, VariableEnum v, Yaml) const { - os << indent_ << enum_name(v) << ": " << f_[static_cast(enum_integer(v))] - << '\n'; + out += indent_; + out.append(enum_name(v)); + out += ": "; + out += f_[static_cast(enum_integer(v))](); + out += '\n'; } - void print(std::ostream& os, Yaml) const override + void append(std::string& out, Yaml) const override { if (empty()) { return; } - os << indent_ << label_ << ":\n"; + + out += indent_ + label_ + ":\n"; indent_.append(2, ' '); for (auto v : variables_) { - print(os, v, Yaml()); + appendVariable(out, v, Yaml()); } indent_.erase(indent_.size() - 2); } @@ -233,13 +237,13 @@ namespace GridKit static constexpr auto enum_size_ = enum_count(); /// Set of functions associated with each enum value - std::array f_; + std::array f_; /// Set of selected enum values - std::vector variables_; + std::vector variables_; /// Indent string used for formatting - mutable std::string indent_{" "}; + mutable std::string indent_{" "}; /// Monitor disambiguation label - std::string label_; + std::string label_; }; } // namespace Model } // namespace GridKit diff --git a/application/PhasorDynamics/AnalysisUtilities.hpp b/application/PhasorDynamics/AnalysisUtilities.hpp index 780c8a8fc..58cf0ba13 100644 --- a/application/PhasorDynamics/AnalysisUtilities.hpp +++ b/application/PhasorDynamics/AnalysisUtilities.hpp @@ -162,7 +162,7 @@ namespace GridKit if (model_output_file.empty()) { // Add study output file to model if one did not already exist - data.model_data.monitor_sink.emplace_back(data.output_file, csv); + data.model_data.monitor_sink.emplace_back(csv, data.output_file); } else { diff --git a/tests/UnitTests/PhasorDynamics/GenClassicalTests.hpp b/tests/UnitTests/PhasorDynamics/GenClassicalTests.hpp index 77b229471..d17855534 100644 --- a/tests/UnitTests/PhasorDynamics/GenClassicalTests.hpp +++ b/tests/UnitTests/PhasorDynamics/GenClassicalTests.hpp @@ -178,7 +178,8 @@ namespace GridKit monitor.addMonitor(gen.getMonitor()); std::stringstream os; - monitor.print(os, Model::VariableMonitorBase::Csv{}); + monitor.addSink({Model::VariableMonitorFormat::CSV}, os); + monitor.print(); auto values = Tokenizer(os.str(), ',')(); if (values.size() == 3) diff --git a/tests/UnitTests/PhasorDynamics/GenrouTests.hpp b/tests/UnitTests/PhasorDynamics/GenrouTests.hpp index b9d2dbace..81eae0e09 100644 --- a/tests/UnitTests/PhasorDynamics/GenrouTests.hpp +++ b/tests/UnitTests/PhasorDynamics/GenrouTests.hpp @@ -169,7 +169,8 @@ namespace GridKit monitor.addMonitor(gen.getMonitor()); std::stringstream os; - monitor.print(os, Model::VariableMonitorBase::Csv{}); + monitor.addSink({Model::VariableMonitorFormat::CSV}, os); + monitor.print(); auto values = Tokenizer(os.str(), ',')(); if (values.size() == 3) diff --git a/tests/UnitTests/PhasorDynamics/GensalTests.hpp b/tests/UnitTests/PhasorDynamics/GensalTests.hpp index b84dd70cc..ebc207686 100644 --- a/tests/UnitTests/PhasorDynamics/GensalTests.hpp +++ b/tests/UnitTests/PhasorDynamics/GensalTests.hpp @@ -211,7 +211,8 @@ namespace GridKit monitor.addMonitor(gen.getMonitor()); std::stringstream os; - monitor.print(os, Model::VariableMonitorBase::Csv{}); + monitor.addSink({Model::VariableMonitorFormat::CSV}, os); + monitor.print(); auto values = Tokenizer(os.str(), ',')(); if (values.size() == 3) diff --git a/tests/UnitTests/PhasorDynamics/SystemTests.hpp b/tests/UnitTests/PhasorDynamics/SystemTests.hpp index 28d8f3299..94bd68412 100644 --- a/tests/UnitTests/PhasorDynamics/SystemTests.hpp +++ b/tests/UnitTests/PhasorDynamics/SystemTests.hpp @@ -2,6 +2,8 @@ #include #include +#include +#include #include #include