From bf1ba6372c8846d758dea07111ffe65663b968b5 Mon Sep 17 00:00:00 2001 From: Philip Fackler Date: Fri, 7 Nov 2025 10:18:02 -0600 Subject: [PATCH 01/15] Add variable monitor for solver iteration output --- GridKit/Model/Evaluator.hpp | 9 ++ .../Model/PhasorDynamics/Branch/Branch.hpp | 12 +- .../PhasorDynamics/Branch/BranchData.hpp | 18 +++ .../PhasorDynamics/Branch/BranchImpl.hpp | 96 +++++++++++----- GridKit/Model/PhasorDynamics/Bus/BusData.hpp | 36 +++--- .../PhasorDynamics/Bus/BusDataJSONParser.hpp | 29 ++--- GridKit/Model/PhasorDynamics/Bus/BusImpl.hpp | 2 +- .../PhasorDynamics/Bus/BusInfiniteImpl.hpp | 2 +- GridKit/Model/PhasorDynamics/BusBase.hpp | 27 ++++- .../PhasorDynamics/Exciter/IEEET1/Ieeet1.hpp | 10 +- .../Exciter/IEEET1/Ieeet1Data.hpp | 8 ++ .../Exciter/IEEET1/Ieeet1Impl.hpp | 29 ++++- .../SynchronousMachine/GENROUwS/Genrou.hpp | 10 +- .../GENROUwS/GenrouData.hpp | 14 +++ .../GENROUwS/GenrouImpl.hpp | 36 +++++- GridKit/Model/PhasorDynamics/SystemModel.hpp | 36 ++++++ GridKit/Model/VariableMonitor.hpp | 104 ++++++++++++++++++ GridKit/Solver/Dynamic/Ida.cpp | 11 +- GridKit/Utilities/CMakeLists.txt | 1 + GridKit/Utilities/Enum.hpp | 22 ++++ tests/UnitTests/Utilities/CaseFormatTests.hpp | 12 +- 21 files changed, 435 insertions(+), 89 deletions(-) create mode 100644 GridKit/Model/VariableMonitor.hpp create mode 100644 GridKit/Utilities/Enum.hpp diff --git a/GridKit/Model/Evaluator.hpp b/GridKit/Model/Evaluator.hpp index 12b760c6a..90cbc58a7 100644 --- a/GridKit/Model/Evaluator.hpp +++ b/GridKit/Model/Evaluator.hpp @@ -45,6 +45,15 @@ namespace GridKit virtual IdxT size() = 0; virtual IdxT nnz() = 0; + virtual bool monitoring() const + { + return false; + } + + virtual void printMonitoredVariables(std::ostream& = std::cout) const + { + } + /** * @brief Is the Jacobian defined. Used in IDA to determine wether DQ is used or not * diff --git a/GridKit/Model/PhasorDynamics/Branch/Branch.hpp b/GridKit/Model/PhasorDynamics/Branch/Branch.hpp index a20684ca0..14bb2a1f3 100644 --- a/GridKit/Model/PhasorDynamics/Branch/Branch.hpp +++ b/GridKit/Model/PhasorDynamics/Branch/Branch.hpp @@ -8,8 +8,10 @@ */ #pragma once +#include #include #include +#include // Forward declarations. namespace GridKit @@ -51,11 +53,11 @@ namespace GridKit using Component::h_; using Component::J_; + public: using RealT = typename Component::RealT; using bus_type = BusBase; using model_data_type = BranchData; - public: Branch(bus_type* bus1, bus_type* bus2); Branch(bus_type* bus1, bus_type* bus2, RealT R, RealT X, RealT G, RealT B); Branch(bus_type* bus1, bus_type* bus2, const model_data_type& data); @@ -77,7 +79,6 @@ namespace GridKit { } - public: void setR(RealT R) { R_ = R; @@ -103,7 +104,12 @@ namespace GridKit setDerivedParams(); } + bool monitoring() const override; + void printMonitoredVariables(std::ostream&) const override; + private: + void initializeParameters(const model_data_type& data); + void initializeMonitor(); void setDerivedParams(); ScalarT& Vr1() @@ -165,6 +171,8 @@ namespace GridKit /* Derivied parameters */ RealT b_; RealT g_; + + Model::VariableMonitor monitor_; }; } // namespace PhasorDynamics diff --git a/GridKit/Model/PhasorDynamics/Branch/BranchData.hpp b/GridKit/Model/PhasorDynamics/Branch/BranchData.hpp index 5f436f695..960addc54 100644 --- a/GridKit/Model/PhasorDynamics/Branch/BranchData.hpp +++ b/GridKit/Model/PhasorDynamics/Branch/BranchData.hpp @@ -7,6 +7,7 @@ #pragma once #include +#include namespace GridKit { @@ -41,8 +42,25 @@ namespace GridKit im2, p2, q2, + SIZE }; + inline const std::string& enumLabel(BranchMonitorableVariables v) + { + static const std::string labels[] = { + "Ir1", + "Ii1", + "Im1", + "P1", + "Q1", + "Ir2", + "Ii2", + "Im2", + "P2", + "Q2"}; + return labels[Utilities::enumId(v)]; + } + /** * @brief Contains modeling data for a Branch * diff --git a/GridKit/Model/PhasorDynamics/Branch/BranchImpl.hpp b/GridKit/Model/PhasorDynamics/Branch/BranchImpl.hpp index 61379d45d..9ebb0f50f 100644 --- a/GridKit/Model/PhasorDynamics/Branch/BranchImpl.hpp +++ b/GridKit/Model/PhasorDynamics/Branch/BranchImpl.hpp @@ -74,37 +74,11 @@ namespace GridKit template Branch::Branch(bus_type* bus1, bus_type* bus2, const model_data_type& data) : bus1_(bus1), - bus2_(bus2) + bus2_(bus2), + monitor_(data) { - if (data.parameters.contains(model_data_type::Parameters::R)) - { - R_ = std::get(data.parameters.at(model_data_type::Parameters::R)); - } - - if (data.parameters.contains(model_data_type::Parameters::X)) - { - X_ = std::get(data.parameters.at(model_data_type::Parameters::X)); - } - - if (data.parameters.contains(model_data_type::Parameters::G)) - { - G_ = std::get(data.parameters.at(model_data_type::Parameters::G)); - } - - if (data.parameters.contains(model_data_type::Parameters::B)) - { - B_ = std::get(data.parameters.at(model_data_type::Parameters::B)); - } - - if (data.ports.contains(model_data_type::Ports::bus1)) - { - bus1_id_ = data.ports.at(model_data_type::Ports::bus1); - } - - if (data.ports.contains(model_data_type::Ports::bus2)) - { - bus2_id_ = data.ports.at(model_data_type::Ports::bus2); - } + initializeParameters(data); + initializeMonitor(); size_ = 0; setDerivedParams(); @@ -277,6 +251,68 @@ namespace GridKit return 0; } + template + void Branch::initializeParameters(const model_data_type& data) + { + if (data.parameters.contains(model_data_type::Parameters::R)) + { + R_ = std::get(data.parameters.at(model_data_type::Parameters::R)); + } + + if (data.parameters.contains(model_data_type::Parameters::X)) + { + X_ = std::get(data.parameters.at(model_data_type::Parameters::X)); + } + + if (data.parameters.contains(model_data_type::Parameters::G)) + { + G_ = std::get(data.parameters.at(model_data_type::Parameters::G)); + } + + if (data.parameters.contains(model_data_type::Parameters::B)) + { + B_ = std::get(data.parameters.at(model_data_type::Parameters::B)); + } + + if (data.ports.contains(model_data_type::Ports::bus1)) + { + bus1_id_ = data.ports.at(model_data_type::Ports::bus1); + } + + if (data.ports.contains(model_data_type::Ports::bus2)) + { + bus2_id_ = data.ports.at(model_data_type::Ports::bus2); + } + } + + template + void Branch::initializeMonitor() + { + using Variable = typename model_data_type::MonitorableVariables; + monitor_.set(Variable::ir1, [this] { return Ir1(); }); + monitor_.set(Variable::ii1, [this] { return Ii1(); }); + // monitor_.set(Variable::im1, [this] { return ?(); }); + // monitor_.set(Variable::p1, [this] { return ?(); }); + // monitor_.set(Variable::q1, [this] { return ?(); }); + monitor_.set(Variable::ir2, [this] { return Ir2(); }); + monitor_.set(Variable::ii2, [this] { return Ii2(); }); + // monitor_.set(Variable::im2, [this] { return ?(); }); + // monitor_.set(Variable::p2, [this] { return ?(); }); + // monitor_.set(Variable::q2, [this] { return ?(); }); + } + + template + bool Branch::monitoring() const + { + return !monitor_.empty(); + } + + template + void Branch::printMonitoredVariables(std::ostream& os) const + { + monitor_.print(os); + } + /** * @brief Derived parameters * diff --git a/GridKit/Model/PhasorDynamics/Bus/BusData.hpp b/GridKit/Model/PhasorDynamics/Bus/BusData.hpp index 3a1ab6f88..48f905006 100644 --- a/GridKit/Model/PhasorDynamics/Bus/BusData.hpp +++ b/GridKit/Model/PhasorDynamics/Bus/BusData.hpp @@ -6,15 +6,33 @@ */ #pragma once -#include #include +#include #include #include +#include + namespace GridKit { namespace PhasorDynamics { + /// Indices of the variables able to be monitored on this component + enum class BusMonitorableVariables : size_t + { + VR, + VI, + VM, + VA, + SIZE + }; + + inline const std::string& enumLabel(BusMonitorableVariables v) + { + static const std::string labels[] = {"Vr", "Vi", "Vm", "Va"}; + return labels[Utilities::enumId(v)]; + } + /** * @brief Contains modeling data for a Bus * @@ -48,21 +66,11 @@ namespace GridKit RealT v_base{1.0}; ///< Voltage base in volts std::optional freq_base; ///< Override for the system-wide base frequency std::optional va_base; ///< Override for the system-wide power base - - /// Indices of the variables able to be monitored on this component - enum class MonitorableVariables : size_t - { - VR, - VI, - VM, - VA, - MAXIMUM, - }; + using MonitorableVariables = BusMonitorableVariables; /// Set indicating the variables being monitored - std::bitset>(MonitorableVariables::MAXIMUM)> - monitored_variables; + std::set monitored_variables; }; + } // namespace PhasorDynamics } // namespace GridKit diff --git a/GridKit/Model/PhasorDynamics/Bus/BusDataJSONParser.hpp b/GridKit/Model/PhasorDynamics/Bus/BusDataJSONParser.hpp index e4cb6672c..491ca88cb 100644 --- a/GridKit/Model/PhasorDynamics/Bus/BusDataJSONParser.hpp +++ b/GridKit/Model/PhasorDynamics/Bus/BusDataJSONParser.hpp @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -80,32 +81,20 @@ namespace GridKit if (j.contains("mon")) { + using magic_enum::case_insensitive; + using magic_enum::enum_cast; + using MonitorableVariables = typename BusData::MonitorableVariables; for (auto& raw_monitored_variable : j.at("mon")) { - auto monitored = raw_monitored_variable.get(); - if (monitored == "Vr") + auto var_name = raw_monitored_variable.get(); + auto monitored = enum_cast(var_name, case_insensitive); + if (monitored.has_value()) { - bd.monitored_variables.set(static_cast( - BusData::MonitorableVariables::VR)); - } - else if (monitored == "Vi") - { - bd.monitored_variables.set(static_cast( - BusData::MonitorableVariables::VI)); - } - else if (monitored == "Vm") - { - bd.monitored_variables.set(static_cast( - BusData::MonitorableVariables::VM)); - } - else if (monitored == "Va") - { - bd.monitored_variables.set(static_cast( - BusData::MonitorableVariables::VA)); + bd.monitored_variables.insert(monitored.value()); } else { - Log::error() << "\n\tInvalid monitored variable: \"" << monitored + Log::error() << "\n\tInvalid monitored variable: \"" << var_name << "\" in \"mon\" list." << error_context.str() << std::endl; } diff --git a/GridKit/Model/PhasorDynamics/Bus/BusImpl.hpp b/GridKit/Model/PhasorDynamics/Bus/BusImpl.hpp index 4957e77f5..e142662b5 100644 --- a/GridKit/Model/PhasorDynamics/Bus/BusImpl.hpp +++ b/GridKit/Model/PhasorDynamics/Bus/BusImpl.hpp @@ -55,7 +55,7 @@ namespace GridKit */ template Bus::Bus(const DataT& data) - : BusBase(data.bus_id), + : BusBase(data), Vr0_(data.Vr0), Vi0_(data.Vi0) { diff --git a/GridKit/Model/PhasorDynamics/Bus/BusInfiniteImpl.hpp b/GridKit/Model/PhasorDynamics/Bus/BusInfiniteImpl.hpp index d8f3e9d48..470a6e68d 100644 --- a/GridKit/Model/PhasorDynamics/Bus/BusInfiniteImpl.hpp +++ b/GridKit/Model/PhasorDynamics/Bus/BusInfiniteImpl.hpp @@ -54,7 +54,7 @@ namespace GridKit */ template BusInfinite::BusInfinite(const DataT& data) - : BusBase(data.bus_id), + : BusBase(data), Vr_(data.Vr0), Vi_(data.Vi0) { diff --git a/GridKit/Model/PhasorDynamics/BusBase.hpp b/GridKit/Model/PhasorDynamics/BusBase.hpp index deb63fe51..f6d57613b 100644 --- a/GridKit/Model/PhasorDynamics/BusBase.hpp +++ b/GridKit/Model/PhasorDynamics/BusBase.hpp @@ -1,10 +1,12 @@ #pragma once +#include #include #include #include #include +#include namespace GridKit { @@ -22,14 +24,15 @@ namespace GridKit using MatrixT = typename Model::Evaluator::MatrixT; using BusTypeT = typename BusData::BusType; - BusBase() - : size_(0) - { - } + BusBase() = default; - BusBase(IdxT bus_id) - : bus_id_(bus_id) + explicit BusBase(const BusData& data) + : bus_id_(data.bus_id), + monitor_("Bus " + data.name, data.monitored_variables) { + using MonitorableVariables = typename BusData::MonitorableVariables; + monitor_.set(MonitorableVariables::VR, [this]() { return Vr(); }); + monitor_.set(MonitorableVariables::VI, [this]() { return Vi(); }); } virtual ~BusBase() @@ -161,6 +164,16 @@ namespace GridKit return residual_indices_; } + bool monitoring() const override + { + return !monitor_.empty(); + } + + void printMonitoredVariables(std::ostream& os) const override + { + monitor_.print(os); + } + protected: IdxT bus_id_{static_cast(-1)}; @@ -171,6 +184,8 @@ namespace GridKit std::map residual_indices_; ///< Map between local and global (system-level) /// residual indices + Model::VariableMonitor monitor_; + std::vector y_; std::vector yp_; std::vector tag_; diff --git a/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1.hpp b/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1.hpp index ff20ca4c2..f6001136b 100644 --- a/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1.hpp +++ b/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1.hpp @@ -11,6 +11,7 @@ #include #include +#include // Forward declarations namespace GridKit @@ -75,12 +76,12 @@ namespace GridKit using Component::y_; using Component::yp_; + public: using RealT = typename Component::RealT; using model_data_type = Ieeet1Data; using signal_type = SignalNode; using bus_type = BusBase; - public: Ieeet1(bus_type* bus); Ieeet1(signal_type* efd_signal, signal_type* speed_signal, @@ -112,6 +113,9 @@ namespace GridKit return signals_; } + bool monitoring() const override; + void printMonitoredVariables(std::ostream& os) const override; + private: // Signal pointers signal_type* efd_signal_; @@ -150,8 +154,12 @@ namespace GridKit /// Component signal extension ComponentSignals signals_; + Model::VariableMonitor monitor_; + // Parameter initialization function void initModelParams(const model_data_type& data); + + void initializeMonitor(); }; } // namespace Exciter diff --git a/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1Data.hpp b/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1Data.hpp index 093d6c87b..f4dabf789 100644 --- a/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1Data.hpp +++ b/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1Data.hpp @@ -8,6 +8,7 @@ #pragma once #include +#include namespace GridKit { @@ -47,8 +48,15 @@ namespace GridKit { efd, ksat, + SIZE }; + inline const std::string& enumLabel(Ieeet1MonitorableVariables v) + { + static std::string labels[] = {"efd", "ksat"}; + return labels[Utilities::enumId(v)]; + } + /** * @brief Contains modeling data for a IEEET1 Exciter model. * diff --git a/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1Impl.hpp b/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1Impl.hpp index 17640d4a2..cff4e3e49 100644 --- a/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1Impl.hpp +++ b/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1Impl.hpp @@ -56,12 +56,15 @@ namespace GridKit const model_data_type& data) : efd_signal_(efd_signal), speed_signal_(speed_signal), - bus_(bus) + bus_(bus), + monitor_(data) { // Parse data struct into model this->initModelParams(data); + initializeMonitor(); + // 9 Internal Variables size_ = 9; } @@ -77,12 +80,15 @@ namespace GridKit template Ieeet1::Ieeet1(bus_type* bus, const model_data_type& data) - : bus_(bus) + : bus_(bus), + monitor_(data) { // Parse data struct into model this->initModelParams(data); + initializeMonitor(); + // 9 Internal Variables size_ = 9; } @@ -390,6 +396,25 @@ namespace GridKit SB_ = Se1_ / (E1_ - SA_) / (E1_ - SA_); } + template + bool Ieeet1::monitoring() const + { + return !monitor_.empty(); + } + + template + void Ieeet1::printMonitoredVariables(std::ostream& os) const + { + monitor_.print(os); + } + + template + void Ieeet1::initializeMonitor() + { + using Variable = model_data_type::MonitorableVariables; + monitor_.set(Variable::efd, [this] { return efd_signal_->read(); }); + // monitor_.set(Variable::ksat, [this] { return ?; }); + } } // namespace Exciter } // namespace PhasorDynamics } // namespace GridKit diff --git a/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROUwS/Genrou.hpp b/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROUwS/Genrou.hpp index 09461f5d1..5ed18ca31 100644 --- a/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROUwS/Genrou.hpp +++ b/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROUwS/Genrou.hpp @@ -10,6 +10,8 @@ #include #include +#include +#include // Forward declarations. namespace GridKit @@ -81,12 +83,12 @@ namespace GridKit using Component::J_; using Component::mva_system_base_; + public: using RealT = typename Component::RealT; using bus_type = BusBase; using model_data_type = GenrouData; using signal_type = SignalNode; - public: Genrou(bus_type* bus, IdxT unit_id); Genrou(bus_type* bus, signal_type* omega, @@ -149,8 +151,12 @@ namespace GridKit return signals_; } + bool monitoring() const override; + void printMonitoredVariables(std::ostream&) const override; + private: void initializeParameters(const model_data_type& data); + void initializeMonitor(); void setDerivedParams(); ScalarT& Vr() @@ -233,6 +239,8 @@ namespace GridKit /* Local copies of external variables */ std::vector ws_; std::map ws_indices_; + + Model::VariableMonitor monitor_; }; } // namespace PhasorDynamics diff --git a/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROUwS/GenrouData.hpp b/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROUwS/GenrouData.hpp index 9133914bf..0568e5e60 100644 --- a/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROUwS/GenrouData.hpp +++ b/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROUwS/GenrouData.hpp @@ -7,6 +7,7 @@ #pragma once #include +#include namespace GridKit { @@ -54,8 +55,21 @@ namespace GridKit q, delta, omega, + SIZE }; + inline const std::string& enumLabel(GenrouMonitorableVariables v) + { + static const std::string labels[] = { + "Ir", + "Ii", + "P", + "Q", + "Delta", + "Omega"}; + return labels[Utilities::enumId(v)]; + } + /** * @brief Contains modeling data for a Genrou generator model. * diff --git a/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROUwS/GenrouImpl.hpp b/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROUwS/GenrouImpl.hpp index 67e874df8..b724f19dc 100644 --- a/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROUwS/GenrouImpl.hpp +++ b/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROUwS/GenrouImpl.hpp @@ -112,9 +112,11 @@ namespace GridKit template Genrou::Genrou(bus_type* bus, const model_data_type& data) : bus_(bus), - unit_id_(1) + unit_id_(1), + monitor_(data) { initializeParameters(data); + initializeMonitor(); size_ = 19; setDerivedParams(); @@ -126,11 +128,13 @@ namespace GridKit template Genrou::Genrou(bus_type* bus, signal_type* omega, signal_type* pmech, const model_data_type& data) : bus_(bus), - unit_id_(1) + unit_id_(1), + monitor_(data) { signals_.template attachSignalNode(pmech); signals_.template assignSignalNode(omega); initializeParameters(data); + initializeMonitor(); size_ = 19; setDerivedParams(); @@ -142,12 +146,14 @@ namespace GridKit template Genrou::Genrou(bus_type* bus, signal_type* omega, signal_type* pmech, signal_type* efd, const model_data_type& data) : bus_(bus), - unit_id_(1) + unit_id_(1), + monitor_(data) { signals_.template attachSignalNode(pmech); signals_.template assignSignalNode(omega); signals_.template attachSignalNode(efd); initializeParameters(data); + initializeMonitor(); size_ = 19; setDerivedParams(); @@ -259,6 +265,30 @@ namespace GridKit } } + template + void Genrou::initializeMonitor() + { + using Variable = typename model_data_type::MonitorableVariables; + monitor_.set(Variable::ir, [this] { return y_[15]; }); + monitor_.set(Variable::ii, [this] { return y_[16]; }); + // monitor_.set(Variable::p, [this] { return Ii(); }); + // monitor_.set(Variable::q, [this] { return Ii(); }); + monitor_.set(Variable::delta, [this] { return y_[0]; }); + monitor_.set(Variable::omega, [this] { return y_[1]; }); + } + + template + bool Genrou::monitoring() const + { + return !monitor_.empty(); + } + + template + void Genrou::printMonitoredVariables(std::ostream& os) const + { + monitor_.print(os); + } + /** * @brief Set the component ID */ diff --git a/GridKit/Model/PhasorDynamics/SystemModel.hpp b/GridKit/Model/PhasorDynamics/SystemModel.hpp index d3c5dc23d..1cd4a6107 100644 --- a/GridKit/Model/PhasorDynamics/SystemModel.hpp +++ b/GridKit/Model/PhasorDynamics/SystemModel.hpp @@ -417,9 +417,25 @@ namespace GridKit } } + monitoring_ = checkMonitoring(); + return 0; } + bool checkMonitoring() const + { + bool mon = false; + for (const auto& bus : buses_) + { + mon = mon || bus->monitoring(); + } + for (const auto& component : components_) + { + mon = mon || component->monitoring(); + } + return mon; + } + /** * @todo Tagging differential variables * @@ -576,12 +592,31 @@ namespace GridKit return 0; } + bool monitoring() const override + { + return monitoring_; + } + + void printMonitoredVariables(std::ostream& os = std::cout) const override + { + os << "t: " << this->time_ << ":\n"; + for (const auto& bus : buses_) + { + bus->printMonitoredVariables(os); + } + for (const auto& component : components_) + { + component->printMonitoredVariables(os); + } + } + /** * @brief Update time * */ void updateTime(RealT t, RealT a) override { + this->time_ = t; for (const auto& component : components_) { component->updateTime(t, a); @@ -703,6 +738,7 @@ namespace GridKit bool owns_components_{false}; + bool monitoring_{false}; }; // class SystemModel } // namespace PhasorDynamics diff --git a/GridKit/Model/VariableMonitor.hpp b/GridKit/Model/VariableMonitor.hpp new file mode 100644 index 000000000..8de9bab2e --- /dev/null +++ b/GridKit/Model/VariableMonitor.hpp @@ -0,0 +1,104 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace GridKit +{ + namespace Model + { + namespace Detail + { + template + struct Types + { + }; + + template typename TT> + struct Types> + { + using ScalarT = TScalar; + using IdxT = TIdx; + }; + } // namespace Detail + + template typename TData> + class VariableMonitor + { + public: + using ScalarT = typename Detail::Types::ScalarT; + using IdxT = typename Detail::Types::IdxT; + using RealT = typename GridKit::ScalarTraits::RealT; + using ObjData = TData; + using VariableEnum = typename ObjData::MonitorableVariables; + using EnumId = std::underlying_type_t; + using ValueFunction = std::function; + + VariableMonitor() = default; + + VariableMonitor(const std::string& label, + const std::set& variables) + : label_(label) + { + std::ranges::copy(variables, std::back_inserter(variables_)); + } + + VariableMonitor(const ObjData& data) + : VariableMonitor(data.device_class + " " + data.disambiguation_string, + data.monitored_variables) + { + } + + void print(std::ostream& os, VariableEnum v) const + { + os << indent_ << indent_ << enumLabel(v) << ": " << f(v) << '\n'; + } + + void print(std::ostream& os) const + { + if (empty()) + { + return; + } + os << indent_ << label_ << ":\n"; + for (auto v : variables_) + { + print(os, v); + } + } + + bool empty() const + { + return variables_.empty(); + } + + void push(VariableEnum v, ValueFunction g) + { + // variables_.emplace_back(v, g); + } + + void set(VariableEnum v, ValueFunction f) + { + f_[Utilities::enumId(v)] = f; + } + + private: + auto f(VariableEnum v) const + { + return f_[Utilities::enumId(v)](); + } + + std::array> f_; + std::vector variables_; + std::string indent_{" "}; + std::string label_; + }; + } // namespace Model +} // namespace GridKit diff --git a/GridKit/Solver/Dynamic/Ida.cpp b/GridKit/Solver/Dynamic/Ida.cpp index 69ec9e202..9089d5ccb 100644 --- a/GridKit/Solver/Dynamic/Ida.cpp +++ b/GridKit/Solver/Dynamic/Ida.cpp @@ -322,7 +322,7 @@ namespace AnalysisManager retval = IDASolve(solver_, tout, &tret, yy_, yp_, IDA_NORMAL); checkOutput(retval, "IDASolve"); - if (step_callback.has_value()) + if (step_callback.has_value() || model_->monitoring()) { // The callback may try to observe upated values in the model, so we // should update them here (At this point, the model's values are one @@ -331,7 +331,14 @@ namespace AnalysisManager copyVec(yy_, model_->y()); copyVec(yp_, model_->yp()); - (*step_callback)(tret); + if (model_->monitoring()) + { + model_->printMonitoredVariables(); + } + if (step_callback.has_value()) + { + (*step_callback)(tret); + } } if (retval == IDA_SUCCESS) diff --git a/GridKit/Utilities/CMakeLists.txt b/GridKit/Utilities/CMakeLists.txt index e88012421..f17e2acce 100644 --- a/GridKit/Utilities/CMakeLists.txt +++ b/GridKit/Utilities/CMakeLists.txt @@ -11,6 +11,7 @@ add_subdirectory(CliOptions) install( FILES Colors.hpp + Enum.hpp FileIO.hpp MapFromCOO.hpp DESTINATION diff --git a/GridKit/Utilities/Enum.hpp b/GridKit/Utilities/Enum.hpp new file mode 100644 index 000000000..96b35820b --- /dev/null +++ b/GridKit/Utilities/Enum.hpp @@ -0,0 +1,22 @@ +#pragma once + +namespace GridKit +{ + namespace Utilities + { + template + concept SizedEnum = requires { + std::is_enum_v; + TEnum::SIZE; + }; + + template + inline constexpr std::underlying_type_t enumId(TEnum v) noexcept + { + return static_cast>(v); + } + + template + inline constexpr auto enumSize = enumId(TEnum::SIZE); + } // namespace Utilities +} // namespace GridKit diff --git a/tests/UnitTests/Utilities/CaseFormatTests.hpp b/tests/UnitTests/Utilities/CaseFormatTests.hpp index 1fb08b37b..85ede8630 100644 --- a/tests/UnitTests/Utilities/CaseFormatTests.hpp +++ b/tests/UnitTests/Utilities/CaseFormatTests.hpp @@ -80,15 +80,15 @@ namespace GridKit success *= result.bus[0].Vr0 == 0.994988; success *= result.bus[0].Vi0 == 0.099997; success *= result.bus[0].v_base == 115e3; - success *= result.bus[0].monitored_variables[static_cast(BusData::MonitorableVariables::VR)]; - success *= result.bus[0].monitored_variables[static_cast(BusData::MonitorableVariables::VI)]; + success *= result.bus[0].monitored_variables.contains(BusData::MonitorableVariables::VR); + success *= result.bus[0].monitored_variables.contains(BusData::MonitorableVariables::VI); success *= result.bus[1].bus_id == 2; success *= result.bus[1].bus_type == BusType::SLACK; success *= result.bus[1].name == "Bus 2"; success *= result.bus[1].Vr0 == 1.0; success *= result.bus[1].Vi0 == 0.0; success *= result.bus[1].v_base == 115e3; - success *= result.bus[1].monitored_variables.none(); + success *= result.bus[1].monitored_variables.empty(); success *= std::get(result.branch[0].parameters[BranchParameters::R]) == 0.0; success *= std::get(result.branch[0].parameters[BranchParameters::X]) == 0.1; @@ -193,15 +193,15 @@ namespace GridKit success *= result.bus[0].Vr0 == 0.994988; success *= result.bus[0].Vi0 == 0.099997; success *= result.bus[0].v_base == 115e3; - success *= result.bus[0].monitored_variables[static_cast(BusData::MonitorableVariables::VR)]; - success *= result.bus[0].monitored_variables[static_cast(BusData::MonitorableVariables::VI)]; + success *= result.bus[0].monitored_variables.contains(BusData::MonitorableVariables::VR); + success *= result.bus[0].monitored_variables.contains(BusData::MonitorableVariables::VI); success *= result.bus[1].bus_id == 2; success *= result.bus[1].bus_type == BusType::SLACK; success *= result.bus[1].name == "Bus 2"; success *= result.bus[1].Vr0 == 1.0; success *= result.bus[1].Vi0 == 0.0; success *= result.bus[1].v_base == 115e3; - success *= result.bus[1].monitored_variables.none(); + success *= result.bus[1].monitored_variables.empty(); success *= result.signal[0].signal_id == 1; success *= result.signal[0].name == "Machine Speed Deviation"; From 74e32f3e85cd619ba600624f60c286e64b5858ba Mon Sep 17 00:00:00 2001 From: Philip Fackler Date: Fri, 21 Nov 2025 16:03:28 -0600 Subject: [PATCH 02/15] Update enum values style; fix template parameter convention for VariableMonitor --- .../PhasorDynamics/Branch/BranchData.hpp | 20 ++++++------- .../PhasorDynamics/Branch/BranchImpl.hpp | 20 ++++++------- .../ComponentDataJSONParser.hpp | 12 ++++++-- .../Exciter/IEEET1/Ieeet1Data.hpp | 4 +-- .../Exciter/IEEET1/Ieeet1Impl.hpp | 4 +-- .../GENROUwS/GenrouData.hpp | 12 ++++---- .../GENROUwS/GenrouImpl.hpp | 12 ++++---- GridKit/Model/VariableMonitor.hpp | 30 +++++-------------- tests/UnitTests/Utilities/CaseFormatTests.hpp | 8 ++--- 9 files changed, 56 insertions(+), 66 deletions(-) diff --git a/GridKit/Model/PhasorDynamics/Branch/BranchData.hpp b/GridKit/Model/PhasorDynamics/Branch/BranchData.hpp index 960addc54..e1d93d089 100644 --- a/GridKit/Model/PhasorDynamics/Branch/BranchData.hpp +++ b/GridKit/Model/PhasorDynamics/Branch/BranchData.hpp @@ -32,16 +32,16 @@ namespace GridKit /// Variables able to be monitored for a branch enum class BranchMonitorableVariables { - ir1, - ii1, - im1, - p1, - q1, - ir2, - ii2, - im2, - p2, - q2, + IR1, + II1, + IM1, + P1, + Q1, + IR2, + II2, + IM2, + P2, + Q2, SIZE }; diff --git a/GridKit/Model/PhasorDynamics/Branch/BranchImpl.hpp b/GridKit/Model/PhasorDynamics/Branch/BranchImpl.hpp index 9ebb0f50f..a1146aa19 100644 --- a/GridKit/Model/PhasorDynamics/Branch/BranchImpl.hpp +++ b/GridKit/Model/PhasorDynamics/Branch/BranchImpl.hpp @@ -289,16 +289,16 @@ namespace GridKit void Branch::initializeMonitor() { using Variable = typename model_data_type::MonitorableVariables; - monitor_.set(Variable::ir1, [this] { return Ir1(); }); - monitor_.set(Variable::ii1, [this] { return Ii1(); }); - // monitor_.set(Variable::im1, [this] { return ?(); }); - // monitor_.set(Variable::p1, [this] { return ?(); }); - // monitor_.set(Variable::q1, [this] { return ?(); }); - monitor_.set(Variable::ir2, [this] { return Ir2(); }); - monitor_.set(Variable::ii2, [this] { return Ii2(); }); - // monitor_.set(Variable::im2, [this] { return ?(); }); - // monitor_.set(Variable::p2, [this] { return ?(); }); - // monitor_.set(Variable::q2, [this] { return ?(); }); + monitor_.set(Variable::IR1, [this] { return Ir1(); }); + monitor_.set(Variable::II1, [this] { return Ii1(); }); + // monitor_.set(Variable::IM1, [this] { return ?(); }); + // monitor_.set(Variable::P1, [this] { return ?(); }); + // monitor_.set(Variable::Q1, [this] { return ?(); }); + monitor_.set(Variable::IR2, [this] { return Ir2(); }); + monitor_.set(Variable::II2, [this] { return Ii2(); }); + // monitor_.set(Variable::IM2, [this] { return ?(); }); + // monitor_.set(Variable::P2, [this] { return ?(); }); + // monitor_.set(Variable::Q2, [this] { return ?(); }); } template diff --git a/GridKit/Model/PhasorDynamics/ComponentDataJSONParser.hpp b/GridKit/Model/PhasorDynamics/ComponentDataJSONParser.hpp index 90b00de50..9f091ab6c 100644 --- a/GridKit/Model/PhasorDynamics/ComponentDataJSONParser.hpp +++ b/GridKit/Model/PhasorDynamics/ComponentDataJSONParser.hpp @@ -16,6 +16,12 @@ namespace GridKit using json = nlohmann::json; using Log = ::GridKit::Utilities::Logger; + template + inline auto enum_parse(KeyT&& key) + { + return magic_enum::enum_cast(key, magic_enum::case_insensitive); + } + /// JSON parser function for the `ComponentData` class and descendants template (raw_parameter.key()); + auto key = enum_parse(raw_parameter.key()); if (key.has_value()) { // NOTE: this is necessary because it doesn't seem like nlohmann/json @@ -74,7 +80,7 @@ namespace GridKit for (auto& raw_port : j.at("ports").items()) { - auto key = magic_enum::enum_cast(raw_port.key()); + auto key = enum_parse(raw_port.key()); if (key.has_value()) { raw_port.value().get_to(c.ports[key.value()]); @@ -92,7 +98,7 @@ namespace GridKit for (auto& raw_monitored_variable : j.at("mon")) { auto var_name = raw_monitored_variable.get(); - auto monitored = magic_enum::enum_cast(var_name); + auto monitored = enum_parse(var_name); if (monitored.has_value()) { c.monitored_variables.insert(monitored.value()); diff --git a/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1Data.hpp b/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1Data.hpp index f4dabf789..ae87e5307 100644 --- a/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1Data.hpp +++ b/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1Data.hpp @@ -46,8 +46,8 @@ namespace GridKit /// Variables able to be monitored for a IEEET1 Exciter model enum class Ieeet1MonitorableVariables { - efd, - ksat, + EFD, + KSAT, SIZE }; diff --git a/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1Impl.hpp b/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1Impl.hpp index cff4e3e49..7df7c1bd1 100644 --- a/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1Impl.hpp +++ b/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1Impl.hpp @@ -412,8 +412,8 @@ namespace GridKit void Ieeet1::initializeMonitor() { using Variable = model_data_type::MonitorableVariables; - monitor_.set(Variable::efd, [this] { return efd_signal_->read(); }); - // monitor_.set(Variable::ksat, [this] { return ?; }); + monitor_.set(Variable::EFD, [this] { return efd_signal_->read(); }); + // monitor_.set(Variable::KSAT, [this] { return ?; }); } } // namespace Exciter } // namespace PhasorDynamics diff --git a/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROUwS/GenrouData.hpp b/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROUwS/GenrouData.hpp index 0568e5e60..20c26538d 100644 --- a/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROUwS/GenrouData.hpp +++ b/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROUwS/GenrouData.hpp @@ -49,12 +49,12 @@ namespace GridKit /// Variables able to be monitored for a Genrou generator model enum class GenrouMonitorableVariables { - ir, - ii, - p, - q, - delta, - omega, + IR, + II, + P, + Q, + DELTA, + OMEGA, SIZE }; diff --git a/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROUwS/GenrouImpl.hpp b/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROUwS/GenrouImpl.hpp index b724f19dc..c3a42e700 100644 --- a/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROUwS/GenrouImpl.hpp +++ b/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROUwS/GenrouImpl.hpp @@ -269,12 +269,12 @@ namespace GridKit void Genrou::initializeMonitor() { using Variable = typename model_data_type::MonitorableVariables; - monitor_.set(Variable::ir, [this] { return y_[15]; }); - monitor_.set(Variable::ii, [this] { return y_[16]; }); - // monitor_.set(Variable::p, [this] { return Ii(); }); - // monitor_.set(Variable::q, [this] { return Ii(); }); - monitor_.set(Variable::delta, [this] { return y_[0]; }); - monitor_.set(Variable::omega, [this] { return y_[1]; }); + monitor_.set(Variable::IR, [this] { return y_[15]; }); + monitor_.set(Variable::II, [this] { return y_[16]; }); + // monitor_.set(Variable::P, [this] { return ?(); }); + // monitor_.set(Variable::Q, [this] { return ?(); }); + monitor_.set(Variable::DELTA, [this] { return y_[0]; }); + monitor_.set(Variable::OMEGA, [this] { return y_[1]; }); } template diff --git a/GridKit/Model/VariableMonitor.hpp b/GridKit/Model/VariableMonitor.hpp index 8de9bab2e..e376e70b7 100644 --- a/GridKit/Model/VariableMonitor.hpp +++ b/GridKit/Model/VariableMonitor.hpp @@ -14,29 +14,18 @@ namespace GridKit { namespace Model { - namespace Detail - { - template - struct Types - { - }; - template typename TT> - struct Types> - { - using ScalarT = TScalar; - using IdxT = TIdx; - }; - } // namespace Detail - - template typename TData> + template typename DataT> class VariableMonitor { + }; + + template typename EvalT, template typename DataT> + class VariableMonitor, DataT> + { public: - using ScalarT = typename Detail::Types::ScalarT; - using IdxT = typename Detail::Types::IdxT; using RealT = typename GridKit::ScalarTraits::RealT; - using ObjData = TData; + using ObjData = DataT; using VariableEnum = typename ObjData::MonitorableVariables; using EnumId = std::underlying_type_t; using ValueFunction = std::function; @@ -79,11 +68,6 @@ namespace GridKit return variables_.empty(); } - void push(VariableEnum v, ValueFunction g) - { - // variables_.emplace_back(v, g); - } - void set(VariableEnum v, ValueFunction f) { f_[Utilities::enumId(v)] = f; diff --git a/tests/UnitTests/Utilities/CaseFormatTests.hpp b/tests/UnitTests/Utilities/CaseFormatTests.hpp index 85ede8630..35dc784df 100644 --- a/tests/UnitTests/Utilities/CaseFormatTests.hpp +++ b/tests/UnitTests/Utilities/CaseFormatTests.hpp @@ -119,8 +119,8 @@ namespace GridKit success *= std::get(result.genrou[0].parameters[GenrouParameters::S12]) == 0.0; success *= result.genrou[0].ports[GenrouPorts::bus] == 1; success *= result.genrou[0].disambiguation_string == "1"; - success *= result.genrou[0].monitored_variables.contains(GenrouMonitorableVariables::delta); - success *= result.genrou[0].monitored_variables.contains(GenrouMonitorableVariables::omega); + success *= result.genrou[0].monitored_variables.contains(GenrouMonitorableVariables::DELTA); + success *= result.genrou[0].monitored_variables.contains(GenrouMonitorableVariables::OMEGA); success *= std::get(result.bus_fault[0].parameters[BusFaultParameters::R]) == 0.0; success *= std::get(result.bus_fault[0].parameters[BusFaultParameters::X]) == 1e-3; @@ -242,8 +242,8 @@ namespace GridKit success *= result.genrou[0].ports[GenrouPorts::pmech] == 2; success *= result.genrou[0].ports[GenrouPorts::efd] == 3; success *= result.genrou[0].disambiguation_string == "DV1"; - success *= result.genrou[0].monitored_variables.contains(GenrouMonitorableVariables::delta); - success *= result.genrou[0].monitored_variables.contains(GenrouMonitorableVariables::omega); + success *= result.genrou[0].monitored_variables.contains(GenrouMonitorableVariables::DELTA); + success *= result.genrou[0].monitored_variables.contains(GenrouMonitorableVariables::OMEGA); success *= std::get(result.gov[0].parameters[Governor::Tgov1Parameters::R]) == 0.05; success *= std::get(result.gov[0].parameters[Governor::Tgov1Parameters::T1]) == 0.5; From 18e28945a8cb7882dec9982f4e33c131eec68570 Mon Sep 17 00:00:00 2001 From: Philip Fackler Date: Wed, 26 Nov 2025 15:50:19 -0600 Subject: [PATCH 03/15] Enable multiple styles specified through JSON input --- GridKit/Model/Evaluator.hpp | 8 +- .../Model/PhasorDynamics/Branch/Branch.hpp | 6 +- .../PhasorDynamics/Branch/BranchImpl.hpp | 24 +- GridKit/Model/PhasorDynamics/BusBase.hpp | 19 +- .../ComponentDataJSONParser.hpp | 17 +- .../PhasorDynamics/Exciter/IEEET1/Ieeet1.hpp | 6 +- .../Exciter/IEEET1/Ieeet1Impl.hpp | 15 +- .../SynchronousMachine/GENROUwS/Genrou.hpp | 6 +- .../GENROUwS/GenrouImpl.hpp | 24 +- GridKit/Model/PhasorDynamics/SystemModel.hpp | 48 +- .../Model/PhasorDynamics/SystemModelData.hpp | 4 + .../SystemModelDataJSONParser.hpp | 31 +- GridKit/Model/VariableMonitor.hpp | 443 +++++++++++++++++- .../Tiny/ThreeBus/Basic/ThreeBusBasic.json | 14 + 14 files changed, 561 insertions(+), 104 deletions(-) diff --git a/GridKit/Model/Evaluator.hpp b/GridKit/Model/Evaluator.hpp index 90cbc58a7..7eaa3fd0a 100644 --- a/GridKit/Model/Evaluator.hpp +++ b/GridKit/Model/Evaluator.hpp @@ -5,6 +5,7 @@ #include #include #include +#include #include namespace GridKit @@ -50,10 +51,15 @@ namespace GridKit return false; } - virtual void printMonitoredVariables(std::ostream& = std::cout) const + virtual void printMonitoredVariables() const { } + virtual const VariableMonitorBase* getMonitor() const + { + return nullptr; + } + /** * @brief Is the Jacobian defined. Used in IDA to determine wether DQ is used or not * diff --git a/GridKit/Model/PhasorDynamics/Branch/Branch.hpp b/GridKit/Model/PhasorDynamics/Branch/Branch.hpp index 14bb2a1f3..c95f731ca 100644 --- a/GridKit/Model/PhasorDynamics/Branch/Branch.hpp +++ b/GridKit/Model/PhasorDynamics/Branch/Branch.hpp @@ -104,8 +104,10 @@ namespace GridKit setDerivedParams(); } - bool monitoring() const override; - void printMonitoredVariables(std::ostream&) const override; + const Model::VariableMonitorBase* getMonitor() const override + { + return &monitor_; + } private: void initializeParameters(const model_data_type& data); diff --git a/GridKit/Model/PhasorDynamics/Branch/BranchImpl.hpp b/GridKit/Model/PhasorDynamics/Branch/BranchImpl.hpp index a1146aa19..862cfab2c 100644 --- a/GridKit/Model/PhasorDynamics/Branch/BranchImpl.hpp +++ b/GridKit/Model/PhasorDynamics/Branch/BranchImpl.hpp @@ -289,30 +289,22 @@ namespace GridKit void Branch::initializeMonitor() { using Variable = typename model_data_type::MonitorableVariables; - monitor_.set(Variable::IR1, [this] { return Ir1(); }); - monitor_.set(Variable::II1, [this] { return Ii1(); }); + monitor_.set(Variable::IR1, [this] + { return Ir1(); }); + monitor_.set(Variable::II1, [this] + { return Ii1(); }); // monitor_.set(Variable::IM1, [this] { return ?(); }); // monitor_.set(Variable::P1, [this] { return ?(); }); // monitor_.set(Variable::Q1, [this] { return ?(); }); - monitor_.set(Variable::IR2, [this] { return Ir2(); }); - monitor_.set(Variable::II2, [this] { return Ii2(); }); + monitor_.set(Variable::IR2, [this] + { return Ir2(); }); + monitor_.set(Variable::II2, [this] + { return Ii2(); }); // monitor_.set(Variable::IM2, [this] { return ?(); }); // monitor_.set(Variable::P2, [this] { return ?(); }); // monitor_.set(Variable::Q2, [this] { return ?(); }); } - template - bool Branch::monitoring() const - { - return !monitor_.empty(); - } - - template - void Branch::printMonitoredVariables(std::ostream& os) const - { - monitor_.print(os); - } - /** * @brief Derived parameters * diff --git a/GridKit/Model/PhasorDynamics/BusBase.hpp b/GridKit/Model/PhasorDynamics/BusBase.hpp index f6d57613b..0a9984af1 100644 --- a/GridKit/Model/PhasorDynamics/BusBase.hpp +++ b/GridKit/Model/PhasorDynamics/BusBase.hpp @@ -28,11 +28,13 @@ namespace GridKit explicit BusBase(const BusData& data) : bus_id_(data.bus_id), - monitor_("Bus " + data.name, data.monitored_variables) + monitor_("Bus_" + data.name, data.monitored_variables) { - using MonitorableVariables = typename BusData::MonitorableVariables; - monitor_.set(MonitorableVariables::VR, [this]() { return Vr(); }); - monitor_.set(MonitorableVariables::VI, [this]() { return Vi(); }); + using Variable = typename BusData::MonitorableVariables; + monitor_.set(Variable::VR, [this]() + { return Vr(); }); + monitor_.set(Variable::VI, [this]() + { return Vi(); }); } virtual ~BusBase() @@ -164,14 +166,9 @@ namespace GridKit return residual_indices_; } - bool monitoring() const override + const Model::VariableMonitorBase* getMonitor() const override { - return !monitor_.empty(); - } - - void printMonitoredVariables(std::ostream& os) const override - { - monitor_.print(os); + return &monitor_; } protected: diff --git a/GridKit/Model/PhasorDynamics/ComponentDataJSONParser.hpp b/GridKit/Model/PhasorDynamics/ComponentDataJSONParser.hpp index 9f091ab6c..2b5a6ae68 100644 --- a/GridKit/Model/PhasorDynamics/ComponentDataJSONParser.hpp +++ b/GridKit/Model/PhasorDynamics/ComponentDataJSONParser.hpp @@ -16,12 +16,6 @@ namespace GridKit using json = nlohmann::json; using Log = ::GridKit::Utilities::Logger; - template - inline auto enum_parse(KeyT&& key) - { - return magic_enum::enum_cast(key, magic_enum::case_insensitive); - } - /// JSON parser function for the `ComponentData` class and descendants template void from_json(const json& j, ComponentData& c) { + auto enum_parse = [](EnumT, KeyT&& key) + { + return magic_enum::enum_cast(key, magic_enum::case_insensitive); + }; + j.at("class").get_to(c.device_class); j.at("id").get_to(c.disambiguation_string); @@ -44,7 +43,7 @@ namespace GridKit for (auto& raw_parameter : j.at("params").items()) { - auto key = enum_parse(raw_parameter.key()); + auto key = enum_parse(Parameters(), raw_parameter.key()); if (key.has_value()) { // NOTE: this is necessary because it doesn't seem like nlohmann/json @@ -80,7 +79,7 @@ namespace GridKit for (auto& raw_port : j.at("ports").items()) { - auto key = enum_parse(raw_port.key()); + auto key = enum_parse(Ports(), raw_port.key()); if (key.has_value()) { raw_port.value().get_to(c.ports[key.value()]); @@ -98,7 +97,7 @@ namespace GridKit for (auto& raw_monitored_variable : j.at("mon")) { auto var_name = raw_monitored_variable.get(); - auto monitored = enum_parse(var_name); + auto monitored = enum_parse(MonitorableVariables(), var_name); if (monitored.has_value()) { c.monitored_variables.insert(monitored.value()); diff --git a/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1.hpp b/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1.hpp index f6001136b..063e2d482 100644 --- a/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1.hpp +++ b/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1.hpp @@ -113,8 +113,10 @@ namespace GridKit return signals_; } - bool monitoring() const override; - void printMonitoredVariables(std::ostream& os) const override; + const Model::VariableMonitorBase* getMonitor() const override + { + return &monitor_; + } private: // Signal pointers diff --git a/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1Impl.hpp b/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1Impl.hpp index 7df7c1bd1..e1d963ff2 100644 --- a/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1Impl.hpp +++ b/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1Impl.hpp @@ -396,23 +396,12 @@ namespace GridKit SB_ = Se1_ / (E1_ - SA_) / (E1_ - SA_); } - template - bool Ieeet1::monitoring() const - { - return !monitor_.empty(); - } - - template - void Ieeet1::printMonitoredVariables(std::ostream& os) const - { - monitor_.print(os); - } - template void Ieeet1::initializeMonitor() { using Variable = model_data_type::MonitorableVariables; - monitor_.set(Variable::EFD, [this] { return efd_signal_->read(); }); + monitor_.set(Variable::EFD, [this] + { return efd_signal_->read(); }); // monitor_.set(Variable::KSAT, [this] { return ?; }); } } // namespace Exciter diff --git a/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROUwS/Genrou.hpp b/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROUwS/Genrou.hpp index 5ed18ca31..2902e2f18 100644 --- a/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROUwS/Genrou.hpp +++ b/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROUwS/Genrou.hpp @@ -151,8 +151,10 @@ namespace GridKit return signals_; } - bool monitoring() const override; - void printMonitoredVariables(std::ostream&) const override; + const Model::VariableMonitorBase* getMonitor() const override + { + return &monitor_; + } private: void initializeParameters(const model_data_type& data); diff --git a/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROUwS/GenrouImpl.hpp b/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROUwS/GenrouImpl.hpp index c3a42e700..89935faca 100644 --- a/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROUwS/GenrouImpl.hpp +++ b/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROUwS/GenrouImpl.hpp @@ -269,24 +269,16 @@ namespace GridKit void Genrou::initializeMonitor() { using Variable = typename model_data_type::MonitorableVariables; - monitor_.set(Variable::IR, [this] { return y_[15]; }); - monitor_.set(Variable::II, [this] { return y_[16]; }); + monitor_.set(Variable::IR, [this] + { return y_[15]; }); + monitor_.set(Variable::II, [this] + { return y_[16]; }); // monitor_.set(Variable::P, [this] { return ?(); }); // monitor_.set(Variable::Q, [this] { return ?(); }); - monitor_.set(Variable::DELTA, [this] { return y_[0]; }); - monitor_.set(Variable::OMEGA, [this] { return y_[1]; }); - } - - template - bool Genrou::monitoring() const - { - return !monitor_.empty(); - } - - template - void Genrou::printMonitoredVariables(std::ostream& os) const - { - monitor_.print(os); + monitor_.set(Variable::DELTA, [this] + { return y_[0]; }); + monitor_.set(Variable::OMEGA, [this] + { return y_[1]; }); } /** diff --git a/GridKit/Model/PhasorDynamics/SystemModel.hpp b/GridKit/Model/PhasorDynamics/SystemModel.hpp index 1cd4a6107..68e591a4a 100644 --- a/GridKit/Model/PhasorDynamics/SystemModel.hpp +++ b/GridKit/Model/PhasorDynamics/SystemModel.hpp @@ -75,6 +75,7 @@ namespace GridKit * correctly connected into the system model. */ SystemModel(SystemModelData& data) + : monitor_(time_) { using namespace Governor; using namespace Exciter; @@ -236,6 +237,11 @@ namespace GridKit auto* fault = new BusFault(getBus(bus_index), faultdata); addFault(fault); } + + for (const auto& sink : data.monitor_sink) + { + monitor_.addSink(sink); + } } /** @@ -417,23 +423,24 @@ namespace GridKit } } - monitoring_ = checkMonitoring(); + initializeMonitor(); return 0; } - bool checkMonitoring() const + void initializeMonitor() { - bool mon = false; - for (const auto& bus : buses_) + for (const auto* bus : buses_) { - mon = mon || bus->monitoring(); + monitor_.addMonitor(bus->getMonitor()); } - for (const auto& component : components_) + + for (const auto* component : components_) { - mon = mon || component->monitoring(); + monitor_.addMonitor(component->getMonitor()); } - return mon; + + monitor_.start(); } /** @@ -594,20 +601,21 @@ namespace GridKit bool monitoring() const override { - return monitoring_; + return !monitor_.empty(); } - void printMonitoredVariables(std::ostream& os = std::cout) const override + void printMonitoredVariables() const override { - os << "t: " << this->time_ << ":\n"; - for (const auto& bus : buses_) - { - bus->printMonitoredVariables(os); - } - for (const auto& component : components_) - { - component->printMonitoredVariables(os); - } + monitor_.print(); + // os << "t: " << this->time_ << ":\n"; + // for (const auto& bus : buses_) + // { + // bus->printMonitoredVariables(os); + // } + // for (const auto& component : components_) + // { + // component->printMonitoredVariables(os); + // } } /** @@ -738,7 +746,7 @@ namespace GridKit bool owns_components_{false}; - bool monitoring_{false}; + Model::VariableMonitor monitor_; }; // class SystemModel } // namespace PhasorDynamics diff --git a/GridKit/Model/PhasorDynamics/SystemModelData.hpp b/GridKit/Model/PhasorDynamics/SystemModelData.hpp index da32ebf60..332b01759 100644 --- a/GridKit/Model/PhasorDynamics/SystemModelData.hpp +++ b/GridKit/Model/PhasorDynamics/SystemModelData.hpp @@ -15,6 +15,7 @@ #include #include #include +#include namespace GridKit { @@ -37,6 +38,7 @@ namespace GridKit using GenClassicalDataT = GenClassicalData; using LoadDataT = LoadData; using SignalDataT = SignalNodeData; + using MonitorSinkSpec = Model::VariableMonitorBase::SinkSpec; /// The version of the grid dynamics case format this system model was /// parsed from @@ -84,6 +86,8 @@ namespace GridKit std::vector gov; ///< Governors within the model std::vector exciter; ///< Exciters within the model std::vector signal; ///< Signal nodes + + std::vector monitor_sink; }; ///@{ diff --git a/GridKit/Model/PhasorDynamics/SystemModelDataJSONParser.hpp b/GridKit/Model/PhasorDynamics/SystemModelDataJSONParser.hpp index ebf796b5e..ed62bcb83 100644 --- a/GridKit/Model/PhasorDynamics/SystemModelDataJSONParser.hpp +++ b/GridKit/Model/PhasorDynamics/SystemModelDataJSONParser.hpp @@ -9,12 +9,16 @@ #include #include #include +#include +#include namespace GridKit { namespace PhasorDynamics { - using json = nlohmann::json; + using json = nlohmann::json; + using Log = ::GridKit::Utilities::Logger; + using MonitorFormat = ::GridKit::Model::VariableMonitorFormat; /// JSON parser function implementation for the `SystemModelData` type /// @@ -22,6 +26,11 @@ namespace GridKit template void from_json(const json& j, SystemModelData& sm) { + auto enum_parse = [](EnumT, KeyT&& key) + { + return magic_enum::enum_cast(key, magic_enum::case_insensitive); + }; + auto header = j.at("header"); if (header.contains("format_version")) @@ -46,6 +55,26 @@ namespace GridKit header.at("freq_base").get_to(sm.freq_base); header.at("va_base").get_to(sm.va_base); + if (j.contains("monitors")) + { + for (auto&& raw_mon : j.at("monitors")) + { + auto file_name = raw_mon.value("file_name", std::string{}); + auto fmt_str = raw_mon.at("format").get(); + auto format = enum_parse(MonitorFormat{}, fmt_str); + if (format.has_value()) + { + sm.monitor_sink.emplace_back(file_name, format.value()); + } + else + { + Log::error() << "\n\tInvalid monitor format: \"" << fmt_str << "\"." + << "\n\tSee the \"monitors\" list in your JSON file." + << std::endl; + } + } + } + /// @todo Give signal nodes their own array!!! /// Modify JSON format accordingly diff --git a/GridKit/Model/VariableMonitor.hpp b/GridKit/Model/VariableMonitor.hpp index e376e70b7..b3c1bdff5 100644 --- a/GridKit/Model/VariableMonitor.hpp +++ b/GridKit/Model/VariableMonitor.hpp @@ -2,7 +2,12 @@ #include #include +#include #include +#include +#include +#include +#include #include #include #include @@ -12,8 +17,80 @@ namespace GridKit { + namespace PhasorDynamics + { + template + class SystemModel; + + template + class SystemModelData; + } // namespace PhasorDynamics + namespace Model { + enum class VariableMonitorFormat + { + CSV, + JSON, + YAML + }; + + class VariableMonitorBase + { + public: + struct Csv + { + }; + + struct Json + { + }; + + struct Yaml + { + }; + + using Format = VariableMonitorFormat; + + struct SinkSpec + { + std::string file_name; + Format format; + }; + + virtual ~VariableMonitorBase() + { + } + + virtual bool empty() const = 0; + + virtual void printHeader(std::ostream&, Csv) const = 0; + virtual void print(std::ostream&, Csv) const = 0; + + virtual void printFooter(std::ostream&, Csv) const + { + } + + virtual void printHeader(std::ostream&, Json) const + { + } + + virtual void print(std::ostream&, Json) const = 0; + + virtual void printFooter(std::ostream&, Json) const + { + } + + virtual void printHeader(std::ostream&, Yaml) const + { + } + + virtual void print(std::ostream&, Yaml) const = 0; + + virtual void printFooter(std::ostream&, Yaml) const + { + } + }; template typename DataT> class VariableMonitor @@ -21,7 +98,7 @@ namespace GridKit }; template typename EvalT, template typename DataT> - class VariableMonitor, DataT> + class VariableMonitor, DataT> : public VariableMonitorBase { public: using RealT = typename GridKit::ScalarTraits::RealT; @@ -29,6 +106,9 @@ namespace GridKit using VariableEnum = typename ObjData::MonitorableVariables; using EnumId = std::underlying_type_t; using ValueFunction = std::function; + using Csv = VariableMonitorBase::Csv; + using Json = VariableMonitorBase::Json; + using Yaml = VariableMonitorBase::Yaml; VariableMonitor() = default; @@ -40,17 +120,66 @@ namespace GridKit } VariableMonitor(const ObjData& data) - : VariableMonitor(data.device_class + " " + data.disambiguation_string, + : VariableMonitor(data.device_class + "_" + data.disambiguation_string, data.monitored_variables) { } - void print(std::ostream& os, VariableEnum v) const + virtual ~VariableMonitor() + { + } + + void printHeader(std::ostream& os, Csv) const override + { + for (auto v : variables_) + { + os << delim_ << label_ << '_' << enumLabel(v); + } + } + + void print(std::ostream& os, Csv) const override + { + for (auto v : variables_) + { + os << delim_ << f(v); + } + } + + void print(std::ostream& os, VariableEnum v, Json) const + { + os << indent_ << std::quoted(enumLabel(v)) << ": " << f(v) << ",\n"; + } + + void print(std::ostream& os, Json) const override + { + if (empty()) + { + return; + } + os << indent_ << std::quoted(label_) << ": {\n"; + indent_.append(2, ' '); + std::ostringstream v_os; + for (auto v : variables_) + { + print(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 + { + } + + void print(std::ostream& os, VariableEnum v, Yaml) const { os << indent_ << indent_ << enumLabel(v) << ": " << f(v) << '\n'; } - void print(std::ostream& os) const + void print(std::ostream& os, Yaml) const override { if (empty()) { @@ -59,11 +188,11 @@ namespace GridKit os << indent_ << label_ << ":\n"; for (auto v : variables_) { - print(os, v); + print(os, v, Yaml()); } } - bool empty() const + bool empty() const override { return variables_.empty(); } @@ -76,13 +205,305 @@ namespace GridKit private: auto f(VariableEnum v) const { - return f_[Utilities::enumId(v)](); + return static_cast(f_[Utilities::enumId(v)]()); } - std::array> f_; - std::vector variables_; - std::string indent_{" "}; - std::string label_; + static constexpr auto enum_size_ = Utilities::enumSize; + std::array f_; + std::vector variables_; + mutable std::string indent_{" "}; + std::string delim_{","}; + std::string label_; }; + + template + class VariableMonitor, PhasorDynamics::SystemModelData> : public VariableMonitorBase + { + public: + using RealT = typename GridKit::ScalarTraits::RealT; + using ValueFunction = std::function; + using Format = VariableMonitorFormat; + using SinkSpec = VariableMonitorBase::SinkSpec; + using Csv = VariableMonitorBase::Csv; + using Json = VariableMonitorBase::Json; + using Yaml = VariableMonitorBase::Yaml; + + VariableMonitor() = default; + + explicit VariableMonitor(const RealT& time_var) + : time_(&time_var) + { + sinks_.emplace_back(std::cout, Format::CSV); + } + + virtual ~VariableMonitor() + { + stop(); + } + + void addMonitor(const VariableMonitorBase* monitor) + { + if (monitor && !monitor->empty()) + { + monitors_.push_back(monitor); + } + } + + void addSink(const SinkSpec& spec) + { + if (spec.file_name.empty()) + { + sinks_.front().format = spec.format; + } + else + { + sinks_.emplace_back(spec.file_name, spec.format); + } + } + + void addVariable(const std::string& label, const RealT* value) + { + variables_.emplace_back(label, value); + } + + bool empty() const + { + return (!time_) || monitors_.empty(); + } + + void start() const + { + if (!empty()) + { + printHeader(); + } + } + + void stop() const + { + if (!empty()) + { + printFooter(); + } + } + + using VariableMonitorBase::printHeader; + + void printHeader(std::ostream& os, Csv) const override + { + os << "t"; + for (auto&& var : variables_) + { + os << delim_ << var.label; + } + } + + void printHeader(std::ostream& os, Json) const override + { + os << "[\n"; + } + + template + void printFullHeader(std::ostream& os, FormatT format) const + { + this->printHeader(os, format); + for (auto* mon : monitors_) + { + mon->printHeader(os, format); + } + os << '\n'; + } + + void printHeader() const + { + for (auto&& sink : sinks_) + { + switch (sink.format) + { + case Format::CSV: + printFullHeader(sink.os, Csv()); + break; + case Format::JSON: + printFullHeader(sink.os, Json()); + break; + case Format::YAML: + printFullHeader(sink.os, Yaml()); + break; + } + } + } + + void print(std::ostream& os, Csv) const override + { + os << *time_; + for (auto&& var : variables_) + { + os << delim_ << *var.value; + } + + for (auto* mon : monitors_) + { + mon->print(os, Csv()); + } + } + + void print(std::ostream& os, Json) const override + { + static bool after_first = false; + + std::string indent = " "; + if (after_first) + { + os << indent << ",\n"; + } + os << indent << "{\n"; + indent.append(2, ' '); + + os << indent << std::quoted("t") << ": " << *time_ << ",\n"; + for (auto&& var : variables_) + { + os << indent << std::quoted(var.label) << ": " << *var.value << ",\n"; + } + + after_first = false; + for (auto* mon : monitors_) + { + if (after_first) + { + os << ",\n"; + } + mon->print(os, Json()); + after_first = true; + } + + indent.erase(indent.size() - 2); + os << '\n' + << indent << "}"; + + after_first = true; + } + + void print(std::ostream& os, Yaml) const override + { + std::string indent = " "; + os << indent << "- t: " << *time_ << '\n'; + indent.append(2, ' '); + for (auto&& var : variables_) + { + os << indent << var.label << ": " << *var.value << '\n'; + } + + for (auto* mon : monitors_) + { + mon->print(os, Yaml()); + } + } + + template + void printFull(std::ostream& os, FormatT format) 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, format); + os << '\n'; + os << std::defaultfloat; + os.precision(orig_prec); + } + + void print() const + { + for (auto&& sink : sinks_) + { + switch (sink.format) + { + case Format::CSV: + printFull(sink.os, Csv()); + break; + case Format::JSON: + printFull(sink.os, Json()); + break; + case Format::YAML: + printFull(sink.os, Yaml()); + break; + } + } + } + + using VariableMonitorBase::printFooter; + + void printFooter(std::ostream& os, Json) const override + { + os << "]"; + } + + template + void printFullFooter(std::ostream& os, FormatT format) const + { + for (auto* mon : monitors_) + { + mon->printFooter(os, format); + } + this->printFooter(os, format); + } + + void printFooter() const + { + for (auto&& sink : sinks_) + { + switch (sink.format) + { + case Format::CSV: + printFullFooter(sink.os, Csv()); + break; + case Format::JSON: + printFullFooter(sink.os, Json()); + break; + case Format::YAML: + printFullFooter(sink.os, Yaml()); + break; + } + } + } + + private: + const RealT* time_{nullptr}; + + struct Sink + { + Sink() = delete; + + Sink(std::ostream& out, Format fmt) + : os(out), format(fmt) + { + } + + Sink(const std::string& fileName, Format fmt) + : file_stream(std::make_unique(fileName)), + os(*file_stream), + format(fmt) + { + } + + std::unique_ptr file_stream; + std::ostream& os; + Format format; + }; + + std::vector sinks_; + std::vector monitors_; + + struct Variable + { + std::string label; + const RealT* value; + }; + + std::vector variables_; + + std::string delim_{","}; + }; + } // namespace Model } // namespace GridKit diff --git a/examples/PhasorDynamics/Tiny/ThreeBus/Basic/ThreeBusBasic.json b/examples/PhasorDynamics/Tiny/ThreeBus/Basic/ThreeBusBasic.json index dbddf43fd..bad6c710f 100644 --- a/examples/PhasorDynamics/Tiny/ThreeBus/Basic/ThreeBusBasic.json +++ b/examples/PhasorDynamics/Tiny/ThreeBus/Basic/ThreeBusBasic.json @@ -8,6 +8,20 @@ "freq_base": 60.0, "va_base": 100000000.0 }, + "monitors": [ + { + "file_name": "mon.csv", + "format": "csv" + }, + { + "file_name": "mon.json", + "format": "json" + }, + { + "file_name": "mon.yaml", + "format": "yaml" + } + ], "buses": [ { "number": 0, From fb12702fc4a5f606d7d940b0c764f29cab84b566 Mon Sep 17 00:00:00 2001 From: Nicholson Koukpaizan Date: Mon, 1 Dec 2025 16:05:04 -0500 Subject: [PATCH 04/15] Install VariableMonitor.hpp. --- GridKit/Model/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/GridKit/Model/CMakeLists.txt b/GridKit/Model/CMakeLists.txt index df92faf5c..c49f5df27 100644 --- a/GridKit/Model/CMakeLists.txt +++ b/GridKit/Model/CMakeLists.txt @@ -10,3 +10,4 @@ add_subdirectory(PowerFlow) add_subdirectory(PowerElectronics) install(FILES Evaluator.hpp DESTINATION include/GridKit/Model) +install(FILES VariableMonitor.hpp DESTINATION include/GridKit/Model) From 96a1895c74aaa849fb1f8c905c8c962cbea816c5 Mon Sep 17 00:00:00 2001 From: Philip Fackler Date: Wed, 3 Dec 2025 16:12:04 -0600 Subject: [PATCH 05/15] A bit of cleanup and handling edge cases better --- GridKit/Model/CMakeLists.txt | 7 +- GridKit/Model/Evaluator.hpp | 6 +- GridKit/Model/PhasorDynamics/SystemModel.hpp | 14 +- .../Model/PhasorDynamics/SystemModelData.hpp | 2 +- .../SystemModelDataJSONParser.hpp | 14 +- GridKit/Model/VariableMonitor.hpp | 199 ++++++------------ GridKit/Model/VariableMonitorBase.hpp | 78 +++++++ GridKit/Solver/Dynamic/Ida.cpp | 9 + 8 files changed, 182 insertions(+), 147 deletions(-) create mode 100644 GridKit/Model/VariableMonitorBase.hpp diff --git a/GridKit/Model/CMakeLists.txt b/GridKit/Model/CMakeLists.txt index c49f5df27..73d248ec5 100644 --- a/GridKit/Model/CMakeLists.txt +++ b/GridKit/Model/CMakeLists.txt @@ -9,5 +9,8 @@ add_subdirectory(PhasorDynamics) add_subdirectory(PowerFlow) add_subdirectory(PowerElectronics) -install(FILES Evaluator.hpp DESTINATION include/GridKit/Model) -install(FILES VariableMonitor.hpp DESTINATION include/GridKit/Model) +install(FILES + Evaluator.hpp + VariableMonitorBase.hpp + VariableMonitor.hpp + DESTINATION include/GridKit/Model) diff --git a/GridKit/Model/Evaluator.hpp b/GridKit/Model/Evaluator.hpp index 7eaa3fd0a..244b198f4 100644 --- a/GridKit/Model/Evaluator.hpp +++ b/GridKit/Model/Evaluator.hpp @@ -5,7 +5,7 @@ #include #include #include -#include +#include #include namespace GridKit @@ -60,6 +60,10 @@ namespace GridKit return nullptr; } + virtual void stopMonitor() const + { + } + /** * @brief Is the Jacobian defined. Used in IDA to determine wether DQ is used or not * diff --git a/GridKit/Model/PhasorDynamics/SystemModel.hpp b/GridKit/Model/PhasorDynamics/SystemModel.hpp index 68e591a4a..9ae90d86e 100644 --- a/GridKit/Model/PhasorDynamics/SystemModel.hpp +++ b/GridKit/Model/PhasorDynamics/SystemModel.hpp @@ -443,6 +443,11 @@ namespace GridKit monitor_.start(); } + void stopMonitor() const override + { + monitor_.stop(); + } + /** * @todo Tagging differential variables * @@ -607,15 +612,6 @@ namespace GridKit void printMonitoredVariables() const override { monitor_.print(); - // os << "t: " << this->time_ << ":\n"; - // for (const auto& bus : buses_) - // { - // bus->printMonitoredVariables(os); - // } - // for (const auto& component : components_) - // { - // component->printMonitoredVariables(os); - // } } /** diff --git a/GridKit/Model/PhasorDynamics/SystemModelData.hpp b/GridKit/Model/PhasorDynamics/SystemModelData.hpp index 332b01759..ac68151e3 100644 --- a/GridKit/Model/PhasorDynamics/SystemModelData.hpp +++ b/GridKit/Model/PhasorDynamics/SystemModelData.hpp @@ -15,7 +15,7 @@ #include #include #include -#include +#include namespace GridKit { diff --git a/GridKit/Model/PhasorDynamics/SystemModelDataJSONParser.hpp b/GridKit/Model/PhasorDynamics/SystemModelDataJSONParser.hpp index ed62bcb83..8b378385a 100644 --- a/GridKit/Model/PhasorDynamics/SystemModelDataJSONParser.hpp +++ b/GridKit/Model/PhasorDynamics/SystemModelDataJSONParser.hpp @@ -9,7 +9,7 @@ #include #include #include -#include +#include #include namespace GridKit @@ -57,14 +57,24 @@ namespace GridKit if (j.contains("monitors")) { + sm.monitor_sink.emplace_back("", MonitorFormat::CSV, ","); for (auto&& raw_mon : j.at("monitors")) { auto file_name = raw_mon.value("file_name", std::string{}); auto fmt_str = raw_mon.at("format").get(); auto format = enum_parse(MonitorFormat{}, fmt_str); + auto delim = raw_mon.value("delim", std::string(",")); if (format.has_value()) { - sm.monitor_sink.emplace_back(file_name, format.value()); + if (file_name.empty()) + { + sm.monitor_sink.front().format = format.value(); + sm.monitor_sink.front().delim = delim; + } + else + { + sm.monitor_sink.emplace_back(file_name, format.value(), delim); + } } else { diff --git a/GridKit/Model/VariableMonitor.hpp b/GridKit/Model/VariableMonitor.hpp index b3c1bdff5..c40784d48 100644 --- a/GridKit/Model/VariableMonitor.hpp +++ b/GridKit/Model/VariableMonitor.hpp @@ -10,8 +10,11 @@ #include #include #include +#include +#include #include +#include #include #include @@ -28,70 +31,6 @@ namespace GridKit namespace Model { - enum class VariableMonitorFormat - { - CSV, - JSON, - YAML - }; - - class VariableMonitorBase - { - public: - struct Csv - { - }; - - struct Json - { - }; - - struct Yaml - { - }; - - using Format = VariableMonitorFormat; - - struct SinkSpec - { - std::string file_name; - Format format; - }; - - virtual ~VariableMonitorBase() - { - } - - virtual bool empty() const = 0; - - virtual void printHeader(std::ostream&, Csv) const = 0; - virtual void print(std::ostream&, Csv) const = 0; - - virtual void printFooter(std::ostream&, Csv) const - { - } - - virtual void printHeader(std::ostream&, Json) const - { - } - - virtual void print(std::ostream&, Json) const = 0; - - virtual void printFooter(std::ostream&, Json) const - { - } - - virtual void printHeader(std::ostream&, Yaml) const - { - } - - virtual void print(std::ostream&, Yaml) const = 0; - - virtual void printFooter(std::ostream&, Yaml) const - { - } - }; - template typename DataT> class VariableMonitor { @@ -129,19 +68,19 @@ namespace GridKit { } - void printHeader(std::ostream& os, Csv) const override + void printHeader(std::ostream& os, Csv csv) const override { for (auto v : variables_) { - os << delim_ << label_ << '_' << enumLabel(v); + os << csv.delim << label_ << '_' << enumLabel(v); } } - void print(std::ostream& os, Csv) const override + void print(std::ostream& os, Csv csv) const override { for (auto v : variables_) { - os << delim_ << f(v); + os << csv.delim << f(v); } } @@ -159,6 +98,7 @@ namespace GridKit 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()); @@ -212,7 +152,6 @@ namespace GridKit std::array f_; std::vector variables_; mutable std::string indent_{" "}; - std::string delim_{","}; std::string label_; }; @@ -233,12 +172,10 @@ namespace GridKit explicit VariableMonitor(const RealT& time_var) : time_(&time_var) { - sinks_.emplace_back(std::cout, Format::CSV); } virtual ~VariableMonitor() { - stop(); } void addMonitor(const VariableMonitorBase* monitor) @@ -253,11 +190,11 @@ namespace GridKit { if (spec.file_name.empty()) { - sinks_.front().format = spec.format; + sinks_.push_back(make_sink(spec, std::cout)); } else { - sinks_.emplace_back(spec.file_name, spec.format); + sinks_.push_back(make_sink(spec, spec.file_name)); } } @@ -289,12 +226,12 @@ namespace GridKit using VariableMonitorBase::printHeader; - void printHeader(std::ostream& os, Csv) const override + void printHeader(std::ostream& os, Csv csv) const override { os << "t"; for (auto&& var : variables_) { - os << delim_ << var.label; + os << csv.delim << var.label; } } @@ -304,12 +241,12 @@ namespace GridKit } template - void printFullHeader(std::ostream& os, FormatT format) const + void printFullHeader(std::ostream& os, FormatT fmt) const { - this->printHeader(os, format); + this->printHeader(os, fmt); for (auto* mon : monitors_) { - mon->printHeader(os, format); + mon->printHeader(os, fmt); } os << '\n'; } @@ -318,41 +255,30 @@ namespace GridKit { for (auto&& sink : sinks_) { - switch (sink.format) - { - case Format::CSV: - printFullHeader(sink.os, Csv()); - break; - case Format::JSON: - printFullHeader(sink.os, Json()); - break; - case Format::YAML: - printFullHeader(sink.os, Yaml()); - break; - } + std::visit([this](auto&& sink) + { printFullHeader(sink.os, sink.format); }, + sink); } } - void print(std::ostream& os, Csv) const override + void print(std::ostream& os, Csv csv) const override { os << *time_; for (auto&& var : variables_) { - os << delim_ << *var.value; + os << csv.delim << *var.value; } for (auto* mon : monitors_) { - mon->print(os, Csv()); + mon->print(os, csv); } } - void print(std::ostream& os, Json) const override + void print(std::ostream& os, Json json) const override { - static bool after_first = false; - std::string indent = " "; - if (after_first) + if (json.after_first) { os << indent << ",\n"; } @@ -365,7 +291,7 @@ namespace GridKit os << indent << std::quoted(var.label) << ": " << *var.value << ",\n"; } - after_first = false; + auto after_first = false; for (auto* mon : monitors_) { if (after_first) @@ -379,8 +305,6 @@ namespace GridKit indent.erase(indent.size() - 2); os << '\n' << indent << "}"; - - after_first = true; } void print(std::ostream& os, Yaml) const override @@ -400,13 +324,13 @@ namespace GridKit } template - void printFull(std::ostream& os, FormatT format) const + 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, format); + this->print(os, fmt); os << '\n'; os << std::defaultfloat; os.precision(orig_prec); @@ -416,18 +340,15 @@ namespace GridKit { for (auto&& sink : sinks_) { - switch (sink.format) - { - case Format::CSV: - printFull(sink.os, Csv()); - break; - case Format::JSON: - printFull(sink.os, Json()); - break; - case Format::YAML: - printFull(sink.os, Yaml()); - break; - } + std::visit([this](auto&& sink) + { + printFull(sink.os, sink.format); + using T = std::remove_cvref_t; + if constexpr (std::is_same_v>) + { + sink.format.after_first = true; + } }, + sink); } } @@ -435,7 +356,7 @@ namespace GridKit void printFooter(std::ostream& os, Json) const override { - os << "]"; + os << "\n]\n"; } template @@ -452,46 +373,60 @@ namespace GridKit { for (auto&& sink : sinks_) { - switch (sink.format) - { - case Format::CSV: - printFullFooter(sink.os, Csv()); - break; - case Format::JSON: - printFullFooter(sink.os, Json()); - break; - case Format::YAML: - printFullFooter(sink.os, Yaml()); - break; - } + std::visit([this](auto&& sink) + { printFullFooter(sink.os, sink.format); }, + sink); } } private: const RealT* time_{nullptr}; + template struct Sink { Sink() = delete; - Sink(std::ostream& out, Format fmt) + Sink(std::ostream& out, FormatT fmt) : os(out), format(fmt) { } - Sink(const std::string& fileName, Format fmt) + Sink(const std::string& fileName, FormatT fmt) : file_stream(std::make_unique(fileName)), os(*file_stream), format(fmt) { } + Sink(Sink&&) = default; + std::unique_ptr file_stream; std::ostream& os; - Format format; + FormatT format; }; - std::vector sinks_; + using SinkVariant = std::variant, Sink, Sink>; + + template + static SinkVariant make_sink(SinkSpec spec, T&& arg) + { + switch (spec.format) + { + case Format::CSV: + return Sink(std::forward(arg), Csv{spec.delim}); + break; + case Format::JSON: + return Sink(std::forward(arg), Json{}); + break; + case Format::YAML: + return Sink(std::forward(arg), Yaml{}); + break; + } + throw std::runtime_error("Invalid monitor output format"); + } + + std::vector sinks_; std::vector monitors_; struct Variable @@ -502,7 +437,7 @@ namespace GridKit std::vector variables_; - std::string delim_{","}; + mutable bool after_first_{false}; }; } // namespace Model diff --git a/GridKit/Model/VariableMonitorBase.hpp b/GridKit/Model/VariableMonitorBase.hpp new file mode 100644 index 000000000..fd6664c6f --- /dev/null +++ b/GridKit/Model/VariableMonitorBase.hpp @@ -0,0 +1,78 @@ +#pragma once + +#include +#include + +namespace GridKit +{ + namespace Model + { + enum class VariableMonitorFormat + { + CSV, + JSON, + YAML + }; + + class VariableMonitorBase + { + public: + struct Csv + { + std::string delim{","}; + }; + + struct Json + { + mutable bool after_first{false}; + }; + + struct Yaml + { + }; + + using Format = VariableMonitorFormat; + + struct SinkSpec + { + std::string file_name; + Format format; + std::string delim; + }; + + virtual ~VariableMonitorBase() + { + } + + virtual bool empty() const = 0; + + virtual void printHeader(std::ostream&, Csv) const = 0; + virtual void print(std::ostream&, Csv) const = 0; + + virtual void printFooter(std::ostream&, Csv) const + { + } + + virtual void printHeader(std::ostream&, Json) const + { + } + + virtual void print(std::ostream&, Json) const = 0; + + virtual void printFooter(std::ostream&, Json) const + { + } + + virtual void printHeader(std::ostream&, Yaml) const + { + } + + virtual void print(std::ostream&, Yaml) const = 0; + + virtual void printFooter(std::ostream&, Yaml) const + { + } + }; + + } // namespace Model +} // namespace GridKit diff --git a/GridKit/Solver/Dynamic/Ida.cpp b/GridKit/Solver/Dynamic/Ida.cpp index 9089d5ccb..713630e7c 100644 --- a/GridKit/Solver/Dynamic/Ida.cpp +++ b/GridKit/Solver/Dynamic/Ida.cpp @@ -352,6 +352,15 @@ namespace AnalysisManager model_->updateTime(tf, 0.0); copyVec(yy_, model_->y()); copyVec(yp_, model_->yp()); + if (model_->monitoring()) + { + // model_->printMonitoredVariables(); + model_->stopMonitor(); + } + // if (step_callback.has_value()) + // { + // (*step_callback)(tret); + // } // std::cout << "\n"; return retval; From 23d1683b6b36778f9d46de6be33f6164be6154a7 Mon Sep 17 00:00:00 2001 From: Philip Fackler Date: Thu, 4 Dec 2025 10:31:35 -0600 Subject: [PATCH 06/15] Add documentation --- CHANGELOG.md | 1 + GridKit/Model/Evaluator.hpp | 12 ++ .../Model/PhasorDynamics/Branch/Branch.hpp | 1 + .../PhasorDynamics/Branch/BranchData.hpp | 3 + GridKit/Model/PhasorDynamics/Bus/BusData.hpp | 5 + GridKit/Model/PhasorDynamics/BusBase.hpp | 1 + .../PhasorDynamics/Exciter/IEEET1/Ieeet1.hpp | 2 + .../Exciter/IEEET1/Ieeet1Data.hpp | 3 + GridKit/Model/PhasorDynamics/INPUT_FORMAT.md | 19 ++ .../SynchronousMachine/GENROUwS/Genrou.hpp | 2 + .../GENROUwS/GenrouData.hpp | 3 + GridKit/Model/PhasorDynamics/SystemModel.hpp | 4 + .../Model/PhasorDynamics/SystemModelData.hpp | 1 + GridKit/Model/VariableMonitor.hpp | 193 ++++++++++++++++-- GridKit/Model/VariableMonitorBase.hpp | 70 ++++++- GridKit/Solver/Dynamic/Ida.cpp | 5 - GridKit/Utilities/Enum.hpp | 11 + tests/UnitTests/Utilities/CaseFormatTests.hpp | 12 ++ 18 files changed, 314 insertions(+), 34 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 161b37c3e..04aa4071b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,7 @@ - Added a header file defining constants to be used throughout the code. - Added `GridKitDocs` target for Doxygen documentation. - Added a header file defining common math functions (e.g., sigmoid) to be used throughout the code. +- Added capability to print monitored variables in multiple formats, triggered from `Ida::runSimulation`. ## v0.1 diff --git a/GridKit/Model/Evaluator.hpp b/GridKit/Model/Evaluator.hpp index 244b198f4..8d8b7a3d7 100644 --- a/GridKit/Model/Evaluator.hpp +++ b/GridKit/Model/Evaluator.hpp @@ -46,20 +46,32 @@ namespace GridKit virtual IdxT size() = 0; virtual IdxT nnz() = 0; + /** + * @brief Is there something to monitor? Defaults to `false` + */ virtual bool monitoring() const { return false; } + /** + * @brief Print variables at current state + */ virtual void printMonitoredVariables() const { } + /** + * @brief Get non-owning reference to monitor + */ virtual const VariableMonitorBase* getMonitor() const { return nullptr; } + /** + * @brief Tell monitor to wrap up + */ virtual void stopMonitor() const { } diff --git a/GridKit/Model/PhasorDynamics/Branch/Branch.hpp b/GridKit/Model/PhasorDynamics/Branch/Branch.hpp index c95f731ca..b91ce8acd 100644 --- a/GridKit/Model/PhasorDynamics/Branch/Branch.hpp +++ b/GridKit/Model/PhasorDynamics/Branch/Branch.hpp @@ -174,6 +174,7 @@ namespace GridKit RealT b_; RealT g_; + /// Variable monitor Model::VariableMonitor monitor_; }; diff --git a/GridKit/Model/PhasorDynamics/Branch/BranchData.hpp b/GridKit/Model/PhasorDynamics/Branch/BranchData.hpp index e1d93d089..cb876ded4 100644 --- a/GridKit/Model/PhasorDynamics/Branch/BranchData.hpp +++ b/GridKit/Model/PhasorDynamics/Branch/BranchData.hpp @@ -45,6 +45,9 @@ namespace GridKit SIZE }; + /** + * @brief Convert enum value to string label + */ inline const std::string& enumLabel(BranchMonitorableVariables v) { static const std::string labels[] = { diff --git a/GridKit/Model/PhasorDynamics/Bus/BusData.hpp b/GridKit/Model/PhasorDynamics/Bus/BusData.hpp index 48f905006..a6a82b29f 100644 --- a/GridKit/Model/PhasorDynamics/Bus/BusData.hpp +++ b/GridKit/Model/PhasorDynamics/Bus/BusData.hpp @@ -27,6 +27,9 @@ namespace GridKit SIZE }; + /** + * @brief Convert enum value to string label + */ inline const std::string& enumLabel(BusMonitorableVariables v) { static const std::string labels[] = {"Vr", "Vi", "Vm", "Va"}; @@ -66,6 +69,8 @@ namespace GridKit RealT v_base{1.0}; ///< Voltage base in volts std::optional freq_base; ///< Override for the system-wide base frequency std::optional va_base; ///< Override for the system-wide power base + + /// Alias using MonitorableVariables = BusMonitorableVariables; /// Set indicating the variables being monitored diff --git a/GridKit/Model/PhasorDynamics/BusBase.hpp b/GridKit/Model/PhasorDynamics/BusBase.hpp index 0a9984af1..625d0e425 100644 --- a/GridKit/Model/PhasorDynamics/BusBase.hpp +++ b/GridKit/Model/PhasorDynamics/BusBase.hpp @@ -181,6 +181,7 @@ namespace GridKit std::map residual_indices_; ///< Map between local and global (system-level) /// residual indices + /// Variable monitor Model::VariableMonitor monitor_; std::vector y_; diff --git a/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1.hpp b/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1.hpp index 063e2d482..13cf14f68 100644 --- a/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1.hpp +++ b/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1.hpp @@ -156,11 +156,13 @@ namespace GridKit /// Component signal extension ComponentSignals signals_; + /// Variable monitor Model::VariableMonitor monitor_; // Parameter initialization function void initModelParams(const model_data_type& data); + /// Associate variable getter functions with enum values void initializeMonitor(); }; diff --git a/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1Data.hpp b/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1Data.hpp index ae87e5307..6c8dc459f 100644 --- a/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1Data.hpp +++ b/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1Data.hpp @@ -51,6 +51,9 @@ namespace GridKit SIZE }; + /** + * @brief Convert enum value to string label + */ inline const std::string& enumLabel(Ieeet1MonitorableVariables v) { static std::string labels[] = {"efd", "ksat"}; diff --git a/GridKit/Model/PhasorDynamics/INPUT_FORMAT.md b/GridKit/Model/PhasorDynamics/INPUT_FORMAT.md index ca6d9cdd3..f54e9b510 100644 --- a/GridKit/Model/PhasorDynamics/INPUT_FORMAT.md +++ b/GridKit/Model/PhasorDynamics/INPUT_FORMAT.md @@ -55,6 +55,25 @@ Contained in the `header` key is an object with the following items: `freq_base` | A floating point value indicating the system frequency base in hertz (Hz). This is commonly 60 Hz `va_base` | A floating point value indicating the system power base in volt-amperes (VA). This is commonly 100e6 VA +### Monitors + +Contained in the `monitors` key is an array of objects, each of which describes +an output for monitored variables (those listed in the `mon` field of a +[bus](#buses) or [device](#devices). The following fields are supported: + + Name | Description + -------------------|------------------------------------------------------ + `file_name` | Optional string indicating output file name. If omitted, `stdout` is used. + `format` | One of { "CSV", "JSON", "YAML" } (case-insensitive) + `delim` | Optional string specifying delimiter to use for CSV output (default is `","`). + +__NOTE__: If `monitors` entry is omitted entirely, you will get the default CSV +output to `stdout`. If you wish to change the console output format, use an +entry here without the `file_name` field. For example: +```json + "monitors": [ { "format": "YAML" } ] +``` + ### Buses Contained in the `buses` key is an array of objects, each of which represent diff --git a/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROUwS/Genrou.hpp b/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROUwS/Genrou.hpp index 2902e2f18..e2eba8e48 100644 --- a/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROUwS/Genrou.hpp +++ b/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROUwS/Genrou.hpp @@ -158,6 +158,7 @@ namespace GridKit private: void initializeParameters(const model_data_type& data); + /// Associate variable getter functions with enum values void initializeMonitor(); void setDerivedParams(); @@ -242,6 +243,7 @@ namespace GridKit std::vector ws_; std::map ws_indices_; + /// Variable monitor Model::VariableMonitor monitor_; }; diff --git a/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROUwS/GenrouData.hpp b/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROUwS/GenrouData.hpp index 20c26538d..0df2d2dde 100644 --- a/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROUwS/GenrouData.hpp +++ b/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROUwS/GenrouData.hpp @@ -58,6 +58,9 @@ namespace GridKit SIZE }; + /** + * @brief Convert enum value to string label + */ inline const std::string& enumLabel(GenrouMonitorableVariables v) { static const std::string labels[] = { diff --git a/GridKit/Model/PhasorDynamics/SystemModel.hpp b/GridKit/Model/PhasorDynamics/SystemModel.hpp index 9ae90d86e..0fa90306d 100644 --- a/GridKit/Model/PhasorDynamics/SystemModel.hpp +++ b/GridKit/Model/PhasorDynamics/SystemModel.hpp @@ -428,6 +428,9 @@ namespace GridKit return 0; } + /** + * @brief Add monitors from buses and components and start monitor + */ void initializeMonitor() { for (const auto* bus : buses_) @@ -742,6 +745,7 @@ namespace GridKit bool owns_components_{false}; + /// Variable monitor Model::VariableMonitor monitor_; }; // class SystemModel diff --git a/GridKit/Model/PhasorDynamics/SystemModelData.hpp b/GridKit/Model/PhasorDynamics/SystemModelData.hpp index ac68151e3..18ef9e8f4 100644 --- a/GridKit/Model/PhasorDynamics/SystemModelData.hpp +++ b/GridKit/Model/PhasorDynamics/SystemModelData.hpp @@ -87,6 +87,7 @@ namespace GridKit std::vector exciter; ///< Exciters within the model std::vector signal; ///< Signal nodes + /// Monitor sink specs std::vector monitor_sink; }; diff --git a/GridKit/Model/VariableMonitor.hpp b/GridKit/Model/VariableMonitor.hpp index c40784d48..9677daff4 100644 --- a/GridKit/Model/VariableMonitor.hpp +++ b/GridKit/Model/VariableMonitor.hpp @@ -36,21 +36,52 @@ namespace GridKit { }; - template typename EvalT, template typename DataT> - class VariableMonitor, DataT> : public VariableMonitorBase + /** + * @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. + */ + template typename EvalT, + template typename DataT> + class VariableMonitor, DataT> + : public VariableMonitorBase { + template typename> + friend class VariableMonitor; + public: + /// Underlying real value type using RealT = typename GridKit::ScalarTraits::RealT; + /// Type of (EvalT)Data class expected to have MonitorableVariables enum using ObjData = DataT; + /// Enum of valid monitorable variables using VariableEnum = typename ObjData::MonitorableVariables; - using EnumId = std::underlying_type_t; + /// Abstraction type for functions returning a monitored value using ValueFunction = std::function; + ///@{ + /// @brief Alias using Csv = VariableMonitorBase::Csv; using Json = VariableMonitorBase::Json; using Yaml = VariableMonitorBase::Yaml; + ///@} VariableMonitor() = default; + /** + * @brief Construct monitor with component/bus label and set of variables + * selected for monitoring + * + * @param label Monitor label used to disambiguate like variables from + * different objects of same type + * @param variables Variables selected for monitoring + */ VariableMonitor(const std::string& label, const std::set& variables) : label_(label) @@ -58,6 +89,13 @@ namespace GridKit std::ranges::copy(variables, std::back_inserter(variables_)); } + /** + * @brief Construct from ObjData object + * + * Constructs the monitor label from elements of the data object + * + * @param data Expected to be derived from ComponentData + */ VariableMonitor(const ObjData& data) : VariableMonitor(data.device_class + "_" + data.disambiguation_string, data.monitored_variables) @@ -68,6 +106,23 @@ namespace GridKit { } + bool empty() const override + { + return variables_.empty(); + } + + /** + * @brief Associate a value getter with a variable enum value + * + * @note This does not designate the variable for printing. It defines how + * to get the variable if it is printed. + */ + void set(VariableEnum v, ValueFunction f) + { + f_[Utilities::enumId(v)] = f; + } + + protected: void printHeader(std::ostream& os, Csv csv) const override { for (auto v : variables_) @@ -84,6 +139,9 @@ namespace GridKit } } + /** + * @brief Print single variable + */ void print(std::ostream& os, VariableEnum v, Json) const { os << indent_ << std::quoted(enumLabel(v)) << ": " << f(v) << ",\n"; @@ -114,9 +172,12 @@ namespace GridKit { } + /** + * @brief Print single variable + */ void print(std::ostream& os, VariableEnum v, Yaml) const { - os << indent_ << indent_ << enumLabel(v) << ": " << f(v) << '\n'; + os << indent_ << enumLabel(v) << ": " << f(v) << '\n'; } void print(std::ostream& os, Yaml) const override @@ -126,49 +187,68 @@ namespace GridKit return; } os << indent_ << label_ << ":\n"; + indent_.append(2, ' '); for (auto v : variables_) { print(os, v, Yaml()); } - } - - bool empty() const override - { - return variables_.empty(); - } - - void set(VariableEnum v, ValueFunction f) - { - f_[Utilities::enumId(v)] = f; + indent_.erase(indent_.size() - 2); } private: + /// Compile-time constant size: length of enum value list + static constexpr auto enum_size_ = Utilities::enumSize; + + /** + * @brief Convenience function to access value associated with enum value + */ auto f(VariableEnum v) const { return static_cast(f_[Utilities::enumId(v)]()); } - static constexpr auto enum_size_ = Utilities::enumSize; + /// Set of functions associated with each enum value std::array f_; + /// Set of selected enum values std::vector variables_; + /// Indent string used for formatting mutable std::string indent_{" "}; + /// Monitor disambiguation label std::string label_; }; + /** + * @brief Monitor associated with the SystemModel; controls output from + * component and bus monitors. + * + * High-level print functions (without parameters) manage printing for all + * monitors for multiple output sinks. + */ template - class VariableMonitor, PhasorDynamics::SystemModelData> : public VariableMonitorBase + class VariableMonitor, + PhasorDynamics::SystemModelData> + : public VariableMonitorBase { public: + /// Underlying real value type using RealT = typename GridKit::ScalarTraits::RealT; + /// Abstraction type for functions returning a monitored value using ValueFunction = std::function; + ///@{ + /// @brief Alias using Format = VariableMonitorFormat; using SinkSpec = VariableMonitorBase::SinkSpec; using Csv = VariableMonitorBase::Csv; using Json = VariableMonitorBase::Json; using Yaml = VariableMonitorBase::Yaml; + ///@} + /// Default to empty monitor VariableMonitor() = default; + /** + * @brief Constructor expects a time variable to monitor + */ explicit VariableMonitor(const RealT& time_var) : time_(&time_var) { @@ -178,6 +258,15 @@ namespace GridKit { } + /** + * @brief Add a monitor to the output + * + * Each component and bus could have a monitor for their respective + * values. + * + * @param monitor Monitor to add (raw pointer indicates ownership is + * elsewhere) + */ void addMonitor(const VariableMonitorBase* monitor) { if (monitor && !monitor->empty()) @@ -186,6 +275,13 @@ namespace GridKit } } + /** + * @brief Add output sink based on spec + * + * If `spec.file_name` is empty, `std::cout` is used. + * + * @param spec Specifies details for the sink. + */ void addSink(const SinkSpec& spec) { if (spec.file_name.empty()) @@ -198,16 +294,26 @@ namespace GridKit } } + /** + * @brief Provide additional top-level variables (alongside time) to be + * printed before submonitors. + * + * @param label Header label for CSV; key for JSON or YAML + * @param value Pointer to monitored variable + */ void addVariable(const std::string& label, const RealT* value) { variables_.emplace_back(label, value); } - bool empty() const + bool empty() const override { return (!time_) || monitors_.empty(); } + /** + * @brief Print header if we're monitoring + */ void start() const { if (!empty()) @@ -216,6 +322,9 @@ namespace GridKit } } + /** + * @brief Print footer if we're monitoring + */ void stop() const { if (!empty()) @@ -224,6 +333,7 @@ namespace GridKit } } + /// @copydoc VariableMonitorBase::printHeader using VariableMonitorBase::printHeader; void printHeader(std::ostream& os, Csv csv) const override @@ -240,6 +350,9 @@ namespace GridKit os << "[\n"; } + /** + * @brief Organize header output for this and all submonitors + */ template void printFullHeader(std::ostream& os, FormatT fmt) const { @@ -251,6 +364,9 @@ namespace GridKit os << '\n'; } + /** + * @brief Print header for all sinks + */ void printHeader() const { for (auto&& sink : sinks_) @@ -323,6 +439,9 @@ namespace GridKit } } + /** + * @brief Organize variable output for this and all submonitors + */ template void printFull(std::ostream& os, FormatT fmt) const { @@ -336,6 +455,9 @@ namespace GridKit os.precision(orig_prec); } + /** + * @brief Print variables to each sink + */ void print() const { for (auto&& sink : sinks_) @@ -352,6 +474,7 @@ namespace GridKit } } + /// @copydoc VariableMonitorBase::printFooter using VariableMonitorBase::printFooter; void printFooter(std::ostream& os, Json) const override @@ -359,6 +482,9 @@ namespace GridKit os << "\n]\n"; } + /** + * @brief Organize footer output for this and all submonitors + */ template void printFullFooter(std::ostream& os, FormatT format) const { @@ -369,6 +495,9 @@ namespace GridKit this->printFooter(os, format); } + /** + * @brief Print footer for all sinks + */ void printFooter() const { for (auto&& sink : sinks_) @@ -380,18 +509,28 @@ namespace GridKit } private: + /// Time variable; printed first const RealT* time_{nullptr}; + /** + * @brief Define sink for a specific output format + */ template struct Sink { Sink() = delete; + /** + * @brief Version for an output stream that already exists + */ Sink(std::ostream& out, FormatT fmt) : os(out), format(fmt) { } + /** + * @brief Version for opening an output stream for the given file + */ Sink(const std::string& fileName, FormatT fmt) : file_stream(std::make_unique(fileName)), os(*file_stream), @@ -399,15 +538,25 @@ namespace GridKit { } + /** + * @brief Have to move because of unique_ptr + */ Sink(Sink&&) = default; + /// Output file stream (if we opened one) std::unique_ptr file_stream; + /// Output stream for printing std::ostream& os; + /// Output format object which may have useful members FormatT format; }; + /// Variant type for all possible sink types using SinkVariant = std::variant, Sink, Sink>; + /** + * @brief Factory function mapping format enum value to sink type + */ template static SinkVariant make_sink(SinkSpec spec, T&& arg) { @@ -426,18 +575,24 @@ namespace GridKit throw std::runtime_error("Invalid monitor output format"); } + /// Collection of output sinks std::vector sinks_; + /// Collection of submonitors std::vector monitors_; + /** + * @brief Key/Value object for extra top-level variables + */ struct Variable { + /// Header label for CSV; key for JSON or YAML std::string label; + /// Pointer to monitored variable const RealT* value; }; + /// Collection of extra top-level monitored variables std::vector variables_; - - mutable bool after_first_{false}; }; } // namespace Model diff --git a/GridKit/Model/VariableMonitorBase.hpp b/GridKit/Model/VariableMonitorBase.hpp index fd6664c6f..94840f20e 100644 --- a/GridKit/Model/VariableMonitorBase.hpp +++ b/GridKit/Model/VariableMonitorBase.hpp @@ -1,5 +1,9 @@ #pragma once +/** + * @file + */ + #include #include @@ -7,36 +11,61 @@ namespace GridKit { namespace Model { + /** + * @enum VariableMonitorFormat + * Available formats for monitor output + */ enum class VariableMonitorFormat { - CSV, - JSON, - YAML + CSV, ///< CSV format + JSON, ///< JSON format + YAML ///< YAML format }; + /** + * @brief Abstract class for managing output of monitored variables + * + * This class is used for both the high-level control monitor and the + * individual component and bus monitors. + */ class VariableMonitorBase { + template typename> + friend class VariableMonitor; + public: + /// Type used for dispatch struct Csv { + /// Delimiter for CSV line output std::string delim{","}; }; + /// Type used for dispatch struct Json { + /// Implementation detail used to prevent a comma before the first block mutable bool after_first{false}; }; + /// Type used for dispatch struct Yaml { }; + /// Short alias for local use using Format = VariableMonitorFormat; + /** + * @brief Defines information necessary to create a monitor sink + */ struct SinkSpec { + /// Output file name (empty for stdout) std::string file_name; + /// Output format Format format; + /// Delimiter (used only with CSV format currently) std::string delim; }; @@ -44,34 +73,55 @@ namespace GridKit { } + /** + * @brief Is there nothing to monitor? + */ virtual bool empty() const = 0; + protected: + ///@{ + /** + * @brief Print items relevant to the start of a file + */ virtual void printHeader(std::ostream&, Csv) const = 0; - virtual void print(std::ostream&, Csv) const = 0; - virtual void printFooter(std::ostream&, Csv) const + virtual void printHeader(std::ostream&, Json) const { } - virtual void printHeader(std::ostream&, Json) const + virtual void printHeader(std::ostream&, Yaml) const { } + ///@} + + ///@{ + /** + * @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 printFooter(std::ostream&, Json) const + ///@} + + ///@{ + /** + * @brief Print items relevant to the end of a file + */ + virtual void printFooter(std::ostream&, Csv) const { } - virtual void printHeader(std::ostream&, Yaml) const + virtual void printFooter(std::ostream&, Json) const { } - virtual void print(std::ostream&, Yaml) const = 0; - virtual void printFooter(std::ostream&, Yaml) const { } + + ///@} }; } // namespace Model diff --git a/GridKit/Solver/Dynamic/Ida.cpp b/GridKit/Solver/Dynamic/Ida.cpp index 713630e7c..e6e605175 100644 --- a/GridKit/Solver/Dynamic/Ida.cpp +++ b/GridKit/Solver/Dynamic/Ida.cpp @@ -354,13 +354,8 @@ namespace AnalysisManager copyVec(yp_, model_->yp()); if (model_->monitoring()) { - // model_->printMonitoredVariables(); model_->stopMonitor(); } - // if (step_callback.has_value()) - // { - // (*step_callback)(tret); - // } // std::cout << "\n"; return retval; diff --git a/GridKit/Utilities/Enum.hpp b/GridKit/Utilities/Enum.hpp index 96b35820b..ec266c464 100644 --- a/GridKit/Utilities/Enum.hpp +++ b/GridKit/Utilities/Enum.hpp @@ -1,21 +1,32 @@ #pragma once +/** + * @file + */ + namespace GridKit { namespace Utilities { + /** + * @brief Requires `TEnum` to be an `enum` and have a `SIZE` member + */ template concept SizedEnum = requires { std::is_enum_v; TEnum::SIZE; }; + /** + * @brief Convert an enum value to its underlying integer type + */ template inline constexpr std::underlying_type_t enumId(TEnum v) noexcept { return static_cast>(v); } + /// Length of an enum template inline constexpr auto enumSize = enumId(TEnum::SIZE); } // namespace Utilities diff --git a/tests/UnitTests/Utilities/CaseFormatTests.hpp b/tests/UnitTests/Utilities/CaseFormatTests.hpp index 35dc784df..22f180ed3 100644 --- a/tests/UnitTests/Utilities/CaseFormatTests.hpp +++ b/tests/UnitTests/Utilities/CaseFormatTests.hpp @@ -30,6 +30,7 @@ namespace GridKit TestOutcome simpleParse() { using namespace GridKit::PhasorDynamics; + using namespace GridKit::Model; using BusData = BusData; using BusType = typename BusData::BusType; @@ -44,6 +45,12 @@ namespace GridKit "freq_base": 60.0, "va_base": 100e6 }, + "monitors": [ + { + "file_name": "mon.json", + "format": "json" + } + ], "buses": [ { "number": 1, "class": "bus", "name": "Bus 1", "init": {"Vr":0.994988, "Vi":0.099997}, "v_base": 115e3, "mon": ["Vr", "Vi"] }, { "number": 2, "class": "infinite_bus", "name": "Bus 2", "init": {"Vr":1.0, "Vi":0.0}, "v_base": 115e3 } @@ -68,6 +75,11 @@ namespace GridKit success *= result.freq_base == 60.0; success *= result.va_base == 100.0e6; + success *= result.monitor_sink[0].file_name.empty(); + success *= result.monitor_sink[0].format == VariableMonitorFormat::CSV; + success *= result.monitor_sink[1].file_name == "mon.json"; + success *= result.monitor_sink[1].format == VariableMonitorFormat::JSON; + success *= result.bus.size() == 2; success *= result.branch.size() == 1; success *= result.bus_fault.size() == 1; From b4ae55365e545e49e26b66bd9a65216cd2f8268f Mon Sep 17 00:00:00 2001 From: Philip Fackler Date: Thu, 4 Dec 2025 13:40:07 -0600 Subject: [PATCH 07/15] Add explicit deduction guide for Sink --- GridKit/Model/VariableMonitor.hpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/GridKit/Model/VariableMonitor.hpp b/GridKit/Model/VariableMonitor.hpp index 9677daff4..8b1fce7b3 100644 --- a/GridKit/Model/VariableMonitor.hpp +++ b/GridKit/Model/VariableMonitor.hpp @@ -551,6 +551,9 @@ namespace GridKit FormatT format; }; + template + Sink(ArgT&&, FormatT) -> Sink; + /// Variant type for all possible sink types using SinkVariant = std::variant, Sink, Sink>; From 89542e630c512bf6678579624a8fbb5d936e31a2 Mon Sep 17 00:00:00 2001 From: Philip Fackler Date: Thu, 4 Dec 2025 15:36:47 -0600 Subject: [PATCH 08/15] Use separate stopMonitor function to allow multiple calls to runSimulation --- GridKit/Model/Evaluator.hpp | 9 ++- GridKit/Model/PhasorDynamics/SystemModel.hpp | 6 +- GridKit/Model/VariableMonitor.hpp | 71 ++++++++++++++++--- GridKit/Solver/Dynamic/Ida.cpp | 6 +- .../Tiny/ThreeBus/Basic/ThreeBusBasicJson.cpp | 2 + 5 files changed, 82 insertions(+), 12 deletions(-) diff --git a/GridKit/Model/Evaluator.hpp b/GridKit/Model/Evaluator.hpp index 8d8b7a3d7..819afaff6 100644 --- a/GridKit/Model/Evaluator.hpp +++ b/GridKit/Model/Evaluator.hpp @@ -69,10 +69,17 @@ namespace GridKit return nullptr; } + /** + * @brief Get monitor ready for output + */ + virtual void startMonitor() + { + } + /** * @brief Tell monitor to wrap up */ - virtual void stopMonitor() const + virtual void stopMonitor() { } diff --git a/GridKit/Model/PhasorDynamics/SystemModel.hpp b/GridKit/Model/PhasorDynamics/SystemModel.hpp index 0fa90306d..9177c0951 100644 --- a/GridKit/Model/PhasorDynamics/SystemModel.hpp +++ b/GridKit/Model/PhasorDynamics/SystemModel.hpp @@ -424,6 +424,7 @@ namespace GridKit } initializeMonitor(); + startMonitor(); return 0; } @@ -442,11 +443,14 @@ namespace GridKit { monitor_.addMonitor(component->getMonitor()); } + } + void startMonitor() override + { monitor_.start(); } - void stopMonitor() const override + void stopMonitor() override { monitor_.stop(); } diff --git a/GridKit/Model/VariableMonitor.hpp b/GridKit/Model/VariableMonitor.hpp index 8b1fce7b3..ebb73753a 100644 --- a/GridKit/Model/VariableMonitor.hpp +++ b/GridKit/Model/VariableMonitor.hpp @@ -314,10 +314,11 @@ namespace GridKit /** * @brief Print header if we're monitoring */ - void start() const + void start() { if (!empty()) { + startSinks(); printHeader(); } } @@ -325,11 +326,12 @@ namespace GridKit /** * @brief Print footer if we're monitoring */ - void stop() const + void stop() { if (!empty()) { printFooter(); + stopSinks(); } } @@ -372,7 +374,7 @@ namespace GridKit for (auto&& sink : sinks_) { std::visit([this](auto&& sink) - { printFullHeader(sink.os, sink.format); }, + { printFullHeader(*sink.os, sink.format); }, sink); } } @@ -464,7 +466,7 @@ namespace GridKit { std::visit([this](auto&& sink) { - printFull(sink.os, sink.format); + printFull(*sink.os, sink.format); using T = std::remove_cvref_t; if constexpr (std::is_same_v>) { @@ -503,7 +505,7 @@ namespace GridKit for (auto&& sink : sinks_) { std::visit([this](auto&& sink) - { printFullFooter(sink.os, sink.format); }, + { printFullFooter(*sink.os, sink.format); }, sink); } } @@ -524,7 +526,7 @@ namespace GridKit * @brief Version for an output stream that already exists */ Sink(std::ostream& out, FormatT fmt) - : os(out), format(fmt) + : os(&out), format(fmt) { } @@ -532,8 +534,7 @@ namespace GridKit * @brief Version for opening an output stream for the given file */ Sink(const std::string& fileName, FormatT fmt) - : file_stream(std::make_unique(fileName)), - os(*file_stream), + : file_name(fileName), format(fmt) { } @@ -543,10 +544,36 @@ namespace GridKit */ Sink(Sink&&) = default; + void start() + { + if (file_name.empty()) + { + return; + } + if (file_stream) + { + return; + } + file_stream = std::make_unique(file_name); + os = file_stream.get(); + } + + void stop() + { + if (file_name.empty()) + { + return; + } + file_stream.reset(); + os = nullptr; + } + + /// Output file name + std::string file_name; /// Output file stream (if we opened one) std::unique_ptr file_stream; /// Output stream for printing - std::ostream& os; + std::ostream* os{nullptr}; /// Output format object which may have useful members FormatT format; }; @@ -578,6 +605,32 @@ namespace GridKit throw std::runtime_error("Invalid monitor output format"); } + /** + * @brief Get sinks ready for output + */ + void startSinks() + { + for (auto&& sink : sinks_) + { + std::visit([this](auto&& sink) + { sink.start(); }, + sink); + } + } + + /** + * @brief Finalize sinks and close owned files + */ + void stopSinks() + { + for (auto&& sink : sinks_) + { + std::visit([this](auto&& sink) + { sink.stop(); }, + sink); + } + } + /// Collection of output sinks std::vector sinks_; /// Collection of submonitors diff --git a/GridKit/Solver/Dynamic/Ida.cpp b/GridKit/Solver/Dynamic/Ida.cpp index e6e605175..4a761cb24 100644 --- a/GridKit/Solver/Dynamic/Ida.cpp +++ b/GridKit/Solver/Dynamic/Ida.cpp @@ -354,7 +354,11 @@ namespace AnalysisManager copyVec(yp_, model_->yp()); if (model_->monitoring()) { - model_->stopMonitor(); + model_->printMonitoredVariables(); + } + if (step_callback.has_value()) + { + (*step_callback)(tret); } // std::cout << "\n"; diff --git a/examples/PhasorDynamics/Tiny/ThreeBus/Basic/ThreeBusBasicJson.cpp b/examples/PhasorDynamics/Tiny/ThreeBus/Basic/ThreeBusBasicJson.cpp index 1b99fda08..e01906ffe 100644 --- a/examples/PhasorDynamics/Tiny/ThreeBus/Basic/ThreeBusBasicJson.cpp +++ b/examples/PhasorDynamics/Tiny/ThreeBus/Basic/ThreeBusBasicJson.cpp @@ -174,6 +174,8 @@ int main(int argc, const char* argv[]) ida.runSimulation(10.0, nout, output_cb); double stop = static_cast(clock()); + sys.stopMonitor(); + /* Check worst-case error */ real_type worst_error = 0; real_type worst_error_time = 0; From 9928e55882141cd636468234ab66896eca2a8c30 Mon Sep 17 00:00:00 2001 From: Philip Fackler Date: Mon, 15 Dec 2025 10:33:51 -0600 Subject: [PATCH 09/15] Update component and bus variables during --- GridKit/Model/PhasorDynamics/SystemModel.hpp | 25 ++++++++++++++++++++ GridKit/Solver/Dynamic/Ida.cpp | 8 ++----- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/GridKit/Model/PhasorDynamics/SystemModel.hpp b/GridKit/Model/PhasorDynamics/SystemModel.hpp index 9177c0951..838f014c2 100644 --- a/GridKit/Model/PhasorDynamics/SystemModel.hpp +++ b/GridKit/Model/PhasorDynamics/SystemModel.hpp @@ -621,6 +621,29 @@ namespace GridKit monitor_.print(); } + /** + * @brief Update variables in buses and components + */ + void updateVariables() + { + for (const auto& bus : buses_) + { + for (IdxT j = 0; j < bus->size(); ++j) + { + bus->y()[j] = y_[bus->getVariableIndex(j)]; + bus->yp()[j] = yp_[bus->getVariableIndex(j)]; + } + } + for (const auto& component : components_) + { + for (IdxT j = 0; j < component->size(); ++j) + { + component->y()[j] = y_[component->getVariableIndex(j)]; + component->yp()[j] = yp_[component->getVariableIndex(j)]; + } + } + } + /** * @brief Update time * @@ -632,6 +655,8 @@ namespace GridKit { component->updateTime(t, a); } + + updateVariables(); } /** diff --git a/GridKit/Solver/Dynamic/Ida.cpp b/GridKit/Solver/Dynamic/Ida.cpp index 4a761cb24..baa303a53 100644 --- a/GridKit/Solver/Dynamic/Ida.cpp +++ b/GridKit/Solver/Dynamic/Ida.cpp @@ -327,9 +327,9 @@ namespace AnalysisManager // The callback may try to observe upated values in the model, so we // should update them here (At this point, the model's values are one // internal integrator step out of date) - model_->updateTime(tret, 0.0); copyVec(yy_, model_->y()); copyVec(yp_, model_->yp()); + model_->updateTime(tret, 0.0); if (model_->monitoring()) { @@ -349,17 +349,13 @@ namespace AnalysisManager } // Final copy out. No guarantee last residual evaluation is final step. - model_->updateTime(tf, 0.0); copyVec(yy_, model_->y()); copyVec(yp_, model_->yp()); + model_->updateTime(tf, 0.0); if (model_->monitoring()) { model_->printMonitoredVariables(); } - if (step_callback.has_value()) - { - (*step_callback)(tret); - } // std::cout << "\n"; return retval; From df8980025b253e2ebd60720ef7322baf16deaece Mon Sep 17 00:00:00 2001 From: Philip Fackler Date: Wed, 17 Dec 2025 14:17:41 -0600 Subject: [PATCH 10/15] Add each variable monitor only if non-empty --- GridKit/Model/PhasorDynamics/SystemModel.hpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/GridKit/Model/PhasorDynamics/SystemModel.hpp b/GridKit/Model/PhasorDynamics/SystemModel.hpp index 838f014c2..8932bba9d 100644 --- a/GridKit/Model/PhasorDynamics/SystemModel.hpp +++ b/GridKit/Model/PhasorDynamics/SystemModel.hpp @@ -436,12 +436,20 @@ namespace GridKit { for (const auto* bus : buses_) { - monitor_.addMonitor(bus->getMonitor()); + auto* mon = bus->getMonitor(); + if (mon && !mon->empty()) + { + monitor_.addMonitor(mon); + } } for (const auto* component : components_) { - monitor_.addMonitor(component->getMonitor()); + auto* mon = component->getMonitor(); + if (mon && !mon->empty()) + { + monitor_.addMonitor(mon); + } } } From 230f4b7a9b92505309c1b90da59be2af26506ba7 Mon Sep 17 00:00:00 2001 From: Philip Fackler Date: Fri, 19 Dec 2025 10:02:55 -0600 Subject: [PATCH 11/15] Fixed typo --- GridKit/Model/PhasorDynamics/Branch/Branch.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GridKit/Model/PhasorDynamics/Branch/Branch.hpp b/GridKit/Model/PhasorDynamics/Branch/Branch.hpp index b91ce8acd..48e40ed38 100644 --- a/GridKit/Model/PhasorDynamics/Branch/Branch.hpp +++ b/GridKit/Model/PhasorDynamics/Branch/Branch.hpp @@ -170,7 +170,7 @@ namespace GridKit IdxT bus1_id_{0}; IdxT bus2_id_{0}; - /* Derivied parameters */ + /* Derived parameters */ RealT b_; RealT g_; From b4aaf234424c550405b0d3b59523f3f15aff3507 Mon Sep 17 00:00:00 2001 From: pelesh Date: Fri, 19 Dec 2025 11:20:29 -0500 Subject: [PATCH 12/15] Update CONTRIBUTING.md (#319) * Update CONTRIBUTING.md * Add specific guidelines for enums --- CONTRIBUTING.md | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 36ba1ea97..72aa3a7a9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -249,17 +249,6 @@ For consistency, use the same name everywhere the same type is used. template ; // No, `_type` used in a template parameter name ``` -### Enums (enumerated types) - -Always define `enum`s inside `GridKit` namespace. Type names should be -capitalized and the constant names should be uppercase with underscores -(but there is no underscore at the end!). - -```c++ - enum ExampleEnum { CONST_ONE = 0, - CONST_TWO = 8, - YET_ANOTHER_CONST = 17 }; -``` ### Constants @@ -274,6 +263,23 @@ name. Use all caps (screaming snake case). constexpr double EXP = 2.7183 // Yes ``` +### Enums (enumerated types) + +Always define `enum`s inside `GridKit` namespace. The `enum` name should +be upper camel case, same as class names. The `enum` element names should +match symbol for physics quantity they represent or they should be uppercase +(same as names for constants) if they do not represent a physics quantity. +For example, enum element for real component of voltage $V_r$ should be `Vr`. +A name for `enum` element for a "fast mode", for example, should be something +like `FAST_MODE`, capitalized with underscores separating words (but no +underscore at the end!). + +```c++ + enum ExampleEnum { Vr, // Yes, it matches symbol for real voltage component + VR, // No, the element name should match the physics symbol + FAST_MODE}; // Yes, element name is all caps. +``` + ### Pointers and references The pointer `*` or reference `&` belong to the type and there should be no space between them and the type name. From 636dd2b75d9655db8edc6738d72851c0efdca3b7 Mon Sep 17 00:00:00 2001 From: Philip Fackler Date: Fri, 19 Dec 2025 11:19:25 -0600 Subject: [PATCH 13/15] Fixed enum value formatting and some missing items --- .../PhasorDynamics/Branch/BranchData.hpp | 40 +++++++++---------- .../PhasorDynamics/Branch/BranchImpl.hpp | 20 +++++----- GridKit/Model/PhasorDynamics/Bus/BusData.hpp | 8 ++-- GridKit/Model/PhasorDynamics/BusBase.hpp | 4 +- .../PhasorDynamics/BusFault/BusFault.hpp | 9 +++++ .../PhasorDynamics/BusFault/BusFaultData.hpp | 13 ++++++ .../PhasorDynamics/BusFault/BusFaultImpl.hpp | 8 ++++ .../ComponentDataJSONParser.hpp | 11 ++--- .../Exciter/IEEET1/Ieeet1Data.hpp | 4 +- .../Exciter/IEEET1/Ieeet1Impl.hpp | 4 +- GridKit/Model/PhasorDynamics/Load/Load.hpp | 9 +++++ .../Model/PhasorDynamics/Load/LoadData.hpp | 14 ++++++- .../Model/PhasorDynamics/Load/LoadImpl.hpp | 4 ++ .../GENROUwS/GenrouData.hpp | 24 +++++------ .../GENROUwS/GenrouImpl.hpp | 12 +++--- .../GenClassical/GenClassical.hpp | 11 +++++ .../GenClassical/GenClassicalData.hpp | 16 ++++++++ .../GenClassical/GenClassicalImpl.hpp | 18 +++++++++ tests/UnitTests/Utilities/CaseFormatTests.hpp | 16 ++++---- 19 files changed, 169 insertions(+), 76 deletions(-) diff --git a/GridKit/Model/PhasorDynamics/Branch/BranchData.hpp b/GridKit/Model/PhasorDynamics/Branch/BranchData.hpp index cb876ded4..b5083df21 100644 --- a/GridKit/Model/PhasorDynamics/Branch/BranchData.hpp +++ b/GridKit/Model/PhasorDynamics/Branch/BranchData.hpp @@ -32,16 +32,16 @@ namespace GridKit /// Variables able to be monitored for a branch enum class BranchMonitorableVariables { - IR1, - II1, - IM1, - P1, - Q1, - IR2, - II2, - IM2, - P2, - Q2, + ir1, + ii1, + im1, + p1, + q1, + ir2, + ii2, + im2, + p2, + q2, SIZE }; @@ -51,16 +51,16 @@ namespace GridKit inline const std::string& enumLabel(BranchMonitorableVariables v) { static const std::string labels[] = { - "Ir1", - "Ii1", - "Im1", - "P1", - "Q1", - "Ir2", - "Ii2", - "Im2", - "P2", - "Q2"}; + "ir1", + "ii1", + "im1", + "p1", + "q1", + "ir2", + "ii2", + "im2", + "p2", + "q2"}; return labels[Utilities::enumId(v)]; } diff --git a/GridKit/Model/PhasorDynamics/Branch/BranchImpl.hpp b/GridKit/Model/PhasorDynamics/Branch/BranchImpl.hpp index 862cfab2c..c7355ae97 100644 --- a/GridKit/Model/PhasorDynamics/Branch/BranchImpl.hpp +++ b/GridKit/Model/PhasorDynamics/Branch/BranchImpl.hpp @@ -289,20 +289,20 @@ namespace GridKit void Branch::initializeMonitor() { using Variable = typename model_data_type::MonitorableVariables; - monitor_.set(Variable::IR1, [this] + monitor_.set(Variable::ir1, [this] { return Ir1(); }); - monitor_.set(Variable::II1, [this] + monitor_.set(Variable::ii1, [this] { return Ii1(); }); - // monitor_.set(Variable::IM1, [this] { return ?(); }); - // monitor_.set(Variable::P1, [this] { return ?(); }); - // monitor_.set(Variable::Q1, [this] { return ?(); }); - monitor_.set(Variable::IR2, [this] + // monitor_.set(Variable::im1, [this] { return ?(); }); + // monitor_.set(Variable::p1, [this] { return ?(); }); + // monitor_.set(Variable::q1, [this] { return ?(); }); + monitor_.set(Variable::ir2, [this] { return Ir2(); }); - monitor_.set(Variable::II2, [this] + monitor_.set(Variable::ii2, [this] { return Ii2(); }); - // monitor_.set(Variable::IM2, [this] { return ?(); }); - // monitor_.set(Variable::P2, [this] { return ?(); }); - // monitor_.set(Variable::Q2, [this] { return ?(); }); + // monitor_.set(Variable::im2, [this] { return ?(); }); + // monitor_.set(Variable::p2, [this] { return ?(); }); + // monitor_.set(Variable::q2, [this] { return ?(); }); } /** diff --git a/GridKit/Model/PhasorDynamics/Bus/BusData.hpp b/GridKit/Model/PhasorDynamics/Bus/BusData.hpp index a6a82b29f..79077cf64 100644 --- a/GridKit/Model/PhasorDynamics/Bus/BusData.hpp +++ b/GridKit/Model/PhasorDynamics/Bus/BusData.hpp @@ -20,10 +20,10 @@ namespace GridKit /// Indices of the variables able to be monitored on this component enum class BusMonitorableVariables : size_t { - VR, - VI, - VM, - VA, + Vr, + Vi, + Vm, + Va, SIZE }; diff --git a/GridKit/Model/PhasorDynamics/BusBase.hpp b/GridKit/Model/PhasorDynamics/BusBase.hpp index 625d0e425..c7ebef8af 100644 --- a/GridKit/Model/PhasorDynamics/BusBase.hpp +++ b/GridKit/Model/PhasorDynamics/BusBase.hpp @@ -31,9 +31,9 @@ namespace GridKit monitor_("Bus_" + data.name, data.monitored_variables) { using Variable = typename BusData::MonitorableVariables; - monitor_.set(Variable::VR, [this]() + monitor_.set(Variable::Vr, [this] { return Vr(); }); - monitor_.set(Variable::VI, [this]() + monitor_.set(Variable::Vi, [this] { return Vi(); }); } diff --git a/GridKit/Model/PhasorDynamics/BusFault/BusFault.hpp b/GridKit/Model/PhasorDynamics/BusFault/BusFault.hpp index 934efb88c..28aea824f 100644 --- a/GridKit/Model/PhasorDynamics/BusFault/BusFault.hpp +++ b/GridKit/Model/PhasorDynamics/BusFault/BusFault.hpp @@ -4,6 +4,7 @@ #include #include #include +#include // Forward declaration of BusData structure namespace GridKit @@ -77,6 +78,11 @@ namespace GridKit status_ = status; } + const Model::VariableMonitorBase* getMonitor() const override + { + return &monitor_; + } + private: void setDerivedParams(); @@ -113,6 +119,9 @@ namespace GridKit /* Derivied parameters */ RealT B_; RealT G_; + + /// Variable monitor + Model::VariableMonitor monitor_; }; } // namespace PhasorDynamics diff --git a/GridKit/Model/PhasorDynamics/BusFault/BusFaultData.hpp b/GridKit/Model/PhasorDynamics/BusFault/BusFaultData.hpp index 518706659..2bc7b0286 100644 --- a/GridKit/Model/PhasorDynamics/BusFault/BusFaultData.hpp +++ b/GridKit/Model/PhasorDynamics/BusFault/BusFaultData.hpp @@ -33,8 +33,21 @@ namespace GridKit state, ir, ii, + SIZE }; + /** + * @brief Convert enum value to string label + */ + inline const std::string& enumLabel(BusFaultMonitorableVariables v) + { + static const std::string labels[] = { + "state", + "ir", + "ii"}; + return labels[Utilities::enumId(v)]; + } + /** * @brief Contains modeling data for a short-to-ground fault * diff --git a/GridKit/Model/PhasorDynamics/BusFault/BusFaultImpl.hpp b/GridKit/Model/PhasorDynamics/BusFault/BusFaultImpl.hpp index bed953f2e..c0c900224 100644 --- a/GridKit/Model/PhasorDynamics/BusFault/BusFaultImpl.hpp +++ b/GridKit/Model/PhasorDynamics/BusFault/BusFaultImpl.hpp @@ -79,6 +79,14 @@ namespace GridKit bus_id_ = data.ports.at(DataT::Ports::bus); } + using Variable = typename DataT::MonitorableVariables; + // monitor_.set(Variable::state, [this] + // { return status_; }); + monitor_.set(Variable::ir, [this] + { return Ir(); }); + monitor_.set(Variable::ii, [this] + { return Ii(); }); + size_ = 0; setDerivedParams(); } diff --git a/GridKit/Model/PhasorDynamics/ComponentDataJSONParser.hpp b/GridKit/Model/PhasorDynamics/ComponentDataJSONParser.hpp index 2b5a6ae68..90b00de50 100644 --- a/GridKit/Model/PhasorDynamics/ComponentDataJSONParser.hpp +++ b/GridKit/Model/PhasorDynamics/ComponentDataJSONParser.hpp @@ -27,11 +27,6 @@ namespace GridKit && std::is_enum_v void from_json(const json& j, ComponentData& c) { - auto enum_parse = [](EnumT, KeyT&& key) - { - return magic_enum::enum_cast(key, magic_enum::case_insensitive); - }; - j.at("class").get_to(c.device_class); j.at("id").get_to(c.disambiguation_string); @@ -43,7 +38,7 @@ namespace GridKit for (auto& raw_parameter : j.at("params").items()) { - auto key = enum_parse(Parameters(), raw_parameter.key()); + auto key = magic_enum::enum_cast(raw_parameter.key()); if (key.has_value()) { // NOTE: this is necessary because it doesn't seem like nlohmann/json @@ -79,7 +74,7 @@ namespace GridKit for (auto& raw_port : j.at("ports").items()) { - auto key = enum_parse(Ports(), raw_port.key()); + auto key = magic_enum::enum_cast(raw_port.key()); if (key.has_value()) { raw_port.value().get_to(c.ports[key.value()]); @@ -97,7 +92,7 @@ namespace GridKit for (auto& raw_monitored_variable : j.at("mon")) { auto var_name = raw_monitored_variable.get(); - auto monitored = enum_parse(MonitorableVariables(), var_name); + auto monitored = magic_enum::enum_cast(var_name); if (monitored.has_value()) { c.monitored_variables.insert(monitored.value()); diff --git a/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1Data.hpp b/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1Data.hpp index 6c8dc459f..c3ba2be21 100644 --- a/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1Data.hpp +++ b/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1Data.hpp @@ -46,8 +46,8 @@ namespace GridKit /// Variables able to be monitored for a IEEET1 Exciter model enum class Ieeet1MonitorableVariables { - EFD, - KSAT, + efd, + ksat, SIZE }; diff --git a/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1Impl.hpp b/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1Impl.hpp index e1d963ff2..12529a0ca 100644 --- a/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1Impl.hpp +++ b/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1Impl.hpp @@ -400,9 +400,9 @@ namespace GridKit void Ieeet1::initializeMonitor() { using Variable = model_data_type::MonitorableVariables; - monitor_.set(Variable::EFD, [this] + monitor_.set(Variable::efd, [this] { return efd_signal_->read(); }); - // monitor_.set(Variable::KSAT, [this] { return ?; }); + // monitor_.set(Variable::ksat, [this] { return ?; }); } } // namespace Exciter } // namespace PhasorDynamics diff --git a/GridKit/Model/PhasorDynamics/Load/Load.hpp b/GridKit/Model/PhasorDynamics/Load/Load.hpp index 49ba3ebea..26cfeed8d 100644 --- a/GridKit/Model/PhasorDynamics/Load/Load.hpp +++ b/GridKit/Model/PhasorDynamics/Load/Load.hpp @@ -2,6 +2,8 @@ #include #include +#include +#include // Forward declarations. namespace GridKit @@ -99,6 +101,11 @@ namespace GridKit return bus_->Ii(); } + const Model::VariableMonitorBase* getMonitor() const override + { + return &monitor_; + } + public: __attribute__((always_inline)) inline int evaluateBusResidual(ScalarT*, ScalarT*, ScalarT*, ScalarT*); @@ -110,6 +117,8 @@ namespace GridKit /* Derivied parameters */ RealT b_; RealT g_; + + Model::VariableMonitor monitor_; }; } // namespace PhasorDynamics diff --git a/GridKit/Model/PhasorDynamics/Load/LoadData.hpp b/GridKit/Model/PhasorDynamics/Load/LoadData.hpp index 2b07512ca..f7ff34540 100644 --- a/GridKit/Model/PhasorDynamics/Load/LoadData.hpp +++ b/GridKit/Model/PhasorDynamics/Load/LoadData.hpp @@ -28,10 +28,20 @@ namespace GridKit /// Variables able to be monitored for a load enum class LoadMonitorableVariables { - // TODO: presumably some variables would make sense to monitor here - NONE + p, + q, + SIZE }; + /** + * @brief Convert enum value to string label + */ + inline const std::string& enumLabel(LoadMonitorableVariables v) + { + static const std::string labels[] = {"p", "q"}; + return labels[Utilities::enumId(v)]; + } + /** * @brief Contains modeling data for a load * diff --git a/GridKit/Model/PhasorDynamics/Load/LoadImpl.hpp b/GridKit/Model/PhasorDynamics/Load/LoadImpl.hpp index 8403f1dd1..c8c2c827d 100644 --- a/GridKit/Model/PhasorDynamics/Load/LoadImpl.hpp +++ b/GridKit/Model/PhasorDynamics/Load/LoadImpl.hpp @@ -52,6 +52,10 @@ namespace GridKit X_ = std::get(data.parameters.at(model_data_type::Parameters::X)); } + using Variable = typename model_data_type::MonitorableVariables; + // monitor_.set(Variable::p, [this] { return ?; }); + // monitor_.set(Variable::q, [this] { return ?; }); + size_ = 0; setDerivedParams(); } diff --git a/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROUwS/GenrouData.hpp b/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROUwS/GenrouData.hpp index 0df2d2dde..47ed138fa 100644 --- a/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROUwS/GenrouData.hpp +++ b/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROUwS/GenrouData.hpp @@ -49,12 +49,12 @@ namespace GridKit /// Variables able to be monitored for a Genrou generator model enum class GenrouMonitorableVariables { - IR, - II, - P, - Q, - DELTA, - OMEGA, + ir, + ii, + p, + q, + delta, + omega, SIZE }; @@ -64,12 +64,12 @@ namespace GridKit inline const std::string& enumLabel(GenrouMonitorableVariables v) { static const std::string labels[] = { - "Ir", - "Ii", - "P", - "Q", - "Delta", - "Omega"}; + "ir", + "ii", + "p", + "q", + "delta", + "omega"}; return labels[Utilities::enumId(v)]; } diff --git a/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROUwS/GenrouImpl.hpp b/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROUwS/GenrouImpl.hpp index 89935faca..216d97a61 100644 --- a/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROUwS/GenrouImpl.hpp +++ b/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROUwS/GenrouImpl.hpp @@ -269,15 +269,15 @@ namespace GridKit void Genrou::initializeMonitor() { using Variable = typename model_data_type::MonitorableVariables; - monitor_.set(Variable::IR, [this] + monitor_.set(Variable::ir, [this] { return y_[15]; }); - monitor_.set(Variable::II, [this] + monitor_.set(Variable::ii, [this] { return y_[16]; }); - // monitor_.set(Variable::P, [this] { return ?(); }); - // monitor_.set(Variable::Q, [this] { return ?(); }); - monitor_.set(Variable::DELTA, [this] + // monitor_.set(Variable::p, [this] { return ?(); }); + // monitor_.set(Variable::q, [this] { return ?(); }); + monitor_.set(Variable::delta, [this] { return y_[0]; }); - monitor_.set(Variable::OMEGA, [this] + monitor_.set(Variable::omega, [this] { return y_[1]; }); } diff --git a/GridKit/Model/PhasorDynamics/SynchronousMachine/GenClassical/GenClassical.hpp b/GridKit/Model/PhasorDynamics/SynchronousMachine/GenClassical/GenClassical.hpp index c2eec409c..c4af29145 100644 --- a/GridKit/Model/PhasorDynamics/SynchronousMachine/GenClassical/GenClassical.hpp +++ b/GridKit/Model/PhasorDynamics/SynchronousMachine/GenClassical/GenClassical.hpp @@ -9,6 +9,8 @@ #include #include +#include +#include // Forward declarations. namespace GridKit @@ -90,7 +92,13 @@ namespace GridKit ep_set_ = ep; } + const Model::VariableMonitorBase* getMonitor() const override + { + return &monitor_; + } + private: + void initializeMonitor(); void setDerivedParams(); ScalarT& Vr() @@ -141,6 +149,9 @@ namespace GridKit /* Setpoints for control variables (determined at initialization) */ ScalarT pmech_set_; ScalarT ep_set_; + + /// Variable monitor + Model::VariableMonitor monitor_; }; } // namespace PhasorDynamics diff --git a/GridKit/Model/PhasorDynamics/SynchronousMachine/GenClassical/GenClassicalData.hpp b/GridKit/Model/PhasorDynamics/SynchronousMachine/GenClassical/GenClassicalData.hpp index 308bafb6b..190a634ea 100644 --- a/GridKit/Model/PhasorDynamics/SynchronousMachine/GenClassical/GenClassicalData.hpp +++ b/GridKit/Model/PhasorDynamics/SynchronousMachine/GenClassical/GenClassicalData.hpp @@ -41,8 +41,24 @@ namespace GridKit q, delta, omega, + SIZE }; + /** + * @brief Convert enum value to string label + */ + inline const std::string& enumLabel(GenClassicalMonitorableVariables v) + { + static const std::string labels[] = { + "ir", + "ii", + "p", + "q", + "delta", + "omega"}; + return labels[Utilities::enumId(v)]; + } + /** * @brief Contains modeling data for a GenClassical generator model. * diff --git a/GridKit/Model/PhasorDynamics/SynchronousMachine/GenClassical/GenClassicalImpl.hpp b/GridKit/Model/PhasorDynamics/SynchronousMachine/GenClassical/GenClassicalImpl.hpp index 29debe178..5437a959c 100644 --- a/GridKit/Model/PhasorDynamics/SynchronousMachine/GenClassical/GenClassicalImpl.hpp +++ b/GridKit/Model/PhasorDynamics/SynchronousMachine/GenClassical/GenClassicalImpl.hpp @@ -113,10 +113,28 @@ namespace GridKit bus_id_ = data.ports.at(DataT::Ports::bus); } + initializeMonitor(); + size_ = 5; setDerivedParams(); } + template + void GenClassical::initializeMonitor() + { + using Variable = typename DataT::MonitorableVariables; + monitor_.set(Variable::ir, [this] + { return y_[3]; }); + monitor_.set(Variable::ii, [this] + { return y_[4]; }); + // monitor_.set(Variable::p, [this] { return ?(); }); + // monitor_.set(Variable::q, [this] { return ?(); }); + monitor_.set(Variable::delta, [this] + { return y_[0]; }); + monitor_.set(Variable::omega, [this] + { return y_[1]; }); + } + /** * @brief Set the component ID */ diff --git a/tests/UnitTests/Utilities/CaseFormatTests.hpp b/tests/UnitTests/Utilities/CaseFormatTests.hpp index 22f180ed3..950af3959 100644 --- a/tests/UnitTests/Utilities/CaseFormatTests.hpp +++ b/tests/UnitTests/Utilities/CaseFormatTests.hpp @@ -92,8 +92,8 @@ namespace GridKit success *= result.bus[0].Vr0 == 0.994988; success *= result.bus[0].Vi0 == 0.099997; success *= result.bus[0].v_base == 115e3; - success *= result.bus[0].monitored_variables.contains(BusData::MonitorableVariables::VR); - success *= result.bus[0].monitored_variables.contains(BusData::MonitorableVariables::VI); + success *= result.bus[0].monitored_variables.contains(BusData::MonitorableVariables::Vr); + success *= result.bus[0].monitored_variables.contains(BusData::MonitorableVariables::Vi); success *= result.bus[1].bus_id == 2; success *= result.bus[1].bus_type == BusType::SLACK; success *= result.bus[1].name == "Bus 2"; @@ -131,8 +131,8 @@ namespace GridKit success *= std::get(result.genrou[0].parameters[GenrouParameters::S12]) == 0.0; success *= result.genrou[0].ports[GenrouPorts::bus] == 1; success *= result.genrou[0].disambiguation_string == "1"; - success *= result.genrou[0].monitored_variables.contains(GenrouMonitorableVariables::DELTA); - success *= result.genrou[0].monitored_variables.contains(GenrouMonitorableVariables::OMEGA); + success *= result.genrou[0].monitored_variables.contains(GenrouMonitorableVariables::delta); + success *= result.genrou[0].monitored_variables.contains(GenrouMonitorableVariables::omega); success *= std::get(result.bus_fault[0].parameters[BusFaultParameters::R]) == 0.0; success *= std::get(result.bus_fault[0].parameters[BusFaultParameters::X]) == 1e-3; @@ -205,8 +205,8 @@ namespace GridKit success *= result.bus[0].Vr0 == 0.994988; success *= result.bus[0].Vi0 == 0.099997; success *= result.bus[0].v_base == 115e3; - success *= result.bus[0].monitored_variables.contains(BusData::MonitorableVariables::VR); - success *= result.bus[0].monitored_variables.contains(BusData::MonitorableVariables::VI); + success *= result.bus[0].monitored_variables.contains(BusData::MonitorableVariables::Vr); + success *= result.bus[0].monitored_variables.contains(BusData::MonitorableVariables::Vi); success *= result.bus[1].bus_id == 2; success *= result.bus[1].bus_type == BusType::SLACK; success *= result.bus[1].name == "Bus 2"; @@ -254,8 +254,8 @@ namespace GridKit success *= result.genrou[0].ports[GenrouPorts::pmech] == 2; success *= result.genrou[0].ports[GenrouPorts::efd] == 3; success *= result.genrou[0].disambiguation_string == "DV1"; - success *= result.genrou[0].monitored_variables.contains(GenrouMonitorableVariables::DELTA); - success *= result.genrou[0].monitored_variables.contains(GenrouMonitorableVariables::OMEGA); + success *= result.genrou[0].monitored_variables.contains(GenrouMonitorableVariables::delta); + success *= result.genrou[0].monitored_variables.contains(GenrouMonitorableVariables::omega); success *= std::get(result.gov[0].parameters[Governor::Tgov1Parameters::R]) == 0.05; success *= std::get(result.gov[0].parameters[Governor::Tgov1Parameters::T1]) == 0.5; From cece7cf55c68567d0b02dccf07391037c82dbecd Mon Sep 17 00:00:00 2001 From: Philip Fackler Date: Fri, 19 Dec 2025 13:28:22 -0600 Subject: [PATCH 14/15] Enable different variable types --- .../PhasorDynamics/BusFault/BusFaultImpl.hpp | 4 +- .../Model/PhasorDynamics/Load/LoadImpl.hpp | 2 +- GridKit/Model/VariableMonitor.hpp | 110 ++++++++++++------ 3 files changed, 79 insertions(+), 37 deletions(-) diff --git a/GridKit/Model/PhasorDynamics/BusFault/BusFaultImpl.hpp b/GridKit/Model/PhasorDynamics/BusFault/BusFaultImpl.hpp index c0c900224..ea28a3563 100644 --- a/GridKit/Model/PhasorDynamics/BusFault/BusFaultImpl.hpp +++ b/GridKit/Model/PhasorDynamics/BusFault/BusFaultImpl.hpp @@ -80,8 +80,8 @@ namespace GridKit } using Variable = typename DataT::MonitorableVariables; - // monitor_.set(Variable::state, [this] - // { return status_; }); + monitor_.set(Variable::state, [this] + { return status_; }); monitor_.set(Variable::ir, [this] { return Ir(); }); monitor_.set(Variable::ii, [this] diff --git a/GridKit/Model/PhasorDynamics/Load/LoadImpl.hpp b/GridKit/Model/PhasorDynamics/Load/LoadImpl.hpp index c8c2c827d..f6a393c1b 100644 --- a/GridKit/Model/PhasorDynamics/Load/LoadImpl.hpp +++ b/GridKit/Model/PhasorDynamics/Load/LoadImpl.hpp @@ -52,7 +52,7 @@ namespace GridKit X_ = std::get(data.parameters.at(model_data_type::Parameters::X)); } - using Variable = typename model_data_type::MonitorableVariables; + // using Variable = typename model_data_type::MonitorableVariables; // monitor_.set(Variable::p, [this] { return ?; }); // monitor_.set(Variable::q, [this] { return ?; }); diff --git a/GridKit/Model/VariableMonitor.hpp b/GridKit/Model/VariableMonitor.hpp index ebb73753a..8aa9ffa1b 100644 --- a/GridKit/Model/VariableMonitor.hpp +++ b/GridKit/Model/VariableMonitor.hpp @@ -58,18 +58,16 @@ namespace GridKit public: /// Underlying real value type - using RealT = typename GridKit::ScalarTraits::RealT; + using RealT = typename GridKit::ScalarTraits::RealT; /// Type of (EvalT)Data class expected to have MonitorableVariables enum - using ObjData = DataT; + using ObjData = DataT; /// Enum of valid monitorable variables - using VariableEnum = typename ObjData::MonitorableVariables; - /// Abstraction type for functions returning a monitored value - using ValueFunction = std::function; + using VariableEnum = typename ObjData::MonitorableVariables; ///@{ /// @brief Alias - using Csv = VariableMonitorBase::Csv; - using Json = VariableMonitorBase::Json; - using Yaml = VariableMonitorBase::Yaml; + using Csv = VariableMonitorBase::Csv; + using Json = VariableMonitorBase::Json; + using Yaml = VariableMonitorBase::Yaml; ///@} VariableMonitor() = default; @@ -117,12 +115,66 @@ namespace GridKit * @note This does not designate the variable for printing. It defines how * to get the variable if it is printed. */ - void set(VariableEnum v, ValueFunction f) + template + void set(VariableEnum v, FuncT f) { - f_[Utilities::enumId(v)] = f; + f_[Utilities::enumId(v)] = ValuePrinter{f}; } - protected: + private: + ///@{ + /** + * @brief Functors to handle printing different types + */ + template + struct ValuePrinterImpl + { + FuncT f; + + void operator()(std::ostream& os) const + { + os << f(); + } + }; + + template + struct ValuePrinterImpl + { + FuncT f; + + void operator()(std::ostream& os) const + { + os << static_cast(f()); + } + }; + + template + using ValuePrinterType = + ValuePrinterImpl>; + + class ValuePrinter + { + public: + ValuePrinter() = default; + + template + ValuePrinter(FuncT f) + : impl_{ValuePrinterType(f)} + { + } + + private: + std::function impl_; + + friend std::ostream& operator<<(std::ostream& os, const ValuePrinter& p) + { + p.impl_(os); + return os; + } + }; + + ///@} + void printHeader(std::ostream& os, Csv csv) const override { for (auto v : variables_) @@ -135,7 +187,7 @@ namespace GridKit { for (auto v : variables_) { - os << csv.delim << f(v); + os << csv.delim << f_[Utilities::enumId(v)]; } } @@ -144,7 +196,7 @@ namespace GridKit */ void print(std::ostream& os, VariableEnum v, Json) const { - os << indent_ << std::quoted(enumLabel(v)) << ": " << f(v) << ",\n"; + os << indent_ << std::quoted(enumLabel(v)) << ": " << f_[Utilities::enumId(v)] << ",\n"; } void print(std::ostream& os, Json) const override @@ -177,7 +229,7 @@ namespace GridKit */ void print(std::ostream& os, VariableEnum v, Yaml) const { - os << indent_ << enumLabel(v) << ": " << f(v) << '\n'; + os << indent_ << enumLabel(v) << ": " << f_[Utilities::enumId(v)] << '\n'; } void print(std::ostream& os, Yaml) const override @@ -199,22 +251,14 @@ namespace GridKit /// Compile-time constant size: length of enum value list static constexpr auto enum_size_ = Utilities::enumSize; - /** - * @brief Convenience function to access value associated with enum value - */ - auto f(VariableEnum v) const - { - return static_cast(f_[Utilities::enumId(v)]()); - } - /// 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_; }; /** @@ -231,16 +275,14 @@ namespace GridKit { public: /// Underlying real value type - using RealT = typename GridKit::ScalarTraits::RealT; - /// Abstraction type for functions returning a monitored value - using ValueFunction = std::function; + using RealT = typename GridKit::ScalarTraits::RealT; ///@{ /// @brief Alias - using Format = VariableMonitorFormat; - using SinkSpec = VariableMonitorBase::SinkSpec; - using Csv = VariableMonitorBase::Csv; - using Json = VariableMonitorBase::Json; - using Yaml = VariableMonitorBase::Yaml; + using Format = VariableMonitorFormat; + using SinkSpec = VariableMonitorBase::SinkSpec; + using Csv = VariableMonitorBase::Csv; + using Json = VariableMonitorBase::Json; + using Yaml = VariableMonitorBase::Yaml; ///@} /// Default to empty monitor From 047928415149d807b85021ed32e02d2cd15c812b Mon Sep 17 00:00:00 2001 From: Philip Fackler Date: Fri, 19 Dec 2025 14:48:01 -0600 Subject: [PATCH 15/15] Switched to private implementation using magic_enum --- GridKit/Model/CMakeLists.txt | 2 +- GridKit/Model/Evaluator.hpp | 2 +- .../Model/PhasorDynamics/Branch/Branch.hpp | 8 +- .../PhasorDynamics/Branch/BranchData.hpp | 23 +- .../PhasorDynamics/Branch/BranchImpl.hpp | 37 +- .../PhasorDynamics/Branch/CMakeLists.txt | 9 +- GridKit/Model/PhasorDynamics/Bus/BusData.hpp | 14 +- GridKit/Model/PhasorDynamics/Bus/BusImpl.hpp | 1 + .../PhasorDynamics/Bus/BusInfiniteImpl.hpp | 1 + .../Model/PhasorDynamics/Bus/CMakeLists.txt | 6 + GridKit/Model/PhasorDynamics/BusBase.hpp | 26 +- GridKit/Model/PhasorDynamics/BusBaseImpl.hpp | 33 + .../PhasorDynamics/BusFault/BusFault.hpp | 12 +- .../PhasorDynamics/BusFault/BusFaultData.hpp | 15 +- .../PhasorDynamics/BusFault/BusFaultImpl.hpp | 27 +- .../PhasorDynamics/BusFault/CMakeLists.txt | 9 +- .../Exciter/IEEET1/CMakeLists.txt | 4 + .../PhasorDynamics/Exciter/IEEET1/Ieeet1.hpp | 10 +- .../Exciter/IEEET1/Ieeet1Data.hpp | 13 +- .../Exciter/IEEET1/Ieeet1Impl.hpp | 22 +- .../Model/PhasorDynamics/Load/CMakeLists.txt | 7 +- GridKit/Model/PhasorDynamics/Load/Load.hpp | 10 +- .../Model/PhasorDynamics/Load/LoadData.hpp | 12 +- .../Model/PhasorDynamics/Load/LoadImpl.hpp | 11 +- .../GENROUwS/CMakeLists.txt | 6 + .../SynchronousMachine/GENROUwS/Genrou.hpp | 10 +- .../GENROUwS/GenrouData.hpp | 19 +- .../GENROUwS/GenrouImpl.hpp | 38 +- .../GenClassical/CMakeLists.txt | 11 +- .../GenClassical/GenClassical.hpp | 12 +- .../GenClassical/GenClassicalData.hpp | 18 +- .../GenClassical/GenClassicalImpl.hpp | 35 +- GridKit/Model/PhasorDynamics/SystemModel.hpp | 3 +- .../Model/PhasorDynamics/SystemModelData.hpp | 2 +- .../SystemModelDataJSONParser.hpp | 1 - GridKit/Model/VariableMonitor.hpp | 678 ++---------------- GridKit/Model/VariableMonitorBase.hpp | 128 ---- GridKit/Model/VariableMonitorController.hpp | 444 ++++++++++++ GridKit/Model/VariableMonitorImpl.hpp | 245 +++++++ GridKit/Utilities/CMakeLists.txt | 1 - GridKit/Utilities/Enum.hpp | 33 - 41 files changed, 991 insertions(+), 1007 deletions(-) create mode 100644 GridKit/Model/PhasorDynamics/BusBaseImpl.hpp delete mode 100644 GridKit/Model/VariableMonitorBase.hpp create mode 100644 GridKit/Model/VariableMonitorController.hpp create mode 100644 GridKit/Model/VariableMonitorImpl.hpp delete mode 100644 GridKit/Utilities/Enum.hpp diff --git a/GridKit/Model/CMakeLists.txt b/GridKit/Model/CMakeLists.txt index 73d248ec5..74a0910aa 100644 --- a/GridKit/Model/CMakeLists.txt +++ b/GridKit/Model/CMakeLists.txt @@ -11,6 +11,6 @@ add_subdirectory(PowerElectronics) install(FILES Evaluator.hpp - VariableMonitorBase.hpp VariableMonitor.hpp + VariableMonitorController.hpp DESTINATION include/GridKit/Model) diff --git a/GridKit/Model/Evaluator.hpp b/GridKit/Model/Evaluator.hpp index 819afaff6..9bc9f010e 100644 --- a/GridKit/Model/Evaluator.hpp +++ b/GridKit/Model/Evaluator.hpp @@ -5,7 +5,7 @@ #include #include #include -#include +#include #include namespace GridKit diff --git a/GridKit/Model/PhasorDynamics/Branch/Branch.hpp b/GridKit/Model/PhasorDynamics/Branch/Branch.hpp index 48e40ed38..c917e4311 100644 --- a/GridKit/Model/PhasorDynamics/Branch/Branch.hpp +++ b/GridKit/Model/PhasorDynamics/Branch/Branch.hpp @@ -57,6 +57,7 @@ namespace GridKit using RealT = typename Component::RealT; using bus_type = BusBase; using model_data_type = BranchData; + using MonitorT = Model::VariableMonitor; Branch(bus_type* bus1, bus_type* bus2); Branch(bus_type* bus1, bus_type* bus2, RealT R, RealT X, RealT G, RealT B); @@ -104,10 +105,7 @@ namespace GridKit setDerivedParams(); } - const Model::VariableMonitorBase* getMonitor() const override - { - return &monitor_; - } + const Model::VariableMonitorBase* getMonitor() const override; private: void initializeParameters(const model_data_type& data); @@ -175,7 +173,7 @@ namespace GridKit RealT g_; /// Variable monitor - Model::VariableMonitor monitor_; + std::unique_ptr monitor_; }; } // namespace PhasorDynamics diff --git a/GridKit/Model/PhasorDynamics/Branch/BranchData.hpp b/GridKit/Model/PhasorDynamics/Branch/BranchData.hpp index b5083df21..98528c3d1 100644 --- a/GridKit/Model/PhasorDynamics/Branch/BranchData.hpp +++ b/GridKit/Model/PhasorDynamics/Branch/BranchData.hpp @@ -7,7 +7,6 @@ #pragma once #include -#include namespace GridKit { @@ -41,29 +40,9 @@ namespace GridKit ii2, im2, p2, - q2, - SIZE + q2 }; - /** - * @brief Convert enum value to string label - */ - inline const std::string& enumLabel(BranchMonitorableVariables v) - { - static const std::string labels[] = { - "ir1", - "ii1", - "im1", - "p1", - "q1", - "ir2", - "ii2", - "im2", - "p2", - "q2"}; - return labels[Utilities::enumId(v)]; - } - /** * @brief Contains modeling data for a Branch * diff --git a/GridKit/Model/PhasorDynamics/Branch/BranchImpl.hpp b/GridKit/Model/PhasorDynamics/Branch/BranchImpl.hpp index c7355ae97..4b6016e3e 100644 --- a/GridKit/Model/PhasorDynamics/Branch/BranchImpl.hpp +++ b/GridKit/Model/PhasorDynamics/Branch/BranchImpl.hpp @@ -13,6 +13,7 @@ #include #include #include +#include namespace GridKit { @@ -75,7 +76,7 @@ namespace GridKit Branch::Branch(bus_type* bus1, bus_type* bus2, const model_data_type& data) : bus1_(bus1), bus2_(bus2), - monitor_(data) + monitor_(std::make_unique(data)) { initializeParameters(data); initializeMonitor(); @@ -285,24 +286,30 @@ namespace GridKit } } + template + const Model::VariableMonitorBase* Branch::getMonitor() const + { + return monitor_.get(); + } + template void Branch::initializeMonitor() { using Variable = typename model_data_type::MonitorableVariables; - monitor_.set(Variable::ir1, [this] - { return Ir1(); }); - monitor_.set(Variable::ii1, [this] - { return Ii1(); }); - // monitor_.set(Variable::im1, [this] { return ?(); }); - // monitor_.set(Variable::p1, [this] { return ?(); }); - // monitor_.set(Variable::q1, [this] { return ?(); }); - monitor_.set(Variable::ir2, [this] - { return Ir2(); }); - monitor_.set(Variable::ii2, [this] - { return Ii2(); }); - // monitor_.set(Variable::im2, [this] { return ?(); }); - // monitor_.set(Variable::p2, [this] { return ?(); }); - // monitor_.set(Variable::q2, [this] { return ?(); }); + monitor_->set(Variable::ir1, [this] + { return Ir1(); }); + monitor_->set(Variable::ii1, [this] + { return Ii1(); }); + // monitor_->set(Variable::im1, [this] { return ?(); }); + // monitor_->set(Variable::p1, [this] { return ?(); }); + // monitor_->set(Variable::q1, [this] { return ?(); }); + monitor_->set(Variable::ir2, [this] + { return Ir2(); }); + monitor_->set(Variable::ii2, [this] + { return Ii2(); }); + // monitor_->set(Variable::im2, [this] { return ?(); }); + // monitor_->set(Variable::p2, [this] { return ?(); }); + // monitor_->set(Variable::q2, [this] { return ?(); }); } /** diff --git a/GridKit/Model/PhasorDynamics/Branch/CMakeLists.txt b/GridKit/Model/PhasorDynamics/Branch/CMakeLists.txt index fa2ef2450..10761d6f9 100644 --- a/GridKit/Model/PhasorDynamics/Branch/CMakeLists.txt +++ b/GridKit/Model/PhasorDynamics/Branch/CMakeLists.txt @@ -15,6 +15,8 @@ if(GRIDKIT_ENABLE_ENZYME) BranchEnzyme.cpp HEADERS ${_install_headers} + INCLUDE_DIRECTORIES + PRIVATE ${GRIDKIT_THIRD_PARTY_DIR}/magic-enum/include LINK_LIBRARIES PRIVATE ClangEnzymeFlags COMPILE_OPTIONS @@ -24,11 +26,14 @@ else() SOURCES Branch.cpp HEADERS - ${_install_headers}) + ${_install_headers} + INCLUDE_DIRECTORIES + PRIVATE ${GRIDKIT_THIRD_PARTY_DIR}/magic-enum/include) endif() gridkit_add_library(phasor_dynamics_branch_dependency_tracking - SOURCES BranchDependencyTracking.cpp) + SOURCES BranchDependencyTracking.cpp + INCLUDE_DIRECTORIES PRIVATE ${GRIDKIT_THIRD_PARTY_DIR}/magic-enum/include) # Link to interface target for all components target_link_libraries(phasor_dynamics_components diff --git a/GridKit/Model/PhasorDynamics/Bus/BusData.hpp b/GridKit/Model/PhasorDynamics/Bus/BusData.hpp index 79077cf64..34091c1ba 100644 --- a/GridKit/Model/PhasorDynamics/Bus/BusData.hpp +++ b/GridKit/Model/PhasorDynamics/Bus/BusData.hpp @@ -11,8 +11,6 @@ #include #include -#include - namespace GridKit { namespace PhasorDynamics @@ -23,19 +21,9 @@ namespace GridKit Vr, Vi, Vm, - Va, - SIZE + Va }; - /** - * @brief Convert enum value to string label - */ - inline const std::string& enumLabel(BusMonitorableVariables v) - { - static const std::string labels[] = {"Vr", "Vi", "Vm", "Va"}; - return labels[Utilities::enumId(v)]; - } - /** * @brief Contains modeling data for a Bus * diff --git a/GridKit/Model/PhasorDynamics/Bus/BusImpl.hpp b/GridKit/Model/PhasorDynamics/Bus/BusImpl.hpp index e142662b5..37b70c01d 100644 --- a/GridKit/Model/PhasorDynamics/Bus/BusImpl.hpp +++ b/GridKit/Model/PhasorDynamics/Bus/BusImpl.hpp @@ -4,6 +4,7 @@ #include #include +#include namespace GridKit { diff --git a/GridKit/Model/PhasorDynamics/Bus/BusInfiniteImpl.hpp b/GridKit/Model/PhasorDynamics/Bus/BusInfiniteImpl.hpp index 470a6e68d..56d05d82a 100644 --- a/GridKit/Model/PhasorDynamics/Bus/BusInfiniteImpl.hpp +++ b/GridKit/Model/PhasorDynamics/Bus/BusInfiniteImpl.hpp @@ -4,6 +4,7 @@ #include #include +#include namespace GridKit { diff --git a/GridKit/Model/PhasorDynamics/Bus/CMakeLists.txt b/GridKit/Model/PhasorDynamics/Bus/CMakeLists.txt index 010eb9027..b5086ed76 100644 --- a/GridKit/Model/PhasorDynamics/Bus/CMakeLists.txt +++ b/GridKit/Model/PhasorDynamics/Bus/CMakeLists.txt @@ -17,6 +17,8 @@ if(GRIDKIT_ENABLE_ENZYME) BusInfinite.cpp HEADERS ${_install_headers} + INCLUDE_DIRECTORIES + PRIVATE ${GRIDKIT_THIRD_PARTY_DIR}/magic-enum/include LINK_LIBRARIES PUBLIC GridKit::phasor_dynamics_core) else() @@ -26,6 +28,8 @@ else() BusInfinite.cpp HEADERS ${_install_headers} + INCLUDE_DIRECTORIES + PRIVATE ${GRIDKIT_THIRD_PARTY_DIR}/magic-enum/include LINK_LIBRARIES PUBLIC GridKit::phasor_dynamics_core) endif() @@ -34,6 +38,8 @@ gridkit_add_library(phasor_dynamics_bus_dependency_tracking SOURCES BusDependencyTracking.cpp BusInfiniteDependencyTracking.cpp + INCLUDE_DIRECTORIES + PRIVATE ${GRIDKIT_THIRD_PARTY_DIR}/magic-enum/include LINK_LIBRARIES PUBLIC GridKit::phasor_dynamics_core) diff --git a/GridKit/Model/PhasorDynamics/BusBase.hpp b/GridKit/Model/PhasorDynamics/BusBase.hpp index c7ebef8af..603f96992 100644 --- a/GridKit/Model/PhasorDynamics/BusBase.hpp +++ b/GridKit/Model/PhasorDynamics/BusBase.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include @@ -16,30 +17,20 @@ namespace GridKit * @brief BusBase model implementation base class. * */ - template + template class BusBase : public Model::Evaluator { public: using RealT = typename Model::Evaluator::RealT; using MatrixT = typename Model::Evaluator::MatrixT; using BusTypeT = typename BusData::BusType; + using MonitorT = Model::VariableMonitor; BusBase() = default; - explicit BusBase(const BusData& data) - : bus_id_(data.bus_id), - monitor_("Bus_" + data.name, data.monitored_variables) - { - using Variable = typename BusData::MonitorableVariables; - monitor_.set(Variable::Vr, [this] - { return Vr(); }); - monitor_.set(Variable::Vi, [this] - { return Vi(); }); - } + explicit BusBase(const BusData& data); - virtual ~BusBase() - { - } + virtual ~BusBase(); /// Pure virtual function, returns bus type (DEFAULT or SLACK). virtual BusTypeT BusType() const @@ -166,10 +157,7 @@ namespace GridKit return residual_indices_; } - const Model::VariableMonitorBase* getMonitor() const override - { - return &monitor_; - } + const Model::VariableMonitorBase* getMonitor() const override; protected: IdxT bus_id_{static_cast(-1)}; @@ -182,7 +170,7 @@ namespace GridKit /// residual indices /// Variable monitor - Model::VariableMonitor monitor_; + std::unique_ptr monitor_; std::vector y_; std::vector yp_; diff --git a/GridKit/Model/PhasorDynamics/BusBaseImpl.hpp b/GridKit/Model/PhasorDynamics/BusBaseImpl.hpp new file mode 100644 index 000000000..741d9c551 --- /dev/null +++ b/GridKit/Model/PhasorDynamics/BusBaseImpl.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include +#include + +namespace GridKit +{ + namespace PhasorDynamics + { + template + BusBase::BusBase(const BusData& data) + : bus_id_(data.bus_id), + monitor_(std::make_unique("Bus_" + data.name, data.monitored_variables)) + { + using Variable = typename BusData::MonitorableVariables; + monitor_->set(Variable::Vr, [this] + { return Vr(); }); + monitor_->set(Variable::Vi, [this] + { return Vi(); }); + } + + template + BusBase::~BusBase() + { + } + + template + inline const Model::VariableMonitorBase* BusBase::getMonitor() const + { + return monitor_.get(); + } + } // namespace PhasorDynamics +} // namespace GridKit diff --git a/GridKit/Model/PhasorDynamics/BusFault/BusFault.hpp b/GridKit/Model/PhasorDynamics/BusFault/BusFault.hpp index 28aea824f..77c85e42d 100644 --- a/GridKit/Model/PhasorDynamics/BusFault/BusFault.hpp +++ b/GridKit/Model/PhasorDynamics/BusFault/BusFault.hpp @@ -34,15 +34,16 @@ namespace GridKit using Component::wb_; using Component::h_; + public: using bus_type = BusBase; using RealT = typename Component::RealT; using DataT = BusFaultData; + using MonitorT = Model::VariableMonitor; - public: BusFault(bus_type* bus); BusFault(bus_type* bus, RealT R, RealT X, int status); BusFault(bus_type* bus, const DataT& data); - ~BusFault() = default; + ~BusFault(); int setGridKitComponentID(IdxT) override; int allocate() override; @@ -78,10 +79,7 @@ namespace GridKit status_ = status; } - const Model::VariableMonitorBase* getMonitor() const override - { - return &monitor_; - } + const Model::VariableMonitorBase* getMonitor() const override; private: void setDerivedParams(); @@ -121,7 +119,7 @@ namespace GridKit RealT G_; /// Variable monitor - Model::VariableMonitor monitor_; + std::unique_ptr monitor_; }; } // namespace PhasorDynamics diff --git a/GridKit/Model/PhasorDynamics/BusFault/BusFaultData.hpp b/GridKit/Model/PhasorDynamics/BusFault/BusFaultData.hpp index 2bc7b0286..4ace959f5 100644 --- a/GridKit/Model/PhasorDynamics/BusFault/BusFaultData.hpp +++ b/GridKit/Model/PhasorDynamics/BusFault/BusFaultData.hpp @@ -32,22 +32,9 @@ namespace GridKit { state, ir, - ii, - SIZE + ii }; - /** - * @brief Convert enum value to string label - */ - inline const std::string& enumLabel(BusFaultMonitorableVariables v) - { - static const std::string labels[] = { - "state", - "ir", - "ii"}; - return labels[Utilities::enumId(v)]; - } - /** * @brief Contains modeling data for a short-to-ground fault * diff --git a/GridKit/Model/PhasorDynamics/BusFault/BusFaultImpl.hpp b/GridKit/Model/PhasorDynamics/BusFault/BusFaultImpl.hpp index ea28a3563..97761a533 100644 --- a/GridKit/Model/PhasorDynamics/BusFault/BusFaultImpl.hpp +++ b/GridKit/Model/PhasorDynamics/BusFault/BusFaultImpl.hpp @@ -6,6 +6,7 @@ #include #include #include +#include namespace GridKit { @@ -57,7 +58,8 @@ namespace GridKit */ template BusFault::BusFault(bus_type* bus, const DataT& data) - : bus_(bus) + : bus_(bus), + monitor_(std::make_unique(data)) { if (data.parameters.contains(DataT::Parameters::R)) { @@ -80,17 +82,22 @@ namespace GridKit } using Variable = typename DataT::MonitorableVariables; - monitor_.set(Variable::state, [this] - { return status_; }); - monitor_.set(Variable::ir, [this] - { return Ir(); }); - monitor_.set(Variable::ii, [this] - { return Ii(); }); + monitor_->set(Variable::state, [this] + { return status_; }); + monitor_->set(Variable::ir, [this] + { return Ir(); }); + monitor_->set(Variable::ii, [this] + { return Ii(); }); size_ = 0; setDerivedParams(); } + template + BusFault::~BusFault() + { + } + /** * @brief Set the component ID */ @@ -171,6 +178,12 @@ namespace GridKit return 0; } + template + const Model::VariableMonitorBase* BusFault::getMonitor() const + { + return monitor_.get(); + } + /** * @brief Derived parameters * diff --git a/GridKit/Model/PhasorDynamics/BusFault/CMakeLists.txt b/GridKit/Model/PhasorDynamics/BusFault/CMakeLists.txt index c73dfea37..40f438fb4 100644 --- a/GridKit/Model/PhasorDynamics/BusFault/CMakeLists.txt +++ b/GridKit/Model/PhasorDynamics/BusFault/CMakeLists.txt @@ -9,6 +9,8 @@ if(GRIDKIT_ENABLE_ENZYME) BusFaultEnzyme.cpp HEADERS ${_install_headers} + INCLUDE_DIRECTORIES + PRIVATE ${GRIDKIT_THIRD_PARTY_DIR}/magic-enum/include LINK_LIBRARIES PRIVATE ClangEnzymeFlags COMPILE_OPTIONS @@ -18,11 +20,14 @@ else() SOURCES BusFault.cpp HEADERS - ${_install_headers}) + ${_install_headers} + INCLUDE_DIRECTORIES + PRIVATE ${GRIDKIT_THIRD_PARTY_DIR}/magic-enum/include) endif() gridkit_add_library(phasor_dynamics_bus_fault_dependency_tracking - SOURCES BusFaultDependencyTracking.cpp) + SOURCES BusFaultDependencyTracking.cpp + INCLUDE_DIRECTORIES PRIVATE ${GRIDKIT_THIRD_PARTY_DIR}/magic-enum/include) # Link to interface target for all components target_link_libraries(phasor_dynamics_components diff --git a/GridKit/Model/PhasorDynamics/Exciter/IEEET1/CMakeLists.txt b/GridKit/Model/PhasorDynamics/Exciter/IEEET1/CMakeLists.txt index b9976b85a..b043b66a4 100644 --- a/GridKit/Model/PhasorDynamics/Exciter/IEEET1/CMakeLists.txt +++ b/GridKit/Model/PhasorDynamics/Exciter/IEEET1/CMakeLists.txt @@ -13,6 +13,8 @@ gridkit_add_library(phasor_dynamics_exciter_ieeet1 Ieeet1.cpp HEADERS ${_install_headers} + INCLUDE_DIRECTORIES + PRIVATE ${GRIDKIT_THIRD_PARTY_DIR}/magic-enum/include LINK_LIBRARIES GridKit::phasor_dynamics_core GridKit::phasor_dynamics_signal) @@ -20,6 +22,8 @@ gridkit_add_library(phasor_dynamics_exciter_ieeet1 gridkit_add_library(phasor_dynamics_exciter_ieeet1_dependency_tracking SOURCES Ieeet1DependencyTracking.cpp + INCLUDE_DIRECTORIES + PRIVATE ${GRIDKIT_THIRD_PARTY_DIR}/magic-enum/include LINK_LIBRARIES GridKit::phasor_dynamics_core GridKit::phasor_dynamics_signal_dependency_tracking) diff --git a/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1.hpp b/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1.hpp index 13cf14f68..fffb5674a 100644 --- a/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1.hpp +++ b/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1.hpp @@ -81,6 +81,7 @@ namespace GridKit using model_data_type = Ieeet1Data; using signal_type = SignalNode; using bus_type = BusBase; + using MonitorT = Model::VariableMonitor; Ieeet1(bus_type* bus); Ieeet1(signal_type* efd_signal, @@ -89,7 +90,7 @@ namespace GridKit const model_data_type& data); Ieeet1(bus_type* bus, const model_data_type& data); - ~Ieeet1() = default; + ~Ieeet1(); int setGridKitComponentID(IdxT) override; int allocate() override; @@ -113,10 +114,7 @@ namespace GridKit return signals_; } - const Model::VariableMonitorBase* getMonitor() const override - { - return &monitor_; - } + const Model::VariableMonitorBase* getMonitor() const override; private: // Signal pointers @@ -157,7 +155,7 @@ namespace GridKit ComponentSignals signals_; /// Variable monitor - Model::VariableMonitor monitor_; + std::unique_ptr monitor_; // Parameter initialization function void initModelParams(const model_data_type& data); diff --git a/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1Data.hpp b/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1Data.hpp index c3ba2be21..0d4553f82 100644 --- a/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1Data.hpp +++ b/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1Data.hpp @@ -8,7 +8,6 @@ #pragma once #include -#include namespace GridKit { @@ -47,19 +46,9 @@ namespace GridKit enum class Ieeet1MonitorableVariables { efd, - ksat, - SIZE + ksat }; - /** - * @brief Convert enum value to string label - */ - inline const std::string& enumLabel(Ieeet1MonitorableVariables v) - { - static std::string labels[] = {"efd", "ksat"}; - return labels[Utilities::enumId(v)]; - } - /** * @brief Contains modeling data for a IEEET1 Exciter model. * diff --git a/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1Impl.hpp b/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1Impl.hpp index 12529a0ca..6e9351242 100644 --- a/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1Impl.hpp +++ b/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1Impl.hpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #define _USE_MATH_DEFINES @@ -57,7 +58,7 @@ namespace GridKit : efd_signal_(efd_signal), speed_signal_(speed_signal), bus_(bus), - monitor_(data) + monitor_(std::make_unique(data)) { // Parse data struct into model @@ -81,7 +82,7 @@ namespace GridKit Ieeet1::Ieeet1(bus_type* bus, const model_data_type& data) : bus_(bus), - monitor_(data) + monitor_(std::make_unique(data)) { // Parse data struct into model @@ -93,6 +94,11 @@ namespace GridKit size_ = 9; } + template + Ieeet1::~Ieeet1() + { + } + /** * @brief Set the component ID */ @@ -396,13 +402,19 @@ namespace GridKit SB_ = Se1_ / (E1_ - SA_) / (E1_ - SA_); } + template + const Model::VariableMonitorBase* Ieeet1::getMonitor() const + { + return monitor_.get(); + } + template void Ieeet1::initializeMonitor() { using Variable = model_data_type::MonitorableVariables; - monitor_.set(Variable::efd, [this] - { return efd_signal_->read(); }); - // monitor_.set(Variable::ksat, [this] { return ?; }); + monitor_->set(Variable::efd, [this] + { return efd_signal_->read(); }); + // monitor_->set(Variable::ksat, [this] { return ?; }); } } // namespace Exciter } // namespace PhasorDynamics diff --git a/GridKit/Model/PhasorDynamics/Load/CMakeLists.txt b/GridKit/Model/PhasorDynamics/Load/CMakeLists.txt index 77a2632aa..ef86891b4 100644 --- a/GridKit/Model/PhasorDynamics/Load/CMakeLists.txt +++ b/GridKit/Model/PhasorDynamics/Load/CMakeLists.txt @@ -15,6 +15,8 @@ if(GRIDKIT_ENABLE_ENZYME) LoadEnzyme.cpp HEADERS ${_install_headers} + INCLUDE_DIRECTORIES + PRIVATE ${GRIDKIT_THIRD_PARTY_DIR}/magic-enum/include LINK_LIBRARIES PRIVATE ClangEnzymeFlags COMPILE_OPTIONS @@ -23,12 +25,15 @@ else() gridkit_add_library(phasor_dynamics_load SOURCES Load.cpp + INCLUDE_DIRECTORIES + PRIVATE ${GRIDKIT_THIRD_PARTY_DIR}/magic-enum/include HEADERS ${_install_headers}) endif() gridkit_add_library(phasor_dynamics_load_dependency_tracking - SOURCES LoadDependencyTracking.cpp) + SOURCES LoadDependencyTracking.cpp + INCLUDE_DIRECTORIES PRIVATE ${GRIDKIT_THIRD_PARTY_DIR}/magic-enum/include) # Link to interface target for all components target_link_libraries(phasor_dynamics_components diff --git a/GridKit/Model/PhasorDynamics/Load/Load.hpp b/GridKit/Model/PhasorDynamics/Load/Load.hpp index 26cfeed8d..091853b50 100644 --- a/GridKit/Model/PhasorDynamics/Load/Load.hpp +++ b/GridKit/Model/PhasorDynamics/Load/Load.hpp @@ -40,11 +40,12 @@ namespace GridKit using Component::wb_; using Component::h_; + public: using RealT = typename Component::RealT; using bus_type = BusBase; using model_data_type = LoadData; + using MonitorT = Model::VariableMonitor; - public: Load(bus_type* bus); Load(bus_type* bus, RealT R, RealT X); Load(bus_type* bus, const model_data_type& data); @@ -101,10 +102,7 @@ namespace GridKit return bus_->Ii(); } - const Model::VariableMonitorBase* getMonitor() const override - { - return &monitor_; - } + const Model::VariableMonitorBase* getMonitor() const override; public: __attribute__((always_inline)) inline int evaluateBusResidual(ScalarT*, ScalarT*, ScalarT*, ScalarT*); @@ -118,7 +116,7 @@ namespace GridKit RealT b_; RealT g_; - Model::VariableMonitor monitor_; + std::unique_ptr monitor_; }; } // namespace PhasorDynamics diff --git a/GridKit/Model/PhasorDynamics/Load/LoadData.hpp b/GridKit/Model/PhasorDynamics/Load/LoadData.hpp index f7ff34540..ccddccfae 100644 --- a/GridKit/Model/PhasorDynamics/Load/LoadData.hpp +++ b/GridKit/Model/PhasorDynamics/Load/LoadData.hpp @@ -29,19 +29,9 @@ namespace GridKit enum class LoadMonitorableVariables { p, - q, - SIZE + q }; - /** - * @brief Convert enum value to string label - */ - inline const std::string& enumLabel(LoadMonitorableVariables v) - { - static const std::string labels[] = {"p", "q"}; - return labels[Utilities::enumId(v)]; - } - /** * @brief Contains modeling data for a load * diff --git a/GridKit/Model/PhasorDynamics/Load/LoadImpl.hpp b/GridKit/Model/PhasorDynamics/Load/LoadImpl.hpp index f6a393c1b..144d49eb4 100644 --- a/GridKit/Model/PhasorDynamics/Load/LoadImpl.hpp +++ b/GridKit/Model/PhasorDynamics/Load/LoadImpl.hpp @@ -6,6 +6,7 @@ #include #include #include +#include namespace GridKit { @@ -53,8 +54,8 @@ namespace GridKit } // using Variable = typename model_data_type::MonitorableVariables; - // monitor_.set(Variable::p, [this] { return ?; }); - // monitor_.set(Variable::q, [this] { return ?; }); + // monitor_->set(Variable::p, [this] { return ?; }); + // monitor_->set(Variable::q, [this] { return ?; }); size_ = 0; setDerivedParams(); @@ -154,5 +155,11 @@ namespace GridKit g_ = R_ / (R_ * R_ + X_ * X_); } + template + const Model::VariableMonitorBase* Load::getMonitor() const + { + return monitor_.get(); + } + } // namespace PhasorDynamics } // namespace GridKit diff --git a/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROUwS/CMakeLists.txt b/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROUwS/CMakeLists.txt index 18b23ed0b..eda3242f6 100644 --- a/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROUwS/CMakeLists.txt +++ b/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROUwS/CMakeLists.txt @@ -9,6 +9,8 @@ if(GRIDKIT_ENABLE_ENZYME) GenrouEnzyme.cpp HEADERS ${_install_headers} + INCLUDE_DIRECTORIES + PRIVATE ${GRIDKIT_THIRD_PARTY_DIR}/magic-enum/include LINK_LIBRARIES PUBLIC GridKit::phasor_dynamics_core PUBLIC GridKit::phasor_dynamics_signal @@ -21,6 +23,8 @@ else() Genrou.cpp HEADERS ${_install_headers} + INCLUDE_DIRECTORIES + PRIVATE ${GRIDKIT_THIRD_PARTY_DIR}/magic-enum/include LINK_LIBRARIES PUBLIC GridKit::phasor_dynamics_core PUBLIC GridKit::phasor_dynamics_signal) @@ -29,6 +33,8 @@ endif() gridkit_add_library(phasor_dynamics_genrou_dependency_tracking SOURCES GenrouDependencyTracking.cpp + INCLUDE_DIRECTORIES + PRIVATE ${GRIDKIT_THIRD_PARTY_DIR}/magic-enum/include LINK_LIBRARIES PUBLIC GridKit::phasor_dynamics_core PUBLIC GridKit::phasor_dynamics_signal_dependency_tracking) diff --git a/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROUwS/Genrou.hpp b/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROUwS/Genrou.hpp index e2eba8e48..8c9aaa61c 100644 --- a/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROUwS/Genrou.hpp +++ b/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROUwS/Genrou.hpp @@ -88,6 +88,7 @@ namespace GridKit using bus_type = BusBase; using model_data_type = GenrouData; using signal_type = SignalNode; + using MonitorT = Model::VariableMonitor; Genrou(bus_type* bus, IdxT unit_id); Genrou(bus_type* bus, @@ -120,7 +121,7 @@ namespace GridKit RealT Xl, RealT S10, RealT S12); - ~Genrou() = default; + ~Genrou(); int setGridKitComponentID(IdxT) override; int allocate() override; @@ -151,10 +152,7 @@ namespace GridKit return signals_; } - const Model::VariableMonitorBase* getMonitor() const override - { - return &monitor_; - } + const Model::VariableMonitorBase* getMonitor() const override; private: void initializeParameters(const model_data_type& data); @@ -244,7 +242,7 @@ namespace GridKit std::map ws_indices_; /// Variable monitor - Model::VariableMonitor monitor_; + std::unique_ptr monitor_; }; } // namespace PhasorDynamics diff --git a/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROUwS/GenrouData.hpp b/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROUwS/GenrouData.hpp index 47ed138fa..a631d1675 100644 --- a/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROUwS/GenrouData.hpp +++ b/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROUwS/GenrouData.hpp @@ -7,7 +7,6 @@ #pragma once #include -#include namespace GridKit { @@ -54,25 +53,9 @@ namespace GridKit p, q, delta, - omega, - SIZE + omega }; - /** - * @brief Convert enum value to string label - */ - inline const std::string& enumLabel(GenrouMonitorableVariables v) - { - static const std::string labels[] = { - "ir", - "ii", - "p", - "q", - "delta", - "omega"}; - return labels[Utilities::enumId(v)]; - } - /** * @brief Contains modeling data for a Genrou generator model. * diff --git a/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROUwS/GenrouImpl.hpp b/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROUwS/GenrouImpl.hpp index 216d97a61..7f3ae05fd 100644 --- a/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROUwS/GenrouImpl.hpp +++ b/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROUwS/GenrouImpl.hpp @@ -9,6 +9,7 @@ #include #include #include +#include #include namespace GridKit @@ -113,7 +114,7 @@ namespace GridKit Genrou::Genrou(bus_type* bus, const model_data_type& data) : bus_(bus), unit_id_(1), - monitor_(data) + monitor_(std::make_unique(data)) { initializeParameters(data); initializeMonitor(); @@ -129,7 +130,7 @@ namespace GridKit Genrou::Genrou(bus_type* bus, signal_type* omega, signal_type* pmech, const model_data_type& data) : bus_(bus), unit_id_(1), - monitor_(data) + monitor_(std::make_unique(data)) { signals_.template attachSignalNode(pmech); signals_.template assignSignalNode(omega); @@ -147,7 +148,7 @@ namespace GridKit Genrou::Genrou(bus_type* bus, signal_type* omega, signal_type* pmech, signal_type* efd, const model_data_type& data) : bus_(bus), unit_id_(1), - monitor_(data) + monitor_(std::make_unique(data)) { signals_.template attachSignalNode(pmech); signals_.template assignSignalNode(omega); @@ -159,6 +160,11 @@ namespace GridKit setDerivedParams(); } + template + Genrou::~Genrou() + { + } + /// Helper function to extract and assign model parameters from the model's associated /// data structure. template @@ -265,20 +271,26 @@ namespace GridKit } } + template + const Model::VariableMonitorBase* Genrou::getMonitor() const + { + return monitor_.get(); + } + template void Genrou::initializeMonitor() { using Variable = typename model_data_type::MonitorableVariables; - monitor_.set(Variable::ir, [this] - { return y_[15]; }); - monitor_.set(Variable::ii, [this] - { return y_[16]; }); - // monitor_.set(Variable::p, [this] { return ?(); }); - // monitor_.set(Variable::q, [this] { return ?(); }); - monitor_.set(Variable::delta, [this] - { return y_[0]; }); - monitor_.set(Variable::omega, [this] - { return y_[1]; }); + monitor_->set(Variable::ir, [this] + { return y_[15]; }); + monitor_->set(Variable::ii, [this] + { return y_[16]; }); + // monitor_->set(Variable::p, [this] { return ?(); }); + // monitor_->set(Variable::q, [this] { return ?(); }); + monitor_->set(Variable::delta, [this] + { return y_[0]; }); + monitor_->set(Variable::omega, [this] + { return y_[1]; }); } /** diff --git a/GridKit/Model/PhasorDynamics/SynchronousMachine/GenClassical/CMakeLists.txt b/GridKit/Model/PhasorDynamics/SynchronousMachine/GenClassical/CMakeLists.txt index 400989637..ef1c782bc 100644 --- a/GridKit/Model/PhasorDynamics/SynchronousMachine/GenClassical/CMakeLists.txt +++ b/GridKit/Model/PhasorDynamics/SynchronousMachine/GenClassical/CMakeLists.txt @@ -14,6 +14,8 @@ if(GRIDKIT_ENABLE_ENZYME) GenClassicalEnzyme.cpp HEADERS ${_install_headers} + INCLUDE_DIRECTORIES + PRIVATE ${GRIDKIT_THIRD_PARTY_DIR}/magic-enum/include LINK_LIBRARIES PRIVATE ClangEnzymeFlags COMPILE_OPTIONS @@ -23,11 +25,14 @@ else() SOURCES GenClassical.cpp HEADERS - ${_install_headers}) + ${_install_headers} + INCLUDE_DIRECTORIES + PRIVATE ${GRIDKIT_THIRD_PARTY_DIR}/magic-enum/include) endif() - gridkit_add_library(phasor_dynamics_gen_classical_dependency_tracking - SOURCES GenClassicalDependencyTracking.cpp) +gridkit_add_library(phasor_dynamics_gen_classical_dependency_tracking + SOURCES GenClassicalDependencyTracking.cpp + INCLUDE_DIRECTORIES PRIVATE ${GRIDKIT_THIRD_PARTY_DIR}/magic-enum/include) # Link to interface target for all components target_link_libraries(phasor_dynamics_components diff --git a/GridKit/Model/PhasorDynamics/SynchronousMachine/GenClassical/GenClassical.hpp b/GridKit/Model/PhasorDynamics/SynchronousMachine/GenClassical/GenClassical.hpp index c4af29145..7a6d69df3 100644 --- a/GridKit/Model/PhasorDynamics/SynchronousMachine/GenClassical/GenClassical.hpp +++ b/GridKit/Model/PhasorDynamics/SynchronousMachine/GenClassical/GenClassical.hpp @@ -47,11 +47,12 @@ namespace GridKit using Component::J_; using Component::mva_system_base_; + public: using bus_type = BusBase; using RealT = typename Component::RealT; using DataT = GenClassicalData; + using MonitorT = Model::VariableMonitor; - public: GenClassical(bus_type* bus, int unit_id); GenClassical(bus_type* bus, int unit_id, @@ -62,7 +63,7 @@ namespace GridKit RealT Ra, RealT Xdp); GenClassical(bus_type* bus, const DataT& data); - ~GenClassical() = default; + ~GenClassical(); int setGridKitComponentID(IdxT) override; int allocate() override; @@ -92,10 +93,7 @@ namespace GridKit ep_set_ = ep; } - const Model::VariableMonitorBase* getMonitor() const override - { - return &monitor_; - } + const Model::VariableMonitorBase* getMonitor() const override; private: void initializeMonitor(); @@ -151,7 +149,7 @@ namespace GridKit ScalarT ep_set_; /// Variable monitor - Model::VariableMonitor monitor_; + std::unique_ptr monitor_; }; } // namespace PhasorDynamics diff --git a/GridKit/Model/PhasorDynamics/SynchronousMachine/GenClassical/GenClassicalData.hpp b/GridKit/Model/PhasorDynamics/SynchronousMachine/GenClassical/GenClassicalData.hpp index 190a634ea..c6c41b44a 100644 --- a/GridKit/Model/PhasorDynamics/SynchronousMachine/GenClassical/GenClassicalData.hpp +++ b/GridKit/Model/PhasorDynamics/SynchronousMachine/GenClassical/GenClassicalData.hpp @@ -40,25 +40,9 @@ namespace GridKit p, q, delta, - omega, - SIZE + omega }; - /** - * @brief Convert enum value to string label - */ - inline const std::string& enumLabel(GenClassicalMonitorableVariables v) - { - static const std::string labels[] = { - "ir", - "ii", - "p", - "q", - "delta", - "omega"}; - return labels[Utilities::enumId(v)]; - } - /** * @brief Contains modeling data for a GenClassical generator model. * diff --git a/GridKit/Model/PhasorDynamics/SynchronousMachine/GenClassical/GenClassicalImpl.hpp b/GridKit/Model/PhasorDynamics/SynchronousMachine/GenClassical/GenClassicalImpl.hpp index 5437a959c..58ba7039a 100644 --- a/GridKit/Model/PhasorDynamics/SynchronousMachine/GenClassical/GenClassicalImpl.hpp +++ b/GridKit/Model/PhasorDynamics/SynchronousMachine/GenClassical/GenClassicalImpl.hpp @@ -13,6 +13,7 @@ #include #include #include +#include namespace GridKit { @@ -71,7 +72,8 @@ namespace GridKit template GenClassical::GenClassical(bus_type* bus, const DataT& data) : bus_(bus), - unit_id_(1) + unit_id_(1), + monitor_(std::make_unique(data)) { if (data.parameters.contains(DataT::Parameters::p0)) { @@ -119,20 +121,31 @@ namespace GridKit setDerivedParams(); } + template + GenClassical::~GenClassical() + { + } + + template + const Model::VariableMonitorBase* GenClassical::getMonitor() const + { + return monitor_.get(); + } + template void GenClassical::initializeMonitor() { using Variable = typename DataT::MonitorableVariables; - monitor_.set(Variable::ir, [this] - { return y_[3]; }); - monitor_.set(Variable::ii, [this] - { return y_[4]; }); - // monitor_.set(Variable::p, [this] { return ?(); }); - // monitor_.set(Variable::q, [this] { return ?(); }); - monitor_.set(Variable::delta, [this] - { return y_[0]; }); - monitor_.set(Variable::omega, [this] - { return y_[1]; }); + monitor_->set(Variable::ir, [this] + { return y_[3]; }); + monitor_->set(Variable::ii, [this] + { return y_[4]; }); + // monitor_->set(Variable::p, [this] { return ?(); }); + // monitor_->set(Variable::q, [this] { return ?(); }); + monitor_->set(Variable::delta, [this] + { return y_[0]; }); + monitor_->set(Variable::omega, [this] + { return y_[1]; }); } /** diff --git a/GridKit/Model/PhasorDynamics/SystemModel.hpp b/GridKit/Model/PhasorDynamics/SystemModel.hpp index 8932bba9d..2d787a02a 100644 --- a/GridKit/Model/PhasorDynamics/SystemModel.hpp +++ b/GridKit/Model/PhasorDynamics/SystemModel.hpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -783,7 +784,7 @@ namespace GridKit bool owns_components_{false}; /// Variable monitor - Model::VariableMonitor monitor_; + Model::VariableMonitorController monitor_; }; // class SystemModel } // namespace PhasorDynamics diff --git a/GridKit/Model/PhasorDynamics/SystemModelData.hpp b/GridKit/Model/PhasorDynamics/SystemModelData.hpp index 18ef9e8f4..bf4752ca3 100644 --- a/GridKit/Model/PhasorDynamics/SystemModelData.hpp +++ b/GridKit/Model/PhasorDynamics/SystemModelData.hpp @@ -15,7 +15,7 @@ #include #include #include -#include +#include namespace GridKit { diff --git a/GridKit/Model/PhasorDynamics/SystemModelDataJSONParser.hpp b/GridKit/Model/PhasorDynamics/SystemModelDataJSONParser.hpp index 8b378385a..12241c03c 100644 --- a/GridKit/Model/PhasorDynamics/SystemModelDataJSONParser.hpp +++ b/GridKit/Model/PhasorDynamics/SystemModelDataJSONParser.hpp @@ -9,7 +9,6 @@ #include #include #include -#include #include namespace GridKit diff --git a/GridKit/Model/VariableMonitor.hpp b/GridKit/Model/VariableMonitor.hpp index 8aa9ffa1b..ce7df4f49 100644 --- a/GridKit/Model/VariableMonitor.hpp +++ b/GridKit/Model/VariableMonitor.hpp @@ -14,684 +14,130 @@ #include #include -#include #include -#include namespace GridKit { - namespace PhasorDynamics - { - template - class SystemModel; - - template - class SystemModelData; - } // namespace PhasorDynamics - namespace Model { - template typename DataT> - class VariableMonitor + template + class VariableMonitorController; + + /** + * @enum VariableMonitorFormat + * Available formats for monitor output + */ + enum class VariableMonitorFormat { + CSV, ///< CSV format + JSON, ///< JSON format + YAML ///< YAML format }; /** - * @brief Manage printing variables associated with a specific component or - * bus + * @brief Abstract class for managing output of monitored variables * - * 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. + * This class is used for both the high-level control monitor and the + * individual component and bus monitors. */ - template typename EvalT, - template typename DataT> - class VariableMonitor, DataT> - : public VariableMonitorBase + class VariableMonitorBase { - template typename> - friend class VariableMonitor; + template + friend class VariableMonitorController; public: - /// Underlying real value type - using RealT = typename GridKit::ScalarTraits::RealT; - /// Type of (EvalT)Data class expected to have MonitorableVariables enum - using ObjData = DataT; - /// Enum of valid monitorable variables - using VariableEnum = typename ObjData::MonitorableVariables; - ///@{ - /// @brief Alias - using Csv = VariableMonitorBase::Csv; - using Json = VariableMonitorBase::Json; - using Yaml = VariableMonitorBase::Yaml; - ///@} - - VariableMonitor() = default; - - /** - * @brief Construct monitor with component/bus label and set of variables - * selected for monitoring - * - * @param label Monitor label used to disambiguate like variables from - * different objects of same type - * @param variables Variables selected for monitoring - */ - VariableMonitor(const std::string& label, - const std::set& variables) - : label_(label) - { - std::ranges::copy(variables, std::back_inserter(variables_)); - } - - /** - * @brief Construct from ObjData object - * - * Constructs the monitor label from elements of the data object - * - * @param data Expected to be derived from ComponentData - */ - VariableMonitor(const ObjData& data) - : VariableMonitor(data.device_class + "_" + data.disambiguation_string, - data.monitored_variables) + /// Type used for dispatch + struct Csv { - } - - virtual ~VariableMonitor() - { - } - - bool empty() const override - { - return variables_.empty(); - } - - /** - * @brief Associate a value getter with a variable enum value - * - * @note This does not designate the variable for printing. It defines how - * to get the variable if it is printed. - */ - template - void set(VariableEnum v, FuncT f) - { - f_[Utilities::enumId(v)] = ValuePrinter{f}; - } - - private: - ///@{ - /** - * @brief Functors to handle printing different types - */ - template - struct ValuePrinterImpl - { - FuncT f; - - void operator()(std::ostream& os) const - { - os << f(); - } + /// Delimiter for CSV line output + std::string delim{","}; }; - template - struct ValuePrinterImpl + /// Type used for dispatch + struct Json { - FuncT f; - - void operator()(std::ostream& os) const - { - os << static_cast(f()); - } + /// Implementation detail used to prevent a comma before the first block + mutable bool after_first{false}; }; - template - using ValuePrinterType = - ValuePrinterImpl>; - - class ValuePrinter + /// Type used for dispatch + struct Yaml { - public: - ValuePrinter() = default; - - template - ValuePrinter(FuncT f) - : impl_{ValuePrinterType(f)} - { - } - - private: - std::function impl_; - - friend std::ostream& operator<<(std::ostream& os, const ValuePrinter& p) - { - p.impl_(os); - return os; - } }; - ///@} - - void printHeader(std::ostream& os, Csv csv) const override - { - for (auto v : variables_) - { - os << csv.delim << label_ << '_' << enumLabel(v); - } - } - - void print(std::ostream& os, Csv csv) const override - { - for (auto v : variables_) - { - os << csv.delim << f_[Utilities::enumId(v)]; - } - } + /// Short alias for local use + using Format = VariableMonitorFormat; /** - * @brief Print single variable + * @brief Defines information necessary to create a monitor sink */ - void print(std::ostream& os, VariableEnum v, Json) const - { - os << indent_ << std::quoted(enumLabel(v)) << ": " << f_[Utilities::enumId(v)] << ",\n"; - } - - void print(std::ostream& os, Json) const override + struct SinkSpec { - if (empty()) - { - return; - } - 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()); - } - auto vars = v_os.view(); - vars.remove_suffix(2); - os << vars << '\n'; - indent_.erase(indent_.size() - 2); - os << indent_ << "}"; - } + /// Output file name (empty for stdout) + std::string file_name; + /// Output format + Format format; + /// Delimiter (used only with CSV format currently) + std::string delim; + }; - void printHeader(std::ostream&, Yaml) const override + virtual ~VariableMonitorBase() { } /** - * @brief Print single variable + * @brief Is there nothing to monitor? */ - void print(std::ostream& os, VariableEnum v, Yaml) const - { - os << indent_ << enumLabel(v) << ": " << f_[Utilities::enumId(v)] << '\n'; - } - - void print(std::ostream& os, Yaml) const override - { - if (empty()) - { - return; - } - os << indent_ << label_ << ":\n"; - indent_.append(2, ' '); - for (auto v : variables_) - { - print(os, v, Yaml()); - } - indent_.erase(indent_.size() - 2); - } - - private: - /// Compile-time constant size: length of enum value list - static constexpr auto enum_size_ = Utilities::enumSize; + virtual bool empty() const = 0; - /// Set of functions associated with each enum value - std::array f_; - /// Set of selected enum values - std::vector variables_; - /// Indent string used for formatting - mutable std::string indent_{" "}; - /// Monitor disambiguation label - std::string label_; - }; - - /** - * @brief Monitor associated with the SystemModel; controls output from - * component and bus monitors. - * - * High-level print functions (without parameters) manage printing for all - * monitors for multiple output sinks. - */ - template - class VariableMonitor, - PhasorDynamics::SystemModelData> - : public VariableMonitorBase - { - public: - /// Underlying real value type - using RealT = typename GridKit::ScalarTraits::RealT; + protected: ///@{ - /// @brief Alias - using Format = VariableMonitorFormat; - using SinkSpec = VariableMonitorBase::SinkSpec; - using Csv = VariableMonitorBase::Csv; - using Json = VariableMonitorBase::Json; - using Yaml = VariableMonitorBase::Yaml; - ///@} - - /// Default to empty monitor - VariableMonitor() = default; - - /** - * @brief Constructor expects a time variable to monitor - */ - explicit VariableMonitor(const RealT& time_var) - : time_(&time_var) - { - } - - virtual ~VariableMonitor() - { - } - - /** - * @brief Add a monitor to the output - * - * Each component and bus could have a monitor for their respective - * values. - * - * @param monitor Monitor to add (raw pointer indicates ownership is - * elsewhere) - */ - void addMonitor(const VariableMonitorBase* monitor) - { - if (monitor && !monitor->empty()) - { - monitors_.push_back(monitor); - } - } - /** - * @brief Add output sink based on spec - * - * If `spec.file_name` is empty, `std::cout` is used. - * - * @param spec Specifies details for the sink. + * @brief Print items relevant to the start of a file */ - void addSink(const SinkSpec& spec) - { - if (spec.file_name.empty()) - { - sinks_.push_back(make_sink(spec, std::cout)); - } - else - { - sinks_.push_back(make_sink(spec, spec.file_name)); - } - } + virtual void printHeader(std::ostream&, Csv) const = 0; - /** - * @brief Provide additional top-level variables (alongside time) to be - * printed before submonitors. - * - * @param label Header label for CSV; key for JSON or YAML - * @param value Pointer to monitored variable - */ - void addVariable(const std::string& label, const RealT* value) + virtual void printHeader(std::ostream&, Json) const { - variables_.emplace_back(label, value); } - bool empty() const override + virtual void printHeader(std::ostream&, Yaml) const { - return (!time_) || monitors_.empty(); } - /** - * @brief Print header if we're monitoring - */ - void start() - { - if (!empty()) - { - startSinks(); - printHeader(); - } - } - - /** - * @brief Print footer if we're monitoring - */ - void stop() - { - if (!empty()) - { - printFooter(); - stopSinks(); - } - } - - /// @copydoc VariableMonitorBase::printHeader - using VariableMonitorBase::printHeader; - - void printHeader(std::ostream& os, Csv csv) const override - { - os << "t"; - for (auto&& var : variables_) - { - os << csv.delim << var.label; - } - } - - void printHeader(std::ostream& os, Json) const override - { - os << "[\n"; - } - - /** - * @brief Organize header output for this and all submonitors - */ - template - void printFullHeader(std::ostream& os, FormatT fmt) const - { - this->printHeader(os, fmt); - for (auto* mon : monitors_) - { - mon->printHeader(os, fmt); - } - os << '\n'; - } - - /** - * @brief Print header for all sinks - */ - void printHeader() const - { - for (auto&& sink : sinks_) - { - std::visit([this](auto&& sink) - { printFullHeader(*sink.os, sink.format); }, - sink); - } - } - - void print(std::ostream& os, Csv csv) const override - { - os << *time_; - for (auto&& var : variables_) - { - os << csv.delim << *var.value; - } - - for (auto* mon : monitors_) - { - mon->print(os, csv); - } - } - - void print(std::ostream& os, Json json) const override - { - std::string indent = " "; - if (json.after_first) - { - os << indent << ",\n"; - } - os << indent << "{\n"; - indent.append(2, ' '); - - os << indent << std::quoted("t") << ": " << *time_ << ",\n"; - for (auto&& var : variables_) - { - os << indent << std::quoted(var.label) << ": " << *var.value << ",\n"; - } - - auto after_first = false; - for (auto* mon : monitors_) - { - if (after_first) - { - os << ",\n"; - } - mon->print(os, Json()); - after_first = true; - } - - indent.erase(indent.size() - 2); - os << '\n' - << indent << "}"; - } - - void print(std::ostream& os, Yaml) const override - { - std::string indent = " "; - os << indent << "- t: " << *time_ << '\n'; - indent.append(2, ' '); - for (auto&& var : variables_) - { - os << indent << var.label << ": " << *var.value << '\n'; - } - - for (auto* mon : monitors_) - { - mon->print(os, Yaml()); - } - } - - /** - * @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); - } - - /** - * @brief Print variables to each sink - */ - void print() const - { - for (auto&& sink : sinks_) - { - std::visit([this](auto&& sink) - { - printFull(*sink.os, sink.format); - using T = std::remove_cvref_t; - if constexpr (std::is_same_v>) - { - sink.format.after_first = true; - } }, - sink); - } - } - - /// @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 - { - for (auto* mon : monitors_) - { - mon->printFooter(os, format); - } - this->printFooter(os, format); - } - - /** - * @brief Print footer for all sinks - */ - void printFooter() const - { - for (auto&& sink : sinks_) - { - std::visit([this](auto&& sink) - { printFullFooter(*sink.os, sink.format); }, - sink); - } - } - - private: - /// Time variable; printed first - const RealT* time_{nullptr}; + ///@} + ///@{ /** - * @brief Define sink for a specific output format + * @brief Print monitored variables at current state */ - template - struct Sink - { - Sink() = delete; - - /** - * @brief Version for an output stream that already exists - */ - Sink(std::ostream& out, FormatT fmt) - : os(&out), format(fmt) - { - } + virtual void print(std::ostream&, Csv) const = 0; + virtual void print(std::ostream&, Json) const = 0; + virtual void print(std::ostream&, Yaml) const = 0; - /** - * @brief Version for opening an output stream for the given file - */ - Sink(const std::string& fileName, FormatT fmt) - : file_name(fileName), - format(fmt) - { - } - - /** - * @brief Have to move because of unique_ptr - */ - Sink(Sink&&) = default; - - void start() - { - if (file_name.empty()) - { - return; - } - if (file_stream) - { - return; - } - file_stream = std::make_unique(file_name); - os = file_stream.get(); - } - - void stop() - { - if (file_name.empty()) - { - return; - } - file_stream.reset(); - os = nullptr; - } - - /// Output file name - std::string file_name; - /// Output file stream (if we opened one) - std::unique_ptr file_stream; - /// Output stream for printing - std::ostream* os{nullptr}; - /// Output format object which may have useful members - FormatT format; - }; - - template - Sink(ArgT&&, FormatT) -> Sink; - - /// Variant type for all possible sink types - using SinkVariant = std::variant, Sink, Sink>; + ///@} + ///@{ /** - * @brief Factory function mapping format enum value to sink type + * @brief Print items relevant to the end of a file */ - template - static SinkVariant make_sink(SinkSpec spec, T&& arg) + virtual void printFooter(std::ostream&, Csv) const { - switch (spec.format) - { - case Format::CSV: - return Sink(std::forward(arg), Csv{spec.delim}); - break; - case Format::JSON: - return Sink(std::forward(arg), Json{}); - break; - case Format::YAML: - return Sink(std::forward(arg), Yaml{}); - break; - } - throw std::runtime_error("Invalid monitor output format"); } - /** - * @brief Get sinks ready for output - */ - void startSinks() + virtual void printFooter(std::ostream&, Json) const { - for (auto&& sink : sinks_) - { - std::visit([this](auto&& sink) - { sink.start(); }, - sink); - } } - /** - * @brief Finalize sinks and close owned files - */ - void stopSinks() + virtual void printFooter(std::ostream&, Yaml) const { - for (auto&& sink : sinks_) - { - std::visit([this](auto&& sink) - { sink.stop(); }, - sink); - } } - /// Collection of output sinks - std::vector sinks_; - /// Collection of submonitors - std::vector monitors_; - - /** - * @brief Key/Value object for extra top-level variables - */ - struct Variable - { - /// Header label for CSV; key for JSON or YAML - std::string label; - /// Pointer to monitored variable - const RealT* value; - }; - - /// Collection of extra top-level monitored variables - std::vector variables_; + ///@} }; + template typename DataT> + class VariableMonitor; + } // namespace Model } // namespace GridKit diff --git a/GridKit/Model/VariableMonitorBase.hpp b/GridKit/Model/VariableMonitorBase.hpp deleted file mode 100644 index 94840f20e..000000000 --- a/GridKit/Model/VariableMonitorBase.hpp +++ /dev/null @@ -1,128 +0,0 @@ -#pragma once - -/** - * @file - */ - -#include -#include - -namespace GridKit -{ - namespace Model - { - /** - * @enum VariableMonitorFormat - * Available formats for monitor output - */ - enum class VariableMonitorFormat - { - CSV, ///< CSV format - JSON, ///< JSON format - YAML ///< YAML format - }; - - /** - * @brief Abstract class for managing output of monitored variables - * - * This class is used for both the high-level control monitor and the - * individual component and bus monitors. - */ - class VariableMonitorBase - { - template typename> - friend class VariableMonitor; - - public: - /// Type used for dispatch - struct Csv - { - /// Delimiter for CSV line output - std::string delim{","}; - }; - - /// Type used for dispatch - struct Json - { - /// Implementation detail used to prevent a comma before the first block - mutable bool after_first{false}; - }; - - /// Type used for dispatch - struct Yaml - { - }; - - /// Short alias for local use - using Format = VariableMonitorFormat; - - /** - * @brief Defines information necessary to create a monitor sink - */ - struct SinkSpec - { - /// Output file name (empty for stdout) - std::string file_name; - /// Output format - Format format; - /// Delimiter (used only with CSV format currently) - std::string delim; - }; - - virtual ~VariableMonitorBase() - { - } - - /** - * @brief Is there nothing to monitor? - */ - virtual bool empty() const = 0; - - protected: - ///@{ - /** - * @brief Print items relevant to the start of a file - */ - virtual void printHeader(std::ostream&, Csv) const = 0; - - virtual void printHeader(std::ostream&, Json) const - { - } - - virtual void printHeader(std::ostream&, Yaml) const - { - } - - ///@} - - ///@{ - /** - * @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; - - ///@} - - ///@{ - /** - * @brief Print items relevant to the end of a file - */ - virtual void printFooter(std::ostream&, Csv) const - { - } - - virtual void printFooter(std::ostream&, Json) const - { - } - - virtual void printFooter(std::ostream&, Yaml) const - { - } - - ///@} - }; - - } // namespace Model -} // namespace GridKit diff --git a/GridKit/Model/VariableMonitorController.hpp b/GridKit/Model/VariableMonitorController.hpp new file mode 100644 index 000000000..08bc9a779 --- /dev/null +++ b/GridKit/Model/VariableMonitorController.hpp @@ -0,0 +1,444 @@ +#pragma once + +/** + * @file + */ + +#include +#include + +namespace GridKit +{ + namespace Model + { + /** + * @brief Monitor associated with the SystemModel; controls output from + * component and bus monitors. + * + * High-level print functions (without parameters) manage printing for all + * monitors for multiple output sinks. + */ + template + class VariableMonitorController : public VariableMonitorBase + { + public: + /// Underlying real value type + using RealT = typename GridKit::ScalarTraits::RealT; + ///@{ + /// @brief Alias + using Format = VariableMonitorFormat; + using SinkSpec = VariableMonitorBase::SinkSpec; + using Csv = VariableMonitorBase::Csv; + using Json = VariableMonitorBase::Json; + using Yaml = VariableMonitorBase::Yaml; + ///@} + + /// Default to empty monitor + VariableMonitorController() = default; + + /** + * @brief Constructor expects a time variable to monitor + */ + explicit VariableMonitorController(const RealT& time_var) + : time_(&time_var) + { + } + + virtual ~VariableMonitorController() + { + } + + /** + * @brief Add a monitor to the output + * + * Each component and bus could have a monitor for their respective + * values. + * + * @param monitor Monitor to add (raw pointer indicates ownership is + * elsewhere) + */ + void addMonitor(const VariableMonitorBase* monitor) + { + if (monitor && !monitor->empty()) + { + monitors_.push_back(monitor); + } + } + + /** + * @brief Add output sink based on spec + * + * If `spec.file_name` is empty, `std::cout` is used. + * + * @param spec Specifies details for the sink. + */ + void addSink(const SinkSpec& spec) + { + if (spec.file_name.empty()) + { + sinks_.push_back(make_sink(spec, std::cout)); + } + else + { + sinks_.push_back(make_sink(spec, spec.file_name)); + } + } + + /** + * @brief Provide additional top-level variables (alongside time) to be + * printed before submonitors. + * + * @param label Header label for CSV; key for JSON or YAML + * @param value Pointer to monitored variable + */ + void addVariable(const std::string& label, const RealT* value) + { + variables_.emplace_back(label, value); + } + + bool empty() const override + { + return (!time_) || monitors_.empty(); + } + + /** + * @brief Print header if we're monitoring + */ + void start() + { + if (!empty()) + { + startSinks(); + printHeader(); + } + } + + /** + * @brief Print footer if we're monitoring + */ + void stop() + { + if (!empty()) + { + printFooter(); + stopSinks(); + } + } + + /// @copydoc VariableMonitorBase::printHeader + using VariableMonitorBase::printHeader; + + void printHeader(std::ostream& os, Csv csv) const override + { + os << "t"; + for (auto&& var : variables_) + { + os << csv.delim << var.label; + } + } + + void printHeader(std::ostream& os, Json) const override + { + os << "[\n"; + } + + /** + * @brief Organize header output for this and all submonitors + */ + template + void printFullHeader(std::ostream& os, FormatT fmt) const + { + this->printHeader(os, fmt); + for (auto* mon : monitors_) + { + mon->printHeader(os, fmt); + } + os << '\n'; + } + + /** + * @brief Print header for all sinks + */ + void printHeader() const + { + for (auto&& sink : sinks_) + { + std::visit([this](auto&& sink) + { printFullHeader(*sink.os, sink.format); }, + sink); + } + } + + void print(std::ostream& os, Csv csv) const override + { + os << *time_; + for (auto&& var : variables_) + { + os << csv.delim << *var.value; + } + + for (auto* mon : monitors_) + { + mon->print(os, csv); + } + } + + void print(std::ostream& os, Json json) const override + { + std::string indent = " "; + if (json.after_first) + { + os << indent << ",\n"; + } + os << indent << "{\n"; + indent.append(2, ' '); + + os << indent << std::quoted("t") << ": " << *time_ << ",\n"; + for (auto&& var : variables_) + { + os << indent << std::quoted(var.label) << ": " << *var.value << ",\n"; + } + + auto after_first = false; + for (auto* mon : monitors_) + { + if (after_first) + { + os << ",\n"; + } + mon->print(os, Json()); + after_first = true; + } + + indent.erase(indent.size() - 2); + os << '\n' + << indent << "}"; + } + + void print(std::ostream& os, Yaml) const override + { + std::string indent = " "; + os << indent << "- t: " << *time_ << '\n'; + indent.append(2, ' '); + for (auto&& var : variables_) + { + os << indent << var.label << ": " << *var.value << '\n'; + } + + for (auto* mon : monitors_) + { + mon->print(os, Yaml()); + } + } + + /** + * @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); + } + + /** + * @brief Print variables to each sink + */ + void print() const + { + for (auto&& sink : sinks_) + { + std::visit([this](auto&& sink) + { + printFull(*sink.os, sink.format); + using T = std::remove_cvref_t; + if constexpr (std::is_same_v>) + { + sink.format.after_first = true; + } }, + sink); + } + } + + /// @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 + { + for (auto* mon : monitors_) + { + mon->printFooter(os, format); + } + this->printFooter(os, format); + } + + /** + * @brief Print footer for all sinks + */ + void printFooter() const + { + for (auto&& sink : sinks_) + { + std::visit([this](auto&& sink) + { printFullFooter(*sink.os, sink.format); }, + sink); + } + } + + private: + /// Time variable; printed first + const RealT* time_{nullptr}; + + /** + * @brief Define sink for a specific output format + */ + template + struct Sink + { + Sink() = delete; + + /** + * @brief Version for an output stream that already exists + */ + Sink(std::ostream& out, FormatT fmt) + : os(&out), format(fmt) + { + } + + /** + * @brief Version for opening an output stream for the given file + */ + Sink(const std::string& fileName, FormatT fmt) + : file_name(fileName), + format(fmt) + { + } + + /** + * @brief Have to move because of unique_ptr + */ + Sink(Sink&&) = default; + + void start() + { + if (file_name.empty()) + { + return; + } + if (file_stream) + { + return; + } + file_stream = std::make_unique(file_name); + os = file_stream.get(); + } + + void stop() + { + if (file_name.empty()) + { + return; + } + file_stream.reset(); + os = nullptr; + } + + /// Output file name + std::string file_name; + /// Output file stream (if we opened one) + std::unique_ptr file_stream; + /// Output stream for printing + std::ostream* os{nullptr}; + /// Output format object which may have useful members + FormatT format; + }; + + template + Sink(ArgT&&, FormatT) -> Sink; + + /// Variant type for all possible sink types + using SinkVariant = std::variant, Sink, Sink>; + + /** + * @brief Factory function mapping format enum value to sink type + */ + template + static SinkVariant make_sink(SinkSpec spec, T&& arg) + { + switch (spec.format) + { + case Format::CSV: + return Sink(std::forward(arg), Csv{spec.delim}); + break; + case Format::JSON: + return Sink(std::forward(arg), Json{}); + break; + case Format::YAML: + return Sink(std::forward(arg), Yaml{}); + break; + } + throw std::runtime_error("Invalid monitor output format"); + } + + /** + * @brief Get sinks ready for output + */ + void startSinks() + { + for (auto&& sink : sinks_) + { + std::visit([this](auto&& sink) + { sink.start(); }, + sink); + } + } + + /** + * @brief Finalize sinks and close owned files + */ + void stopSinks() + { + for (auto&& sink : sinks_) + { + std::visit([this](auto&& sink) + { sink.stop(); }, + sink); + } + } + + /// Collection of output sinks + std::vector sinks_; + /// Collection of submonitors + std::vector monitors_; + + /** + * @brief Key/Value object for extra top-level variables + */ + struct Variable + { + /// Header label for CSV; key for JSON or YAML + std::string label; + /// Pointer to monitored variable + const RealT* value; + }; + + /// Collection of extra top-level monitored variables + std::vector variables_; + }; + } // namespace Model +} // namespace GridKit diff --git a/GridKit/Model/VariableMonitorImpl.hpp b/GridKit/Model/VariableMonitorImpl.hpp new file mode 100644 index 000000000..fdcca543b --- /dev/null +++ b/GridKit/Model/VariableMonitorImpl.hpp @@ -0,0 +1,245 @@ +#pragma once + +#include + +#include + +namespace GridKit +{ + namespace Model + { + template + class VariableMonitorController; + + using magic_enum::enum_count; + using magic_enum::enum_integer; + using magic_enum::enum_name; + + /** + * @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. + */ + template typename EvalT, + template typename DataT> + class VariableMonitor, DataT> + : public VariableMonitorBase + { + template + friend class VariableMonitorController; + + public: + /// Underlying real value type + using RealT = typename GridKit::ScalarTraits::RealT; + /// Type of (EvalT)Data class expected to have MonitorableVariables enum + using ObjData = DataT; + /// Enum of valid monitorable variables + using VariableEnum = typename ObjData::MonitorableVariables; + ///@{ + /// @brief Alias + using Csv = VariableMonitorBase::Csv; + using Json = VariableMonitorBase::Json; + using Yaml = VariableMonitorBase::Yaml; + ///@} + + VariableMonitor() = default; + + /** + * @brief Construct monitor with component/bus label and set of variables + * selected for monitoring + * + * @param label Monitor label used to disambiguate like variables from + * different objects of same type + * @param variables Variables selected for monitoring + */ + VariableMonitor(const std::string& label, + const std::set& variables) + : label_(label) + { + std::ranges::copy(variables, std::back_inserter(variables_)); + } + + /** + * @brief Construct from ObjData object + * + * Constructs the monitor label from elements of the data object + * + * @param data Expected to be derived from ComponentData + */ + VariableMonitor(const ObjData& data) + : VariableMonitor(data.device_class + "_" + data.disambiguation_string, + data.monitored_variables) + { + } + + virtual ~VariableMonitor() + { + } + + bool empty() const override + { + return variables_.empty(); + } + + /** + * @brief Associate a value getter with a variable enum value + * + * @note This does not designate the variable for printing. It defines how + * to get the variable if it is printed. + */ + template + void set(VariableEnum v, FuncT f) + { + f_[enum_integer(v)] = ValuePrinter{f}; + } + + private: + ///@{ + /** + * @brief Functors to handle printing different types + */ + template + struct ValuePrinterImpl + { + FuncT f; + + void operator()(std::ostream& os) const + { + os << f(); + } + }; + + template + struct ValuePrinterImpl + { + FuncT f; + + void operator()(std::ostream& os) const + { + os << static_cast(f()); + } + }; + + template + using ValuePrinterType = + ValuePrinterImpl>; + + class ValuePrinter + { + public: + ValuePrinter() = default; + + template + ValuePrinter(FuncT f) + : impl_{ValuePrinterType(f)} + { + } + + private: + std::function impl_; + + friend std::ostream& operator<<(std::ostream& os, const ValuePrinter& p) + { + p.impl_(os); + return os; + } + }; + + ///@} + + 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_[enum_integer(v)]; + } + } + + /** + * @brief Print single variable + */ + void print(std::ostream& os, VariableEnum v, Json) const + { + os << indent_ << std::quoted(enum_name(v)) << ": " + << f_[enum_integer(v)] << ",\n"; + } + + void print(std::ostream& os, Json) const override + { + if (empty()) + { + return; + } + 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()); + } + 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 + { + } + + /** + * @brief Print single variable + */ + void print(std::ostream& os, VariableEnum v, Yaml) const + { + os << indent_ << enum_name(v) << ": " << f_[enum_integer(v)] + << '\n'; + } + + void print(std::ostream& os, Yaml) const override + { + if (empty()) + { + return; + } + os << indent_ << label_ << ":\n"; + indent_.append(2, ' '); + for (auto v : variables_) + { + print(os, v, Yaml()); + } + indent_.erase(indent_.size() - 2); + } + + private: + /// Compile-time constant size: length of enum value list + static constexpr auto enum_size_ = enum_count(); + + /// Set of functions associated with each enum value + std::array f_; + /// Set of selected enum values + std::vector variables_; + /// Indent string used for formatting + mutable std::string indent_{" "}; + /// Monitor disambiguation label + std::string label_; + }; + } // namespace Model +} // namespace GridKit diff --git a/GridKit/Utilities/CMakeLists.txt b/GridKit/Utilities/CMakeLists.txt index f17e2acce..e88012421 100644 --- a/GridKit/Utilities/CMakeLists.txt +++ b/GridKit/Utilities/CMakeLists.txt @@ -11,7 +11,6 @@ add_subdirectory(CliOptions) install( FILES Colors.hpp - Enum.hpp FileIO.hpp MapFromCOO.hpp DESTINATION diff --git a/GridKit/Utilities/Enum.hpp b/GridKit/Utilities/Enum.hpp deleted file mode 100644 index ec266c464..000000000 --- a/GridKit/Utilities/Enum.hpp +++ /dev/null @@ -1,33 +0,0 @@ -#pragma once - -/** - * @file - */ - -namespace GridKit -{ - namespace Utilities - { - /** - * @brief Requires `TEnum` to be an `enum` and have a `SIZE` member - */ - template - concept SizedEnum = requires { - std::is_enum_v; - TEnum::SIZE; - }; - - /** - * @brief Convert an enum value to its underlying integer type - */ - template - inline constexpr std::underlying_type_t enumId(TEnum v) noexcept - { - return static_cast>(v); - } - - /// Length of an enum - template - inline constexpr auto enumSize = enumId(TEnum::SIZE); - } // namespace Utilities -} // namespace GridKit