diff --git a/CHANGELOG.md b/CHANGELOG.md index 161b37c3..04aa4071 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/CONTRIBUTING.md b/CONTRIBUTING.md index 36ba1ea9..72aa3a7a 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. diff --git a/GridKit/Model/CMakeLists.txt b/GridKit/Model/CMakeLists.txt index df92faf5..74a0910a 100644 --- a/GridKit/Model/CMakeLists.txt +++ b/GridKit/Model/CMakeLists.txt @@ -9,4 +9,8 @@ add_subdirectory(PhasorDynamics) add_subdirectory(PowerFlow) add_subdirectory(PowerElectronics) -install(FILES Evaluator.hpp DESTINATION include/GridKit/Model) +install(FILES + Evaluator.hpp + VariableMonitor.hpp + VariableMonitorController.hpp + DESTINATION include/GridKit/Model) diff --git a/GridKit/Model/Evaluator.hpp b/GridKit/Model/Evaluator.hpp index 12b760c6..9bc9f010 100644 --- a/GridKit/Model/Evaluator.hpp +++ b/GridKit/Model/Evaluator.hpp @@ -5,6 +5,7 @@ #include #include #include +#include #include namespace GridKit @@ -45,6 +46,43 @@ 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 Get monitor ready for output + */ + virtual void startMonitor() + { + } + + /** + * @brief Tell monitor to wrap up + */ + virtual void stopMonitor() + { + } + /** * @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 a20684ca..c917e431 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,12 @@ namespace GridKit using Component::h_; using Component::J_; + public: using RealT = typename Component::RealT; using bus_type = BusBase; using model_data_type = BranchData; + using MonitorT = Model::VariableMonitor; - 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 +80,6 @@ namespace GridKit { } - public: void setR(RealT R) { R_ = R; @@ -103,7 +105,11 @@ namespace GridKit setDerivedParams(); } + const Model::VariableMonitorBase* getMonitor() const override; + private: + void initializeParameters(const model_data_type& data); + void initializeMonitor(); void setDerivedParams(); ScalarT& Vr1() @@ -162,9 +168,12 @@ namespace GridKit IdxT bus1_id_{0}; IdxT bus2_id_{0}; - /* Derivied parameters */ + /* Derived parameters */ RealT b_; RealT g_; + + /// Variable monitor + std::unique_ptr monitor_; }; } // namespace PhasorDynamics diff --git a/GridKit/Model/PhasorDynamics/Branch/BranchData.hpp b/GridKit/Model/PhasorDynamics/Branch/BranchData.hpp index 5f436f69..98528c3d 100644 --- a/GridKit/Model/PhasorDynamics/Branch/BranchData.hpp +++ b/GridKit/Model/PhasorDynamics/Branch/BranchData.hpp @@ -40,7 +40,7 @@ namespace GridKit ii2, im2, p2, - q2, + q2 }; /** diff --git a/GridKit/Model/PhasorDynamics/Branch/BranchImpl.hpp b/GridKit/Model/PhasorDynamics/Branch/BranchImpl.hpp index 61379d45..4b6016e3 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 { @@ -74,37 +75,11 @@ namespace GridKit template Branch::Branch(bus_type* bus1, bus_type* bus2, const model_data_type& data) : bus1_(bus1), - bus2_(bus2) + bus2_(bus2), + monitor_(std::make_unique(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 +252,66 @@ 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 + 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 ?(); }); + } + /** * @brief Derived parameters * diff --git a/GridKit/Model/PhasorDynamics/Branch/CMakeLists.txt b/GridKit/Model/PhasorDynamics/Branch/CMakeLists.txt index fa2ef245..10761d6f 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 3a1ab6f8..34091c1b 100644 --- a/GridKit/Model/PhasorDynamics/Bus/BusData.hpp +++ b/GridKit/Model/PhasorDynamics/Bus/BusData.hpp @@ -6,8 +6,8 @@ */ #pragma once -#include #include +#include #include #include @@ -15,6 +15,15 @@ namespace GridKit { namespace PhasorDynamics { + /// Indices of the variables able to be monitored on this component + enum class BusMonitorableVariables : size_t + { + Vr, + Vi, + Vm, + Va + }; + /** * @brief Contains modeling data for a Bus * @@ -49,20 +58,12 @@ namespace GridKit 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, - }; + /// Alias + 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 e4cb6672..491ca88c 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 4957e77f..37b70c01 100644 --- a/GridKit/Model/PhasorDynamics/Bus/BusImpl.hpp +++ b/GridKit/Model/PhasorDynamics/Bus/BusImpl.hpp @@ -4,6 +4,7 @@ #include #include +#include namespace GridKit { @@ -55,7 +56,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 d8f3e9d4..56d05d82 100644 --- a/GridKit/Model/PhasorDynamics/Bus/BusInfiniteImpl.hpp +++ b/GridKit/Model/PhasorDynamics/Bus/BusInfiniteImpl.hpp @@ -4,6 +4,7 @@ #include #include +#include namespace GridKit { @@ -54,7 +55,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/Bus/CMakeLists.txt b/GridKit/Model/PhasorDynamics/Bus/CMakeLists.txt index 010eb902..b5086ed7 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 deb63fe5..603f9699 100644 --- a/GridKit/Model/PhasorDynamics/BusBase.hpp +++ b/GridKit/Model/PhasorDynamics/BusBase.hpp @@ -1,10 +1,13 @@ #pragma once +#include +#include #include #include #include #include +#include namespace GridKit { @@ -14,27 +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() - : size_(0) - { - } + BusBase() = default; - BusBase(IdxT bus_id) - : bus_id_(bus_id) - { - } + explicit BusBase(const BusData& data); - virtual ~BusBase() - { - } + virtual ~BusBase(); /// Pure virtual function, returns bus type (DEFAULT or SLACK). virtual BusTypeT BusType() const @@ -161,6 +157,8 @@ namespace GridKit return residual_indices_; } + const Model::VariableMonitorBase* getMonitor() const override; + protected: IdxT bus_id_{static_cast(-1)}; @@ -171,6 +169,9 @@ namespace GridKit std::map residual_indices_; ///< Map between local and global (system-level) /// residual indices + /// Variable monitor + std::unique_ptr monitor_; + std::vector y_; std::vector yp_; std::vector tag_; diff --git a/GridKit/Model/PhasorDynamics/BusBaseImpl.hpp b/GridKit/Model/PhasorDynamics/BusBaseImpl.hpp new file mode 100644 index 00000000..741d9c55 --- /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 934efb88..77c85e42 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 @@ -33,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; @@ -77,6 +79,8 @@ namespace GridKit status_ = status; } + const Model::VariableMonitorBase* getMonitor() const override; + private: void setDerivedParams(); @@ -113,6 +117,9 @@ namespace GridKit /* Derivied parameters */ RealT B_; RealT G_; + + /// Variable monitor + std::unique_ptr monitor_; }; } // namespace PhasorDynamics diff --git a/GridKit/Model/PhasorDynamics/BusFault/BusFaultData.hpp b/GridKit/Model/PhasorDynamics/BusFault/BusFaultData.hpp index 51870665..4ace959f 100644 --- a/GridKit/Model/PhasorDynamics/BusFault/BusFaultData.hpp +++ b/GridKit/Model/PhasorDynamics/BusFault/BusFaultData.hpp @@ -32,7 +32,7 @@ namespace GridKit { state, ir, - ii, + ii }; /** diff --git a/GridKit/Model/PhasorDynamics/BusFault/BusFaultImpl.hpp b/GridKit/Model/PhasorDynamics/BusFault/BusFaultImpl.hpp index bed953f2..97761a53 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)) { @@ -79,10 +81,23 @@ 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(); } + template + BusFault::~BusFault() + { + } + /** * @brief Set the component ID */ @@ -163,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 c73dfea3..40f438fb 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 b9976b85..b043b66a 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 ff20ca4c..fffb5674 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,13 @@ 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; + using MonitorT = Model::VariableMonitor; - public: Ieeet1(bus_type* bus); Ieeet1(signal_type* efd_signal, signal_type* speed_signal, @@ -88,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; @@ -112,6 +114,8 @@ namespace GridKit return signals_; } + const Model::VariableMonitorBase* getMonitor() const override; + private: // Signal pointers signal_type* efd_signal_; @@ -150,8 +154,14 @@ namespace GridKit /// Component signal extension ComponentSignals signals_; + /// Variable monitor + std::unique_ptr monitor_; + // Parameter initialization function void initModelParams(const model_data_type& data); + + /// Associate variable getter functions with enum values + void initializeMonitor(); }; } // namespace Exciter diff --git a/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1Data.hpp b/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1Data.hpp index 093d6c87..0d4553f8 100644 --- a/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1Data.hpp +++ b/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1Data.hpp @@ -46,7 +46,7 @@ namespace GridKit enum class Ieeet1MonitorableVariables { efd, - ksat, + ksat }; /** diff --git a/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1Impl.hpp b/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1Impl.hpp index 17640d4a..6e935124 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 @@ -56,12 +57,15 @@ namespace GridKit const model_data_type& data) : efd_signal_(efd_signal), speed_signal_(speed_signal), - bus_(bus) + bus_(bus), + monitor_(std::make_unique(data)) { // Parse data struct into model this->initModelParams(data); + initializeMonitor(); + // 9 Internal Variables size_ = 9; } @@ -77,16 +81,24 @@ namespace GridKit template Ieeet1::Ieeet1(bus_type* bus, const model_data_type& data) - : bus_(bus) + : bus_(bus), + monitor_(std::make_unique(data)) { // Parse data struct into model this->initModelParams(data); + initializeMonitor(); + // 9 Internal Variables size_ = 9; } + template + Ieeet1::~Ieeet1() + { + } + /** * @brief Set the component ID */ @@ -390,6 +402,20 @@ 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 ?; }); + } } // namespace Exciter } // namespace PhasorDynamics } // namespace GridKit diff --git a/GridKit/Model/PhasorDynamics/INPUT_FORMAT.md b/GridKit/Model/PhasorDynamics/INPUT_FORMAT.md index ca6d9cdd..f54e9b51 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/Load/CMakeLists.txt b/GridKit/Model/PhasorDynamics/Load/CMakeLists.txt index 77a2632a..ef86891b 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 49ba3ebe..091853b5 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 @@ -38,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); @@ -99,6 +102,8 @@ namespace GridKit return bus_->Ii(); } + const Model::VariableMonitorBase* getMonitor() const override; + public: __attribute__((always_inline)) inline int evaluateBusResidual(ScalarT*, ScalarT*, ScalarT*, ScalarT*); @@ -110,6 +115,8 @@ namespace GridKit /* Derivied parameters */ RealT b_; RealT g_; + + std::unique_ptr monitor_; }; } // namespace PhasorDynamics diff --git a/GridKit/Model/PhasorDynamics/Load/LoadData.hpp b/GridKit/Model/PhasorDynamics/Load/LoadData.hpp index 2b07512c..ccddccfa 100644 --- a/GridKit/Model/PhasorDynamics/Load/LoadData.hpp +++ b/GridKit/Model/PhasorDynamics/Load/LoadData.hpp @@ -28,8 +28,8 @@ 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 }; /** diff --git a/GridKit/Model/PhasorDynamics/Load/LoadImpl.hpp b/GridKit/Model/PhasorDynamics/Load/LoadImpl.hpp index 8403f1dd..144d49eb 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 { @@ -52,6 +53,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(); } @@ -150,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 18b23ed0..eda3242f 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 09461f5d..8c9aaa61 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,13 @@ 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; + using MonitorT = Model::VariableMonitor; - public: Genrou(bus_type* bus, IdxT unit_id); Genrou(bus_type* bus, signal_type* omega, @@ -118,7 +121,7 @@ namespace GridKit RealT Xl, RealT S10, RealT S12); - ~Genrou() = default; + ~Genrou(); int setGridKitComponentID(IdxT) override; int allocate() override; @@ -149,8 +152,12 @@ namespace GridKit return signals_; } + const Model::VariableMonitorBase* getMonitor() const override; + private: void initializeParameters(const model_data_type& data); + /// Associate variable getter functions with enum values + void initializeMonitor(); void setDerivedParams(); ScalarT& Vr() @@ -233,6 +240,9 @@ namespace GridKit /* Local copies of external variables */ std::vector ws_; std::map ws_indices_; + + /// Variable 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 9133914b..a631d167 100644 --- a/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROUwS/GenrouData.hpp +++ b/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROUwS/GenrouData.hpp @@ -53,7 +53,7 @@ namespace GridKit p, q, delta, - omega, + omega }; /** diff --git a/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROUwS/GenrouImpl.hpp b/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROUwS/GenrouImpl.hpp index 67e874df..7f3ae05f 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 @@ -112,9 +113,11 @@ namespace GridKit template Genrou::Genrou(bus_type* bus, const model_data_type& data) : bus_(bus), - unit_id_(1) + unit_id_(1), + monitor_(std::make_unique(data)) { initializeParameters(data); + initializeMonitor(); size_ = 19; setDerivedParams(); @@ -126,11 +129,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_(std::make_unique(data)) { signals_.template attachSignalNode(pmech); signals_.template assignSignalNode(omega); initializeParameters(data); + initializeMonitor(); size_ = 19; setDerivedParams(); @@ -142,17 +147,24 @@ 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_(std::make_unique(data)) { signals_.template attachSignalNode(pmech); signals_.template assignSignalNode(omega); signals_.template attachSignalNode(efd); initializeParameters(data); + initializeMonitor(); size_ = 19; setDerivedParams(); } + template + Genrou::~Genrou() + { + } + /// Helper function to extract and assign model parameters from the model's associated /// data structure. template @@ -259,6 +271,28 @@ 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]; }); + } + /** * @brief Set the component ID */ diff --git a/GridKit/Model/PhasorDynamics/SynchronousMachine/GenClassical/CMakeLists.txt b/GridKit/Model/PhasorDynamics/SynchronousMachine/GenClassical/CMakeLists.txt index 40098963..ef1c782b 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 c2eec409..7a6d69df 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 @@ -45,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, @@ -60,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; @@ -90,7 +93,10 @@ namespace GridKit ep_set_ = ep; } + const Model::VariableMonitorBase* getMonitor() const override; + private: + void initializeMonitor(); void setDerivedParams(); ScalarT& Vr() @@ -141,6 +147,9 @@ namespace GridKit /* Setpoints for control variables (determined at initialization) */ ScalarT pmech_set_; ScalarT ep_set_; + + /// Variable 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 308bafb6..c6c41b44 100644 --- a/GridKit/Model/PhasorDynamics/SynchronousMachine/GenClassical/GenClassicalData.hpp +++ b/GridKit/Model/PhasorDynamics/SynchronousMachine/GenClassical/GenClassicalData.hpp @@ -40,7 +40,7 @@ namespace GridKit p, q, delta, - omega, + omega }; /** diff --git a/GridKit/Model/PhasorDynamics/SynchronousMachine/GenClassical/GenClassicalImpl.hpp b/GridKit/Model/PhasorDynamics/SynchronousMachine/GenClassical/GenClassicalImpl.hpp index 29debe17..58ba7039 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)) { @@ -113,10 +115,39 @@ namespace GridKit bus_id_ = data.ports.at(DataT::Ports::bus); } + initializeMonitor(); + size_ = 5; 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]; }); + } + /** * @brief Set the component ID */ diff --git a/GridKit/Model/PhasorDynamics/SystemModel.hpp b/GridKit/Model/PhasorDynamics/SystemModel.hpp index d3c5dc23..2d787a02 100644 --- a/GridKit/Model/PhasorDynamics/SystemModel.hpp +++ b/GridKit/Model/PhasorDynamics/SystemModel.hpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -75,6 +76,7 @@ namespace GridKit * correctly connected into the system model. */ SystemModel(SystemModelData& data) + : monitor_(time_) { using namespace Governor; using namespace Exciter; @@ -236,6 +238,11 @@ namespace GridKit auto* fault = new BusFault(getBus(bus_index), faultdata); addFault(fault); } + + for (const auto& sink : data.monitor_sink) + { + monitor_.addSink(sink); + } } /** @@ -417,9 +424,46 @@ namespace GridKit } } + initializeMonitor(); + startMonitor(); + return 0; } + /** + * @brief Add monitors from buses and components and start monitor + */ + void initializeMonitor() + { + for (const auto* bus : buses_) + { + auto* mon = bus->getMonitor(); + if (mon && !mon->empty()) + { + monitor_.addMonitor(mon); + } + } + + for (const auto* component : components_) + { + auto* mon = component->getMonitor(); + if (mon && !mon->empty()) + { + monitor_.addMonitor(mon); + } + } + } + + void startMonitor() override + { + monitor_.start(); + } + + void stopMonitor() override + { + monitor_.stop(); + } + /** * @todo Tagging differential variables * @@ -576,16 +620,52 @@ namespace GridKit return 0; } + bool monitoring() const override + { + return !monitor_.empty(); + } + + void printMonitoredVariables() const override + { + 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 * */ void updateTime(RealT t, RealT a) override { + this->time_ = t; for (const auto& component : components_) { component->updateTime(t, a); } + + updateVariables(); } /** @@ -703,6 +783,8 @@ namespace GridKit bool owns_components_{false}; + /// Variable monitor + Model::VariableMonitorController monitor_; }; // class SystemModel } // namespace PhasorDynamics diff --git a/GridKit/Model/PhasorDynamics/SystemModelData.hpp b/GridKit/Model/PhasorDynamics/SystemModelData.hpp index da32ebf6..bf4752ca 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,9 @@ namespace GridKit std::vector gov; ///< Governors within the model std::vector exciter; ///< Exciters within the model std::vector signal; ///< Signal nodes + + /// Monitor sink specs + std::vector monitor_sink; }; ///@{ diff --git a/GridKit/Model/PhasorDynamics/SystemModelDataJSONParser.hpp b/GridKit/Model/PhasorDynamics/SystemModelDataJSONParser.hpp index ebf796b5..12241c03 100644 --- a/GridKit/Model/PhasorDynamics/SystemModelDataJSONParser.hpp +++ b/GridKit/Model/PhasorDynamics/SystemModelDataJSONParser.hpp @@ -9,12 +9,15 @@ #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 +25,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 +54,36 @@ namespace GridKit header.at("freq_base").get_to(sm.freq_base); header.at("va_base").get_to(sm.va_base); + 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()) + { + 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 + { + 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 new file mode 100644 index 00000000..ce7df4f4 --- /dev/null +++ b/GridKit/Model/VariableMonitor.hpp @@ -0,0 +1,143 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace GridKit +{ + namespace Model + { + template + class VariableMonitorController; + + /** + * @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 + friend class VariableMonitorController; + + 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 + { + } + + ///@} + }; + + template typename DataT> + class VariableMonitor; + + } // namespace Model +} // namespace GridKit diff --git a/GridKit/Model/VariableMonitorController.hpp b/GridKit/Model/VariableMonitorController.hpp new file mode 100644 index 00000000..08bc9a77 --- /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 00000000..fdcca543 --- /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/Solver/Dynamic/Ida.cpp b/GridKit/Solver/Dynamic/Ida.cpp index 69ec9e20..baa303a5 100644 --- a/GridKit/Solver/Dynamic/Ida.cpp +++ b/GridKit/Solver/Dynamic/Ida.cpp @@ -322,16 +322,23 @@ 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 // internal integrator step out of date) - model_->updateTime(tret, 0.0); copyVec(yy_, model_->y()); copyVec(yp_, model_->yp()); + model_->updateTime(tret, 0.0); - (*step_callback)(tret); + if (model_->monitoring()) + { + model_->printMonitoredVariables(); + } + if (step_callback.has_value()) + { + (*step_callback)(tret); + } } if (retval == IDA_SUCCESS) @@ -342,9 +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(); + } // std::cout << "\n"; return retval; diff --git a/examples/PhasorDynamics/Tiny/ThreeBus/Basic/ThreeBusBasic.json b/examples/PhasorDynamics/Tiny/ThreeBus/Basic/ThreeBusBasic.json index dbddf43f..bad6c710 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, diff --git a/examples/PhasorDynamics/Tiny/ThreeBus/Basic/ThreeBusBasicJson.cpp b/examples/PhasorDynamics/Tiny/ThreeBus/Basic/ThreeBusBasicJson.cpp index 1b99fda0..e01906ff 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; diff --git a/tests/UnitTests/Utilities/CaseFormatTests.hpp b/tests/UnitTests/Utilities/CaseFormatTests.hpp index 1fb08b37..950af395 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; @@ -80,15 +92,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 +205,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";