diff --git a/GridKit/Model/CMakeLists.txt b/GridKit/Model/CMakeLists.txt index af94b489f..a28d08def 100644 --- a/GridKit/Model/CMakeLists.txt +++ b/GridKit/Model/CMakeLists.txt @@ -9,5 +9,10 @@ add_subdirectory(PhasorDynamics) add_subdirectory(PowerFlow) add_subdirectory(PowerElectronics) -install(FILES Evaluator.hpp VariableMonitor.hpp VariableMonitorController.hpp - DESTINATION include/GridKit/Model) +install( + FILES ConfigurationChecks.hpp + Evaluator.hpp + ParameterReader.hpp + VariableMonitor.hpp + VariableMonitorController.hpp + DESTINATION include/GridKit/Model) diff --git a/GridKit/Model/ConfigurationChecks.hpp b/GridKit/Model/ConfigurationChecks.hpp new file mode 100644 index 000000000..aded652e3 --- /dev/null +++ b/GridKit/Model/ConfigurationChecks.hpp @@ -0,0 +1,58 @@ +/** + * @file ConfigurationChecks.hpp + * @author Luke Lowery (lukel@tamu.edu) + * @brief Collects the configuration problems found by a model. + */ + +#pragma once + +#include +#include +#include + +namespace GridKit +{ + namespace Model + { + /** + * @brief Collects the configuration problems found by a model. + * + * A model's verify() fills one instance with a message for every + * condition that does not hold and returns it. The caller decides how + * the messages are reported; passed() is true when there are none. + */ + class ConfigurationChecks + { + public: + /// Record a problem. + void fail(std::string message) + { + errors_.push_back(std::move(message)); + } + + /// Record the message when the condition does not hold. + void check(bool condition, std::string message) + { + if (!condition) + { + fail(std::move(message)); + } + } + + /// True when no check has failed. + bool passed() const + { + return errors_.empty(); + } + + /// Every recorded message, in the order the checks ran. + const std::vector& errors() const + { + return errors_; + } + + private: + std::vector errors_; + }; + } // namespace Model +} // namespace GridKit diff --git a/GridKit/Model/ParameterReader.hpp b/GridKit/Model/ParameterReader.hpp new file mode 100644 index 000000000..30c8dedaf --- /dev/null +++ b/GridKit/Model/ParameterReader.hpp @@ -0,0 +1,199 @@ +/** + * @file ParameterReader.hpp + * @author Luke Lowery (lukel@tamu.edu) + * @brief Typed parameter loading from model data containers. + */ + +#pragma once + +#include +#include +#include +#include +#include + +#include + +#include + +namespace GridKit +{ + namespace Model + { + /** + * @brief Reads typed parameters out of a model data container. + * + * A provided value with the wrong type or a non-finite value cannot + * produce a valid model, so every method throws std::invalid_argument + * at the first such value; the message names the model and the + * parameter. An omitted optional parameter leaves the target untouched. + * Real parameters accept integer values. + * + * @tparam ModelDataT A model data container satisfying + * PhasorDynamics::ModelData. + */ + template + class ParameterReader + { + public: + using RealT = typename ModelDataT::RealT; + using IdxT = typename ModelDataT::IdxT; + using ParametersT = typename ModelDataT::Parameters; + + /** + * @param[in] data Model data container to read from. + * @param[in] model Model name used in rejection messages. + */ + ParameterReader(const ModelDataT& data, const char* model) + : parameters_(data.parameters), + model_(model) + { + } + + /** + * @brief Load an optional real parameter. Integer values are accepted. + * + * @param[in] key Parameter to look up. + * @param[out] target Stores the finite numeric value when provided. + * @return true when the parameter was provided. + */ + bool loadReal(ParametersT key, RealT& target) const + { + if (!parameters_.contains(key)) + { + return false; + } + + const auto& value = parameters_.at(key); + RealT parsed_value{}; + if (const auto* real_value = std::get_if(&value)) + { + parsed_value = *real_value; + } + else if (const auto* index_value = std::get_if(&value)) + { + parsed_value = static_cast(*index_value); + } + else + { + reject(key, "must be numeric"); + } + + if (!std::isfinite(parsed_value)) + { + reject(key, "must be finite"); + } + + target = parsed_value; + return true; + } + + /** + * @brief Load a real parameter that must be provided. + * + * @param[in] key Parameter to look up. + * @param[out] target Stores the finite numeric value. + * @return true, since a missing parameter is rejected. + */ + bool requireReal(ParametersT key, RealT& target) const + { + if (!parameters_.contains(key)) + { + reject(key, "is required"); + } + return loadReal(key, target); + } + + /** + * @brief Load an optional boolean switch parameter. + * + * @param[in] key Parameter to look up. + * @param[out] target Stores the boolean value when provided. + * @return true when the parameter was provided. + */ + bool loadSwitch(ParametersT key, bool& target) const + { + if (!parameters_.contains(key)) + { + return false; + } + + const auto& value = parameters_.at(key); + if (const auto* bool_value = std::get_if(&value)) + { + target = *bool_value; + return true; + } + + reject(key, "must be boolean"); + } + + /** + * @brief Load a switch parameter that must be provided. A boolean or an + * integer 0/1 value is accepted. + * + * @param[in] key Parameter to look up. + * @param[out] target Stores the switch value. + * @return true, since a missing parameter is rejected. + */ + bool requireSwitch(ParametersT key, bool& target) const + { + if (!parameters_.contains(key)) + { + reject(key, "is required"); + } + + const auto& value = parameters_.at(key); + if (const auto* bool_value = std::get_if(&value)) + { + target = *bool_value; + return true; + } + if (const auto* index_value = std::get_if(&value); + index_value && (*index_value == 0 || *index_value == 1)) + { + target = (*index_value == 1); + return true; + } + + reject(key, "must be bool or 0/1"); + } + + /** + * @brief Load an optional integer selector parameter. + * + * @param[in] key Parameter to look up. + * @param[out] target Stores the integer value when provided. + * @return true when the parameter was provided. + */ + bool loadSelector(ParametersT key, IdxT& target) const + { + if (!parameters_.contains(key)) + { + return false; + } + + const auto& value = parameters_.at(key); + if (const auto* index_value = std::get_if(&value)) + { + target = *index_value; + return true; + } + + reject(key, "must be an integer selector"); + } + + private: + /// Reject the model data with a message naming the model and parameter. + [[noreturn]] void reject(ParametersT key, const char* reason) const + { + throw std::invalid_argument(std::string(model_) + ": parameter '" + + std::string(magic_enum::enum_name(key)) + + "' " + reason); + } + + const std::map>& parameters_; + const char* model_; + }; + } // namespace Model +} // namespace GridKit diff --git a/GridKit/Model/PhasorDynamics/Branch/Branch.hpp b/GridKit/Model/PhasorDynamics/Branch/Branch.hpp index b3f3fff8e..89bc1f7fe 100644 --- a/GridKit/Model/PhasorDynamics/Branch/Branch.hpp +++ b/GridKit/Model/PhasorDynamics/Branch/Branch.hpp @@ -65,14 +65,14 @@ namespace GridKit Branch(BusT* bus1, BusT* bus2, const ModelDataT& data); virtual ~Branch(); - virtual int setGridKitComponentID(IdxT) override final; - virtual int allocate() override final; - virtual int initialize() override final; - virtual int tagDifferentiable() override final; - virtual int setAbsoluteTolerance(RealT rel_tol) override final; - virtual int evaluateResidual() override final; - virtual int evaluateJacobian() override final; - virtual int verify() const override final; + virtual int setGridKitComponentID(IdxT) override final; + virtual int allocate() override final; + virtual int initialize() override final; + virtual int tagDifferentiable() override final; + virtual int setAbsoluteTolerance(RealT rel_tol) override final; + virtual int evaluateResidual() override final; + virtual int evaluateJacobian() override final; + virtual Model::ConfigurationChecks verify() const override final; void setR(RealT R) { @@ -118,9 +118,6 @@ namespace GridKit void setDerivedParams(); void terminalCurrent1(ScalarT& Ir, ScalarT& Ii); void terminalCurrent2(ScalarT& Ir, ScalarT& Ii); - bool readRealParameter(const ModelDataT& data, - typename ModelDataT::Parameters parameter, - RealT& target); static __attribute__((always_inline)) inline void addAdmittanceContribution(const RealT G, const RealT B, @@ -207,8 +204,6 @@ namespace GridKit RealT g22_{0.0}; RealT b22_{0.0}; - int parameter_error_count_{0}; - /// Variable monitor std::unique_ptr monitor_; }; diff --git a/GridKit/Model/PhasorDynamics/Branch/BranchImpl.hpp b/GridKit/Model/PhasorDynamics/Branch/BranchImpl.hpp index f7b1391f1..aeb97bb0f 100644 --- a/GridKit/Model/PhasorDynamics/Branch/BranchImpl.hpp +++ b/GridKit/Model/PhasorDynamics/Branch/BranchImpl.hpp @@ -7,10 +7,8 @@ * */ -#include - -#include - +#include +#include #include #include #include @@ -154,34 +152,25 @@ namespace GridKit } template - int Branch::verify() const + Model::ConfigurationChecks Branch::verify() const { - int ret = parameter_error_count_; - - auto check = [&](bool condition, const char* message) - { - if (!condition) - { - Log::error() << "Branch: " << message << '\n'; - ret += 1; - } - }; - - check(bus1_ != nullptr, "bus1 pointer is null"); - check(bus2_ != nullptr, "bus2 pointer is null"); - - check(std::isfinite(R_), "R must be finite"); - check(std::isfinite(X_), "X must be finite"); - check(std::isfinite(G_), "G must be finite"); - check(std::isfinite(B_), "B must be finite"); - check(std::isfinite(Gmag_), "Gmag must be finite"); - check(std::isfinite(Bmag_), "Bmag must be finite"); - check(std::isfinite(tap_), "tap must be finite"); - check(std::isfinite(phase_), "phase must be finite"); - check(R_ * R_ + X_ * X_ > RealT{0.0}, "R and X cannot both be zero"); - check(tap_ > RealT{0.0}, "tap must be positive"); - - return ret; + Model::ConfigurationChecks checks; + + checks.check(bus1_ != nullptr, "bus1 pointer is null"); + checks.check(bus2_ != nullptr, "bus2 pointer is null"); + + checks.check(std::isfinite(R_), "R must be finite"); + checks.check(std::isfinite(X_), "X must be finite"); + checks.check(std::isfinite(G_), "G must be finite"); + checks.check(std::isfinite(B_), "B must be finite"); + checks.check(std::isfinite(Gmag_), "Gmag must be finite"); + checks.check(std::isfinite(Bmag_), "Bmag must be finite"); + checks.check(std::isfinite(tap_), "tap must be finite"); + checks.check(std::isfinite(phase_), "phase must be finite"); + checks.check(R_ * R_ + X_ * X_ > RealT{0.0}, "R and X cannot both be zero"); + checks.check(tap_ > RealT{0.0}, "tap must be positive"); + + return checks; } template @@ -351,14 +340,16 @@ namespace GridKit using Parameter = typename ModelDataT::Parameters; using Buses = typename ModelDataT::Buses; - readRealParameter(data, Parameter::R, R_); - readRealParameter(data, Parameter::X, X_); - readRealParameter(data, Parameter::G, G_); - readRealParameter(data, Parameter::B, B_); - readRealParameter(data, Parameter::Gmag, Gmag_); - readRealParameter(data, Parameter::Bmag, Bmag_); - readRealParameter(data, Parameter::tap, tap_); - readRealParameter(data, Parameter::phase, phase_); + Model::ParameterReader reader(data, "Branch"); + + reader.loadReal(Parameter::R, R_); + reader.loadReal(Parameter::X, X_); + reader.loadReal(Parameter::G, G_); + reader.loadReal(Parameter::B, B_); + reader.loadReal(Parameter::Gmag, Gmag_); + reader.loadReal(Parameter::Bmag, Bmag_); + reader.loadReal(Parameter::tap, tap_); + reader.loadReal(Parameter::phase, phase_); if (data.buses.contains(Buses::bus1)) { @@ -371,35 +362,6 @@ namespace GridKit } } - template - bool Branch::readRealParameter(const ModelDataT& data, - typename ModelDataT::Parameters parameter, - RealT& target) - { - if (!data.parameters.contains(parameter)) - { - return false; - } - - const auto& value = data.parameters.at(parameter); - if (const auto* real_value = std::get_if(&value)) - { - target = *real_value; - return true; - } - - if (const auto* integer_value = std::get_if(&value)) - { - target = static_cast(*integer_value); - return true; - } - - Log::error() << "Branch: parameter " << magic_enum::enum_name(parameter) - << " must be numeric\n"; - parameter_error_count_ += 1; - return false; - } - template const Model::VariableMonitorBase* Branch::getMonitor() const { diff --git a/GridKit/Model/PhasorDynamics/BusBase.hpp b/GridKit/Model/PhasorDynamics/BusBase.hpp index 7fed07b6c..ca38950c9 100644 --- a/GridKit/Model/PhasorDynamics/BusBase.hpp +++ b/GridKit/Model/PhasorDynamics/BusBase.hpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -40,9 +41,10 @@ namespace GridKit virtual ~BusBase(); - virtual int verify() const + /// Report every configuration problem; passed() when the bus can initialize. + virtual Model::ConfigurationChecks verify() const { - return 0; + return {}; } IdxT size() override final diff --git a/GridKit/Model/PhasorDynamics/BusFault/BusFault.hpp b/GridKit/Model/PhasorDynamics/BusFault/BusFault.hpp index 405f78e6c..0348da421 100644 --- a/GridKit/Model/PhasorDynamics/BusFault/BusFault.hpp +++ b/GridKit/Model/PhasorDynamics/BusFault/BusFault.hpp @@ -53,9 +53,9 @@ namespace GridKit int evaluateResidual() override final; int evaluateJacobian() override final; - int verify() const override final + Model::ConfigurationChecks verify() const override final { - return 0; + return {}; } void updateTime(RealT /* t */, RealT /* a */) override final diff --git a/GridKit/Model/PhasorDynamics/BusFault/BusFaultImpl.hpp b/GridKit/Model/PhasorDynamics/BusFault/BusFaultImpl.hpp index 89d121246..c6b92b2a8 100644 --- a/GridKit/Model/PhasorDynamics/BusFault/BusFaultImpl.hpp +++ b/GridKit/Model/PhasorDynamics/BusFault/BusFaultImpl.hpp @@ -2,6 +2,8 @@ #include +#include +#include #include #include #include @@ -59,20 +61,11 @@ namespace GridKit using Parameter = typename ModelDataT::Parameters; using Buses = typename ModelDataT::Buses; - if (data.parameters.contains(Parameter::R)) - { - R_ = std::get(data.parameters.at(Parameter::R)); - } + Model::ParameterReader reader(data, "BusFault"); - if (data.parameters.contains(Parameter::X)) - { - X_ = std::get(data.parameters.at(Parameter::X)); - } - - if (data.parameters.contains(Parameter::state0)) - { - status_ = std::get(data.parameters.at(Parameter::state0)); - } + reader.loadReal(Parameter::R, R_); + reader.loadReal(Parameter::X, X_); + reader.loadSwitch(Parameter::state0, status_); if (data.buses.contains(Buses::bus)) { diff --git a/GridKit/Model/PhasorDynamics/BusToSignalAdapter/BusToSignalAdapter.hpp b/GridKit/Model/PhasorDynamics/BusToSignalAdapter/BusToSignalAdapter.hpp index d1a480f79..24cf56e45 100644 --- a/GridKit/Model/PhasorDynamics/BusToSignalAdapter/BusToSignalAdapter.hpp +++ b/GridKit/Model/PhasorDynamics/BusToSignalAdapter/BusToSignalAdapter.hpp @@ -51,14 +51,14 @@ namespace GridKit BusToSignalAdapter(BusT* bus, const ModelDataT& data); ~BusToSignalAdapter(); - int setGridKitComponentID(IdxT) override final; - int allocate() override final; - int verify() const override final; - int initialize() override final; - int tagDifferentiable() override final; - int setAbsoluteTolerance(RealT rel_tol) override final; - int evaluateResidual() override final; - int evaluateJacobian() override final; + int setGridKitComponentID(IdxT) override final; + int allocate() override final; + Model::ConfigurationChecks verify() const override final; + int initialize() override final; + int tagDifferentiable() override final; + int setAbsoluteTolerance(RealT rel_tol) override final; + int evaluateResidual() override final; + int evaluateJacobian() override final; SignalPortsT& getPorts() { diff --git a/GridKit/Model/PhasorDynamics/BusToSignalAdapter/BusToSignalAdapterImpl.hpp b/GridKit/Model/PhasorDynamics/BusToSignalAdapter/BusToSignalAdapterImpl.hpp index 52cf4fdf1..72e72488b 100644 --- a/GridKit/Model/PhasorDynamics/BusToSignalAdapter/BusToSignalAdapterImpl.hpp +++ b/GridKit/Model/PhasorDynamics/BusToSignalAdapter/BusToSignalAdapterImpl.hpp @@ -93,27 +93,19 @@ namespace GridKit * @brief verify method checks that attached signals are also linked */ template - int BusToSignalAdapter::verify() const + Model::ConfigurationChecks BusToSignalAdapter::verify() const { using SignalIn = BusToSignalAdapterSignalInputs; - int ret = 0; + Model::ConfigurationChecks checks; - auto ir_port = ports_.in.template port(); - if (ir_port.connected() && !ir_port.linked()) - { - Log::error() << "BusToSignalAdapter: Ir signal attached with no linked source\n"; - ret += 1; - } + const auto ir_port = ports_.in.template port(); + checks.check(!ir_port.connected() || ir_port.linked(), "Ir signal attached with no linked source"); - auto ii_port = ports_.in.template port(); - if (ii_port.connected() && !ii_port.linked()) - { - Log::error() << "BusToSignalAdapter: Ii signal attached with no linked source\n"; - ret += 1; - } + const auto ii_port = ports_.in.template port(); + checks.check(!ii_port.connected() || ii_port.linked(), "Ii signal attached with no linked source"); - return ret; + return checks; } /** diff --git a/GridKit/Model/PhasorDynamics/Component.hpp b/GridKit/Model/PhasorDynamics/Component.hpp index a4bf74c7b..8393f1fc1 100644 --- a/GridKit/Model/PhasorDynamics/Component.hpp +++ b/GridKit/Model/PhasorDynamics/Component.hpp @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -61,7 +62,8 @@ namespace GridKit } } - virtual int verify() const = 0; + /// Report every configuration problem; passed() when the model can initialize. + virtual Model::ConfigurationChecks verify() const = 0; IdxT size() override final { diff --git a/GridKit/Model/PhasorDynamics/Controller/REECB/Reecb.hpp b/GridKit/Model/PhasorDynamics/Controller/REECB/Reecb.hpp index 6ec4bb77c..438a9de95 100644 --- a/GridKit/Model/PhasorDynamics/Controller/REECB/Reecb.hpp +++ b/GridKit/Model/PhasorDynamics/Controller/REECB/Reecb.hpp @@ -114,14 +114,14 @@ namespace GridKit Reecb(BusT* bus, const ModelDataT& data); ~Reecb(); - int setGridKitComponentID(IdxT component_id) override final; - int allocate() override final; - int verify() const override final; - int initialize() override final; - int tagDifferentiable() override final; - int setAbsoluteTolerance(RealT rel_tol) override final; - int evaluateResidual() override final; - int evaluateJacobian() override final; + int setGridKitComponentID(IdxT component_id) override final; + int allocate() override final; + Model::ConfigurationChecks verify() const override final; + int initialize() override final; + int tagDifferentiable() override final; + int setAbsoluteTolerance(RealT rel_tol) override final; + int evaluateResidual() override final; + int evaluateJacobian() override final; SignalPortsT& getPorts() { @@ -173,15 +173,6 @@ namespace GridKit bool buildInitialPoint(InitialPoint& point); void commitInitialPoint(const InitialPoint& point); - - void loadRealParameter(const ModelDataT& data, - ReecbParameters parameter, - RealT& target, - const char* name); - void loadBooleanParameter(const ModelDataT& data, - ReecbParameters parameter, - bool& target, - const char* name); bool floorTimeConstant(RealT& value, const char* name); void initializeParameters(const ModelDataT& data); void initializeMonitor(); @@ -232,7 +223,6 @@ namespace GridKit RealT Imax_{1.3}; bool Vref0_given_{false}; - IdxT parameter_error_count_{0}; // Derived parameters RealT pf_on_{0}; diff --git a/GridKit/Model/PhasorDynamics/Controller/REECB/ReecbImpl.hpp b/GridKit/Model/PhasorDynamics/Controller/REECB/ReecbImpl.hpp index 2ed478014..effbce7b8 100644 --- a/GridKit/Model/PhasorDynamics/Controller/REECB/ReecbImpl.hpp +++ b/GridKit/Model/PhasorDynamics/Controller/REECB/ReecbImpl.hpp @@ -9,8 +9,13 @@ #include #include #include +#include +#include +#include #include +#include +#include #include #include #include @@ -158,123 +163,109 @@ namespace GridKit * attached optional signals. Operating-point feasibility is checked by * initialize(). * - * @return Number of configuration errors; zero when valid. + * @return The configuration checks; passed() when valid. */ template - int Reecb::verify() const + Model::ConfigurationChecks Reecb::verify() const { - int ret = static_cast(parameter_error_count_); + Model::ConfigurationChecks checks; - auto check = [&](bool condition, const char* message) - { - if (!condition) - { - Log::error() << "Reecb: " << message << '\n'; - ret += 1; - } - }; - - check(bus_ != nullptr, "terminal bus is required"); + checks.check(bus_ != nullptr, "terminal bus is required"); const bool valid_component_base = std::isfinite(va_component_base_) && va_component_base_ > ZERO; const bool valid_system_base = std::isfinite(va_system_base_) && va_system_base_ > ZERO; - check(valid_component_base, "component power base must be finite and positive"); - check(valid_system_base, "system power base must be finite and positive"); + checks.check(valid_component_base, "component power base must be finite and positive"); + checks.check(valid_system_base, "system power base must be finite and positive"); if (valid_component_base && valid_system_base) { const RealT system_to_component = va_system_base_ / va_component_base_; const RealT component_to_system = va_component_base_ / va_system_base_; - check(std::isfinite(system_to_component) - && system_to_component > ZERO - && std::isfinite(component_to_system) - && component_to_system > ZERO, - "system/component power-base conversion ratios must be finite and positive"); + checks.check(std::isfinite(system_to_component) + && system_to_component > ZERO + && std::isfinite(component_to_system) + && component_to_system > ZERO, + "system/component power-base conversion ratios must be finite and positive"); } - check(std::isfinite(Trv_), "Trv must be finite"); - check(std::isfinite(Tp_), "Tp must be finite"); - check(std::isfinite(Vref0_), "Vref0 must be finite"); + checks.check(std::isfinite(Trv_), "Trv must be finite"); + checks.check(std::isfinite(Tp_), "Tp must be finite"); + checks.check(std::isfinite(Vref0_), "Vref0 must be finite"); const bool finite_voltage_thresholds = std::isfinite(Vdip_) && std::isfinite(Vup_); - check(finite_voltage_thresholds, "Vdip and Vup must be finite"); + checks.check(finite_voltage_thresholds, "Vdip and Vup must be finite"); if (finite_voltage_thresholds) { - check(Vdip_ < Vup_, "Vdip must be less than Vup"); + checks.check(Vdip_ < Vup_, "Vdip must be less than Vup"); } const bool finite_voltage_deadband = std::isfinite(dbd1_) && std::isfinite(dbd2_); - check(finite_voltage_deadband, "dbd1 and dbd2 must be finite"); + checks.check(finite_voltage_deadband, "dbd1 and dbd2 must be finite"); if (finite_voltage_deadband) { - check(dbd1_ <= ZERO && ZERO <= dbd2_, "dbd1 <= 0 <= dbd2 is required"); + checks.check(dbd1_ <= ZERO && ZERO <= dbd2_, "dbd1 <= 0 <= dbd2 is required"); } - check(std::isfinite(kqv_) && kqv_ >= ZERO, "kqv must be finite and non-negative"); + checks.check(std::isfinite(kqv_) && kqv_ >= ZERO, "kqv must be finite and non-negative"); const bool finite_injection_limits = std::isfinite(Iql1_) && std::isfinite(Iqh1_); - check(finite_injection_limits, "Iql1 and Iqh1 must be finite"); + checks.check(finite_injection_limits, "Iql1 and Iqh1 must be finite"); if (finite_injection_limits) { - check(Iql1_ <= Iqh1_, "Iql1 must be less than or equal to Iqh1"); + checks.check(Iql1_ <= Iqh1_, "Iql1 must be less than or equal to Iqh1"); } const bool finite_reactive_limits = std::isfinite(Qmin_) && std::isfinite(Qmax_); - check(finite_reactive_limits, "Qmin and Qmax must be finite"); + checks.check(finite_reactive_limits, "Qmin and Qmax must be finite"); if (finite_reactive_limits) { - check(Qmin_ <= Qmax_, "Qmin must be less than or equal to Qmax"); + checks.check(Qmin_ <= Qmax_, "Qmin must be less than or equal to Qmax"); } - check(std::isfinite(Kqp_) && Kqp_ >= ZERO, "Kqp must be finite and non-negative"); - check(std::isfinite(Kqi_) && Kqi_ >= ZERO, "Kqi must be finite and non-negative"); + checks.check(std::isfinite(Kqp_) && Kqp_ >= ZERO, "Kqp must be finite and non-negative"); + checks.check(std::isfinite(Kqi_) && Kqi_ >= ZERO, "Kqi must be finite and non-negative"); const bool finite_voltage_limits = std::isfinite(Vmin_) && std::isfinite(Vmax_); - check(finite_voltage_limits, "Vmin and Vmax must be finite"); + checks.check(finite_voltage_limits, "Vmin and Vmax must be finite"); if (finite_voltage_limits) { - check(Vmin_ <= Vmax_, "Vmin must be less than or equal to Vmax"); + checks.check(Vmin_ <= Vmax_, "Vmin must be less than or equal to Vmax"); } - check(std::isfinite(Kvp_) && Kvp_ >= ZERO, "Kvp must be finite and non-negative"); - check(std::isfinite(Kvi_) && Kvi_ >= ZERO, "Kvi must be finite and non-negative"); - check(std::isfinite(Tiq_), "Tiq must be finite"); - check(std::isfinite(Tpord_), "Tpord must be finite"); + checks.check(std::isfinite(Kvp_) && Kvp_ >= ZERO, "Kvp must be finite and non-negative"); + checks.check(std::isfinite(Kvi_) && Kvi_ >= ZERO, "Kvi must be finite and non-negative"); + checks.check(std::isfinite(Tiq_), "Tiq must be finite"); + checks.check(std::isfinite(Tpord_), "Tpord must be finite"); const bool finite_ramp_limits = std::isfinite(dPmin_) && std::isfinite(dPmax_); - check(finite_ramp_limits, "dPmin and dPmax must be finite"); + checks.check(finite_ramp_limits, "dPmin and dPmax must be finite"); if (finite_ramp_limits) { - check(dPmin_ < ZERO && ZERO < dPmax_, "dPmin < 0 < dPmax is required"); + checks.check(dPmin_ < ZERO && ZERO < dPmax_, "dPmin < 0 < dPmax is required"); } const bool finite_active_limits = std::isfinite(Pmin_) && std::isfinite(Pmax_); - check(finite_active_limits, "Pmin and Pmax must be finite"); + checks.check(finite_active_limits, "Pmin and Pmax must be finite"); if (finite_active_limits) { - check(Pmin_ <= Pmax_, "Pmin must be less than or equal to Pmax"); + checks.check(Pmin_ <= Pmax_, "Pmin must be less than or equal to Pmax"); } - check(std::isfinite(Imax_) && Imax_ > ZERO, "Imax must be finite and positive"); - - auto check_optional_signal = [&](const char* name) - { - if (ports_.in.template port().connected() - && !ports_.in.template port().linked()) - { - Log::error() << "Reecb: " << name << " signal attached with no linked source\n"; - ret += 1; - } - }; + checks.check(std::isfinite(Imax_) && Imax_ > ZERO, "Imax must be finite and positive"); - check_optional_signal.template operator()("pe"); - check_optional_signal.template operator()("qgen"); - check_optional_signal.template operator()("qext"); - check_optional_signal.template operator()("pfaref"); - check_optional_signal.template operator()("pref"); + const auto pe_port = ports_.in.template port(); + checks.check(!pe_port.connected() || pe_port.linked(), "pe signal attached with no linked source"); + const auto qgen_port = ports_.in.template port(); + checks.check(!qgen_port.connected() || qgen_port.linked(), "qgen signal attached with no linked source"); + const auto qext_port = ports_.in.template port(); + checks.check(!qext_port.connected() || qext_port.linked(), "qext signal attached with no linked source"); + const auto pfaref_port = ports_.in.template port(); + checks.check(!pfaref_port.connected() || pfaref_port.linked(), "pfaref signal attached with no linked source"); + const auto pref_port = ports_.in.template port(); + checks.check(!pref_port.connected() || pref_port.linked(), "pref signal attached with no linked source"); - return ret; + return checks; } /** @@ -303,9 +294,13 @@ namespace GridKit return 1; } - if (verify() > 0) + const auto checks = verify(); + for (const auto& error : checks.errors()) + { + Log::error() << "Reecb: " << error << '\n'; + } + if (!checks.passed()) { - Log::error() << "Reecb: cannot initialize with invalid configuration\n"; return 1; } @@ -1183,80 +1178,6 @@ namespace GridKit return InitialCurrentLimit{imax, cap}; } - /** - * @brief Load one real-valued parameter - * - * Real and integer serialized values are accepted. Any other stored type - * records a loading error while preserving the existing value. - * - * @param[in] data Model parameter data. - * @param[in] parameter Parameter key to load. - * @param[in,out] target Stored parameter value. - * @param[in] name Serialized parameter name for diagnostics. - */ - template - void Reecb::loadRealParameter( - const ModelDataT& data, - ReecbParameters parameter, - RealT& target, - const char* name) - { - if (!data.parameters.contains(parameter)) - { - return; - } - - const auto& value = data.parameters.at(parameter); - if (const auto* real_value = std::get_if(&value)) - { - target = *real_value; - } - else if (const auto* index_value = std::get_if(&value)) - { - target = static_cast(*index_value); - } - else - { - Log::error() << "Reecb: parameter '" << name << "' must be numeric\n"; - ++parameter_error_count_; - } - } - - /** - * @brief Load one optional Boolean parameter - * - * Any non-Boolean stored type records a loading error while preserving - * the existing default. - * - * @param[in] data Model parameter data. - * @param[in] parameter Parameter key to load. - * @param[in,out] target Stored Boolean value. - * @param[in] name Serialized parameter name for diagnostics. - */ - template - void Reecb::loadBooleanParameter( - const ModelDataT& data, - ReecbParameters parameter, - bool& target, - const char* name) - { - if (!data.parameters.contains(parameter)) - { - return; - } - - const auto& value = data.parameters.at(parameter); - if (const auto* bool_value = std::get_if(&value)) - { - target = *bool_value; - } - else - { - Log::error() << "Reecb: parameter '" << name << "' must be boolean\n"; - ++parameter_error_count_; - } - } - /** * @brief Validate and floor one explicit controller lag * @@ -1275,17 +1196,11 @@ namespace GridKit { if (!std::isfinite(value)) { - Log::error() << "Reecb: " << name << " must be finite\n"; - ++parameter_error_count_; - value = TIME_CONSTANT_MINIMUM; - return false; + throw std::invalid_argument(std::string("Reecb: ") + name + " must be finite"); } if (value < ZERO) { - Log::error() << "Reecb: " << name << " must be non-negative\n"; - ++parameter_error_count_; - value = TIME_CONSTANT_MINIMUM; - return false; + throw std::invalid_argument(std::string("Reecb: ") + name + " must be non-negative"); } const bool raised = value < TIME_CONSTANT_MINIMUM; @@ -1305,54 +1220,44 @@ namespace GridKit void Reecb::initializeParameters(const ModelDataT& data) { using Params = typename ModelDataT::Parameters; + Vref0_given_ = data.parameters.contains(Params::Vref0); - parameter_error_count_ = 0; - Vref0_given_ = false; + Model::ParameterReader reader(data, "Reecb"); - if (data.parameters.contains(Params::mva)) + RealT mva{}; + if (reader.requireReal(Params::mva, mva)) { - RealT mva{}; - loadRealParameter(data, Params::mva, mva, "mva"); this->setComponentBase(mva * static_cast(1.0e6)); } - else - { - Log::error() << "Reecb: missing required parameter 'mva'\n"; - ++parameter_error_count_; - } - loadBooleanParameter(data, Params::PfFlag, PfFlag_, "PfFlag"); - loadBooleanParameter(data, Params::VFlag, VFlag_, "VFlag"); - loadBooleanParameter(data, Params::QFlag, QFlag_, "QFlag"); - loadBooleanParameter(data, Params::Pqflag, Pqflag_, "Pqflag"); - loadRealParameter(data, Params::Trv, Trv_, "Trv"); - loadRealParameter(data, Params::Tp, Tp_, "Tp"); - if (data.parameters.contains(Params::Vref0)) - { - loadRealParameter(data, Params::Vref0, Vref0_, "Vref0"); - Vref0_given_ = true; - } - loadRealParameter(data, Params::Vdip, Vdip_, "Vdip"); - loadRealParameter(data, Params::Vup, Vup_, "Vup"); - loadRealParameter(data, Params::dbd1, dbd1_, "dbd1"); - loadRealParameter(data, Params::dbd2, dbd2_, "dbd2"); - loadRealParameter(data, Params::kqv, kqv_, "kqv"); - loadRealParameter(data, Params::Iql1, Iql1_, "Iql1"); - loadRealParameter(data, Params::Iqh1, Iqh1_, "Iqh1"); - loadRealParameter(data, Params::Qmax, Qmax_, "Qmax"); - loadRealParameter(data, Params::Qmin, Qmin_, "Qmin"); - loadRealParameter(data, Params::Kqp, Kqp_, "Kqp"); - loadRealParameter(data, Params::Kqi, Kqi_, "Kqi"); - loadRealParameter(data, Params::Vmax, Vmax_, "Vmax"); - loadRealParameter(data, Params::Vmin, Vmin_, "Vmin"); - loadRealParameter(data, Params::Kvp, Kvp_, "Kvp"); - loadRealParameter(data, Params::Kvi, Kvi_, "Kvi"); - loadRealParameter(data, Params::Tiq, Tiq_, "Tiq"); - loadRealParameter(data, Params::Tpord, Tpord_, "Tpord"); - loadRealParameter(data, Params::dPmax, dPmax_, "dPmax"); - loadRealParameter(data, Params::dPmin, dPmin_, "dPmin"); - loadRealParameter(data, Params::Pmax, Pmax_, "Pmax"); - loadRealParameter(data, Params::Pmin, Pmin_, "Pmin"); - loadRealParameter(data, Params::Imax, Imax_, "Imax"); + reader.loadSwitch(Params::PfFlag, PfFlag_); + reader.loadSwitch(Params::VFlag, VFlag_); + reader.loadSwitch(Params::QFlag, QFlag_); + reader.loadSwitch(Params::Pqflag, Pqflag_); + reader.loadReal(Params::Trv, Trv_); + reader.loadReal(Params::Tp, Tp_); + reader.loadReal(Params::Vref0, Vref0_); + reader.loadReal(Params::Vdip, Vdip_); + reader.loadReal(Params::Vup, Vup_); + reader.loadReal(Params::dbd1, dbd1_); + reader.loadReal(Params::dbd2, dbd2_); + reader.loadReal(Params::kqv, kqv_); + reader.loadReal(Params::Iql1, Iql1_); + reader.loadReal(Params::Iqh1, Iqh1_); + reader.loadReal(Params::Qmax, Qmax_); + reader.loadReal(Params::Qmin, Qmin_); + reader.loadReal(Params::Kqp, Kqp_); + reader.loadReal(Params::Kqi, Kqi_); + reader.loadReal(Params::Vmax, Vmax_); + reader.loadReal(Params::Vmin, Vmin_); + reader.loadReal(Params::Kvp, Kvp_); + reader.loadReal(Params::Kvi, Kvi_); + reader.loadReal(Params::Tiq, Tiq_); + reader.loadReal(Params::Tpord, Tpord_); + reader.loadReal(Params::dPmax, dPmax_); + reader.loadReal(Params::dPmin, dPmin_); + reader.loadReal(Params::Pmax, Pmax_); + reader.loadReal(Params::Pmin, Pmin_); + reader.loadReal(Params::Imax, Imax_); setDerivedParameters(); } diff --git a/GridKit/Model/PhasorDynamics/Controller/REPCA/Repca.hpp b/GridKit/Model/PhasorDynamics/Controller/REPCA/Repca.hpp index 16f9af8de..b90d0516e 100644 --- a/GridKit/Model/PhasorDynamics/Controller/REPCA/Repca.hpp +++ b/GridKit/Model/PhasorDynamics/Controller/REPCA/Repca.hpp @@ -119,14 +119,14 @@ namespace GridKit Repca(BusT* bus, const ModelDataT& data); ~Repca(); - int setGridKitComponentID(IdxT component_id) override final; - int allocate() override final; - int verify() const override final; - int initialize() override final; - int tagDifferentiable() override final; - int setAbsoluteTolerance(RealT rel_tol) override final; - int evaluateResidual() override final; - int evaluateJacobian() override final; + int setGridKitComponentID(IdxT component_id) override final; + int allocate() override final; + Model::ConfigurationChecks verify() const override final; + int initialize() override final; + int tagDifferentiable() override final; + int setAbsoluteTolerance(RealT rel_tol) override final; + int evaluateResidual() override final; + int evaluateJacobian() override final; SignalPortsT& getPorts() { @@ -197,8 +197,6 @@ namespace GridKit RealT Pmax_{static_cast(2.0)}; RealT Pmin_{ZERO}; RealT Tlag_{static_cast(3.0)}; - - IdxT parameter_error_count_{0}; RealT vcomp_on_{ONE}; RealT vcomp_off_{ZERO}; RealT ref_on_{ONE}; diff --git a/GridKit/Model/PhasorDynamics/Controller/REPCA/RepcaImpl.hpp b/GridKit/Model/PhasorDynamics/Controller/REPCA/RepcaImpl.hpp index df9da208a..6892f1b9b 100644 --- a/GridKit/Model/PhasorDynamics/Controller/REPCA/RepcaImpl.hpp +++ b/GridKit/Model/PhasorDynamics/Controller/REPCA/RepcaImpl.hpp @@ -7,9 +7,14 @@ #pragma once #include +#include #include -#include +#include +#include +#include +#include +#include #include #include #include @@ -142,23 +147,14 @@ namespace GridKit * bus, required measurement signals, and attached optional reference * signals. Command-output assignment is optional. * - * @return Number of configuration errors; zero when valid. + * @return The configuration checks; passed() when valid. */ template - int Repca::verify() const + Model::ConfigurationChecks Repca::verify() const { - int ret = static_cast(parameter_error_count_); + Model::ConfigurationChecks checks; - auto check = [&](bool condition, const char* message) - { - if (!condition) - { - Log::error() << "Repca: " << message << '\n'; - ret += 1; - } - }; - - check(bus_ != nullptr, "regulated bus is required"); + checks.check(bus_ != nullptr, "regulated bus is required"); const bool valid_component_base = std::isfinite(mva_base_) && mva_base_ > ZERO @@ -166,69 +162,60 @@ namespace GridKit && va_component_base_ > ZERO; const bool valid_system_base = std::isfinite(va_system_base_) && va_system_base_ > ZERO; - check(valid_component_base, - "mva must define a finite positive component power base"); - check(valid_system_base, "system power base must be finite and positive"); + checks.check(valid_component_base, + "mva must define a finite positive component power base"); + checks.check(valid_system_base, "system power base must be finite and positive"); if (valid_component_base && valid_system_base) { const RealT system_to_component = va_system_base_ / va_component_base_; const RealT component_to_system = va_component_base_ / va_system_base_; - check(std::isfinite(system_to_component) - && system_to_component > ZERO - && std::isfinite(component_to_system) - && component_to_system > ZERO, - "system/component power-base conversion ratios must be finite and positive"); + checks.check(std::isfinite(system_to_component) + && system_to_component > ZERO + && std::isfinite(component_to_system) + && component_to_system > ZERO, + "system/component power-base conversion ratios must be finite and positive"); } - check(dbdlow_ <= ZERO && ZERO <= dbdupper_, - "dbdlow <= 0 <= dbdupper is required"); - check(emin_ <= ZERO && ZERO <= emax_, - "emin <= 0 <= emax is required"); - check(Qmin_ <= Qmax_, "Qmin must be less than or equal to Qmax"); - check(fdbd1_ <= ZERO && ZERO <= fdbd2_, - "fdbd1 <= 0 <= fdbd2 is required"); - check(Ddn_ >= ZERO, "Ddn must be non-negative"); - check(Dup_ >= ZERO, "Dup must be non-negative"); - check(femin_ <= ZERO && ZERO <= femax_, - "femin <= 0 <= femax is required"); - check(Pmin_ <= Pmax_, "Pmin must be less than or equal to Pmax"); - - auto check_required_signal = [&](const char* name) - { - if (!ports_.in.template port().connected()) - { - Log::error() << "Repca: " << name << " signal is required\n"; - ret += 1; - } - else if (!ports_.in.template port().linked()) - { - Log::error() << "Repca: " << name << " signal attached with no linked source\n"; - ret += 1; - } - }; - - check_required_signal.template operator()("ir"); - check_required_signal.template operator()("ii"); - check_required_signal.template operator()("p"); - check_required_signal.template operator()("q"); - - auto check_optional_signal = [&](const char* name) - { - if (ports_.in.template port().connected() - && !ports_.in.template port().linked()) - { - Log::error() << "Repca: " << name << " signal attached with no linked source\n"; - ret += 1; - } - }; - - check_optional_signal.template operator()("vref"); - check_optional_signal.template operator()("pref"); - check_optional_signal.template operator()("qref"); - check_optional_signal.template operator()("freq"); - check_optional_signal.template operator()("freqref"); - - return ret; + checks.check(dbdlow_ <= ZERO && ZERO <= dbdupper_, + "dbdlow <= 0 <= dbdupper is required"); + checks.check(emin_ <= ZERO && ZERO <= emax_, + "emin <= 0 <= emax is required"); + checks.check(Qmin_ <= Qmax_, "Qmin must be less than or equal to Qmax"); + checks.check(fdbd1_ <= ZERO && ZERO <= fdbd2_, + "fdbd1 <= 0 <= fdbd2 is required"); + checks.check(Ddn_ >= ZERO, "Ddn must be non-negative"); + checks.check(Dup_ >= ZERO, "Dup must be non-negative"); + checks.check(femin_ <= ZERO && ZERO <= femax_, + "femin <= 0 <= femax is required"); + checks.check(Pmin_ <= Pmax_, "Pmin must be less than or equal to Pmax"); + + const auto ir_port = ports_.in.template port(); + + checks.check(ir_port.connected(), "ir signal is required"); + + checks.check(!ir_port.connected() || ir_port.linked(), "ir signal attached with no linked source"); + const auto ii_port = ports_.in.template port(); + checks.check(ii_port.connected(), "ii signal is required"); + checks.check(!ii_port.connected() || ii_port.linked(), "ii signal attached with no linked source"); + const auto p_port = ports_.in.template port(); + checks.check(p_port.connected(), "p signal is required"); + checks.check(!p_port.connected() || p_port.linked(), "p signal attached with no linked source"); + const auto q_port = ports_.in.template port(); + checks.check(q_port.connected(), "q signal is required"); + checks.check(!q_port.connected() || q_port.linked(), "q signal attached with no linked source"); + + const auto vref_port = ports_.in.template port(); + checks.check(!vref_port.connected() || vref_port.linked(), "vref signal attached with no linked source"); + const auto pref_port = ports_.in.template port(); + checks.check(!pref_port.connected() || pref_port.linked(), "pref signal attached with no linked source"); + const auto qref_port = ports_.in.template port(); + checks.check(!qref_port.connected() || qref_port.linked(), "qref signal attached with no linked source"); + const auto freq_port = ports_.in.template port(); + checks.check(!freq_port.connected() || freq_port.linked(), "freq signal attached with no linked source"); + const auto freqref_port = ports_.in.template port(); + checks.check(!freqref_port.connected() || freqref_port.linked(), "freqref signal attached with no linked source"); + + return checks; } /** @@ -282,9 +269,13 @@ namespace GridKit return 1; } - if (verify() > 0) + const auto checks = verify(); + for (const auto& error : checks.errors()) + { + Log::error() << "Repca: " << error << '\n'; + } + if (!checks.passed()) { - Log::error() << "Repca: cannot initialize with invalid configuration\n"; return 1; } @@ -847,92 +838,39 @@ namespace GridKit { using Params = typename ModelDataT::Parameters; - parameter_error_count_ = 0; - - auto load_real = [&](auto key, RealT& target, const char* name) - { - if (!data.parameters.contains(key)) - { - return; - } - - const auto& value = data.parameters.at(key); - RealT parsed_value{}; - if (const auto* real_value = std::get_if(&value)) - { - parsed_value = *real_value; - } - else if (const auto* index_value = std::get_if(&value)) - { - parsed_value = static_cast(*index_value); - } - else - { - Log::error() << "Repca: parameter '" << name << "' must be numeric\n"; - ++parameter_error_count_; - return; - } - - if (!std::isfinite(parsed_value)) - { - Log::error() << "Repca: parameter '" << name << "' must be finite\n"; - ++parameter_error_count_; - return; - } - - target = parsed_value; - }; - - auto load_switch = [&](auto key, bool& target, const char* name) - { - if (!data.parameters.contains(key)) - { - return; - } - - const auto& value = data.parameters.at(key); - if (const auto* bool_value = std::get_if(&value)) - { - target = *bool_value; - } - else - { - Log::error() << "Repca: parameter '" << name << "' must be boolean\n"; - ++parameter_error_count_; - } - }; - - load_real(Params::mva, mva_base_, "mva"); - load_switch(Params::VcompFlag, VcompFlag_, "VcompFlag"); - load_switch(Params::RefFlag, RefFlag_, "RefFlag"); - load_switch(Params::Freqflag, Freqflag_, "Freqflag"); - load_real(Params::Tfltr, Tfltr_, "Tfltr"); - load_real(Params::Vfrz, Vfrz_, "Vfrz"); - load_real(Params::Rc, Rc_, "Rc"); - load_real(Params::Xc, Xc_, "Xc"); - load_real(Params::Kc, Kc_, "Kc"); - load_real(Params::dbdlow, dbdlow_, "dbdlow"); - load_real(Params::dbdupper, dbdupper_, "dbdupper"); - load_real(Params::emax, emax_, "emax"); - load_real(Params::emin, emin_, "emin"); - load_real(Params::Kp, Kp_, "Kp"); - load_real(Params::Ki, Ki_, "Ki"); - load_real(Params::Qmax, Qmax_, "Qmax"); - load_real(Params::Qmin, Qmin_, "Qmin"); - load_real(Params::Tft, Tft_, "Tft"); - load_real(Params::Tfv, Tfv_, "Tfv"); - load_real(Params::Tp, Tp_, "Tp"); - load_real(Params::fdbd1, fdbd1_, "fdbd1"); - load_real(Params::fdbd2, fdbd2_, "fdbd2"); - load_real(Params::Ddn, Ddn_, "Ddn"); - load_real(Params::Dup, Dup_, "Dup"); - load_real(Params::femax, femax_, "femax"); - load_real(Params::femin, femin_, "femin"); - load_real(Params::Kpg, Kpg_, "Kpg"); - load_real(Params::Kig, Kig_, "Kig"); - load_real(Params::Pmax, Pmax_, "Pmax"); - load_real(Params::Pmin, Pmin_, "Pmin"); - load_real(Params::Tlag, Tlag_, "Tlag"); + Model::ParameterReader reader(data, "Repca"); + + reader.loadReal(Params::mva, mva_base_); + reader.loadSwitch(Params::VcompFlag, VcompFlag_); + reader.loadSwitch(Params::RefFlag, RefFlag_); + reader.loadSwitch(Params::Freqflag, Freqflag_); + reader.loadReal(Params::Tfltr, Tfltr_); + reader.loadReal(Params::Vfrz, Vfrz_); + reader.loadReal(Params::Rc, Rc_); + reader.loadReal(Params::Xc, Xc_); + reader.loadReal(Params::Kc, Kc_); + reader.loadReal(Params::dbdlow, dbdlow_); + reader.loadReal(Params::dbdupper, dbdupper_); + reader.loadReal(Params::emax, emax_); + reader.loadReal(Params::emin, emin_); + reader.loadReal(Params::Kp, Kp_); + reader.loadReal(Params::Ki, Ki_); + reader.loadReal(Params::Qmax, Qmax_); + reader.loadReal(Params::Qmin, Qmin_); + reader.loadReal(Params::Tft, Tft_); + reader.loadReal(Params::Tfv, Tfv_); + reader.loadReal(Params::Tp, Tp_); + reader.loadReal(Params::fdbd1, fdbd1_); + reader.loadReal(Params::fdbd2, fdbd2_); + reader.loadReal(Params::Ddn, Ddn_); + reader.loadReal(Params::Dup, Dup_); + reader.loadReal(Params::femax, femax_); + reader.loadReal(Params::femin, femin_); + reader.loadReal(Params::Kpg, Kpg_); + reader.loadReal(Params::Kig, Kig_); + reader.loadReal(Params::Pmax, Pmax_); + reader.loadReal(Params::Pmin, Pmin_); + reader.loadReal(Params::Tlag, Tlag_); setDerivedParameters(); } @@ -982,20 +920,15 @@ namespace GridKit { // The lags are raised to the floor below, so negative values must be // rejected here while the value as read is still available. - auto check_non_negative = [&](RealT value, const char* name) + const std::array, 5> lags{ + {{Tfltr_, "Tfltr"}, {Tft_, "Tft"}, {Tfv_, "Tfv"}, {Tp_, "Tp"}, {Tlag_, "Tlag"}}}; + for (const auto& [value, name] : lags) { if (value < ZERO) { - Log::error() << "Repca: " << name << " must be non-negative\n"; - ++parameter_error_count_; + throw std::invalid_argument(std::string("Repca: ") + name + " must be non-negative"); } - }; - - check_non_negative(Tfltr_, "Tfltr"); - check_non_negative(Tft_, "Tft"); - check_non_negative(Tfv_, "Tfv"); - check_non_negative(Tp_, "Tp"); - check_non_negative(Tlag_, "Tlag"); + } if (Tfltr_ < TIME_CONSTANT_MINIMUM || Tfv_ < TIME_CONSTANT_MINIMUM || Tp_ < TIME_CONSTANT_MINIMUM || Tlag_ < TIME_CONSTANT_MINIMUM) diff --git a/GridKit/Model/PhasorDynamics/Converter/REGCA/Regca.hpp b/GridKit/Model/PhasorDynamics/Converter/REGCA/Regca.hpp index bb3ab989c..f3c9be9d7 100644 --- a/GridKit/Model/PhasorDynamics/Converter/REGCA/Regca.hpp +++ b/GridKit/Model/PhasorDynamics/Converter/REGCA/Regca.hpp @@ -102,14 +102,14 @@ namespace GridKit Regca(BusT* bus, const ModelDataT& data); ~Regca(); - int setGridKitComponentID(IdxT component_id) override final; - int allocate() override final; - int verify() const override final; - int initialize() override final; - int tagDifferentiable() override final; - int setAbsoluteTolerance(RealT rel_tol) override final; - int evaluateResidual() override final; - int evaluateJacobian() override final; + int setGridKitComponentID(IdxT component_id) override final; + int allocate() override final; + Model::ConfigurationChecks verify() const override final; + int initialize() override final; + int tagDifferentiable() override final; + int setAbsoluteTolerance(RealT rel_tol) override final; + int evaluateResidual() override final; + int evaluateJacobian() override final; SignalPortsT& getPorts() { @@ -218,8 +218,6 @@ namespace GridKit RealT Vhvmax_{0}; RealT Khv_{0.7}; - IdxT parameter_error_count_{0}; - // Derived parameters RealT use_lvpl_{0}; RealT bypass_lvpl_{1}; diff --git a/GridKit/Model/PhasorDynamics/Converter/REGCA/RegcaImpl.hpp b/GridKit/Model/PhasorDynamics/Converter/REGCA/RegcaImpl.hpp index 233a5be86..aae8a89da 100644 --- a/GridKit/Model/PhasorDynamics/Converter/REGCA/RegcaImpl.hpp +++ b/GridKit/Model/PhasorDynamics/Converter/REGCA/RegcaImpl.hpp @@ -9,8 +9,9 @@ #include #include #include -#include +#include +#include #include #include #include @@ -168,78 +169,25 @@ namespace GridKit { using Params = typename ModelDataT::Parameters; - parameter_error_count_ = 0; - - auto load_required_real = [&](auto key, RealT& target, const char* name) - { - if (!data.parameters.contains(key)) - { - Log::error() << "Regca: missing required parameter '" << name << "'\n"; - ++parameter_error_count_; - return; - } - - const auto& value = data.parameters.at(key); - if (const auto* real_value = std::get_if(&value)) - { - target = *real_value; - } - else if (const auto* index_value = std::get_if(&value)) - { - target = static_cast(*index_value); - } - else - { - Log::error() << "Regca: parameter '" << name << "' must be numeric\n"; - ++parameter_error_count_; - } - }; - - auto load_required_switch = [&](auto key, bool& target, const char* name) - { - if (!data.parameters.contains(key)) - { - Log::error() << "Regca: missing required parameter '" << name << "'\n"; - ++parameter_error_count_; - return; - } - - const auto& value = data.parameters.at(key); - if (const auto* bool_value = std::get_if(&value)) - { - target = *bool_value; - } - else if (const auto* index_value = std::get_if(&value); - index_value && (*index_value == 0 || *index_value == 1)) - { - target = (*index_value == 1); - } - else - { - Log::error() << "Regca: parameter '" << name << "' must be bool or 0/1\n"; - ++parameter_error_count_; - } - }; - - load_required_real(Params::p0, p0_, "p0"); - load_required_real(Params::q0, q0_, "q0"); - load_required_real(Params::mva, mva_base_, "mva"); - load_required_real(Params::Tg, Tg_, "Tg"); - load_required_real(Params::TM, TM_, "TM"); - load_required_real(Params::Rqmax, Rqmax_, "Rqmax"); - load_required_real(Params::Rqmin, Rqmin_, "Rqmin"); - load_required_real(Params::Rpmax, Rpmax_, "Rpmax"); - load_required_switch(Params::sL, sL_, "sL"); - load_required_real(Params::IL1, IL1_, "IL1"); - load_required_real(Params::VL0, VL0_, "VL0"); - load_required_real(Params::VL1, VL1_, "VL1"); - load_required_real(Params::VA0, VA0_, "VA0"); - load_required_real(Params::VA1, VA1_, "VA1"); - load_required_real(Params::Vhvmax, Vhvmax_, "Vhvmax"); - if (data.parameters.contains(Params::Khv)) - { - load_required_real(Params::Khv, Khv_, "Khv"); - } + Model::ParameterReader reader(data, "Regca"); + + reader.requireReal(Params::p0, p0_); + reader.requireReal(Params::q0, q0_); + reader.requireReal(Params::mva, mva_base_); + reader.requireReal(Params::Tg, Tg_); + reader.requireReal(Params::TM, TM_); + reader.requireReal(Params::Rqmax, Rqmax_); + reader.requireReal(Params::Rqmin, Rqmin_); + reader.requireReal(Params::Rpmax, Rpmax_); + reader.requireSwitch(Params::sL, sL_); + reader.requireReal(Params::IL1, IL1_); + reader.requireReal(Params::VL0, VL0_); + reader.requireReal(Params::VL1, VL1_); + reader.requireReal(Params::VA0, VA0_); + reader.requireReal(Params::VA1, VA1_); + reader.requireReal(Params::Vhvmax, Vhvmax_); + + reader.loadReal(Params::Khv, Khv_); setDerivedParameters(); } @@ -365,56 +313,32 @@ namespace GridKit * terminal bus, and checks that attached command ports have linked sources. * Operating-point admissibility is checked by initialize(). * - * @return Number of configuration errors, zero when valid. + * @return The configuration checks; passed() when valid. */ template - int Regca::verify() const + Model::ConfigurationChecks Regca::verify() const { - int ret = static_cast(parameter_error_count_); + Model::ConfigurationChecks checks; - auto check = [&](bool condition, const char* message) - { - if (!condition) - { - Log::error() << "Regca: " << message << '\n'; - ret += 1; - } - }; + checks.check(bus_ != nullptr, "bus pointer is null"); - if (bus_ == nullptr) - { - Log::error() << "Regca: bus pointer is null\n"; - ret += 1; - } + checks.check(mva_base_ > ZERO, "mva must be positive"); + checks.check(Rpmax_ >= ZERO, "Rpmax must be non-negative"); + checks.check(IL1_ >= ZERO, "IL1 must be non-negative"); + checks.check(KL_ > ZERO, "LVPL release slope must be positive"); + checks.check(ZERO <= VL0_ && VL0_ < VL1_, "VL0/VL1 must satisfy 0 <= VL0 < VL1"); + checks.check(ZERO <= VA0_ && VA0_ < VA1_ && VA1_ < Vhvmax_, + "VA0/VA1/Vhvmax must satisfy 0 <= VA0 < VA1 < Vhvmax"); - check(mva_base_ > ZERO, "mva must be positive"); - check(Rpmax_ >= ZERO, "Rpmax must be non-negative"); - check(IL1_ >= ZERO, "IL1 must be non-negative"); - check(std::isfinite(Khv_) && Khv_ >= ZERO, - "Khv must be finite and non-negative"); - check(ZERO <= VL0_ && VL0_ < VL1_, "VL0/VL1 must satisfy 0 <= VL0 < VL1"); - check(ZERO <= VA0_ && VA0_ < VA1_ && VA1_ < Vhvmax_, - "VA0/VA1/Vhvmax must satisfy 0 <= VA0 < VA1 < Vhvmax"); + checks.check(std::isfinite(Khv_) && Khv_ >= ZERO, + "Khv must be finite and non-negative"); - if (ports_.in.template port()) - { - if (!ports_.in.template port().linked()) - { - Log::error() << "Regca: ipcmd signal attached with no linked source\n"; - ret += 1; - } - } - - if (ports_.in.template port()) - { - if (!ports_.in.template port().linked()) - { - Log::error() << "Regca: iqcmd signal attached with no linked source\n"; - ret += 1; - } - } + const auto ipcmd_port = ports_.in.template port(); + checks.check(!ipcmd_port.connected() || ipcmd_port.linked(), "ipcmd signal attached with no linked source"); + const auto iqcmd_port = ports_.in.template port(); + checks.check(!iqcmd_port.connected() || iqcmd_port.linked(), "iqcmd signal attached with no linked source"); - return ret; + return checks; } /** diff --git a/GridKit/Model/PhasorDynamics/Exciter/ESDC1A/Esdc1a.hpp b/GridKit/Model/PhasorDynamics/Exciter/ESDC1A/Esdc1a.hpp index 6d70cb1a9..1323e972d 100644 --- a/GridKit/Model/PhasorDynamics/Exciter/ESDC1A/Esdc1a.hpp +++ b/GridKit/Model/PhasorDynamics/Exciter/ESDC1A/Esdc1a.hpp @@ -97,14 +97,14 @@ namespace GridKit Esdc1a(BusT* bus, const ModelDataT& data); ~Esdc1a(); - int setGridKitComponentID(IdxT component_id) override final; - int allocate() override final; - int verify() const override final; - int initialize() override final; - int tagDifferentiable() override final; - int setAbsoluteTolerance(RealT rel_tol) override final; - int evaluateResidual() override final; - int evaluateJacobian() override final; + int setGridKitComponentID(IdxT component_id) override final; + int allocate() override final; + Model::ConfigurationChecks verify() const override final; + int initialize() override final; + int tagDifferentiable() override final; + int setAbsoluteTolerance(RealT rel_tol) override final; + int evaluateResidual() override final; + int evaluateJacobian() override final; SignalPortsT& getPorts() { @@ -167,8 +167,6 @@ namespace GridKit RealT SB_{0}; RealT Ke_eff_{Ke_}; - IdxT parameter_error_count_{0}; - ScalarT omega_set_{0}; ScalarT vref_set_{0}; ScalarT vs_set_{0}; diff --git a/GridKit/Model/PhasorDynamics/Exciter/ESDC1A/Esdc1aImpl.hpp b/GridKit/Model/PhasorDynamics/Exciter/ESDC1A/Esdc1aImpl.hpp index a4af51239..5fea3deaa 100644 --- a/GridKit/Model/PhasorDynamics/Exciter/ESDC1A/Esdc1aImpl.hpp +++ b/GridKit/Model/PhasorDynamics/Exciter/ESDC1A/Esdc1aImpl.hpp @@ -7,9 +7,15 @@ #pragma once #include +#include +#include #include -#include +#include +#include +#include +#include +#include #include #include #include @@ -140,32 +146,19 @@ namespace GridKit * attached external signals. Seed feasibility is operating-point * dependent and is checked by initialize(). * - * @return Number of configuration errors; zero when valid. + * @return The configuration checks; passed() when valid. */ template - int Esdc1a::verify() const + Model::ConfigurationChecks Esdc1a::verify() const { - int ret = static_cast(parameter_error_count_); + Model::ConfigurationChecks checks; - auto check = [&](bool condition, const char* message) - { - if (!condition) - { - Log::error() << "Esdc1a: " << message << '\n'; - ret += 1; - } - }; + checks.check(bus_ != nullptr, "bus pointer is null"); - if (bus_ == nullptr) - { - Log::error() << "Esdc1a: bus pointer is null\n"; - ret += 1; - } - - check(Ka_ > ZERO, "Ka must be positive"); - check(Vrmin_ <= Vrmax_, "Vrmin must be less than or equal to Vrmax"); - check(UEL_ >= static_cast(0) && UEL_ <= static_cast(3), - "UEL must be 0, 1, 2, or 3"); + checks.check(Ka_ > ZERO, "Ka must be positive"); + checks.check(Vrmin_ <= Vrmax_, "Vrmin must be less than or equal to Vrmax"); + checks.check(UEL_ >= static_cast(0) && UEL_ <= static_cast(3), + "UEL must be 0, 1, 2, or 3"); // Model data uses an exact zero to mean "saturation bypassed", so // this is an exact comparison by intent rather than a tolerance test. @@ -174,46 +167,31 @@ namespace GridKit if (!saturation_disabled) { - check(E1_ > ZERO, "E1 must be positive when saturation is enabled"); - check(E2_ > ZERO, "E2 must be positive when saturation is enabled"); - check(Se1_ >= ZERO, "Se1 must be non-negative when saturation is enabled"); - check(Se2_ >= ZERO, "Se2 must be non-negative when saturation is enabled"); + checks.check(E1_ > ZERO, "E1 must be positive when saturation is enabled"); + checks.check(E2_ > ZERO, "E2 must be positive when saturation is enabled"); + checks.check(Se1_ >= ZERO, "Se1 must be non-negative when saturation is enabled"); + checks.check(Se2_ >= ZERO, "Se2 must be non-negative when saturation is enabled"); const bool sat_ordered = (E2_ > E1_ && Se2_ > Se1_) || (E2_ < E1_ && Se2_ < Se1_); - check(sat_ordered, "E1/E2 and Se1/Se2 must be ordered consistently"); - } - - if (!ports_.out.template port().connected()) - { - Log::error() << "Esdc1a: required efd output signal is not assigned\n"; - ret += 1; + checks.check(sat_ordered, "E1/E2 and Se1/Se2 must be ordered consistently"); } - if (Spdmlt_ && !ports_.in.template port().connected()) - { - Log::error() << "Esdc1a: speed signal is required when Spdmlt is enabled\n"; - ret += 1; - } + checks.check(ports_.out.template port().connected(), + "required efd output signal is not assigned"); - // An attached port must resolve to writable signal storage. The - // enumerator is a template argument, so each port names itself once. - auto check_attached_signal = - [&](const char* name) - { - if (ports_.in.template port().connected() - && !ports_.in.template port().linked()) - { - Log::error() << "Esdc1a: " << name << " signal attached with no linked source\n"; - ret += 1; - } - }; + checks.check(!Spdmlt_ || ports_.in.template port().connected(), + "speed signal is required when Spdmlt is enabled"); - check_attached_signal.template operator()("speed"); - check_attached_signal.template operator()("vref"); - check_attached_signal.template operator()("vs"); - check_attached_signal.template operator()("vuel"); + const auto speed_port = ports_.in.template port(); + checks.check(!speed_port.connected() || speed_port.linked(), "speed signal attached with no linked source"); + const auto vref_port = ports_.in.template port(); + checks.check(!vref_port.connected() || vref_port.linked(), "vref signal attached with no linked source"); + const auto vs_port = ports_.in.template port(); + checks.check(!vs_port.connected() || vs_port.linked(), "vs signal attached with no linked source"); + const auto vuel_port = ports_.in.template port(); + checks.check(!vuel_port.connected() || vuel_port.linked(), "vuel signal attached with no linked source"); - return ret; + return checks; } /** @@ -250,14 +228,18 @@ namespace GridKit const auto VFE = static_cast(Esdc1aInternalVariables::VFE); const auto EFD = static_cast(Esdc1aInternalVariables::EFD); - bool ret = verify() == 0; - if (!ret) + const auto checks = verify(); + for (const auto& error : checks.errors()) + { + Log::error() << "Esdc1a: " << error << '\n'; + } + if (!checks.passed()) { - Log::error() << "Esdc1a: cannot initialize with invalid configuration\n"; return 1; } - auto* y = y_.getData(); + bool ret = true; + auto* y = y_.getData(); // The assigned efd node aliases this entry after allocate(). Its // seeded value remains untouched throughout initialization. @@ -636,8 +618,8 @@ namespace GridKit * * No parameter is required; every parameter keeps the default * documented in the model README when omitted. A non-numeric value, a - * switch outside \f$\{0,1\}\f$, or a non-integer selector is counted and - * reported by verify() rather than throwing. Integer JSON values are + * switch outside \f$\{0,1\}\f$, a non-integer selector, or a negative + * lag is rejected with std::invalid_argument. Integer JSON values are * accepted for real parameters. * * @param[in] data Parameters and monitored-variable selections. @@ -647,99 +629,27 @@ namespace GridKit { using Params = typename ModelDataT::Parameters; - parameter_error_count_ = 0; + Model::ParameterReader reader(data, "Esdc1a"); + + reader.loadReal(Params::Tr, Tr_); + reader.loadReal(Params::Ka, Ka_); + reader.loadReal(Params::Ta, Ta_); + reader.loadReal(Params::Tb, Tb_); + reader.loadReal(Params::Tc, Tc_); + reader.loadReal(Params::Vrmax, Vrmax_); + reader.loadReal(Params::Vrmin, Vrmin_); + reader.loadReal(Params::Ke, Ke_); + reader.loadReal(Params::Te, Te_); + reader.loadReal(Params::Kf, Kf_); + reader.loadReal(Params::Tf1, Tf1_); + reader.loadSwitch(Params::Spdmlt, Spdmlt_); + reader.loadReal(Params::E1, E1_); + reader.loadReal(Params::Se1, Se1_); + reader.loadReal(Params::E2, E2_); + reader.loadReal(Params::Se2, Se2_); + reader.loadSelector(Params::UEL, UEL_); + reader.loadSwitch(Params::exclim, exclim_); - auto load_real = [&](auto key, RealT& target, const char* name) - { - if (!data.parameters.contains(key)) - { - return; - } - - const auto& value = data.parameters.at(key); - RealT parsed_value{}; - if (const auto* real_value = std::get_if(&value)) - { - parsed_value = *real_value; - } - else if (const auto* index_value = std::get_if(&value)) - { - parsed_value = static_cast(*index_value); - } - else - { - Log::error() << "Esdc1a: parameter '" << name << "' must be numeric\n"; - ++parameter_error_count_; - return; - } - - const bool ret = std::isfinite(parsed_value); - if (!ret) - { - Log::error() << "Esdc1a: parameter '" << name << "' must be finite\n"; - ++parameter_error_count_; - return; - } - - target = parsed_value; - }; - - auto load_switch = [&](auto key, bool& target, const char* name) - { - if (!data.parameters.contains(key)) - { - return; - } - - const auto& value = data.parameters.at(key); - if (const auto* bool_value = std::get_if(&value)) - { - target = *bool_value; - } - else - { - Log::error() << "Esdc1a: parameter '" << name << "' must be boolean\n"; - ++parameter_error_count_; - } - }; - - auto load_selector = [&](auto key, IdxT& target, const char* name) - { - if (!data.parameters.contains(key)) - { - return; - } - - const auto& value = data.parameters.at(key); - if (const auto* index_value = std::get_if(&value)) - { - target = *index_value; - } - else - { - Log::error() << "Esdc1a: parameter '" << name << "' must be an integer selector\n"; - ++parameter_error_count_; - } - }; - - load_real(Params::Tr, Tr_, "Tr"); - load_real(Params::Ka, Ka_, "Ka"); - load_real(Params::Ta, Ta_, "Ta"); - load_real(Params::Tb, Tb_, "Tb"); - load_real(Params::Tc, Tc_, "Tc"); - load_real(Params::Vrmax, Vrmax_, "Vrmax"); - load_real(Params::Vrmin, Vrmin_, "Vrmin"); - load_real(Params::Ke, Ke_, "Ke"); - load_real(Params::Te, Te_, "Te"); - load_real(Params::Kf, Kf_, "Kf"); - load_real(Params::Tf1, Tf1_, "Tf1"); - load_switch(Params::Spdmlt, Spdmlt_, "Spdmlt"); - load_real(Params::E1, E1_, "E1"); - load_real(Params::Se1, Se1_, "Se1"); - load_real(Params::E2, E2_, "E2"); - load_real(Params::Se2, Se2_, "Se2"); - load_selector(Params::UEL, UEL_, "UEL"); - load_switch(Params::exclim, exclim_, "exclim"); setDerivedParameters(); } @@ -796,22 +706,16 @@ namespace GridKit void Esdc1a::setDerivedParameters() { // The lags are raised to the floor in place, so a negative value is - // rejected here while the value as read is still available. verify() - // reports the count. - auto check_non_negative = [&](RealT value, const char* name) + // rejected here while the value as read is still available. + const std::array, 5> lags{ + {{Tr_, "Tr"}, {Ta_, "Ta"}, {Tb_, "Tb"}, {Te_, "Te"}, {Tf1_, "Tf1"}}}; + for (const auto& [value, name] : lags) { if (value < ZERO) { - Log::error() << "Esdc1a: " << name << " must be non-negative\n"; - ++parameter_error_count_; + throw std::invalid_argument(std::string("Esdc1a: ") + name + " must be non-negative"); } - }; - - check_non_negative(Tr_, "Tr"); - check_non_negative(Ta_, "Ta"); - check_non_negative(Tb_, "Tb"); - check_non_negative(Te_, "Te"); - check_non_negative(Tf1_, "Tf1"); + } if (Tr_ < TIME_CONSTANT_MINIMUM || Ta_ < TIME_CONSTANT_MINIMUM || Tb_ < TIME_CONSTANT_MINIMUM || Te_ < TIME_CONSTANT_MINIMUM diff --git a/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1.hpp b/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1.hpp index f45eef4f0..c17e8d1a2 100644 --- a/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1.hpp +++ b/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1.hpp @@ -85,14 +85,14 @@ namespace GridKit Ieeet1(BusT* bus, const ModelDataT& data); ~Ieeet1(); - int setGridKitComponentID(IdxT) override final; - int allocate() override final; - int verify() const override final; - int initialize() override final; - int tagDifferentiable() override final; - int setAbsoluteTolerance(RealT rel_tol) override final; - int evaluateResidual() override final; - int evaluateJacobian() override final; + int setGridKitComponentID(IdxT) override final; + int allocate() override final; + Model::ConfigurationChecks verify() const override final; + int initialize() override final; + int tagDifferentiable() override final; + int setAbsoluteTolerance(RealT rel_tol) override final; + int evaluateResidual() override final; + int evaluateJacobian() override final; SignalPortsT& getPorts() { diff --git a/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1Impl.hpp b/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1Impl.hpp index 6be4cf1eb..3fe974dfd 100644 --- a/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1Impl.hpp +++ b/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1Impl.hpp @@ -11,6 +11,8 @@ #include #include +#include +#include #include #include #include @@ -126,56 +128,41 @@ namespace GridKit * @brief Verify parameter values and attached signal links */ template - int Ieeet1::verify() const + Model::ConfigurationChecks Ieeet1::verify() const { - int ret = 0; + Model::ConfigurationChecks checks; - auto check = [&](bool condition, const char* message) - { - if (!condition) - { - Log::error() << "Ieeet1: " << message << '\n'; - ret += 1; - } - }; - - check(Ka_ > ZERO, "Ka must be positive"); - check(Vrmin_ <= Vrmax_, "Vrmin must be less than or equal to Vrmax"); - check(Ispdlim_ == ZERO || Ispdlim_ == ONE, - "Ispdlim must be 0 or 1"); + checks.check(Ka_ > ZERO, "Ka must be positive"); + checks.check(Vrmin_ <= Vrmax_, "Vrmin must be less than or equal to Vrmax"); + checks.check(Ispdlim_ == ZERO || Ispdlim_ == ONE, + "Ispdlim must be 0 or 1"); const bool saturation_disabled = Se1_ == ZERO && Se2_ == ZERO; if (!saturation_disabled) { - check(E1_ > ZERO, "E1 must be positive when saturation is enabled"); - check(E2_ > ZERO, "E2 must be positive when saturation is enabled"); - check(Se1_ >= ZERO, "Se1 must be non-negative when saturation is enabled"); - check(Se2_ >= ZERO, "Se2 must be non-negative when saturation is enabled"); + checks.check(E1_ > ZERO, "E1 must be positive when saturation is enabled"); + checks.check(E2_ > ZERO, "E2 must be positive when saturation is enabled"); + checks.check(Se1_ >= ZERO, "Se1 must be non-negative when saturation is enabled"); + checks.check(Se2_ >= ZERO, "Se2 must be non-negative when saturation is enabled"); const bool sat_ordered = (E2_ > E1_ && Se2_ > Se1_) || (E2_ < E1_ && Se2_ < Se1_); - check(sat_ordered, "E1/E2 and Se1/Se2 must be ordered consistently"); + checks.check(sat_ordered, "E1/E2 and Se1/Se2 must be ordered consistently"); } - auto check_attached_signal = - [&](const char* name) - { - auto port = ports_.in.template port(); - if (port.connected() && !port.linked()) - { - Log::error() << "Ieeet1: " << name << " signal attached with no linked source\n"; - ret += 1; - } - }; - - check_attached_signal.template operator()("speed"); - check_attached_signal.template operator()("vref"); - check_attached_signal.template operator()("vs"); - check_attached_signal.template operator()("vuel"); - check_attached_signal.template operator()("voel"); + const auto speed_port = ports_.in.template port(); + checks.check(!speed_port.connected() || speed_port.linked(), "speed signal attached with no linked source"); + const auto vref_port = ports_.in.template port(); + checks.check(!vref_port.connected() || vref_port.linked(), "vref signal attached with no linked source"); + const auto vs_port = ports_.in.template port(); + checks.check(!vs_port.connected() || vs_port.linked(), "vs signal attached with no linked source"); + const auto vuel_port = ports_.in.template port(); + checks.check(!vuel_port.connected() || vuel_port.linked(), "vuel signal attached with no linked source"); + const auto voel_port = ports_.in.template port(); + checks.check(!voel_port.connected() || voel_port.linked(), "voel signal attached with no linked source"); - return ret; + return checks; } /** @@ -205,9 +192,13 @@ namespace GridKit template int Ieeet1::initialize() { - if (verify() != 0) + const auto checks = verify(); + for (const auto& error : checks.errors()) + { + Log::error() << "Ieeet1: " << error << '\n'; + } + if (!checks.passed()) { - Log::error() << "Ieeet1: cannot initialize with invalid configuration\n"; return 1; } @@ -228,19 +219,10 @@ namespace GridKit } // Setpoint members provide the defaults for unattached signals. - auto read_signal = [&](const ScalarT& default_value) -> ScalarT - { - if (auto port = ports_.in.template port()) - { - return port.readSignal(); - } - return default_value; - }; - - const ScalarT omega = read_signal.template operator()(omega_set_); - const ScalarT vs = read_signal.template operator()(vs_set_); - const ScalarT vuel = read_signal.template operator()(vuel_set_); - const ScalarT voel = read_signal.template operator()(voel_set_); + const ScalarT omega = ports_.in.template port().readOrDefault(omega_set_); + const ScalarT vs = ports_.in.template port().readOrDefault(vs_set_); + const ScalarT vuel = ports_.in.template port().readOrDefault(vuel_set_); + const ScalarT voel = ports_.in.template port().readOrDefault(voel_set_); uel_on_ = ZERO; if (ports_.in.template port()) @@ -436,32 +418,47 @@ namespace GridKit template int Ieeet1::evaluateResidual() { + const auto OMEGA = static_cast(Ieeet1ExternalVariables::OMEGA); + const auto VREF = static_cast(Ieeet1ExternalVariables::VREF); + const auto VS = static_cast(Ieeet1ExternalVariables::VS); + const auto VUEL = static_cast(Ieeet1ExternalVariables::VUEL); + const auto VOEL = static_cast(Ieeet1ExternalVariables::VOEL); + auto* ws = ws_.getData(); // Attached signals are read live; unattached ones keep the latched value. - auto read_signal = [&](const ScalarT& latched) + ws[OMEGA] = omega_set_; + ws[VREF] = vref_set_; + ws[VS] = vs_set_; + ws[VUEL] = vuel_set_; + ws[VOEL] = voel_set_; + std::fill(ws_indices_.begin(), ws_indices_.end(), INVALID_INDEX); + + if (auto port = ports_.in.template port()) { - const auto index = static_cast(variable); - ws[index] = latched; - ws_indices_[index] = INVALID_INDEX; - if (auto port = ports_.in.template port()) - { - ws[index] = port.readSignal(); - ws_indices_[index] = port.signalVariableIndex(); - } - }; - - read_signal.template operator()(omega_set_); - read_signal.template operator()(vref_set_); - read_signal.template operator()(vs_set_); - read_signal.template operator()(vuel_set_); - read_signal.template operator()(voel_set_); + ws[OMEGA] = port.readSignal(); + ws_indices_[OMEGA] = port.signalVariableIndex(); + } + if (auto port = ports_.in.template port()) + { + ws[VREF] = port.readSignal(); + ws_indices_[VREF] = port.signalVariableIndex(); + } + if (auto port = ports_.in.template port()) + { + ws[VS] = port.readSignal(); + ws_indices_[VS] = port.signalVariableIndex(); + } + if (auto port = ports_.in.template port()) + { + ws[VUEL] = port.readSignal(); + ws_indices_[VUEL] = port.signalVariableIndex(); + } + if (auto port = ports_.in.template port()) + { + ws[VOEL] = port.readSignal(); + ws_indices_[VOEL] = port.signalVariableIndex(); + } // Bus voltages auto* wb = wb_.getData(); @@ -487,62 +484,22 @@ namespace GridKit { using Parameter = typename ModelDataT::Parameters; - if (data.parameters.contains(Parameter::Tr)) - { - Tr_ = std::get(data.parameters.at(Parameter::Tr)); - } - if (data.parameters.contains(Parameter::Ka)) - { - Ka_ = std::get(data.parameters.at(Parameter::Ka)); - } - if (data.parameters.contains(Parameter::Ta)) - { - Ta_ = std::get(data.parameters.at(Parameter::Ta)); - } - if (data.parameters.contains(Parameter::Ke)) - { - Ke_ = std::get(data.parameters.at(Parameter::Ke)); - } - if (data.parameters.contains(Parameter::Te)) - { - Te_ = std::get(data.parameters.at(Parameter::Te)); - } - if (data.parameters.contains(Parameter::Kf)) - { - Kf_ = std::get(data.parameters.at(Parameter::Kf)); - } - if (data.parameters.contains(Parameter::Tf)) - { - Tf_ = std::get(data.parameters.at(Parameter::Tf)); - } - if (data.parameters.contains(Parameter::Vrmin)) - { - Vrmin_ = std::get(data.parameters.at(Parameter::Vrmin)); - } - if (data.parameters.contains(Parameter::Vrmax)) - { - Vrmax_ = std::get(data.parameters.at(Parameter::Vrmax)); - } - if (data.parameters.contains(Parameter::E1)) - { - E1_ = std::get(data.parameters.at(Parameter::E1)); - } - if (data.parameters.contains(Parameter::E2)) - { - E2_ = std::get(data.parameters.at(Parameter::E2)); - } - if (data.parameters.contains(Parameter::Se1)) - { - Se1_ = std::get(data.parameters.at(Parameter::Se1)); - } - if (data.parameters.contains(Parameter::Se2)) - { - Se2_ = std::get(data.parameters.at(Parameter::Se2)); - } - if (data.parameters.contains(Parameter::Ispdlim)) - { - Ispdlim_ = std::get(data.parameters.at(Parameter::Ispdlim)); - } + Model::ParameterReader reader(data, "Ieeet1"); + + reader.loadReal(Parameter::Tr, Tr_); + reader.loadReal(Parameter::Ka, Ka_); + reader.loadReal(Parameter::Ta, Ta_); + reader.loadReal(Parameter::Ke, Ke_); + reader.loadReal(Parameter::Te, Te_); + reader.loadReal(Parameter::Kf, Kf_); + reader.loadReal(Parameter::Tf, Tf_); + reader.loadReal(Parameter::Vrmin, Vrmin_); + reader.loadReal(Parameter::Vrmax, Vrmax_); + reader.loadReal(Parameter::E1, E1_); + reader.loadReal(Parameter::E2, E2_); + reader.loadReal(Parameter::Se1, Se1_); + reader.loadReal(Parameter::Se2, Se2_); + reader.loadReal(Parameter::Ispdlim, Ispdlim_); setDerivedParameters(); } diff --git a/GridKit/Model/PhasorDynamics/Exciter/SEXS-PTI/SexsPti.hpp b/GridKit/Model/PhasorDynamics/Exciter/SEXS-PTI/SexsPti.hpp index fbc3687e1..c458bb836 100644 --- a/GridKit/Model/PhasorDynamics/Exciter/SEXS-PTI/SexsPti.hpp +++ b/GridKit/Model/PhasorDynamics/Exciter/SEXS-PTI/SexsPti.hpp @@ -72,14 +72,14 @@ namespace GridKit SexsPti(BusT* bus, const ModelDataT& data); ~SexsPti(); - int setGridKitComponentID(IdxT) override final; - int allocate() override final; - int verify() const override final; - int initialize() override final; - int tagDifferentiable() override final; - int setAbsoluteTolerance(RealT rel_tol) override final; - int evaluateResidual() override final; - int evaluateJacobian() override final; + int setGridKitComponentID(IdxT) override final; + int allocate() override final; + Model::ConfigurationChecks verify() const override final; + int initialize() override final; + int tagDifferentiable() override final; + int setAbsoluteTolerance(RealT rel_tol) override final; + int evaluateResidual() override final; + int evaluateJacobian() override final; SignalPortsT& getPorts() { @@ -101,8 +101,6 @@ namespace GridKit RealT Efdmax_{0}; RealT Efdmin_{0}; - int missing_param_count_{0}; - // Runtime connection masks keep the summing junction Enzyme sparse-solvable RealT uel_on_{0}; RealT oel_on_{0}; diff --git a/GridKit/Model/PhasorDynamics/Exciter/SEXS-PTI/SexsPtiImpl.hpp b/GridKit/Model/PhasorDynamics/Exciter/SEXS-PTI/SexsPtiImpl.hpp index 5c9b035d2..707db5a86 100644 --- a/GridKit/Model/PhasorDynamics/Exciter/SEXS-PTI/SexsPtiImpl.hpp +++ b/GridKit/Model/PhasorDynamics/Exciter/SEXS-PTI/SexsPtiImpl.hpp @@ -6,8 +6,11 @@ * @brief Definition of the SEXS-PTI exciter model. */ +#include #include +#include +#include #include #include #include @@ -91,65 +94,30 @@ namespace GridKit } template - int SexsPti::verify() const + Model::ConfigurationChecks SexsPti::verify() const { - int ret = missing_param_count_; - - if (bus_ == nullptr) - { - Log::error() << "SexsPti: bus pointer is null\n"; - ret += 1; - } - if (Ta_ < 0.0) - { - Log::error() << "SexsPti: Ta must be non-negative\n"; - ret += 1; - } - if (Tb_ <= 0.0) - { - Log::error() << "SexsPti: Tb must be positive\n"; - ret += 1; - } - if (Te_ <= 0.0) - { - Log::error() << "SexsPti: Te must be positive\n"; - ret += 1; - } - if (K_ <= 0.0) - { - Log::error() << "SexsPti: K must be positive\n"; - ret += 1; - } - if (Efdmin_ >= Efdmax_) - { - Log::error() << "SexsPti: Efdmin must be less than Efdmax\n"; - ret += 1; - } - - auto efd_port = ports_.out.template port(); - if (!efd_port.connected() || !efd_port.linked()) - { - Log::error() << "SexsPti: required EFD signal is not assigned\n"; - ret += 1; - } - - auto check_attached_signal = - [&](const char* name) - { - auto port = ports_.in.template port(); - if (port.connected() && !port.linked()) - { - Log::error() << "SexsPti: " << name << " signal attached with no linked source\n"; - ret += 1; - } - }; - - check_attached_signal.template operator()("vref"); - check_attached_signal.template operator()("vs"); - check_attached_signal.template operator()("vuel"); - check_attached_signal.template operator()("voel"); - - return ret; + Model::ConfigurationChecks checks; + + checks.check(bus_ != nullptr, "bus pointer is null"); + checks.check(Ta_ >= 0.0, "Ta must be non-negative"); + checks.check(Tb_ > 0.0, "Tb must be positive"); + checks.check(Te_ > 0.0, "Te must be positive"); + checks.check(K_ > 0.0, "K must be positive"); + checks.check(Efdmin_ < Efdmax_, "Efdmin must be less than Efdmax"); + + checks.check(ports_.out.template port().connected(), + "required EFD signal is not assigned"); + + const auto vref_port = ports_.in.template port(); + checks.check(!vref_port.connected() || vref_port.linked(), "vref signal attached with no linked source"); + const auto vs_port = ports_.in.template port(); + checks.check(!vs_port.connected() || vs_port.linked(), "vs signal attached with no linked source"); + const auto vuel_port = ports_.in.template port(); + checks.check(!vuel_port.connected() || vuel_port.linked(), "vuel signal attached with no linked source"); + const auto voel_port = ports_.in.template port(); + checks.check(!voel_port.connected() || voel_port.linked(), "voel signal attached with no linked source"); + + return checks; } template @@ -165,18 +133,9 @@ namespace GridKit } // Setpoint members provide the defaults for unattached signals. - auto read_signal = [&](const ScalarT& default_value) -> ScalarT - { - if (auto port = ports_.in.template port()) - { - return port.readSignal(); - } - return default_value; - }; - - const ScalarT vs = read_signal.template operator()(vs_set_); - const ScalarT vuel = read_signal.template operator()(vuel_set_); - const ScalarT voel = read_signal.template operator()(voel_set_); + const ScalarT vs = ports_.in.template port().readOrDefault(vs_set_); + const ScalarT vuel = ports_.in.template port().readOrDefault(vuel_set_); + const ScalarT voel = ports_.in.template port().readOrDefault(voel_set_); uel_on_ = ZERO; if (ports_.in.template port()) @@ -294,30 +253,40 @@ namespace GridKit template int SexsPti::evaluateResidual() { + const auto VREF = static_cast(SexsPtiExternalVariables::VREF); + const auto VS = static_cast(SexsPtiExternalVariables::VS); + const auto VUEL = static_cast(SexsPtiExternalVariables::VUEL); + const auto VOEL = static_cast(SexsPtiExternalVariables::VOEL); + auto* ws = ws_.getData(); // Attached signals are read live; unattached ones keep the latched value. - auto read_signal = [&](const ScalarT& latched) + ws[VREF] = vref_set_; + ws[VS] = vs_set_; + ws[VUEL] = vuel_set_; + ws[VOEL] = voel_set_; + std::fill(ws_indices_.begin(), ws_indices_.end(), INVALID_INDEX); + + if (auto port = ports_.in.template port()) { - const auto index = static_cast(variable); - ws[index] = latched; - ws_indices_[index] = INVALID_INDEX; - if (auto port = ports_.in.template port()) - { - ws[index] = port.readSignal(); - ws_indices_[index] = port.signalVariableIndex(); - } - }; - - read_signal.template operator()(vref_set_); - read_signal.template operator()(vs_set_); - read_signal.template operator()(vuel_set_); - read_signal.template operator()(voel_set_); + ws[VREF] = port.readSignal(); + ws_indices_[VREF] = port.signalVariableIndex(); + } + if (auto port = ports_.in.template port()) + { + ws[VS] = port.readSignal(); + ws_indices_[VS] = port.signalVariableIndex(); + } + if (auto port = ports_.in.template port()) + { + ws[VUEL] = port.readSignal(); + ws_indices_[VUEL] = port.signalVariableIndex(); + } + if (auto port = ports_.in.template port()) + { + ws[VOEL] = port.readSignal(); + ws_indices_[VOEL] = port.signalVariableIndex(); + } auto* wb = wb_.getData(); wb[0] = bus_->Vr(); @@ -338,27 +307,14 @@ namespace GridKit { using Params = typename ModelDataT::Parameters; - missing_param_count_ = 0; + Model::ParameterReader reader(data, "SexsPti"); - auto load = [&](auto param, RealT& member, const char* name) - { - if (data.parameters.contains(param)) - { - member = std::get(data.parameters.at(param)); - } - else - { - Log::error() << "SexsPti: missing required parameter '" << name << "'\n"; - ++missing_param_count_; - } - }; - - load(Params::Ta, Ta_, "Ta"); - load(Params::Tb, Tb_, "Tb"); - load(Params::Te, Te_, "Te"); - load(Params::K, K_, "K"); - load(Params::Efdmax, Efdmax_, "Efdmax"); - load(Params::Efdmin, Efdmin_, "Efdmin"); + reader.requireReal(Params::Ta, Ta_); + reader.requireReal(Params::Tb, Tb_); + reader.requireReal(Params::Te, Te_); + reader.requireReal(Params::K, K_); + reader.requireReal(Params::Efdmax, Efdmax_); + reader.requireReal(Params::Efdmin, Efdmin_); } template diff --git a/GridKit/Model/PhasorDynamics/Governor/GASTPTI/GastPti.hpp b/GridKit/Model/PhasorDynamics/Governor/GASTPTI/GastPti.hpp index 300218d25..69e902082 100644 --- a/GridKit/Model/PhasorDynamics/Governor/GASTPTI/GastPti.hpp +++ b/GridKit/Model/PhasorDynamics/Governor/GASTPTI/GastPti.hpp @@ -86,14 +86,14 @@ namespace GridKit explicit GastPti(const ModelDataT& data); ~GastPti(); - int setGridKitComponentID(IdxT component_id) override final; - int allocate() override final; - int verify() const override final; - int initialize() override final; - int tagDifferentiable() override final; - int setAbsoluteTolerance(RealT rel_tol) override final; - int evaluateResidual() override final; - int evaluateJacobian() override final; + int setGridKitComponentID(IdxT component_id) override final; + int allocate() override final; + Model::ConfigurationChecks verify() const override final; + int initialize() override final; + int tagDifferentiable() override final; + int setAbsoluteTolerance(RealT rel_tol) override final; + int evaluateResidual() override final; + int evaluateJacobian() override final; SignalPortsT& getPorts() { @@ -110,10 +110,6 @@ namespace GridKit ScalarT* f); private: - void loadRealParameter(const ModelDataT& data, - GastPtiParameters parameter, - RealT& target, - const char* name); bool floorTimeConstant(RealT& value, const char* name); void initializeParameters(const ModelDataT& data); void initializeMonitor(); @@ -124,20 +120,18 @@ namespace GridKit static constexpr RealT TIME_CONSTANT_MINIMUM = static_cast(1.0e-3); static void logTimeConstantWarning(); - RealT R_{static_cast(0.05)}; - RealT T1_{static_cast(0.4)}; - RealT T2_{static_cast(0.1)}; - RealT T3_{static_cast(3.0)}; - RealT At_{ONE}; - RealT Kt_{static_cast(2.0)}; - RealT Vmax_{ONE}; - RealT Vmin_{ZERO}; - RealT Dturb_{ZERO}; - RealT Vmin_response_{ZERO}; - RealT Vmax_response_{ONE}; - RealT s_valve_{ONE}; - - IdxT parameter_error_count_{0}; + RealT R_{static_cast(0.05)}; + RealT T1_{static_cast(0.4)}; + RealT T2_{static_cast(0.1)}; + RealT T3_{static_cast(3.0)}; + RealT At_{ONE}; + RealT Kt_{static_cast(2.0)}; + RealT Vmax_{ONE}; + RealT Vmin_{ZERO}; + RealT Dturb_{ZERO}; + RealT Vmin_response_{ZERO}; + RealT Vmax_response_{ONE}; + RealT s_valve_{ONE}; ScalarT pref_set_{0}; SignalPortsT ports_; diff --git a/GridKit/Model/PhasorDynamics/Governor/GASTPTI/GastPtiImpl.hpp b/GridKit/Model/PhasorDynamics/Governor/GASTPTI/GastPtiImpl.hpp index e35cc8a2a..5924a4c39 100644 --- a/GridKit/Model/PhasorDynamics/Governor/GASTPTI/GastPtiImpl.hpp +++ b/GridKit/Model/PhasorDynamics/Governor/GASTPTI/GastPtiImpl.hpp @@ -9,8 +9,12 @@ #include #include #include +#include +#include #include +#include +#include #include #include #include @@ -128,87 +132,62 @@ namespace GridKit * mechanical-power output assignment, attached external signals, and * distinct indexed ports. * - * @return int Number of configuration errors; zero when valid. + * @return The configuration checks; passed() when valid. */ template - int GastPti::verify() const + Model::ConfigurationChecks GastPti::verify() const { const auto PMECH = static_cast(GastPtiInternalVariables::PMECH); - int ret = static_cast(parameter_error_count_); + Model::ConfigurationChecks checks; - auto check = [&](bool condition, const char* message) - { - if (!condition) - { - Log::error() << "GastPti: " << message << '\n'; - ret += 1; - } - }; - - check(std::isfinite(R_) && R_ > ZERO, "R must be finite and positive"); - check(std::isfinite(At_) && At_ >= ZERO, - "At must be finite and non-negative"); - check(std::isfinite(Kt_) && Kt_ >= ZERO, - "Kt must be finite and non-negative"); + checks.check(std::isfinite(R_) && R_ > ZERO, "R must be finite and positive"); + checks.check(std::isfinite(At_) && At_ >= ZERO, + "At must be finite and non-negative"); + checks.check(std::isfinite(Kt_) && Kt_ >= ZERO, + "Kt must be finite and non-negative"); const bool finite_limits = std::isfinite(Vmin_) && std::isfinite(Vmax_); - check(finite_limits, "Vmin and Vmax must be finite"); + checks.check(finite_limits, "Vmin and Vmax must be finite"); if (finite_limits) { - check(Vmin_ <= Vmax_, "Vmin must be less than or equal to Vmax"); + checks.check(Vmin_ <= Vmax_, "Vmin must be less than or equal to Vmax"); } - check(std::isfinite(Dturb_) && Dturb_ >= ZERO, - "Dturb must be finite and non-negative"); + checks.check(std::isfinite(Dturb_) && Dturb_ >= ZERO, + "Dturb must be finite and non-negative"); const bool valid_component_base = std::isfinite(va_component_base_) && va_component_base_ > ZERO; const bool valid_system_base = std::isfinite(va_system_base_) && va_system_base_ > ZERO; - check(valid_component_base, "component power base must be finite and positive"); - check(valid_system_base, "system power base must be finite and positive"); + checks.check(valid_component_base, "component power base must be finite and positive"); + checks.check(valid_system_base, "system power base must be finite and positive"); if (valid_component_base && valid_system_base) { const RealT system_to_component = va_system_base_ / va_component_base_; const RealT component_to_system = va_component_base_ / va_system_base_; - check(std::isfinite(system_to_component) - && system_to_component > ZERO - && std::isfinite(component_to_system) - && component_to_system > ZERO, - "system/component power-base conversion ratios must be finite and positive"); + checks.check(std::isfinite(system_to_component) + && system_to_component > ZERO + && std::isfinite(component_to_system) + && component_to_system > ZERO, + "system/component power-base conversion ratios must be finite and positive"); } - check(ports_.out.template port().connected(), - "pmech output must be assigned"); + checks.check(ports_.out.template port().connected(), + "pmech output must be assigned"); - // An attached port must resolve to writable signal storage. - auto check_attached_signal = - [&](const char* name) -> bool - { - auto port = ports_.in.template port(); - - if (port.connected()) - { - if (!port.linked()) - { - Log::error() << "GastPti: " << name << " port attached with no linked source\n"; - ret += 1; - } - else - { - return true; - } - } - return false; - }; + const auto speed_port = ports_.in.template port(); + checks.check(!speed_port.connected() || speed_port.linked(), "speed signal attached with no linked source"); + const auto pref_port = ports_.in.template port(); + checks.check(!pref_port.connected() || pref_port.linked(), "pref signal attached with no linked source"); - const auto omega_linked = - check_attached_signal - .template operator()("speed"); - const auto pref_linked = - check_attached_signal - .template operator()("pref"); + const bool omega_linked = + ports_.in.template port().connected() + && ports_.in.template port().linked(); + const bool pref_linked = + ports_.in.template port().connected() + && ports_.in.template port().linked(); if (variable_indices_.size() == static_cast(size_)) { @@ -216,15 +195,15 @@ namespace GridKit if (omega_linked) { - auto omega = ports_.in.template port(); - check(omega.signalVariableIndex() != pmech_index, - "speed and pmech ports must use distinct signals"); + checks.check(ports_.in.template port().signalVariableIndex() + != pmech_index, + "speed and pmech ports must use distinct signals"); } if (pref_linked) { - auto pref = ports_.in.template port(); - check(pref.signalVariableIndex() != pmech_index, - "pref and pmech ports must use distinct signals"); + checks.check(ports_.in.template port().signalVariableIndex() + != pmech_index, + "pref and pmech ports must use distinct signals"); } if (omega_linked && pref_linked) { @@ -236,13 +215,13 @@ namespace GridKit if (omega_index != INVALID_INDEX || pref_index != INVALID_INDEX) { - check(omega_index != pref_index, - "speed and pref ports must use distinct signals"); + checks.check(omega_index != pref_index, + "speed and pref ports must use distinct signals"); } } } - return ret; + return checks; } /** @@ -283,9 +262,13 @@ namespace GridKit return 1; } - if (verify() > 0) + const auto checks = verify(); + for (const auto& error : checks.errors()) + { + Log::error() << "GastPti: " << error << '\n'; + } + if (!checks.passed()) { - Log::error() << "GastPti: cannot initialize with invalid configuration\n"; return 1; } @@ -567,45 +550,6 @@ namespace GridKit // Private methods // - /** - * @brief Load one optional real-valued parameter - * - * Real and integer serialized values are accepted. Any other stored - * type records a loading error while preserving the existing default. - * - * @param[in] data Model parameter data. - * @param[in] parameter Parameter key to load. - * @param[in,out] target Stored parameter value. - * @param[in] name Serialized parameter name for diagnostics. - */ - template - void GastPti::loadRealParameter( - const ModelDataT& data, - GastPtiParameters parameter, - RealT& target, - const char* name) - { - if (!data.parameters.contains(parameter)) - { - return; - } - - const auto& value = data.parameters.at(parameter); - if (const auto* real_value = std::get_if(&value)) - { - target = *real_value; - } - else if (const auto* index_value = std::get_if(&value)) - { - target = static_cast(*index_value); - } - else - { - Log::error() << "GastPti: parameter '" << name << "' must be numeric\n"; - ++parameter_error_count_; - } - } - /** * @brief Validate and floor one turbine time constant * @@ -623,11 +567,7 @@ namespace GridKit { if (!std::isfinite(value) || value < ZERO) { - Log::error() << "GastPti: " << name - << " must be finite and non-negative\n"; - ++parameter_error_count_; - value = TIME_CONSTANT_MINIMUM; - return false; + throw std::invalid_argument(std::string("GastPti: ") + name + " must be finite and non-negative"); } const bool raised = value < TIME_CONSTANT_MINIMUM; @@ -648,28 +588,22 @@ namespace GridKit { using Params = typename ModelDataT::Parameters; - parameter_error_count_ = 0; - - loadRealParameter(data, Params::R, R_, "R"); - loadRealParameter(data, Params::T1, T1_, "T1"); - loadRealParameter(data, Params::T2, T2_, "T2"); - loadRealParameter(data, Params::T3, T3_, "T3"); - loadRealParameter(data, Params::At, At_, "At"); - loadRealParameter(data, Params::Kt, Kt_, "Kt"); - loadRealParameter(data, Params::Vmax, Vmax_, "Vmax"); - loadRealParameter(data, Params::Vmin, Vmin_, "Vmin"); - loadRealParameter(data, Params::Dturb, Dturb_, "Dturb"); - if (data.parameters.contains(Params::Trate)) + Model::ParameterReader reader(data, "GastPti"); + + reader.loadReal(Params::R, R_); + reader.loadReal(Params::T1, T1_); + reader.loadReal(Params::T2, T2_); + reader.loadReal(Params::T3, T3_); + reader.loadReal(Params::At, At_); + reader.loadReal(Params::Kt, Kt_); + reader.loadReal(Params::Vmax, Vmax_); + reader.loadReal(Params::Vmin, Vmin_); + reader.loadReal(Params::Dturb, Dturb_); + RealT trate{}; + if (reader.requireReal(Params::Trate, trate)) { - RealT trate{}; - loadRealParameter(data, Params::Trate, trate, "Trate"); this->setComponentBase(trate * static_cast(1.0e6)); } - else - { - Log::error() << "GastPti: missing required parameter 'Trate'\n"; - ++parameter_error_count_; - } setDerivedParameters(); } diff --git a/GridKit/Model/PhasorDynamics/Governor/HYGOV/Hygov.hpp b/GridKit/Model/PhasorDynamics/Governor/HYGOV/Hygov.hpp index 10ed07c26..679c059f5 100644 --- a/GridKit/Model/PhasorDynamics/Governor/HYGOV/Hygov.hpp +++ b/GridKit/Model/PhasorDynamics/Governor/HYGOV/Hygov.hpp @@ -97,14 +97,14 @@ namespace GridKit explicit Hygov(const ModelDataT& data); ~Hygov(); - int setGridKitComponentID(IdxT component_id) override final; - int allocate() override final; - int verify() const override final; - int initialize() override final; - int tagDifferentiable() override final; - int setAbsoluteTolerance(RealT rel_tol) override final; - int evaluateResidual() override final; - int evaluateJacobian() override final; + int setGridKitComponentID(IdxT component_id) override final; + int allocate() override final; + Model::ConfigurationChecks verify() const override final; + int initialize() override final; + int tagDifferentiable() override final; + int setAbsoluteTolerance(RealT rel_tol) override final; + int evaluateResidual() override final; + int evaluateJacobian() override final; SignalPortsT& getPorts() { @@ -173,9 +173,7 @@ namespace GridKit std::array Gv_{}; std::array Pgv_{}; - RealT leadlag_gain_{ZERO}; - - IdxT parameter_error_count_{0}; + RealT leadlag_gain_{ZERO}; RealT Gmin_response_{Gmin_}; RealT Gmax_response_{Gmax_}; RealT Hdam_eff_{Hdam_}; diff --git a/GridKit/Model/PhasorDynamics/Governor/HYGOV/HygovImpl.hpp b/GridKit/Model/PhasorDynamics/Governor/HYGOV/HygovImpl.hpp index 71ada99e0..7db081344 100644 --- a/GridKit/Model/PhasorDynamics/Governor/HYGOV/HygovImpl.hpp +++ b/GridKit/Model/PhasorDynamics/Governor/HYGOV/HygovImpl.hpp @@ -7,10 +7,17 @@ #pragma once #include +#include +#include +#include #include #include -#include +#include +#include +#include +#include +#include #include #include #include @@ -124,30 +131,21 @@ namespace GridKit * power feasibility is operating-point dependent and is checked by * initialize(). * - * @return int Number of configuration errors; zero when valid. + * @return The configuration checks; passed() when valid. */ template - int Hygov::verify() const + Model::ConfigurationChecks Hygov::verify() const { - int ret = static_cast(parameter_error_count_); - - auto check = [&](bool condition, const char* message) - { - if (!condition) - { - Log::error() << "Hygov: " << message << '\n'; - ret += 1; - } - }; + Model::ConfigurationChecks checks; const bool valid_component_base = std::isfinite(va_component_base_) && va_component_base_ > ZERO; const bool valid_system_base = std::isfinite(va_system_base_) && va_system_base_ > ZERO; - check(valid_component_base, - "component power base must be finite and positive"); - check(valid_system_base, - "system power base must be finite and positive"); + checks.check(valid_component_base, + "component power base must be finite and positive"); + checks.check(valid_system_base, + "system power base must be finite and positive"); if (valid_component_base && valid_system_base) { const RealT system_to_component = va_system_base_ / va_component_base_; @@ -156,18 +154,18 @@ namespace GridKit && system_to_component > ZERO && std::isfinite(component_to_system) && component_to_system > ZERO; - check(valid_base_ratios, - "system/component power-base conversion ratios must be finite and positive"); + checks.check(valid_base_ratios, + "system/component power-base conversion ratios must be finite and positive"); } - check(Rtemp_ > ZERO, "Rtemp must be nonzero"); - check(Tn_ >= ZERO, "Tn must be non-negative"); - check(Velm_ >= ZERO, "Velm must be non-negative"); - check(Gmin_ < Gmax_, "Gmin must be less than Gmax"); - check(At_ > ZERO, "At must be positive"); - check(Dturb_ >= ZERO, "Dturb must be non-negative"); - check(db1_ >= ZERO, "db1 must be non-negative"); - check(Hdam_ > ZERO, "Hdam must be positive"); + checks.check(Rtemp_ > ZERO, "Rtemp must be nonzero"); + checks.check(Tn_ >= ZERO, "Tn must be non-negative"); + checks.check(Velm_ >= ZERO, "Velm must be non-negative"); + checks.check(Gmin_ < Gmax_, "Gmin must be less than Gmax"); + checks.check(At_ > ZERO, "At must be positive"); + checks.check(Dturb_ >= ZERO, "Dturb must be non-negative"); + checks.check(db1_ >= ZERO, "db1 must be non-negative"); + checks.check(Hdam_ > ZERO, "Hdam must be positive"); bool curve_shape_is_valid = true; for (size_t i = 1; i < Gv_.size(); ++i) @@ -175,8 +173,8 @@ namespace GridKit const bool gate_points_increase = Gv_[i - 1] < Gv_[i]; const bool power_points_increase = Pgv_[i - 1] <= Pgv_[i]; - check(gate_points_increase, "Gv points must be strictly increasing"); - check(power_points_increase, "Pgv points must be non-decreasing"); + checks.check(gate_points_increase, "Gv points must be strictly increasing"); + checks.check(power_points_increase, "Pgv points must be non-decreasing"); if (!gate_points_increase || !power_points_increase) { @@ -185,8 +183,8 @@ namespace GridKit } const bool minimum_gate_is_valid = Gv_[0] <= Gmin_; const bool maximum_gate_is_valid = Gmax_ <= Gv_[5]; - check(minimum_gate_is_valid, "Gmin must be at or above the first Gv point"); - check(maximum_gate_is_valid, "Gmax must be at or below the last Gv point"); + checks.check(minimum_gate_is_valid, "Gmin must be at or above the first Gv point"); + checks.check(maximum_gate_is_valid, "Gmax must be at or below the last Gv point"); const bool can_check_power_range = curve_shape_is_valid && Gmin_ < Gmax_ @@ -204,36 +202,26 @@ namespace GridKit const bool finite_power_range = std::isfinite(minimum_power) && std::isfinite(maximum_power) && std::isfinite(power_range); - check(finite_power_range, - "mechanical-power range must be finite"); + checks.check(finite_power_range, + "mechanical-power range must be finite"); if (finite_power_range) { - check(power_range > INITIALIZATION_TOLERANCE, - "mechanical power must rise across [Gv0, Gv5]"); + checks.check(power_range > INITIALIZATION_TOLERANCE, + "mechanical power must rise across [Gv0, Gv5]"); } } - check(ports_.out.template port().connected(), - "pmech output signal must be assigned"); + checks.check(ports_.out.template port().connected(), + "pmech output signal must be assigned"); - // An attached port must resolve to readable signal storage. The - // enumerator is a template argument, so each port names itself once. - auto check_attached_signal = - [&](const char* name) - { - if (ports_.in.template port().connected() - && !ports_.in.template port().linked()) - { - Log::error() << "Hygov: " << name << " signal attached with no linked source\n"; - ret += 1; - } - }; + const auto speed_port = ports_.in.template port(); + checks.check(!speed_port.connected() || speed_port.linked(), "speed signal attached with no linked source"); + const auto pref_port = ports_.in.template port(); + checks.check(!pref_port.connected() || pref_port.linked(), "pref signal attached with no linked source"); + const auto paux_port = ports_.in.template port(); + checks.check(!paux_port.connected() || paux_port.linked(), "paux signal attached with no linked source"); - check_attached_signal.template operator()("speed"); - check_attached_signal.template operator()("pref"); - check_attached_signal.template operator()("paux"); - - return ret; + return checks; } /** @@ -275,14 +263,18 @@ namespace GridKit const auto H = static_cast(HygovInternalVariables::H); const auto PMECH = static_cast(HygovInternalVariables::PMECH); - bool ret = verify() == 0; - if (!ret) + const auto checks = verify(); + for (const auto& error : checks.errors()) + { + Log::error() << "Hygov: " << error << '\n'; + } + if (!checks.passed()) { - Log::error() << "Hygov: cannot initialize with invalid configuration\n"; return 1; } - auto* y = y_.getData(); + bool ret = true; + auto* y = y_.getData(); // The assigned pmech node aliases this entry after allocate(). Its // system-base value remains untouched throughout initialization. @@ -636,90 +628,46 @@ namespace GridKit { using Params = typename ModelDataT::Parameters; - parameter_error_count_ = 0; - - auto load_real = [&](auto key, RealT& target, const char* name) -> bool - { - if (!data.parameters.contains(key)) - { - return false; - } - - const auto& value = data.parameters.at(key); - RealT parsed_value{}; - if (const auto* real_value = std::get_if(&value)) - { - parsed_value = *real_value; - } - else if (const auto* index_value = std::get_if(&value)) - { - parsed_value = static_cast(*index_value); - } - else - { - Log::error() << "Hygov: parameter '" << name << "' must be numeric\n"; - ++parameter_error_count_; - return false; - } - - const bool ret = std::isfinite(parsed_value); - if (!ret) - { - Log::error() << "Hygov: parameter '" << name << "' must be finite\n"; - ++parameter_error_count_; - return false; - } - - target = parsed_value; - return true; - }; - - if (data.parameters.contains(Params::Trate)) - { - RealT trate{}; - if (load_real(Params::Trate, trate, "Trate")) - { - this->setComponentBase(trate * static_cast(1.0e6)); - } - } - else - { - Log::error() << "Hygov: missing required parameter 'Trate'\n"; - ++parameter_error_count_; - } - load_real(Params::Rperm, Rperm_, "Rperm"); - load_real(Params::Rtemp, Rtemp_, "Rtemp"); - load_real(Params::Tr, Tr_, "Tr"); - load_real(Params::Tf, Tf_, "Tf"); - load_real(Params::Tg, Tg_, "Tg"); - load_real(Params::Velm, Velm_, "Velm"); - load_real(Params::Gmax, Gmax_, "Gmax"); - load_real(Params::Gmin, Gmin_, "Gmin"); - load_real(Params::Tw, Tw_, "Tw"); - load_real(Params::At, At_, "At"); - load_real(Params::Dturb, Dturb_, "Dturb"); - load_real(Params::Qnl, Qnl_, "Qnl"); - load_real(Params::Tn, Tn_, "Tn"); - load_real(Params::Tnp, Tnp_, "Tnp"); - load_real(Params::db1, db1_, "db1"); - if (load_real(Params::db2, db2_, "db2") && db2_ != ZERO) + Model::ParameterReader reader(data, "Hygov"); + + RealT trate{}; + if (reader.requireReal(Params::Trate, trate)) + { + this->setComponentBase(trate * static_cast(1.0e6)); + } + reader.loadReal(Params::Rperm, Rperm_); + reader.loadReal(Params::Rtemp, Rtemp_); + reader.loadReal(Params::Tr, Tr_); + reader.loadReal(Params::Tf, Tf_); + reader.loadReal(Params::Tg, Tg_); + reader.loadReal(Params::Velm, Velm_); + reader.loadReal(Params::Gmax, Gmax_); + reader.loadReal(Params::Gmin, Gmin_); + reader.loadReal(Params::Tw, Tw_); + reader.loadReal(Params::At, At_); + reader.loadReal(Params::Dturb, Dturb_); + reader.loadReal(Params::Qnl, Qnl_); + reader.loadReal(Params::Tn, Tn_); + reader.loadReal(Params::Tnp, Tnp_); + reader.loadReal(Params::db1, db1_); + if (reader.loadReal(Params::db2, db2_) && db2_ != ZERO) { Log::warning() << "Hygov: nonzero db2 requests mechanical backlash, " "but backlash is not implemented and db2 is ignored\n"; } - load_real(Params::Hdam, Hdam_, "Hdam"); - load_real(Params::Gv0, Gv_[0], "Gv0"); - load_real(Params::Gv1, Gv_[1], "Gv1"); - load_real(Params::Gv2, Gv_[2], "Gv2"); - load_real(Params::Gv3, Gv_[3], "Gv3"); - load_real(Params::Gv4, Gv_[4], "Gv4"); - load_real(Params::Gv5, Gv_[5], "Gv5"); - load_real(Params::Pgv0, Pgv_[0], "Pgv0"); - load_real(Params::Pgv1, Pgv_[1], "Pgv1"); - load_real(Params::Pgv2, Pgv_[2], "Pgv2"); - load_real(Params::Pgv3, Pgv_[3], "Pgv3"); - load_real(Params::Pgv4, Pgv_[4], "Pgv4"); - load_real(Params::Pgv5, Pgv_[5], "Pgv5"); + reader.loadReal(Params::Hdam, Hdam_); + reader.loadReal(Params::Gv0, Gv_[0]); + reader.loadReal(Params::Gv1, Gv_[1]); + reader.loadReal(Params::Gv2, Gv_[2]); + reader.loadReal(Params::Gv3, Gv_[3]); + reader.loadReal(Params::Gv4, Gv_[4]); + reader.loadReal(Params::Gv5, Gv_[5]); + reader.loadReal(Params::Pgv0, Pgv_[0]); + reader.loadReal(Params::Pgv1, Pgv_[1]); + reader.loadReal(Params::Pgv2, Pgv_[2]); + reader.loadReal(Params::Pgv3, Pgv_[3]); + reader.loadReal(Params::Pgv4, Pgv_[4]); + reader.loadReal(Params::Pgv5, Pgv_[5]); setDerivedParameters(); } @@ -795,22 +743,16 @@ namespace GridKit } // The lags are raised to the floor in place, so a negative value is - // rejected here while the value as read is still available. verify() - // reports the count. - auto check_non_negative = [&](RealT value, const char* name) + // rejected here while the value as read is still available. + const std::array, 5> lags{ + {{Tr_, "Tr"}, {Tf_, "Tf"}, {Tg_, "Tg"}, {Tw_, "Tw"}, {Tnp_, "Tnp"}}}; + for (const auto& [value, name] : lags) { if (value < ZERO) { - Log::error() << "Hygov: " << name << " must be non-negative\n"; - ++parameter_error_count_; + throw std::invalid_argument(std::string("Hygov: ") + name + " must be non-negative"); } - }; - - check_non_negative(Tr_, "Tr"); - check_non_negative(Tf_, "Tf"); - check_non_negative(Tg_, "Tg"); - check_non_negative(Tw_, "Tw"); - check_non_negative(Tnp_, "Tnp"); + } if (Tr_ < TIME_CONSTANT_MINIMUM || Tf_ < TIME_CONSTANT_MINIMUM || Tg_ < TIME_CONSTANT_MINIMUM || Tw_ < TIME_CONSTANT_MINIMUM diff --git a/GridKit/Model/PhasorDynamics/Governor/Tgov1/CMakeLists.txt b/GridKit/Model/PhasorDynamics/Governor/Tgov1/CMakeLists.txt index d4647a826..2a41f7157 100644 --- a/GridKit/Model/PhasorDynamics/Governor/Tgov1/CMakeLists.txt +++ b/GridKit/Model/PhasorDynamics/Governor/Tgov1/CMakeLists.txt @@ -11,6 +11,7 @@ if(GRIDKIT_ENABLE_ENZYME) phasor_dynamics_governor_tgov1 SOURCES Tgov1Enzyme.cpp HEADERS ${_install_headers} + INCLUDE_DIRECTORIES PRIVATE ${GRIDKIT_THIRD_PARTY_DIR}/magic-enum/include LINK_LIBRARIES PUBLIC GridKit::phasor_dynamics_core @@ -28,12 +29,14 @@ else() phasor_dynamics_governor_tgov1 SOURCES Tgov1.cpp HEADERS ${_install_headers} + INCLUDE_DIRECTORIES PRIVATE ${GRIDKIT_THIRD_PARTY_DIR}/magic-enum/include LINK_LIBRARIES GridKit::phasor_dynamics_core GridKit::phasor_dynamics_signal) endif() gridkit_add_library( phasor_dynamics_governor_tgov1_dependency_tracking SOURCES Tgov1DependencyTracking.cpp + INCLUDE_DIRECTORIES PRIVATE ${GRIDKIT_THIRD_PARTY_DIR}/magic-enum/include LINK_LIBRARIES PUBLIC GridKit::phasor_dynamics_core diff --git a/GridKit/Model/PhasorDynamics/Governor/Tgov1/Tgov1.hpp b/GridKit/Model/PhasorDynamics/Governor/Tgov1/Tgov1.hpp index dcc5e147e..6d4b16c40 100644 --- a/GridKit/Model/PhasorDynamics/Governor/Tgov1/Tgov1.hpp +++ b/GridKit/Model/PhasorDynamics/Governor/Tgov1/Tgov1.hpp @@ -76,13 +76,13 @@ namespace GridKit Tgov1(const ModelDataT&); ~Tgov1() = default; - int setGridKitComponentID(IdxT) override final; - int allocate() override final; - int verify() const override final; - int initialize() override final; - int tagDifferentiable() override final; - int setAbsoluteTolerance(RealT) override final; - int evaluateResidual() override final; + int setGridKitComponentID(IdxT) override final; + int allocate() override final; + Model::ConfigurationChecks verify() const override final; + int initialize() override final; + int tagDifferentiable() override final; + int setAbsoluteTolerance(RealT) override final; + int evaluateResidual() override final; // Still to be implemented int evaluateJacobian() override final; @@ -110,7 +110,7 @@ namespace GridKit // Input States (which can be parameters) ScalarT pref_set_{0}; - // Component ports + /// Component ports SignalPortsT ports_; // Parameter initialization function diff --git a/GridKit/Model/PhasorDynamics/Governor/Tgov1/Tgov1Impl.hpp b/GridKit/Model/PhasorDynamics/Governor/Tgov1/Tgov1Impl.hpp index 01843aa0f..70b934add 100644 --- a/GridKit/Model/PhasorDynamics/Governor/Tgov1/Tgov1Impl.hpp +++ b/GridKit/Model/PhasorDynamics/Governor/Tgov1/Tgov1Impl.hpp @@ -11,6 +11,8 @@ #include #include +#include +#include #include #include #include @@ -90,45 +92,16 @@ namespace GridKit { using Parameter = typename ModelDataT::Parameters; - if (data.parameters.contains(Parameter::Trate)) - { - Trate_ = std::get(data.parameters.at(Parameter::Trate)); - } - - if (data.parameters.contains(Parameter::R)) - { - R_ = std::get(data.parameters.at(Parameter::R)); - } - - if (data.parameters.contains(Parameter::Pvmin)) - { - Pvmin_ = std::get(data.parameters.at(Parameter::Pvmin)); - } - - if (data.parameters.contains(Parameter::Pvmax)) - { - Pvmax_ = std::get(data.parameters.at(Parameter::Pvmax)); - } - - if (data.parameters.contains(Parameter::T1)) - { - T1_ = std::get(data.parameters.at(Parameter::T1)); - } - - if (data.parameters.contains(Parameter::T2)) - { - T2_ = std::get(data.parameters.at(Parameter::T2)); - } - - if (data.parameters.contains(Parameter::T3)) - { - T3_ = std::get(data.parameters.at(Parameter::T3)); - } + Model::ParameterReader reader(data, "Tgov1"); - if (data.parameters.contains(Parameter::Dt)) - { - Dt_ = std::get(data.parameters.at(Parameter::Dt)); - } + reader.loadReal(Parameter::Trate, Trate_); + reader.loadReal(Parameter::R, R_); + reader.loadReal(Parameter::Pvmin, Pvmin_); + reader.loadReal(Parameter::Pvmax, Pvmax_); + reader.loadReal(Parameter::T1, T1_); + reader.loadReal(Parameter::T2, T2_); + reader.loadReal(Parameter::T3, T3_); + reader.loadReal(Parameter::Dt, Dt_); } /** @@ -218,41 +191,24 @@ namespace GridKit * @brief verify method checks that attached signals are also linked */ template - int Tgov1::verify() const + Model::ConfigurationChecks Tgov1::verify() const { - int ret = 0; - auto check = [&](bool condition, const char* message) - { - if (!condition) - { - Log::error() << "Tgov1: " << message << '\n'; - ret += 1; - } - }; - - check(Trate_ > ZERO, "Trate must be positive"); - check(va_system_base_ > ZERO, "system power base must be positive"); - check(R_ != ZERO, "R must be nonzero"); - check(Pvmin_ <= Pvmax_, "Pvmin must be less than or equal to Pvmax"); - check(ports_.out.template port().connected(), - "pmech output signal must be assigned"); - - auto speed_port = ports_.in.template port(); - if (speed_port.connected() && !speed_port.linked()) - { - Log::error() << "Tgov1: speed signal attached with no linked generator\n"; - ret += 1; - } + Model::ConfigurationChecks checks; - auto pref_port = ports_.in.template port(); - if (pref_port.connected() && !pref_port.linked()) - { - Log::error() << "Tgov1: pref signal attached with no linked source\n"; - ret += 1; - } + checks.check(Trate_ > ZERO, "Trate must be positive"); + checks.check(va_system_base_ > ZERO, "system power base must be positive"); + checks.check(R_ != ZERO, "R must be nonzero"); + checks.check(Pvmin_ <= Pvmax_, "Pvmin must be less than or equal to Pvmax"); + checks.check(ports_.out.template port().connected(), + "pmech output signal must be assigned"); - return ret; + const auto speed_port = ports_.in.template port(); + checks.check(!speed_port.connected() || speed_port.linked(), "speed signal attached with no linked source"); + const auto pref_port = ports_.in.template port(); + checks.check(!pref_port.connected() || pref_port.linked(), "pref signal attached with no linked source"); + + return checks; } /** @@ -262,9 +218,13 @@ namespace GridKit template int Tgov1::initialize() { - if (verify() != 0) + const auto checks = verify(); + for (const auto& error : checks.errors()) + { + Log::error() << "Tgov1: " << error << '\n'; + } + if (!checks.passed()) { - Log::error() << "Tgov1: cannot initialize with invalid configuration\n"; return 1; } diff --git a/GridKit/Model/PhasorDynamics/Load/LoadZ/LoadZ.hpp b/GridKit/Model/PhasorDynamics/Load/LoadZ/LoadZ.hpp index c27866a38..ec45304de 100644 --- a/GridKit/Model/PhasorDynamics/Load/LoadZ/LoadZ.hpp +++ b/GridKit/Model/PhasorDynamics/Load/LoadZ/LoadZ.hpp @@ -56,9 +56,9 @@ namespace GridKit virtual int evaluateResidual() override final; virtual int evaluateJacobian() override final; - virtual int verify() const override final + virtual Model::ConfigurationChecks verify() const override final { - return 0; + return {}; } public: diff --git a/GridKit/Model/PhasorDynamics/Load/LoadZ/LoadZImpl.hpp b/GridKit/Model/PhasorDynamics/Load/LoadZ/LoadZImpl.hpp index b3e24fc8a..d8c96d154 100644 --- a/GridKit/Model/PhasorDynamics/Load/LoadZ/LoadZImpl.hpp +++ b/GridKit/Model/PhasorDynamics/Load/LoadZ/LoadZImpl.hpp @@ -3,6 +3,8 @@ #include #include +#include +#include #include #include #include @@ -46,15 +48,10 @@ namespace GridKit monitor_(std::make_unique(data)) { using Parameter = typename ModelDataT::Parameters; - if (data.parameters.contains(Parameter::R)) - { - R_ = std::get(data.parameters.at(Parameter::R)); - } - if (data.parameters.contains(Parameter::X)) - { - X_ = std::get(data.parameters.at(Parameter::X)); - } + Model::ParameterReader reader(data, "LoadZ"); + reader.loadReal(Parameter::R, R_); + reader.loadReal(Parameter::X, X_); size_ = 2; setDerivedParams(); diff --git a/GridKit/Model/PhasorDynamics/Load/LoadZIP/LoadZIP.hpp b/GridKit/Model/PhasorDynamics/Load/LoadZIP/LoadZIP.hpp index 737cba8c7..86090bca7 100644 --- a/GridKit/Model/PhasorDynamics/Load/LoadZIP/LoadZIP.hpp +++ b/GridKit/Model/PhasorDynamics/Load/LoadZIP/LoadZIP.hpp @@ -56,9 +56,9 @@ namespace GridKit int evaluateResidual() override final; int evaluateJacobian() override final; - int verify() const override final + Model::ConfigurationChecks verify() const override final { - return 0; + return {}; } public: diff --git a/GridKit/Model/PhasorDynamics/Load/LoadZIP/LoadZIPImpl.hpp b/GridKit/Model/PhasorDynamics/Load/LoadZIP/LoadZIPImpl.hpp index 929cfb746..a26d69608 100644 --- a/GridKit/Model/PhasorDynamics/Load/LoadZIP/LoadZIPImpl.hpp +++ b/GridKit/Model/PhasorDynamics/Load/LoadZIP/LoadZIPImpl.hpp @@ -1,5 +1,7 @@ #pragma once +#include +#include #include #include #include @@ -56,25 +58,12 @@ namespace GridKit void LoadZIP::initializeParameters(const ModelDataT& data) { using Parameter = typename ModelDataT::Parameters; - if (data.parameters.contains(Parameter::Pnom)) - { - Pnom_ = std::get(data.parameters.at(Parameter::Pnom)); - } - - if (data.parameters.contains(Parameter::Qnom)) - { - Qnom_ = std::get(data.parameters.at(Parameter::Qnom)); - } - if (data.parameters.contains(Parameter::alphaI)) - { - alphaI_ = std::get(data.parameters.at(Parameter::alphaI)); - } - - if (data.parameters.contains(Parameter::alphaP)) - { - alphaP_ = std::get(data.parameters.at(Parameter::alphaP)); - } + Model::ParameterReader reader(data, "LoadZIP"); + reader.loadReal(Parameter::Pnom, Pnom_); + reader.loadReal(Parameter::Qnom, Qnom_); + reader.loadReal(Parameter::alphaI, alphaI_); + reader.loadReal(Parameter::alphaP, alphaP_); setDerivedParams(); } diff --git a/GridKit/Model/PhasorDynamics/SignalIn.hpp b/GridKit/Model/PhasorDynamics/SignalIn.hpp index d996f34c5..549839cb2 100644 --- a/GridKit/Model/PhasorDynamics/SignalIn.hpp +++ b/GridKit/Model/PhasorDynamics/SignalIn.hpp @@ -34,6 +34,16 @@ namespace GridKit return this->signal_node_->getVariableIndex(); } + /// Read the connected signal, or the fallback when disconnected. + ScalarT readOrDefault(ScalarT fallback) const + { + if (this->connected()) + { + return readSignal(); + } + return fallback; + } + /// Write a value to the connected signal node. /// /// @warning Use only during initialization as this violates assumptions. diff --git a/GridKit/Model/PhasorDynamics/SignalSource/CMakeLists.txt b/GridKit/Model/PhasorDynamics/SignalSource/CMakeLists.txt index d193c3b31..4fd677916 100644 --- a/GridKit/Model/PhasorDynamics/SignalSource/CMakeLists.txt +++ b/GridKit/Model/PhasorDynamics/SignalSource/CMakeLists.txt @@ -4,11 +4,13 @@ gridkit_add_library( phasor_dynamics_signalsource SOURCES ConstantSignalSource.cpp HEADERS ${_install_headers} + INCLUDE_DIRECTORIES PRIVATE ${GRIDKIT_THIRD_PARTY_DIR}/magic-enum/include LINK_LIBRARIES GridKit::phasor_dynamics_core GridKit::phasor_dynamics_signal) gridkit_add_library( phasor_dynamics_signalsource_dependency_tracking SOURCES ConstantSignalSourceDependencyTracking.cpp + INCLUDE_DIRECTORIES PRIVATE ${GRIDKIT_THIRD_PARTY_DIR}/magic-enum/include LINK_LIBRARIES GridKit::phasor_dynamics_core GridKit::phasor_dynamics_signal_dependency_tracking) # Link to interface target for all components diff --git a/GridKit/Model/PhasorDynamics/SignalSource/ConstantSignalSource.hpp b/GridKit/Model/PhasorDynamics/SignalSource/ConstantSignalSource.hpp index 6adf70824..cbf8c3600 100644 --- a/GridKit/Model/PhasorDynamics/SignalSource/ConstantSignalSource.hpp +++ b/GridKit/Model/PhasorDynamics/SignalSource/ConstantSignalSource.hpp @@ -43,14 +43,14 @@ namespace GridKit ConstantSignalSource(const ModelDataT& data); ~ConstantSignalSource(); - int setGridKitComponentID(IdxT) override final; - int allocate() override final; - int verify() const override final; - int initialize() override final; - int tagDifferentiable() override final; - int setAbsoluteTolerance(RealT) override final; - int evaluateResidual() override final; - int evaluateJacobian() override final; + int setGridKitComponentID(IdxT) override final; + int allocate() override final; + Model::ConfigurationChecks verify() const override final; + int initialize() override final; + int tagDifferentiable() override final; + int setAbsoluteTolerance(RealT) override final; + int evaluateResidual() override final; + int evaluateJacobian() override final; SignalPortsT& getPorts() { diff --git a/GridKit/Model/PhasorDynamics/SignalSource/ConstantSignalSourceImpl.hpp b/GridKit/Model/PhasorDynamics/SignalSource/ConstantSignalSourceImpl.hpp index 561fbe759..3fb169794 100644 --- a/GridKit/Model/PhasorDynamics/SignalSource/ConstantSignalSourceImpl.hpp +++ b/GridKit/Model/PhasorDynamics/SignalSource/ConstantSignalSourceImpl.hpp @@ -1,4 +1,6 @@ +#include +#include #include #include #include @@ -42,13 +44,20 @@ namespace GridKit void ConstantSignalSource::initializeParameters(const ModelDataT& data) { using Parameters = ModelDataT::Parameters; - if (data.parameters.contains(Parameters::Sr)) + + Model::ParameterReader reader(data, "ConstantSignalSource"); + + // The signal values are differentiable scalars, so each is loaded + // through a real intermediate. + RealT s_real{}; + if (reader.loadReal(Parameters::Sr, s_real)) { - s_real_ = std::get(data.parameters.at(Parameters::Sr)); + s_real_ = s_real; } - if (data.parameters.contains(Parameters::Si)) + RealT s_imag{}; + if (reader.loadReal(Parameters::Si, s_imag)) { - s_imag_ = std::get(data.parameters.at(Parameters::Si)); + s_imag_ = s_imag; } } @@ -84,9 +93,9 @@ namespace GridKit } template - int ConstantSignalSource::verify() const + Model::ConfigurationChecks ConstantSignalSource::verify() const { - return 0; + return {}; } template diff --git a/GridKit/Model/PhasorDynamics/Stabilizer/IEEEST/Ieeest.hpp b/GridKit/Model/PhasorDynamics/Stabilizer/IEEEST/Ieeest.hpp index 448e0144c..2c5679a4f 100644 --- a/GridKit/Model/PhasorDynamics/Stabilizer/IEEEST/Ieeest.hpp +++ b/GridKit/Model/PhasorDynamics/Stabilizer/IEEEST/Ieeest.hpp @@ -78,14 +78,14 @@ namespace GridKit Ieeest(const ModelDataT& data); ~Ieeest(); - int setGridKitComponentID(IdxT) override final; - int allocate() override final; - int verify() const override final; - int initialize() override final; - int tagDifferentiable() override final; - int setAbsoluteTolerance(RealT rel_tol) override final; - int evaluateResidual() override final; - int evaluateJacobian() override final; + int setGridKitComponentID(IdxT) override final; + int allocate() override final; + Model::ConfigurationChecks verify() const override final; + int initialize() override final; + int tagDifferentiable() override final; + int setAbsoluteTolerance(RealT rel_tol) override final; + int evaluateResidual() override final; + int evaluateJacobian() override final; SignalPortsT& getPorts() { diff --git a/GridKit/Model/PhasorDynamics/Stabilizer/IEEEST/IeeestImpl.hpp b/GridKit/Model/PhasorDynamics/Stabilizer/IEEEST/IeeestImpl.hpp index 2bc836363..5ccc70ed0 100644 --- a/GridKit/Model/PhasorDynamics/Stabilizer/IEEEST/IeeestImpl.hpp +++ b/GridKit/Model/PhasorDynamics/Stabilizer/IEEEST/IeeestImpl.hpp @@ -8,6 +8,8 @@ #include +#include +#include #include #include #include @@ -47,78 +49,26 @@ namespace GridKit void Ieeest::initializeParameters(const ModelDataT& data) { using Parameter = typename ModelDataT::Parameters; - if (data.parameters.contains(Parameter::A1)) - { - A1_ = std::get(data.parameters.at(Parameter::A1)); - } - if (data.parameters.contains(Parameter::A2)) - { - A2_ = std::get(data.parameters.at(Parameter::A2)); - } - if (data.parameters.contains(Parameter::A3)) - { - A3_ = std::get(data.parameters.at(Parameter::A3)); - } - if (data.parameters.contains(Parameter::A4)) - { - A4_ = std::get(data.parameters.at(Parameter::A4)); - } - if (data.parameters.contains(Parameter::A5)) - { - A5_ = std::get(data.parameters.at(Parameter::A5)); - } - if (data.parameters.contains(Parameter::A6)) - { - A6_ = std::get(data.parameters.at(Parameter::A6)); - } - if (data.parameters.contains(Parameter::T1)) - { - T1_ = std::get(data.parameters.at(Parameter::T1)); - } - if (data.parameters.contains(Parameter::T2)) - { - T2_ = std::get(data.parameters.at(Parameter::T2)); - } - if (data.parameters.contains(Parameter::T3)) - { - T3_ = std::get(data.parameters.at(Parameter::T3)); - } - if (data.parameters.contains(Parameter::T4)) - { - T4_ = std::get(data.parameters.at(Parameter::T4)); - } - if (data.parameters.contains(Parameter::T5)) - { - T5_ = std::get(data.parameters.at(Parameter::T5)); - } - if (data.parameters.contains(Parameter::T6)) - { - T6_ = std::get(data.parameters.at(Parameter::T6)); - } - if (data.parameters.contains(Parameter::Ks)) - { - Ks_ = std::get(data.parameters.at(Parameter::Ks)); - } - if (data.parameters.contains(Parameter::Lsmin)) - { - Lsmin_ = std::get(data.parameters.at(Parameter::Lsmin)); - } - if (data.parameters.contains(Parameter::Lsmax)) - { - Lsmax_ = std::get(data.parameters.at(Parameter::Lsmax)); - } - if (data.parameters.contains(Parameter::Vcl)) - { - Vcl_ = std::get(data.parameters.at(Parameter::Vcl)); - } - if (data.parameters.contains(Parameter::Vcu)) - { - Vcu_ = std::get(data.parameters.at(Parameter::Vcu)); - } - if (data.parameters.contains(Parameter::Tdelay)) - { - Tdelay_ = std::get(data.parameters.at(Parameter::Tdelay)); - } + + Model::ParameterReader reader(data, "Ieeest"); + reader.loadReal(Parameter::A1, A1_); + reader.loadReal(Parameter::A2, A2_); + reader.loadReal(Parameter::A3, A3_); + reader.loadReal(Parameter::A4, A4_); + reader.loadReal(Parameter::A5, A5_); + reader.loadReal(Parameter::A6, A6_); + reader.loadReal(Parameter::T1, T1_); + reader.loadReal(Parameter::T2, T2_); + reader.loadReal(Parameter::T3, T3_); + reader.loadReal(Parameter::T4, T4_); + reader.loadReal(Parameter::T5, T5_); + reader.loadReal(Parameter::T6, T6_); + reader.loadReal(Parameter::Ks, Ks_); + reader.loadReal(Parameter::Lsmin, Lsmin_); + reader.loadReal(Parameter::Lsmax, Lsmax_); + reader.loadReal(Parameter::Vcl, Vcl_); + reader.loadReal(Parameter::Vcu, Vcu_); + reader.loadReal(Parameter::Tdelay, Tdelay_); a0_ = 1; a1_ = A1_ + A3_; @@ -188,37 +138,30 @@ namespace GridKit } template - int Ieeest::verify() const + Model::ConfigurationChecks Ieeest::verify() const { - int ret = 0; + Model::ConfigurationChecks checks; - auto input_port = ports_.in.template port(); - if (!input_port.connected()) - { - Log::error() << "Ieeest: required input signal U is not attached\n"; - ret += 1; - } - if (input_port.connected() && !input_port.linked()) - { - Log::error() << "Ieeest: input signal U attached with no linked source\n"; - ret += 1; - } + const auto input_port = ports_.in.template port(); + checks.check(input_port.connected(), "required input signal U is not attached"); + checks.check(!input_port.connected() || input_port.linked(), "input signal U attached with no linked source"); - if (a4_ == 0 && a3_ == 0 && a2_ == 0 && a1_ != 0) - { - Log::error() << "Ieeest: a2, a3, and a4 are all zero - no valid notch filter\n"; - ret += 1; - } + checks.check(!(a4_ == 0 && a3_ == 0 && a2_ == 0 && a1_ != 0), + "a2, a3, and a4 are all zero - no valid notch filter"); - return ret; + return checks; } template int Ieeest::initialize() { - if (verify() != 0) + const auto checks = verify(); + for (const auto& error : checks.errors()) + { + Log::error() << "Ieeest: " << error << '\n'; + } + if (!checks.passed()) { - Log::error() << "Ieeest: cannot initialize with invalid configuration\n"; return 1; } diff --git a/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROU/Genrou.hpp b/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROU/Genrou.hpp index e02704ca5..feb9dcf48 100644 --- a/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROU/Genrou.hpp +++ b/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROU/Genrou.hpp @@ -119,13 +119,13 @@ namespace GridKit RealT S12); ~Genrou(); - int setGridKitComponentID(IdxT) override final; - int allocate() override final; - int verify() const override final; - int initialize() override final; - int tagDifferentiable() override final; - int setAbsoluteTolerance(RealT) override final; - int evaluateResidual() override final; + int setGridKitComponentID(IdxT) override final; + int allocate() override final; + Model::ConfigurationChecks verify() const override final; + int initialize() override final; + int tagDifferentiable() override final; + int setAbsoluteTolerance(RealT) override final; + int evaluateResidual() override final; // Still to be implemented int evaluateJacobian() override final; diff --git a/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROU/GenrouImpl.hpp b/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROU/GenrouImpl.hpp index c052fec4f..c2ec0863d 100644 --- a/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROU/GenrouImpl.hpp +++ b/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROU/GenrouImpl.hpp @@ -2,6 +2,8 @@ #include +#include +#include #include #include #include @@ -165,100 +167,27 @@ namespace GridKit { using Parameter = typename ModelDataT::Parameters; using Buses = typename ModelDataT::Buses; - if (data.parameters.contains(Parameter::p0)) - { - p0_ = std::get(data.parameters.at(Parameter::p0)); - } - - if (data.parameters.contains(Parameter::q0)) - { - q0_ = std::get(data.parameters.at(Parameter::q0)); - } - - if (data.parameters.contains(Parameter::H)) - { - H_ = std::get(data.parameters.at(Parameter::H)); - } - - if (data.parameters.contains(Parameter::D)) - { - D_ = std::get(data.parameters.at(Parameter::D)); - } - - if (data.parameters.contains(Parameter::Ra)) - { - Ra_ = std::get(data.parameters.at(Parameter::Ra)); - } - - if (data.parameters.contains(Parameter::Tdop)) - { - Tdop_ = std::get(data.parameters.at(Parameter::Tdop)); - } - - if (data.parameters.contains(Parameter::Tdopp)) - { - Tdopp_ = std::get(data.parameters.at(Parameter::Tdopp)); - } - if (data.parameters.contains(Parameter::Tqopp)) - { - Tqopp_ = std::get(data.parameters.at(Parameter::Tqopp)); - } - - if (data.parameters.contains(Parameter::Tqop)) - { - Tqop_ = std::get(data.parameters.at(Parameter::Tqop)); - } - - if (data.parameters.contains(Parameter::Xd)) - { - Xd_ = std::get(data.parameters.at(Parameter::Xd)); - } - - if (data.parameters.contains(Parameter::Xdp)) - { - Xdp_ = std::get(data.parameters.at(Parameter::Xdp)); - } - - if (data.parameters.contains(Parameter::Xdpp)) - { - Xdpp_ = std::get(data.parameters.at(Parameter::Xdpp)); - } - - if (data.parameters.contains(Parameter::Xq)) - { - Xq_ = std::get(data.parameters.at(Parameter::Xq)); - } - - if (data.parameters.contains(Parameter::Xqp)) - { - Xqp_ = std::get(data.parameters.at(Parameter::Xqp)); - } - - if (data.parameters.contains(Parameter::Xqpp)) - { - Xqpp_ = std::get(data.parameters.at(Parameter::Xqpp)); - } - - if (data.parameters.contains(Parameter::Xl)) - { - Xl_ = std::get(data.parameters.at(Parameter::Xl)); - } - - if (data.parameters.contains(Parameter::S10)) - { - S10_ = std::get(data.parameters.at(Parameter::S10)); - } - - if (data.parameters.contains(Parameter::S12)) - { - S12_ = std::get(data.parameters.at(Parameter::S12)); - } - - if (data.parameters.contains(Parameter::mva)) - { - mva_base_ = std::get(data.parameters.at(Parameter::mva)); - } + Model::ParameterReader reader(data, "Genrou"); + reader.loadReal(Parameter::p0, p0_); + reader.loadReal(Parameter::q0, q0_); + reader.loadReal(Parameter::H, H_); + reader.loadReal(Parameter::D, D_); + reader.loadReal(Parameter::Ra, Ra_); + reader.loadReal(Parameter::Tdop, Tdop_); + reader.loadReal(Parameter::Tdopp, Tdopp_); + reader.loadReal(Parameter::Tqopp, Tqopp_); + reader.loadReal(Parameter::Tqop, Tqop_); + reader.loadReal(Parameter::Xd, Xd_); + reader.loadReal(Parameter::Xdp, Xdp_); + reader.loadReal(Parameter::Xdpp, Xdpp_); + reader.loadReal(Parameter::Xq, Xq_); + reader.loadReal(Parameter::Xqp, Xqp_); + reader.loadReal(Parameter::Xqpp, Xqpp_); + reader.loadReal(Parameter::Xl, Xl_); + reader.loadReal(Parameter::S10, S10_); + reader.loadReal(Parameter::S12, S12_); + reader.loadReal(Parameter::mva, mva_base_); if (data.buses.contains(Buses::bus)) { @@ -349,25 +278,17 @@ namespace GridKit * @brief verify method checks that attached signals are also linked */ template - int Genrou::verify() const + Model::ConfigurationChecks Genrou::verify() const { - int ret = 0; + Model::ConfigurationChecks checks; - auto pmech_port = ports_.in.template port(); - if (pmech_port.connected() && !pmech_port.linked()) - { - Log::error() << "Genrou: pmech signal attached with no linked governor\n"; - ret += 1; - } + const auto pmech_port = ports_.in.template port(); + checks.check(!pmech_port.connected() || pmech_port.linked(), "pmech signal attached with no linked governor"); - auto efd_port = ports_.in.template port(); - if (efd_port.connected() && !efd_port.linked()) - { - Log::error() << "Genrou: efd signal attached with no linked exciter\n"; - ret += 1; - } + const auto efd_port = ports_.in.template port(); + checks.check(!efd_port.connected() || efd_port.linked(), "efd signal attached with no linked exciter"); - return ret; + return checks; } /** diff --git a/GridKit/Model/PhasorDynamics/SynchronousMachine/GENSAL/Gensal.hpp b/GridKit/Model/PhasorDynamics/SynchronousMachine/GENSAL/Gensal.hpp index cf3d379c6..1defcff89 100644 --- a/GridKit/Model/PhasorDynamics/SynchronousMachine/GENSAL/Gensal.hpp +++ b/GridKit/Model/PhasorDynamics/SynchronousMachine/GENSAL/Gensal.hpp @@ -85,13 +85,13 @@ namespace GridKit Gensal(BusT* bus, const ModelDataT& data); ~Gensal(); - int setGridKitComponentID(IdxT) override final; - int allocate() override final; - int verify() const override final; - int initialize() override final; - int tagDifferentiable() override final; - int setAbsoluteTolerance(RealT rel_tol) override final; - int evaluateResidual() override final; + int setGridKitComponentID(IdxT) override final; + int allocate() override final; + Model::ConfigurationChecks verify() const override final; + int initialize() override final; + int tagDifferentiable() override final; + int setAbsoluteTolerance(RealT rel_tol) override final; + int evaluateResidual() override final; // Still to be implemented int evaluateJacobian() override final; diff --git a/GridKit/Model/PhasorDynamics/SynchronousMachine/GENSAL/GensalImpl.hpp b/GridKit/Model/PhasorDynamics/SynchronousMachine/GENSAL/GensalImpl.hpp index 8435bdb71..0d86c563c 100644 --- a/GridKit/Model/PhasorDynamics/SynchronousMachine/GENSAL/GensalImpl.hpp +++ b/GridKit/Model/PhasorDynamics/SynchronousMachine/GENSAL/GensalImpl.hpp @@ -2,6 +2,8 @@ #include +#include +#include #include #include #include @@ -43,85 +45,24 @@ namespace GridKit void Gensal::initializeParameters(const ModelDataT& data) { using Parameter = typename ModelDataT::Parameters; - if (data.parameters.contains(Parameter::p0)) - { - p0_ = std::get(data.parameters.at(Parameter::p0)); - } - - if (data.parameters.contains(Parameter::q0)) - { - q0_ = std::get(data.parameters.at(Parameter::q0)); - } - - if (data.parameters.contains(Parameter::H)) - { - H_ = std::get(data.parameters.at(Parameter::H)); - } - - if (data.parameters.contains(Parameter::D)) - { - D_ = std::get(data.parameters.at(Parameter::D)); - } - - if (data.parameters.contains(Parameter::Ra)) - { - Ra_ = std::get(data.parameters.at(Parameter::Ra)); - } - - if (data.parameters.contains(Parameter::Tdop)) - { - Tdop_ = std::get(data.parameters.at(Parameter::Tdop)); - } - - if (data.parameters.contains(Parameter::Tdopp)) - { - Tdopp_ = std::get(data.parameters.at(Parameter::Tdopp)); - } - - if (data.parameters.contains(Parameter::Tqopp)) - { - Tqopp_ = std::get(data.parameters.at(Parameter::Tqopp)); - } - - if (data.parameters.contains(Parameter::Xd)) - { - Xd_ = std::get(data.parameters.at(Parameter::Xd)); - } - - if (data.parameters.contains(Parameter::Xdp)) - { - Xdp_ = std::get(data.parameters.at(Parameter::Xdp)); - } - - if (data.parameters.contains(Parameter::Xdpp)) - { - Xdpp_ = std::get(data.parameters.at(Parameter::Xdpp)); - } - - if (data.parameters.contains(Parameter::Xq)) - { - Xq_ = std::get(data.parameters.at(Parameter::Xq)); - } - - if (data.parameters.contains(Parameter::Xl)) - { - Xl_ = std::get(data.parameters.at(Parameter::Xl)); - } - if (data.parameters.contains(Parameter::S10)) - { - S10_ = std::get(data.parameters.at(Parameter::S10)); - } - - if (data.parameters.contains(Parameter::S12)) - { - S12_ = std::get(data.parameters.at(Parameter::S12)); - } - - if (data.parameters.contains(Parameter::mva)) - { - mva_base_ = std::get(data.parameters.at(Parameter::mva)); - } + Model::ParameterReader reader(data, "Gensal"); + reader.loadReal(Parameter::p0, p0_); + reader.loadReal(Parameter::q0, q0_); + reader.loadReal(Parameter::H, H_); + reader.loadReal(Parameter::D, D_); + reader.loadReal(Parameter::Ra, Ra_); + reader.loadReal(Parameter::Tdop, Tdop_); + reader.loadReal(Parameter::Tdopp, Tdopp_); + reader.loadReal(Parameter::Tqopp, Tqopp_); + reader.loadReal(Parameter::Xd, Xd_); + reader.loadReal(Parameter::Xdp, Xdp_); + reader.loadReal(Parameter::Xdpp, Xdpp_); + reader.loadReal(Parameter::Xq, Xq_); + reader.loadReal(Parameter::Xl, Xl_); + reader.loadReal(Parameter::S10, S10_); + reader.loadReal(Parameter::S12, S12_); + reader.loadReal(Parameter::mva, mva_base_); } template @@ -225,25 +166,17 @@ namespace GridKit * @brief verify method checks that attached signals are also linked */ template - int Gensal::verify() const + Model::ConfigurationChecks Gensal::verify() const { - int ret = 0; + Model::ConfigurationChecks checks; - auto pmech_port = ports_.in.template port(); - if (pmech_port.connected() && !pmech_port.linked()) - { - Log::error() << "Gensal: pmech signal attached with no linked governor\n"; - ret += 1; - } + const auto pmech_port = ports_.in.template port(); + checks.check(!pmech_port.connected() || pmech_port.linked(), "pmech signal attached with no linked governor"); - auto efd_port = ports_.in.template port(); - if (efd_port.connected() && !efd_port.linked()) - { - Log::error() << "Gensal: efd signal attached with no linked exciter\n"; - ret += 1; - } + const auto efd_port = ports_.in.template port(); + checks.check(!efd_port.connected() || efd_port.linked(), "efd signal attached with no linked exciter"); - return ret; + return checks; } /** diff --git a/GridKit/Model/PhasorDynamics/SynchronousMachine/GenClassical/GenClassical.hpp b/GridKit/Model/PhasorDynamics/SynchronousMachine/GenClassical/GenClassical.hpp index ced9a652e..6192267f1 100644 --- a/GridKit/Model/PhasorDynamics/SynchronousMachine/GenClassical/GenClassical.hpp +++ b/GridKit/Model/PhasorDynamics/SynchronousMachine/GenClassical/GenClassical.hpp @@ -74,13 +74,13 @@ namespace GridKit GenClassical(BusT* bus, const ModelDataT& data); ~GenClassical(); - int setGridKitComponentID(IdxT) override final; - int allocate() override final; - int verify() const override final; - int initialize() override final; - int tagDifferentiable() override final; - int setAbsoluteTolerance(RealT rel_tol) override final; - int evaluateResidual() override final; + int setGridKitComponentID(IdxT) override final; + int allocate() override final; + Model::ConfigurationChecks verify() const override final; + int initialize() override final; + int tagDifferentiable() override final; + int setAbsoluteTolerance(RealT rel_tol) override final; + int evaluateResidual() override final; // Still to be implemented int evaluateJacobian() override final; diff --git a/GridKit/Model/PhasorDynamics/SynchronousMachine/GenClassical/GenClassicalImpl.hpp b/GridKit/Model/PhasorDynamics/SynchronousMachine/GenClassical/GenClassicalImpl.hpp index 0c3b12cf1..acede05af 100644 --- a/GridKit/Model/PhasorDynamics/SynchronousMachine/GenClassical/GenClassicalImpl.hpp +++ b/GridKit/Model/PhasorDynamics/SynchronousMachine/GenClassical/GenClassicalImpl.hpp @@ -10,6 +10,8 @@ #include +#include +#include #include #include #include @@ -47,40 +49,15 @@ namespace GridKit void GenClassical::initializeParameters(const ModelDataT& data) { using Parameter = typename ModelDataT::Parameters; - if (data.parameters.contains(Parameter::p0)) - { - p0_ = std::get(data.parameters.at(Parameter::p0)); - } - - if (data.parameters.contains(Parameter::q0)) - { - q0_ = std::get(data.parameters.at(Parameter::q0)); - } - - if (data.parameters.contains(Parameter::H)) - { - H_ = std::get(data.parameters.at(Parameter::H)); - } - - if (data.parameters.contains(Parameter::D)) - { - D_ = std::get(data.parameters.at(Parameter::D)); - } - if (data.parameters.contains(Parameter::Ra)) - { - Ra_ = std::get(data.parameters.at(Parameter::Ra)); - } - - if (data.parameters.contains(Parameter::Xdp)) - { - Xdp_ = std::get(data.parameters.at(Parameter::Xdp)); - } - - if (data.parameters.contains(Parameter::mva)) - { - mva_base_ = std::get(data.parameters.at(Parameter::mva)); - } + Model::ParameterReader reader(data, "GenClassical"); + reader.loadReal(Parameter::p0, p0_); + reader.loadReal(Parameter::q0, q0_); + reader.loadReal(Parameter::H, H_); + reader.loadReal(Parameter::D, D_); + reader.loadReal(Parameter::Ra, Ra_); + reader.loadReal(Parameter::Xdp, Xdp_); + reader.loadReal(Parameter::mva, mva_base_); } template @@ -166,25 +143,17 @@ namespace GridKit * @brief verify method checks that attached signals are also linked */ template - int GenClassical::verify() const + Model::ConfigurationChecks GenClassical::verify() const { - int ret = 0; + Model::ConfigurationChecks checks; - auto pmech_port = ports_.in.template port(); - if (pmech_port.connected() && !pmech_port.linked()) - { - Log::error() << "GenClassical: pmech signal attached with no linked governor\n"; - ret += 1; - } + const auto pmech_port = ports_.in.template port(); + checks.check(!pmech_port.connected() || pmech_port.linked(), "pmech signal attached with no linked governor"); - auto efd_port = ports_.in.template port(); - if (efd_port.connected() && !efd_port.linked()) - { - Log::error() << "GenClassical: efd signal attached with no linked exciter\n"; - ret += 1; - } + const auto efd_port = ports_.in.template port(); + checks.check(!efd_port.connected() || efd_port.linked(), "efd signal attached with no linked exciter"); - return ret; + return checks; } /** diff --git a/GridKit/Model/PhasorDynamics/SystemModel.hpp b/GridKit/Model/PhasorDynamics/SystemModel.hpp index dc0ed0b7b..7326136d8 100644 --- a/GridKit/Model/PhasorDynamics/SystemModel.hpp +++ b/GridKit/Model/PhasorDynamics/SystemModel.hpp @@ -77,9 +77,9 @@ namespace GridKit int setGridKitComponentID(IdxT component_id) override; - int allocate() override; - int verify() const override; - int initialize() override; + int allocate() override; + Model::ConfigurationChecks verify() const override; + int initialize() override; bool hasJacobian() override; diff --git a/GridKit/Model/PhasorDynamics/SystemModelImpl.hpp b/GridKit/Model/PhasorDynamics/SystemModelImpl.hpp index 598f1d23d..8b786e664 100644 --- a/GridKit/Model/PhasorDynamics/SystemModelImpl.hpp +++ b/GridKit/Model/PhasorDynamics/SystemModelImpl.hpp @@ -1,5 +1,6 @@ #include #include +#include #include #include @@ -472,10 +473,13 @@ namespace GridKit } // Verify component configuration - int errorCount = this->verify(); - if (errorCount > 0) + const auto checks = this->verify(); + for (const auto& error : checks.errors()) + { + Log::error() << error << '\n'; + } + if (!checks.passed()) { - Log::error() << "Component errors: " << errorCount << std::endl; throw std::runtime_error("SystemModel allocation failed"); } @@ -507,21 +511,24 @@ namespace GridKit /** * @brief Verify all components are configured correctly * - * This method accumulates and returns the number of errors given by - * components. It should return 0 when all is well. + * Collects every problem reported by the components, each prefixed with + * the component ID. passed() is true when all is well. */ template - int SystemModel::verify() const + Model::ConfigurationChecks SystemModel::verify() const { - int ret = 0; + Model::ConfigurationChecks checks; - // Verify components for (const auto& component : components_) { - ret += component->verify(); + const auto component_checks = component->verify(); + for (const auto& error : component_checks.errors()) + { + checks.fail("component " + std::to_string(component->getGridKitComponentID()) + ": " + error); + } } - return ret; + return checks; } /** diff --git a/GridKit/Testing/Testing.hpp b/GridKit/Testing/Testing.hpp index ba6839f4f..fbf565292 100644 --- a/GridKit/Testing/Testing.hpp +++ b/GridKit/Testing/Testing.hpp @@ -12,12 +12,28 @@ #include #include #include +#include #include namespace GridKit { namespace Testing { + /// True when constructing a ModelT from the arguments throws + /// std::invalid_argument, the rejection for invalid model data. + template + bool constructionRejected(ArgsT&&... args) + { + try + { + ModelT model(std::forward(args)...); + } + catch (const std::invalid_argument&) + { + return true; + } + return false; + } enum TestOutcome { diff --git a/tests/UnitTests/PhasorDynamics/BranchTests.hpp b/tests/UnitTests/PhasorDynamics/BranchTests.hpp index ba09fd4e5..1de06155e 100644 --- a/tests/UnitTests/PhasorDynamics/BranchTests.hpp +++ b/tests/UnitTests/PhasorDynamics/BranchTests.hpp @@ -307,7 +307,7 @@ namespace GridKit PhasorDynamics::Bus bus2(1.0, 0.0); PhasorDynamics::Branch valid_branch(&bus1, &bus2, 0.0, 0.1, 0.0, 0.0); - success *= (valid_branch.verify() == 0); + success *= (valid_branch.verify().passed()); const auto previous_verbosity = Log::verbosity(); // Suppress expected errors from the invalid branch configurations below. @@ -315,17 +315,17 @@ namespace GridKit Log::setVerbosity(Log::Verbosity::NONE); PhasorDynamics::Branch zero_impedance_branch(&bus1, &bus2, 0.0, 0.0, 0.0, 0.0); - success *= (zero_impedance_branch.verify() != 0); + success *= (!zero_impedance_branch.verify().passed()); PhasorDynamics::Branch zero_tap_branch(&bus1, &bus2, 0.0, 0.1, 0.0, 0.0, 0.0, 0.0); - success *= (zero_tap_branch.verify() != 0); + success *= (!zero_tap_branch.verify().passed()); PhasorDynamics::Branch negative_tap_branch(&bus1, &bus2, 0.0, 0.1, 0.0, 0.0, -1.0, 0.0); - success *= (negative_tap_branch.verify() != 0); + success *= (!negative_tap_branch.verify().passed()); const RealT nan = std::numeric_limits::quiet_NaN(); PhasorDynamics::Branch nonfinite_branch(&bus1, &bus2, nan, 0.1, 0.0, 0.0); - success *= (nonfinite_branch.verify() != 0); + success *= (!nonfinite_branch.verify().passed()); Log::setVerbosity(previous_verbosity); return success.report(__func__); diff --git a/tests/UnitTests/PhasorDynamics/BusToSignalAdapterTests.hpp b/tests/UnitTests/PhasorDynamics/BusToSignalAdapterTests.hpp index baebe3def..74ea84609 100644 --- a/tests/UnitTests/PhasorDynamics/BusToSignalAdapterTests.hpp +++ b/tests/UnitTests/PhasorDynamics/BusToSignalAdapterTests.hpp @@ -80,7 +80,7 @@ namespace GridKit adapter.getPorts().in.template port().connect(&ir_sig); adapter.getPorts().in.template port().connect(&ii_sig); adapter.allocate(); - success *= (adapter.verify() == 0); + success *= (adapter.verify().passed()); success *= (vr_sig.read() == Vr); success *= (vi_sig.read() == Vi); diff --git a/tests/UnitTests/PhasorDynamics/ControllerReecbTests.hpp b/tests/UnitTests/PhasorDynamics/ControllerReecbTests.hpp index 65568cecb..ad1d3b5ad 100644 --- a/tests/UnitTests/PhasorDynamics/ControllerReecbTests.hpp +++ b/tests/UnitTests/PhasorDynamics/ControllerReecbTests.hpp @@ -93,7 +93,7 @@ namespace GridKit Fixture configured(makeData()); success *= (configured.reecb.size() == static_cast(Utilities::enum_size())); success *= (configured.reecb.getMonitor() != nullptr); - success *= (configured.reecb.verify() == 0); + success *= (configured.reecb.verify().passed()); success *= (configured.reecb.initialize() != 0); success *= (configured.reecb.allocate() == 0); success *= (configured.reecb.tagDifferentiable() == 0); @@ -111,20 +111,19 @@ namespace GridKit } Fixture documented_defaults(makeMinimalData()); - success *= (documented_defaults.reecb.verify() == 0); + success *= (documented_defaults.reecb.verify().passed()); success *= defaultsMatchDocumentedValues(); auto missing_mva_data = makeMinimalData(); missing_mva_data.parameters.erase(Params::mva); - Fixture missing_mva(missing_mva_data); - success *= (missing_mva.reecb.verify() > 0); + success *= constructionRejected>(missing_mva_data); // Integer JSON values are accepted for real parameters; booleans are // not numeric. auto integer_numeric = makeData(); integer_numeric.parameters[Params::mva] = static_cast(100); Fixture integer_parameter(integer_numeric); - success *= (integer_parameter.reecb.verify() == 0); + success *= (integer_parameter.reecb.verify().passed()); success *= invalidParameterCase(Params::mva, true); const RealT nan = std::numeric_limits::quiet_NaN(); @@ -215,7 +214,7 @@ namespace GridKit auto data = makeData(); data.parameters[flag] = value; Fixture model(data); - success *= (model.reecb.verify() == 0); + success *= (model.reecb.verify().passed()); } for (const IdxT value : invalid_integral_flag_values) @@ -231,7 +230,7 @@ namespace GridKit PhasorDynamics::Controller::Reecb busless(nullptr, makeData()); busless.setSystemBase(kNominalFrequency, kSystemBaseVa); - success *= (busless.verify() > 0); + success *= (!busless.verify().passed()); success *= unlinkedSignalRejected(); success *= unlinkedSignalRejected(); @@ -1876,7 +1875,7 @@ namespace GridKit bool prepare(RealT iqcmd, RealT ipcmd) { const bool ready = (bus.allocate() == 0) && (reecb.allocate() == 0) - && (reecb.verify() == 0) && (bus.initialize() == 0); + && (reecb.verify().passed()) && (bus.initialize() == 0); if (!ready) { std::cout << "REECB fixture preparation failed\n"; @@ -2295,8 +2294,15 @@ namespace GridKit { auto data = makeData(); data.parameters[parameter] = value; - Fixture fixture(data); - return fixture.reecb.verify() > 0; + try + { + Fixture fixture(data); + return !fixture.reecb.verify().passed(); + } + catch (const std::invalid_argument&) + { + return true; + } } template @@ -2305,7 +2311,7 @@ namespace GridKit PhasorDynamics::SignalNode unlinked_node; Fixture fixture(makeData()); fixture.reecb.getPorts().in.template port().connect(&unlinked_node); - return fixture.reecb.verify() > 0; + return !fixture.reecb.verify().passed(); } /// Fill state and derivative with a recognizable ramp, restoring the diff --git a/tests/UnitTests/PhasorDynamics/ControllerRepcaTests.hpp b/tests/UnitTests/PhasorDynamics/ControllerRepcaTests.hpp index e6f90ca2e..521c00e0e 100644 --- a/tests/UnitTests/PhasorDynamics/ControllerRepcaTests.hpp +++ b/tests/UnitTests/PhasorDynamics/ControllerRepcaTests.hpp @@ -57,27 +57,27 @@ namespace GridKit PhasorDynamics::Controller::Repca empty(&bus); success *= (empty.size() == static_cast(Utilities::enum_size())); success *= (empty.getMonitor() == nullptr); - success *= (empty.verify() > 0); + success *= (!empty.verify().passed()); Fixture configured(makeData()); configured.attachAllInputs(); success *= (configured.repca.size() == static_cast(Utilities::enum_size())); success *= (configured.repca.getMonitor() != nullptr); - success *= (configured.repca.verify() == 0); + success *= (configured.repca.verify().passed()); Fixture documented_defaults(makeMinimalData()); documented_defaults.attachAllInputs(); - success *= (documented_defaults.repca.verify() == 0); + success *= (documented_defaults.repca.verify().passed()); success *= defaultsMatchDocumentedValues(); auto integer_numeric = makeData(); integer_numeric.parameters[Params::mva] = static_cast(100); Fixture integer_parameter(integer_numeric); integer_parameter.attachAllInputs(); - success *= (integer_parameter.repca.verify() == 0); + success *= (integer_parameter.repca.verify().passed()); PhasorDynamics::Controller::Repca missing_signals(&bus, makeData()); - success *= (missing_signals.verify() > 0); + success *= (!missing_signals.verify().passed()); success *= invalidParameterCase(Params::mva, 0.0); success *= invalidParameterCase(Params::Tfv, -0.1); @@ -143,7 +143,7 @@ namespace GridKit { Fixture nonfinite_system_base(makeData(), 1.0, 0.0, infinity); nonfinite_system_base.attachAllInputs(); - success *= (nonfinite_system_base.repca.verify() > 0); + success *= (!nonfinite_system_base.repca.verify().passed()); } { auto tiny_base_data = makeData(); @@ -153,7 +153,7 @@ namespace GridKit 0.0, std::numeric_limits::max()); overflowing_base_ratio.attachAllInputs(); - success *= (overflowing_base_ratio.repca.verify() > 0); + success *= (!overflowing_base_ratio.repca.verify().passed()); } const std::array flag_parameters{{ @@ -182,7 +182,7 @@ namespace GridKit data.parameters[flag] = value; Fixture model(data); model.attachAllInputs(); - success *= (model.repca.verify() == 0); + success *= (model.repca.verify().passed()); } for (const IdxT value : invalid_integral_flag_values) @@ -197,7 +197,7 @@ namespace GridKit } PhasorDynamics::Controller::Repca busless(nullptr, makeData()); - success *= (busless.verify() > 0); + success *= (!busless.verify().passed()); success *= unlinkedSignalRejected(); success *= unlinkedSignalRejected(); @@ -606,7 +606,7 @@ namespace GridKit // An invalid configuration is rejected before any state is written. { auto invalid_data = data; - invalid_data.parameters[Params::Tfv] = -0.1; + invalid_data.parameters[Params::Ddn] = -0.1; Fixture invalid_fixture(invalid_data); invalid_fixture.attachAllInputs(); setInitializationInputs(invalid_fixture); @@ -1398,7 +1398,7 @@ namespace GridKit bool prepare(RealT qext, RealT pext) { const bool success = (bus.allocate() == 0) && (repca.allocate() == 0) - && (repca.verify() == 0) && (bus.initialize() == 0); + && (repca.verify().passed()) && (bus.initialize() == 0); if (!success) { std::cout << "REPCA fixture preparation failed\n"; @@ -1719,9 +1719,16 @@ namespace GridKit { auto data = makeData(); data.parameters[parameter] = value; - Fixture fixture(data); - fixture.attachAllInputs(); - return fixture.repca.verify() > 0; + try + { + Fixture fixture(data); + fixture.attachAllInputs(); + return !fixture.repca.verify().passed(); + } + catch (const std::invalid_argument&) + { + return true; + } } template @@ -1731,7 +1738,7 @@ namespace GridKit fixture.attachAllInputs(); PhasorDynamics::SignalNode unlinked_node; fixture.repca.getPorts().in.template port().connect(&unlinked_node); - return fixture.repca.verify() > 0; + return !fixture.repca.verify().passed(); } /// Fill state and derivative with a recognizable ramp, restoring the diff --git a/tests/UnitTests/PhasorDynamics/ConverterRegcaTests.hpp b/tests/UnitTests/PhasorDynamics/ConverterRegcaTests.hpp index 1288eee31..c39ecc208 100644 --- a/tests/UnitTests/PhasorDynamics/ConverterRegcaTests.hpp +++ b/tests/UnitTests/PhasorDynamics/ConverterRegcaTests.hpp @@ -56,26 +56,24 @@ namespace GridKit PhasorDynamics::Converter::Regca configured(&bus, makeData()); success *= (configured.size() == static_cast(Utilities::enum_size())); success *= (configured.getMonitor() != nullptr); - success *= (configured.verify() == 0); + success *= (configured.verify().passed()); const auto previous_verbosity = Log::verbosity(); // Suppress expected errors and warnings from the invalid cases below. // Use EVERYTHING to inspect those diagnostics. Log::setVerbosity(Log::Verbosity::NONE); - success *= (minimal.verify() > 0); + success *= (!minimal.verify().passed()); for (const Params parameter : {Params::Tg, Params::p0, Params::q0}) { auto data = makeData(); data.parameters.erase(parameter); - PhasorDynamics::Converter::Regca missing(&bus, data); - success *= (missing.verify() > 0); + success *= constructionRejected>(&bus, data); } - auto bad_switch = makeData(); - bad_switch.parameters[Params::sL] = static_cast(2); - PhasorDynamics::Converter::Regca bad_switch_model(&bus, bad_switch); - success *= (bad_switch_model.verify() > 0); + auto bad_switch = makeData(); + bad_switch.parameters[Params::sL] = static_cast(2); + success *= constructionRejected>(&bus, bad_switch); success *= invalidParameterCase(bus, Params::mva, 0.0); success *= invalidParameterCase(bus, Params::IL1, -0.1); @@ -95,12 +93,12 @@ namespace GridKit // A null bus and an attached command with no linked source count as // configuration errors on the same footing as bad parameters. PhasorDynamics::Converter::Regca busless(nullptr, makeData()); - success *= (busless.verify() > 0); + success *= (!busless.verify().passed()); PhasorDynamics::SignalNode unlinked_node; PhasorDynamics::Converter::Regca unlinked(&bus, makeData()); unlinked.getPorts().in.template port().connect(&unlinked_node); - success *= (unlinked.verify() > 0); + success *= (!unlinked.verify().passed()); // Zero time constants are raised to the well-posedness floor with a // warning, and the raised model still initializes to zero residuals. @@ -758,7 +756,7 @@ namespace GridKit bool prepare() { const bool success = (bus.allocate() == 0) && (regca.allocate() == 0) - && (regca.verify() == 0) && (bus.initialize() == 0); + && (regca.verify().passed()) && (bus.initialize() == 0); if (!success) { std::cout << "REGCA fixture preparation failed\n"; @@ -953,8 +951,15 @@ namespace GridKit { auto data = makeData(); data.parameters[param] = value; - PhasorDynamics::Converter::Regca model(&bus, data); - return model.verify() > 0; + try + { + PhasorDynamics::Converter::Regca model(&bus, data); + return !model.verify().passed(); + } + catch (const std::invalid_argument&) + { + return true; + } } bool allResidualsZero(PhasorDynamics::Converter::Regca& regca) const diff --git a/tests/UnitTests/PhasorDynamics/ExciterEsdc1aTests.hpp b/tests/UnitTests/PhasorDynamics/ExciterEsdc1aTests.hpp index 05fc850b0..39cb77194 100644 --- a/tests/UnitTests/PhasorDynamics/ExciterEsdc1aTests.hpp +++ b/tests/UnitTests/PhasorDynamics/ExciterEsdc1aTests.hpp @@ -59,7 +59,7 @@ namespace GridKit PhasorDynamics::Exciter::Esdc1a empty(&bus); success *= (empty.size() == static_cast(Utilities::enum_size())); success *= (empty.getMonitor() == nullptr); - success *= (empty.verify() > 0); + success *= (!empty.verify().passed()); Fixture configured(makeData()); success *= (configured.esdc1a.size() == static_cast(Utilities::enum_size())); @@ -74,7 +74,7 @@ namespace GridKit // A model without the required efd output assignment is rejected. PhasorDynamics::Exciter::Esdc1a unassigned(&bus, makeData()); - success *= (unassigned.verify() > 0); + success *= (!unassigned.verify().passed()); success *= invalidParameterCase(Params::Ka, 0.0); success *= invalidParameterCase(Params::Ta, -0.1); @@ -120,26 +120,26 @@ namespace GridKit reversed_saturation.parameters[Params::E2] = 2.8; reversed_saturation.parameters[Params::Se2] = 0.08; Fixture reversed_saturation_fixture(reversed_saturation); - success *= (reversed_saturation_fixture.esdc1a.verify() == 0); + success *= (reversed_saturation_fixture.esdc1a.verify().passed()); auto crossed_ascending = makeData(); crossed_ascending.parameters[Params::Se1] = 0.33; crossed_ascending.parameters[Params::Se2] = 0.08; Fixture crossed_ascending_fixture(crossed_ascending); - success *= (crossed_ascending_fixture.esdc1a.verify() > 0); + success *= (!crossed_ascending_fixture.esdc1a.verify().passed()); auto crossed_descending = makeData(); crossed_descending.parameters[Params::E1] = 3.7; crossed_descending.parameters[Params::E2] = 2.8; Fixture crossed_descending_fixture(crossed_descending); - success *= (crossed_descending_fixture.esdc1a.verify() > 0); + success *= (!crossed_descending_fixture.esdc1a.verify().passed()); // Integer JSON values are accepted for real parameters; booleans are // not numeric. auto integer_real = makeData(); integer_real.parameters[Params::Ka] = static_cast(40); Fixture integer_real_fixture(integer_real); - success *= (integer_real_fixture.esdc1a.verify() == 0); + success *= (integer_real_fixture.esdc1a.verify().passed()); success *= invalidParameterCase(Params::Ka, true); // Binary selectors accept JSON booleans only. @@ -148,7 +148,7 @@ namespace GridKit boolean_switches.parameters[Params::exclim] = false; Fixture boolean_switch_fixture(boolean_switches); boolean_switch_fixture.attachAllInputs(); - success *= (boolean_switch_fixture.esdc1a.verify() == 0); + success *= (boolean_switch_fixture.esdc1a.verify().passed()); for (const Params flag : {Params::Spdmlt, Params::exclim}) { @@ -164,12 +164,12 @@ namespace GridKit auto speed_required = makeData(); speed_required.parameters[Params::Spdmlt] = true; Fixture speed_required_fixture(speed_required); - success *= (speed_required_fixture.esdc1a.verify() > 0); + success *= (!speed_required_fixture.esdc1a.verify().passed()); PhasorDynamics::SignalNode busless_efd_node; PhasorDynamics::Exciter::Esdc1a busless(nullptr, makeData()); busless.getPorts().out.template port().connect(&busless_efd_node); - success *= (busless.verify() > 0); + success *= (!busless.verify().passed()); success *= unlinkedSignalRejected(); success *= unlinkedSignalRejected(); @@ -940,7 +940,7 @@ namespace GridKit bool prepare(RealT efd) { const bool success = (bus.allocate() == 0) && (esdc1a.allocate() == 0) - && (esdc1a.verify() == 0) && (bus.initialize() == 0); + && (esdc1a.verify().passed()) && (bus.initialize() == 0); if (!success) { std::cout << "ESDC1A fixture preparation failed\n"; @@ -1193,8 +1193,15 @@ namespace GridKit { auto data = makeData(); data.parameters[parameter] = value; - Fixture fixture(data); - return fixture.esdc1a.verify() > 0; + try + { + Fixture fixture(data); + return !fixture.esdc1a.verify().passed(); + } + catch (const std::invalid_argument&) + { + return true; + } } template @@ -1203,7 +1210,7 @@ namespace GridKit PhasorDynamics::SignalNode unlinked_node; Fixture fixture(makeData()); fixture.esdc1a.getPorts().in.template port().connect(&unlinked_node); - return fixture.esdc1a.verify() > 0; + return !fixture.esdc1a.verify().passed(); } template diff --git a/tests/UnitTests/PhasorDynamics/ExciterIeeet1Tests.hpp b/tests/UnitTests/PhasorDynamics/ExciterIeeet1Tests.hpp index 3297ceac6..c75207a88 100644 --- a/tests/UnitTests/PhasorDynamics/ExciterIeeet1Tests.hpp +++ b/tests/UnitTests/PhasorDynamics/ExciterIeeet1Tests.hpp @@ -200,7 +200,7 @@ namespace GridKit bus.allocate(); exciter.allocate(); - success *= (exciter.verify() != 0); + success *= (!exciter.verify().passed()); Log::setVerbosity(previous_verbosity); return success.report(__func__); diff --git a/tests/UnitTests/PhasorDynamics/ExciterSexsPtiTests.hpp b/tests/UnitTests/PhasorDynamics/ExciterSexsPtiTests.hpp index 283c1646d..16d15100c 100644 --- a/tests/UnitTests/PhasorDynamics/ExciterSexsPtiTests.hpp +++ b/tests/UnitTests/PhasorDynamics/ExciterSexsPtiTests.hpp @@ -72,7 +72,7 @@ namespace GridKit exciter.allocate(); efd_node.init(1.2); - success *= (exciter.verify() == 0); + success *= (exciter.verify().passed()); exciter.initialize(); exciter.evaluateResidual(); @@ -124,7 +124,7 @@ namespace GridKit exciter.allocate(); efd_node.init(1.2); - success *= (exciter.verify() == 0); + success *= (exciter.verify().passed()); exciter.initialize(); exciter.evaluateResidual(); @@ -280,13 +280,12 @@ namespace GridKit auto missing = makeTestData(); missing.parameters.erase(Parameter::K); - PhasorDynamics::Exciter::SexsPti missing_model(&bus, missing); - success *= (missing_model.verify() > 0); + success *= constructionRejected>(&bus, missing); auto invalid = makeTestData(); invalid.parameters[Parameter::Tb] = 0.0; PhasorDynamics::Exciter::SexsPti invalid_model(&bus, invalid); - success *= (invalid_model.verify() > 0); + success *= (!invalid_model.verify().passed()); Log::setVerbosity(previous_verbosity); return success.report(__func__); @@ -331,7 +330,7 @@ namespace GridKit // Suppress the expected missing-EFD configuration error below. // Use EVERYTHING to inspect the diagnostic. Log::setVerbosity(Log::Verbosity::NONE); - success *= (missing_efd_system.verify() > 0); + success *= (!missing_efd_system.verify().passed()); Log::setVerbosity(previous_verbosity); return success.report(__func__); diff --git a/tests/UnitTests/PhasorDynamics/GenClassicalTests.hpp b/tests/UnitTests/PhasorDynamics/GenClassicalTests.hpp index aa2ebff67..6b9df59c7 100644 --- a/tests/UnitTests/PhasorDynamics/GenClassicalTests.hpp +++ b/tests/UnitTests/PhasorDynamics/GenClassicalTests.hpp @@ -326,7 +326,7 @@ namespace GridKit gen.setSystemBase(60.0, 100.0e6); gen.allocate(); - success *= gen.verify() == 0; + success *= gen.verify().passed(); success *= speed.linked(); gen.initialize(); diff --git a/tests/UnitTests/PhasorDynamics/GovernorGastPtiTests.hpp b/tests/UnitTests/PhasorDynamics/GovernorGastPtiTests.hpp index 3231b0561..ba383180a 100644 --- a/tests/UnitTests/PhasorDynamics/GovernorGastPtiTests.hpp +++ b/tests/UnitTests/PhasorDynamics/GovernorGastPtiTests.hpp @@ -56,12 +56,12 @@ namespace GridKit PhasorDynamics::Governor::GastPti empty; success *= (empty.size() == static_cast(Utilities::enum_size())); success *= (empty.getMonitor() == nullptr); - success *= (empty.verify() > 0); // required pmech assignment is absent + success *= (!empty.verify().passed()); // required pmech assignment is absent PhasorDynamics::Governor::GastPti configured(makeData()); success *= (configured.size() == static_cast(Utilities::enum_size())); success *= (configured.getMonitor() != nullptr); - success *= (configured.verify() > 0); // required pmech assignment is absent + success *= (!configured.verify().passed()); // required pmech assignment is absent // Framework binding precedes model allocation; verification must not // inspect index maps until allocate() has sized them. @@ -78,16 +78,16 @@ namespace GridKit GastPtiT bound(makeData()); bound.getPorts().out.template port().connect(&bound_pmech); success *= (bound.bind(bound_y, bound_yp, bound_f, bound_abs_tol, 0) == 0); - success *= (bound.verify() == 0); + success *= (bound.verify().passed()); PhasorDynamics::Governor::GastPti minimal(makeMinimalData()); - success *= (minimal.verify() > 0); // required pmech assignment is absent - success *= (verifyData(makeData()) == 0); + success *= (!minimal.verify().passed()); // required pmech assignment is absent + success *= (verifyData(makeData())); success *= defaultsMatchDocumentedValues(); auto missing_trate = makeMinimalData(); missing_trate.parameters.erase(Params::Trate); - success *= (verifyData(missing_trate) > 0); + success *= (!verifyData(missing_trate)); success *= invalidParameterCase(Params::R, 0.0); success *= invalidParameterCase(Params::R, -0.1); @@ -130,17 +130,17 @@ namespace GridKit auto equal = makeData(); equal.parameters[Params::Vmin] = 0.5; equal.parameters[Params::Vmax] = 0.5; - success *= (verifyData(equal) == 0); + success *= (verifyData(equal)); auto reversed = makeData(); reversed.parameters[Params::Vmin] = 0.6; reversed.parameters[Params::Vmax] = 0.5; - success *= (verifyData(reversed) > 0); + success *= (!verifyData(reversed)); // Narrow configured limits remain valid. auto narrow = makeData(); narrow.parameters[Params::Vmax] = 0.01; - success *= (verifyData(narrow) == 0); + success *= (verifyData(narrow)); // Integer JSON values are accepted for real parameters; booleans are // not numeric. @@ -165,7 +165,7 @@ namespace GridKit { Fixture invalid_base(makeData(), system_base); success *= (invalid_base.gastpti.allocate() == 0); - success *= (invalid_base.gastpti.verify() > 0); + success *= (!invalid_base.gastpti.verify().passed()); } success *= unlinkedSignalRejected(); @@ -1014,7 +1014,7 @@ namespace GridKit /// verification, and a machine-seeded mechanical-power node. bool prepare(RealT pmech) { - const bool success = (gastpti.allocate() == 0) && (gastpti.verify() == 0); + const bool success = (gastpti.allocate() == 0) && (gastpti.verify().passed()); if (!success) { std::cout << "GASTPTI fixture preparation failed\n"; @@ -1231,15 +1231,23 @@ namespace GridKit { auto data = makeData(); data.parameters[parameter] = value; - return verifyData(data) > 0; + return !verifyData(data); } - int verifyData(const Data& data) const + /// False when the data is rejected at construction or by verify(). + bool verifyData(const Data& data) const { - PhasorDynamics::SignalNode pmech; - PhasorDynamics::Governor::GastPti model(data); - model.getPorts().out.template port().connect(&pmech); - return model.verify(); + try + { + PhasorDynamics::SignalNode pmech; + PhasorDynamics::Governor::GastPti model(data); + model.getPorts().out.template port().connect(&pmech); + return model.verify().passed(); + } + catch (const std::invalid_argument&) + { + return false; + } } template @@ -1250,7 +1258,7 @@ namespace GridKit PhasorDynamics::Governor::GastPti model(makeData()); model.getPorts().out.template port().connect(&pmech_node); model.getPorts().in.template port().connect(&unlinked_node); - return model.verify() > 0; + return !model.verify().passed(); } bool aliasedSignalsRejected() const @@ -1268,7 +1276,7 @@ namespace GridKit { success = false; } - if (!(pref_alias.verify() > 0)) + if (pref_alias.verify().passed()) { success = false; } @@ -1281,7 +1289,7 @@ namespace GridKit { success = false; } - if (!(speed_alias.verify() > 0)) + if (speed_alias.verify().passed()) { success = false; } @@ -1300,7 +1308,7 @@ namespace GridKit { success = false; } - if (!(input_alias.verify() > 0)) + if (input_alias.verify().passed()) { success = false; } diff --git a/tests/UnitTests/PhasorDynamics/GovernorHygovTests.hpp b/tests/UnitTests/PhasorDynamics/GovernorHygovTests.hpp index f72a8ba1a..825733547 100644 --- a/tests/UnitTests/PhasorDynamics/GovernorHygovTests.hpp +++ b/tests/UnitTests/PhasorDynamics/GovernorHygovTests.hpp @@ -57,7 +57,7 @@ namespace GridKit Fixture configured(makeData()); success *= (configured.hygov.size() == static_cast(Utilities::enum_size())); success *= (configured.hygov.getMonitor() != nullptr); - success *= (configured.hygov.verify() == 0); + success *= (configured.hygov.verify().passed()); const auto previous_verbosity = Log::verbosity(); // Suppress expected errors and warnings from the invalid cases below. @@ -65,15 +65,14 @@ namespace GridKit Log::setVerbosity(Log::Verbosity::NONE); Fixture minimal(makeMinimalData()); - success *= (minimal.hygov.verify() == 0); + success *= (minimal.hygov.verify().passed()); success *= defaultsMatchDocumentedValues(); auto missing_trate_data = makeMinimalData(); missing_trate_data.parameters.erase(Params::Trate); - Fixture missing_trate(missing_trate_data); - success *= (missing_trate.hygov.verify() > 0); + success *= constructionRejected>(missing_trate_data); - success *= (empty.verify() > 0); + success *= (!empty.verify().passed()); const RealT nan = std::numeric_limits::quiet_NaN(); const RealT infinity = std::numeric_limits::infinity(); @@ -116,15 +115,14 @@ namespace GridKit { for (const RealT value : nonfinite_values) { - Fixture invalid_fixture(makeData(), {{parameter, value}}); - success *= (invalid_fixture.hygov.verify() > 0); + success *= parameterRejected(parameter, value); } } // The pmech output is required, so a model without an assigned node // is rejected even when every parameter is valid. PhasorDynamics::Governor::Hygov unassigned(makeData()); - success *= (unassigned.verify() > 0); + success *= (!unassigned.verify().passed()); const std::array, 19> invalid_parameter_values{{ {Params::Trate, 0.0}, @@ -150,8 +148,7 @@ namespace GridKit for (const auto& [parameter, value] : invalid_parameter_values) { - Fixture invalid_fixture(makeData(), {{parameter, value}}); - success *= (invalid_fixture.hygov.verify() > 0); + success *= parameterRejected(parameter, value); } // A curve with no rise cannot yield a unique gate. @@ -161,7 +158,7 @@ namespace GridKit {Params::Pgv3, 0.0}, {Params::Pgv4, 0.0}, {Params::Pgv5, 0.0}}); - success *= (flat_curve.hygov.verify() > 0); + success *= (!flat_curve.hygov.verify().passed()); // A curve that rises only outside the configured response limits is // valid because initialization may expand those limits. @@ -175,33 +172,32 @@ namespace GridKit {Params::Pgv3, 0.5}, {Params::Pgv4, 0.5}, {Params::Pgv5, 1.0}}); - success *= (flat_configured_range.hygov.verify() == 0); + success *= (flat_configured_range.hygov.verify().passed()); // A requested backlash is accepted, warns, and remains inactive. Fixture backlash(makeData(), {{Params::db2, 0.5}}); - success *= (backlash.hygov.verify() == 0); + success *= (backlash.hygov.verify().passed()); // Integer JSON values are accepted for real parameters; booleans are // not numeric. auto integer_real = makeData(); integer_real.parameters[Params::Tw] = static_cast(2); Fixture integer_model(integer_real); - success *= (integer_model.hygov.verify() == 0); + success *= (integer_model.hygov.verify().passed()); - auto bad_numeric_type = makeData(); - bad_numeric_type.parameters[Params::Trate] = true; - Fixture bad_numeric_model(bad_numeric_type); - success *= (bad_numeric_model.hygov.verify() > 0); + auto bad_numeric_type = makeData(); + bad_numeric_type.parameters[Params::Trate] = true; + success *= constructionRejected>(bad_numeric_type); Fixture overflowing_component_base( makeData(), {{Params::Trate, std::numeric_limits::max()}}); - success *= (overflowing_component_base.hygov.verify() > 0); + success *= (!overflowing_component_base.hygov.verify().passed()); Fixture overflowing_base_ratio( makeData(), {{Params::Trate, std::numeric_limits::min()}}); - success *= (overflowing_base_ratio.hygov.verify() > 0); + success *= (!overflowing_base_ratio.hygov.verify().passed()); const std::array invalid_system_bases{{ 0.0, @@ -215,7 +211,7 @@ namespace GridKit for (const RealT system_base : invalid_system_bases) { Fixture invalid_base(makeData(), {}, system_base); - success *= (invalid_base.hygov.verify() > 0); + success *= (!invalid_base.hygov.verify().passed()); } success *= unlinkedSignalRejected(); @@ -1008,7 +1004,7 @@ namespace GridKit /// verification, and a machine-provided mechanical-power value. bool prepare(RealT pmech) { - const bool success = (hygov.allocate() == 0) && (hygov.verify() == 0); + const bool success = (hygov.allocate() == 0) && (hygov.verify().passed()); if (!success) { std::cout << "HYGOV fixture preparation failed\n"; @@ -1269,13 +1265,27 @@ namespace GridKit return success; } + /// True when the value is rejected at construction or by verify(). + bool parameterRejected(Params parameter, RealT value) const + { + try + { + Fixture fixture(makeData(), {{parameter, value}}); + return !fixture.hygov.verify().passed(); + } + catch (const std::invalid_argument&) + { + return true; + } + } + template bool unlinkedSignalRejected() const { PhasorDynamics::SignalNode unlinked_node; Fixture fixture(makeData()); fixture.hygov.getPorts().in.template port().connect(&unlinked_node); - return fixture.hygov.verify() > 0; + return !fixture.hygov.verify().passed(); } template diff --git a/tests/UnitTests/PhasorDynamics/StabilizerIeeestTests.hpp b/tests/UnitTests/PhasorDynamics/StabilizerIeeestTests.hpp index 24e959555..40ccdb0ca 100644 --- a/tests/UnitTests/PhasorDynamics/StabilizerIeeestTests.hpp +++ b/tests/UnitTests/PhasorDynamics/StabilizerIeeestTests.hpp @@ -71,7 +71,7 @@ namespace GridKit stab.getPorts().out.template port().connect(&vss_node); stab.allocate(); - success *= (stab.verify() == 0); + success *= (stab.verify().passed()); stab.initialize(); stab.evaluateResidual(); diff --git a/tests/UnitTests/PhasorDynamics/SystemSingleComponentTests.hpp b/tests/UnitTests/PhasorDynamics/SystemSingleComponentTests.hpp index 95108cd4d..15eb11e82 100644 --- a/tests/UnitTests/PhasorDynamics/SystemSingleComponentTests.hpp +++ b/tests/UnitTests/PhasorDynamics/SystemSingleComponentTests.hpp @@ -184,7 +184,7 @@ namespace GridKit // Suppress the expected missing-bus configuration error below. // Use EVERYTHING to inspect the diagnostic. Log::setVerbosity(Log::Verbosity::NONE); - success *= missing_bus_system.verify() > 0; + success *= !missing_bus_system.verify().passed(); Log::setVerbosity(previous_verbosity); return success.report(__func__); @@ -502,7 +502,7 @@ namespace GridKit // Suppress the expected missing-output configuration error below. // Use EVERYTHING to inspect the diagnostic. Log::setVerbosity(Log::Verbosity::NONE); - success *= missing_output_system.verify() > 0; + success *= !missing_output_system.verify().passed(); Log::setVerbosity(previous_verbosity); return success.report(__func__); diff --git a/tests/UnitTests/PhasorDynamics/SystemTests.hpp b/tests/UnitTests/PhasorDynamics/SystemTests.hpp index b7a1b433d..b1389ed9f 100644 --- a/tests/UnitTests/PhasorDynamics/SystemTests.hpp +++ b/tests/UnitTests/PhasorDynamics/SystemTests.hpp @@ -68,9 +68,9 @@ namespace GridKit return 0; } - int verify() const override final + Model::ConfigurationChecks verify() const override final { - return 0; + return {}; } int initialize() override final @@ -415,7 +415,7 @@ namespace GridKit InitializationFailureComponent component; system.addComponent(&component); - success *= system.verify() == 0; + success *= system.verify().passed(); const auto previous_verbosity = Log::verbosity(); Log::setVerbosity(Log::Verbosity::NONE);