From 61207cf79fc0044e91a43654e848e8e58a305e6f Mon Sep 17 00:00:00 2001 From: lukelowry Date: Wed, 2 Sep 2026 01:03:42 -0500 Subject: [PATCH 1/3] initial lambda extraction --- .../Model/PhasorDynamics/Branch/Branch.hpp | 3 - .../PhasorDynamics/Branch/BranchImpl.hpp | 99 +++---- .../Controller/REECB/ReecbImpl.hpp | 92 +++---- .../Controller/REPCA/RepcaImpl.hpp | 247 ++++++------------ .../Converter/REGCA/RegcaImpl.hpp | 149 +++-------- .../Exciter/ESDC1A/Esdc1aImpl.hpp | 209 ++++----------- .../Exciter/IEEET1/Ieeet1Impl.hpp | 99 ++----- .../Exciter/SEXS-PTI/SexsPtiImpl.hpp | 109 ++------ .../Governor/GASTPTI/GastPtiImpl.hpp | 90 +++---- .../Governor/HYGOV/HygovImpl.hpp | 221 ++++++---------- .../Governor/Tgov1/Tgov1Impl.hpp | 42 +-- GridKit/Model/PhasorDynamics/SignalIn.hpp | 44 ++++ GridKit/Utilities/CMakeLists.txt | 2 + GridKit/Utilities/ConfigurationChecks.hpp | 58 ++++ GridKit/Utilities/ParameterReader.hpp | 199 ++++++++++++++ 15 files changed, 713 insertions(+), 950 deletions(-) create mode 100644 GridKit/Utilities/ConfigurationChecks.hpp create mode 100644 GridKit/Utilities/ParameterReader.hpp diff --git a/GridKit/Model/PhasorDynamics/Branch/Branch.hpp b/GridKit/Model/PhasorDynamics/Branch/Branch.hpp index b3f3fff8e..d9dce3fd5 100644 --- a/GridKit/Model/PhasorDynamics/Branch/Branch.hpp +++ b/GridKit/Model/PhasorDynamics/Branch/Branch.hpp @@ -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, diff --git a/GridKit/Model/PhasorDynamics/Branch/BranchImpl.hpp b/GridKit/Model/PhasorDynamics/Branch/BranchImpl.hpp index f7b1391f1..5a59f3594 100644 --- a/GridKit/Model/PhasorDynamics/Branch/BranchImpl.hpp +++ b/GridKit/Model/PhasorDynamics/Branch/BranchImpl.hpp @@ -7,14 +7,12 @@ * */ -#include - -#include - #include #include #include #include +#include +#include namespace GridKit { @@ -156,32 +154,23 @@ namespace GridKit template int 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; + Utilities::ConfigurationChecks checks("Branch"); + + 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 parameter_error_count_ + checks.errorCount(); } template @@ -351,14 +340,19 @@ 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_); + Utilities::ConfigurationChecks checks("Branch"); + Utilities::ParameterReader reader(data, checks); + + 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_); + + parameter_error_count_ = checks.errorCount(); if (data.buses.contains(Buses::bus1)) { @@ -371,35 +365,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/Controller/REECB/ReecbImpl.hpp b/GridKit/Model/PhasorDynamics/Controller/REECB/ReecbImpl.hpp index 2ed478014..7802b57a9 100644 --- a/GridKit/Model/PhasorDynamics/Controller/REECB/ReecbImpl.hpp +++ b/GridKit/Model/PhasorDynamics/Controller/REECB/ReecbImpl.hpp @@ -17,6 +17,7 @@ #include #include #include +#include #include namespace GridKit @@ -163,118 +164,99 @@ namespace GridKit template int Reecb::verify() const { - int ret = static_cast(parameter_error_count_); + Utilities::ConfigurationChecks checks("Reecb"); - 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) + 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"); + ports_.in.template port().checkOptional(checks, "pe"); + ports_.in.template port().checkOptional(checks, "qgen"); + ports_.in.template port().checkOptional(checks, "qext"); + ports_.in.template port().checkOptional(checks, "pfaref"); + ports_.in.template port().checkOptional(checks, "pref"); - return ret; + return static_cast(parameter_error_count_) + checks.errorCount(); } /** diff --git a/GridKit/Model/PhasorDynamics/Controller/REPCA/RepcaImpl.hpp b/GridKit/Model/PhasorDynamics/Controller/REPCA/RepcaImpl.hpp index df9da208a..e08ec6587 100644 --- a/GridKit/Model/PhasorDynamics/Controller/REPCA/RepcaImpl.hpp +++ b/GridKit/Model/PhasorDynamics/Controller/REPCA/RepcaImpl.hpp @@ -8,7 +8,6 @@ #include #include -#include #include #include @@ -16,7 +15,9 @@ #include #include #include +#include #include +#include namespace GridKit { @@ -147,18 +148,9 @@ namespace GridKit template int Repca::verify() const { - int ret = static_cast(parameter_error_count_); + Utilities::ConfigurationChecks checks("Repca"); - 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 +158,45 @@ 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"); + + ports_.in.template port().checkRequired(checks, "ir"); + ports_.in.template port().checkRequired(checks, "ii"); + ports_.in.template port().checkRequired(checks, "p"); + ports_.in.template port().checkRequired(checks, "q"); + + ports_.in.template port().checkOptional(checks, "vref"); + ports_.in.template port().checkOptional(checks, "pref"); + ports_.in.template port().checkOptional(checks, "qref"); + ports_.in.template port().checkOptional(checks, "freq"); + ports_.in.template port().checkOptional(checks, "freqref"); + + return static_cast(parameter_error_count_) + checks.errorCount(); } /** @@ -849,90 +817,42 @@ namespace GridKit 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"); + Utilities::ConfigurationChecks checks("Repca"); + Utilities::ParameterReader reader(data, checks); + + 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_); + + parameter_error_count_ = static_cast(checks.errorCount()); setDerivedParameters(); } @@ -982,20 +902,13 @@ 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) - { - if (value < ZERO) - { - Log::error() << "Repca: " << name << " must be non-negative\n"; - ++parameter_error_count_; - } - }; - - 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"); + Utilities::ConfigurationChecks checks("Repca"); + checks.check(Tfltr_ >= ZERO, "Tfltr must be non-negative"); + checks.check(Tft_ >= ZERO, "Tft must be non-negative"); + checks.check(Tfv_ >= ZERO, "Tfv must be non-negative"); + checks.check(Tp_ >= ZERO, "Tp must be non-negative"); + checks.check(Tlag_ >= ZERO, "Tlag must be non-negative"); + parameter_error_count_ += static_cast(checks.errorCount()); 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/RegcaImpl.hpp b/GridKit/Model/PhasorDynamics/Converter/REGCA/RegcaImpl.hpp index 233a5be86..b987fb98c 100644 --- a/GridKit/Model/PhasorDynamics/Converter/REGCA/RegcaImpl.hpp +++ b/GridKit/Model/PhasorDynamics/Converter/REGCA/RegcaImpl.hpp @@ -9,7 +9,6 @@ #include #include #include -#include #include #include @@ -17,7 +16,9 @@ #include #include #include +#include #include +#include namespace GridKit { @@ -170,76 +171,28 @@ namespace GridKit 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"); - } + Utilities::ConfigurationChecks checks("Regca"); + Utilities::ParameterReader reader(data, checks); + + 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_); + + parameter_error_count_ = static_cast(checks.errorCount()); setDerivedParameters(); } @@ -370,51 +323,25 @@ namespace GridKit template int Regca::verify() const { - int ret = static_cast(parameter_error_count_); + Utilities::ConfigurationChecks checks("Regca"); - auto check = [&](bool condition, const char* message) - { - if (!condition) - { - Log::error() << "Regca: " << message << '\n'; - ret += 1; - } - }; - - if (bus_ == nullptr) - { - Log::error() << "Regca: bus pointer is null\n"; - ret += 1; - } + checks.check(bus_ != nullptr, "bus pointer is null"); - 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(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"); - if (ports_.in.template port()) - { - if (!ports_.in.template port().linked()) - { - Log::error() << "Regca: ipcmd signal attached with no linked source\n"; - ret += 1; - } - } + 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: iqcmd signal attached with no linked source\n"; - ret += 1; - } - } + ports_.in.template port().checkOptional(checks, "ipcmd"); + ports_.in.template port().checkOptional(checks, "iqcmd"); - return ret; + return static_cast(parameter_error_count_) + checks.errorCount(); } /** diff --git a/GridKit/Model/PhasorDynamics/Exciter/ESDC1A/Esdc1aImpl.hpp b/GridKit/Model/PhasorDynamics/Exciter/ESDC1A/Esdc1aImpl.hpp index a4af51239..2e86ddff5 100644 --- a/GridKit/Model/PhasorDynamics/Exciter/ESDC1A/Esdc1aImpl.hpp +++ b/GridKit/Model/PhasorDynamics/Exciter/ESDC1A/Esdc1aImpl.hpp @@ -8,7 +8,6 @@ #include #include -#include #include #include @@ -16,7 +15,9 @@ #include #include #include +#include #include +#include namespace GridKit { @@ -145,27 +146,14 @@ namespace GridKit template int Esdc1a::verify() const { - int ret = static_cast(parameter_error_count_); + Utilities::ConfigurationChecks checks("Esdc1a"); - 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 +162,27 @@ 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"); + checks.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(ports_.out.template port().connected(), + "required efd output signal is not assigned"); - if (Spdmlt_ && !ports_.in.template port().connected()) - { - Log::error() << "Esdc1a: speed signal is required when Spdmlt is enabled\n"; - ret += 1; - } + checks.check(!Spdmlt_ || ports_.in.template port().connected(), + "speed signal is required when Spdmlt is enabled"); - // 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; - } - }; + ports_.in.template port().checkOptional(checks, "speed"); + ports_.in.template port().checkOptional(checks, "vref"); + ports_.in.template port().checkOptional(checks, "vs"); + ports_.in.template port().checkOptional(checks, "vuel"); - check_attached_signal.template operator()("speed"); - check_attached_signal.template operator()("vref"); - check_attached_signal.template operator()("vs"); - check_attached_signal.template operator()("vuel"); - - return ret; + return static_cast(parameter_error_count_) + checks.errorCount(); } /** @@ -649,97 +618,30 @@ namespace GridKit 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() << "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_; - } - }; + Utilities::ConfigurationChecks checks("Esdc1a"); + Utilities::ParameterReader reader(data, checks); + + 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_); + + parameter_error_count_ = static_cast(checks.errorCount()); - 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(); } @@ -798,20 +700,13 @@ 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) - { - if (value < ZERO) - { - Log::error() << "Esdc1a: " << name << " must be non-negative\n"; - ++parameter_error_count_; - } - }; - - 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"); + Utilities::ConfigurationChecks checks("Esdc1a"); + checks.check(Tr_ >= ZERO, "Tr must be non-negative"); + checks.check(Ta_ >= ZERO, "Ta must be non-negative"); + checks.check(Tb_ >= ZERO, "Tb must be non-negative"); + checks.check(Te_ >= ZERO, "Te must be non-negative"); + checks.check(Tf1_ >= ZERO, "Tf1 must be non-negative"); + parameter_error_count_ += static_cast(checks.errorCount()); 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/Ieeet1Impl.hpp b/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1Impl.hpp index 6be4cf1eb..a38cc0f16 100644 --- a/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1Impl.hpp +++ b/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1Impl.hpp @@ -17,6 +17,7 @@ #include #include #include +#include #include namespace GridKit @@ -128,54 +129,34 @@ namespace GridKit template int Ieeet1::verify() const { - int ret = 0; + Utilities::ConfigurationChecks checks("Ieeet1"); - 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"); + ports_.in.template port().checkOptional(checks, "speed"); + ports_.in.template port().checkOptional(checks, "vref"); + ports_.in.template port().checkOptional(checks, "vs"); + ports_.in.template port().checkOptional(checks, "vuel"); + ports_.in.template port().checkOptional(checks, "voel"); - return ret; + return checks.errorCount(); } /** @@ -228,19 +209,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()) @@ -439,29 +411,12 @@ namespace GridKit auto* ws = ws_.getData(); // Attached signals are read live; unattached ones keep the latched value. - auto read_signal = [&](const ScalarT& latched) - { - 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_); + auto* ws_indices = ws_indices_.data(); + ports_.in.template port().refreshWorkspace(omega_set_, ws[static_cast(Ieeet1ExternalVariables::OMEGA)], ws_indices[static_cast(Ieeet1ExternalVariables::OMEGA)]); + ports_.in.template port().refreshWorkspace(vref_set_, ws[static_cast(Ieeet1ExternalVariables::VREF)], ws_indices[static_cast(Ieeet1ExternalVariables::VREF)]); + ports_.in.template port().refreshWorkspace(vs_set_, ws[static_cast(Ieeet1ExternalVariables::VS)], ws_indices[static_cast(Ieeet1ExternalVariables::VS)]); + ports_.in.template port().refreshWorkspace(vuel_set_, ws[static_cast(Ieeet1ExternalVariables::VUEL)], ws_indices[static_cast(Ieeet1ExternalVariables::VUEL)]); + ports_.in.template port().refreshWorkspace(voel_set_, ws[static_cast(Ieeet1ExternalVariables::VOEL)], ws_indices[static_cast(Ieeet1ExternalVariables::VOEL)]); // Bus voltages auto* wb = wb_.getData(); diff --git a/GridKit/Model/PhasorDynamics/Exciter/SEXS-PTI/SexsPtiImpl.hpp b/GridKit/Model/PhasorDynamics/Exciter/SEXS-PTI/SexsPtiImpl.hpp index 5c9b035d2..27e6fce29 100644 --- a/GridKit/Model/PhasorDynamics/Exciter/SEXS-PTI/SexsPtiImpl.hpp +++ b/GridKit/Model/PhasorDynamics/Exciter/SEXS-PTI/SexsPtiImpl.hpp @@ -14,6 +14,7 @@ #include #include #include +#include #include namespace GridKit @@ -93,63 +94,24 @@ namespace GridKit template int SexsPti::verify() const { - int ret = missing_param_count_; + Utilities::ConfigurationChecks checks("SexsPti"); - 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; - } + 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"); - 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; - } + checks.check(ports_.out.template port().connected(), + "required EFD signal is not assigned"); - 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"); + ports_.in.template port().checkOptional(checks, "vref"); + ports_.in.template port().checkOptional(checks, "vs"); + ports_.in.template port().checkOptional(checks, "vuel"); + ports_.in.template port().checkOptional(checks, "voel"); - return ret; + return missing_param_count_ + checks.errorCount(); } template @@ -165,18 +127,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()) @@ -297,27 +250,11 @@ namespace GridKit auto* ws = ws_.getData(); // Attached signals are read live; unattached ones keep the latched value. - auto read_signal = [&](const ScalarT& latched) - { - 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_); + auto* ws_indices = ws_indices_.data(); + ports_.in.template port().refreshWorkspace(vref_set_, ws[static_cast(SexsPtiExternalVariables::VREF)], ws_indices[static_cast(SexsPtiExternalVariables::VREF)]); + ports_.in.template port().refreshWorkspace(vs_set_, ws[static_cast(SexsPtiExternalVariables::VS)], ws_indices[static_cast(SexsPtiExternalVariables::VS)]); + ports_.in.template port().refreshWorkspace(vuel_set_, ws[static_cast(SexsPtiExternalVariables::VUEL)], ws_indices[static_cast(SexsPtiExternalVariables::VUEL)]); + ports_.in.template port().refreshWorkspace(voel_set_, ws[static_cast(SexsPtiExternalVariables::VOEL)], ws_indices[static_cast(SexsPtiExternalVariables::VOEL)]); auto* wb = wb_.getData(); wb[0] = bus_->Vr(); diff --git a/GridKit/Model/PhasorDynamics/Governor/GASTPTI/GastPtiImpl.hpp b/GridKit/Model/PhasorDynamics/Governor/GASTPTI/GastPtiImpl.hpp index e35cc8a2a..91e5b3e73 100644 --- a/GridKit/Model/PhasorDynamics/Governor/GASTPTI/GastPtiImpl.hpp +++ b/GridKit/Model/PhasorDynamics/Governor/GASTPTI/GastPtiImpl.hpp @@ -16,6 +16,7 @@ #include #include #include +#include #include namespace GridKit @@ -135,80 +136,53 @@ namespace GridKit { const auto PMECH = static_cast(GastPtiInternalVariables::PMECH); - int ret = static_cast(parameter_error_count_); + Utilities::ConfigurationChecks checks("GastPti"); - 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, + 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) + 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; - }; + ports_.in.template port().checkOptional(checks, "speed"); + ports_.in.template port().checkOptional(checks, "pref"); - 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 +190,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 +210,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 static_cast(parameter_error_count_) + checks.errorCount(); } /** diff --git a/GridKit/Model/PhasorDynamics/Governor/HYGOV/HygovImpl.hpp b/GridKit/Model/PhasorDynamics/Governor/HYGOV/HygovImpl.hpp index 71ada99e0..824b76422 100644 --- a/GridKit/Model/PhasorDynamics/Governor/HYGOV/HygovImpl.hpp +++ b/GridKit/Model/PhasorDynamics/Governor/HYGOV/HygovImpl.hpp @@ -9,14 +9,15 @@ #include #include #include -#include #include #include #include #include #include +#include #include +#include namespace GridKit { @@ -129,25 +130,16 @@ namespace GridKit template int 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; - } - }; + Utilities::ConfigurationChecks checks("Hygov"); 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 +148,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 +167,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 +177,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 +196,23 @@ 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"); - - // 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; - } - }; + checks.check(ports_.out.template port().connected(), + "pmech output signal must be assigned"); - check_attached_signal.template operator()("speed"); - check_attached_signal.template operator()("pref"); - check_attached_signal.template operator()("paux"); + ports_.in.template port().checkOptional(checks, "speed"); + ports_.in.template port().checkOptional(checks, "pref"); + ports_.in.template port().checkOptional(checks, "paux"); - return ret; + return static_cast(parameter_error_count_) + checks.errorCount(); } /** @@ -638,88 +617,49 @@ namespace GridKit 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) + Utilities::ConfigurationChecks checks("Hygov"); + Utilities::ParameterReader reader(data, checks); + + 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]); + + parameter_error_count_ = static_cast(checks.errorCount()); setDerivedParameters(); } @@ -797,20 +737,13 @@ 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) - { - if (value < ZERO) - { - Log::error() << "Hygov: " << name << " must be non-negative\n"; - ++parameter_error_count_; - } - }; - - 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"); + Utilities::ConfigurationChecks checks("Hygov"); + checks.check(Tr_ >= ZERO, "Tr must be non-negative"); + checks.check(Tf_ >= ZERO, "Tf must be non-negative"); + checks.check(Tg_ >= ZERO, "Tg must be non-negative"); + checks.check(Tw_ >= ZERO, "Tw must be non-negative"); + checks.check(Tnp_ >= ZERO, "Tnp must be non-negative"); + parameter_error_count_ += static_cast(checks.errorCount()); 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/Tgov1Impl.hpp b/GridKit/Model/PhasorDynamics/Governor/Tgov1/Tgov1Impl.hpp index 01843aa0f..c99f170f2 100644 --- a/GridKit/Model/PhasorDynamics/Governor/Tgov1/Tgov1Impl.hpp +++ b/GridKit/Model/PhasorDynamics/Governor/Tgov1/Tgov1Impl.hpp @@ -15,6 +15,7 @@ #include #include #include +#include #include namespace GridKit @@ -220,39 +221,20 @@ namespace GridKit template int 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; - } + Utilities::ConfigurationChecks checks("Tgov1"); - 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"); + + ports_.in.template port().checkOptional(checks, "speed"); + ports_.in.template port().checkOptional(checks, "pref"); - return ret; + return checks.errorCount(); } /** diff --git a/GridKit/Model/PhasorDynamics/SignalIn.hpp b/GridKit/Model/PhasorDynamics/SignalIn.hpp index d996f34c5..e4db724cf 100644 --- a/GridKit/Model/PhasorDynamics/SignalIn.hpp +++ b/GridKit/Model/PhasorDynamics/SignalIn.hpp @@ -6,7 +6,9 @@ #pragma once +#include #include +#include namespace GridKit { @@ -34,6 +36,48 @@ namespace GridKit return this->signal_node_->getVariableIndex(); } + /// Verify an optional input is linked whenever it is connected. + void checkOptional(Utilities::ConfigurationChecks& checks, const char* name) const + { + if (this->connected() && !this->linked()) + { + checks.fail() << name << " signal attached with no linked source\n"; + } + } + + /// Verify a required input is connected and linked. + void checkRequired(Utilities::ConfigurationChecks& checks, const char* name) const + { + if (!this->connected()) + { + checks.fail() << name << " signal is required\n"; + return; + } + checkOptional(checks, name); + } + + /// Read the connected signal, or the fallback when disconnected. + ScalarT readOrDefault(ScalarT fallback) const + { + if (this->connected()) + { + return readSignal(); + } + return fallback; + } + + /// Refresh an explicitly selected workspace value and global index. + void refreshWorkspace(ScalarT fallback, ScalarT& value, IdxT& index) const + { + value = fallback; + index = INVALID_INDEX; + if (this->connected()) + { + value = readSignal(); + index = signalVariableIndex(); + } + } + /// Write a value to the connected signal node. /// /// @warning Use only during initialization as this violates assumptions. diff --git a/GridKit/Utilities/CMakeLists.txt b/GridKit/Utilities/CMakeLists.txt index ccc633074..f0d02a1d0 100644 --- a/GridKit/Utilities/CMakeLists.txt +++ b/GridKit/Utilities/CMakeLists.txt @@ -15,10 +15,12 @@ add_subdirectory(CliArgs) install(TARGETS Utilities EXPORT gridkit-targets) install( FILES Colors.hpp + ConfigurationChecks.hpp Enum.hpp Errors.hpp FileIO.hpp MapFromCsr.hpp + ParameterReader.hpp Stream.hpp String.hpp DESTINATION include/GridKit/Utilities) diff --git a/GridKit/Utilities/ConfigurationChecks.hpp b/GridKit/Utilities/ConfigurationChecks.hpp new file mode 100644 index 000000000..141c80281 --- /dev/null +++ b/GridKit/Utilities/ConfigurationChecks.hpp @@ -0,0 +1,58 @@ +/** + * @file ConfigurationChecks.hpp + * @author Luke Lowery (lukel@tamu.edu) + * @brief Error accumulator for model configuration validation. + */ + +#pragma once + +#include + +#include + +namespace GridKit +{ + namespace Utilities + { + /** + * @brief Error accumulator for model configuration validation. + * + * One instance collects the errors found while loading parameters or + * verifying a model, logging each with the model-name prefix. The + * caller reports errorCount() as its error total. + */ + class ConfigurationChecks + { + public: + explicit ConfigurationChecks(const char* model) + : model_(model) + { + } + + /// Log one error against this model and count it. + std::ostream& fail() + { + ++error_count_; + return Logger::error() << model_ << ": "; + } + + /// Log and count one error when the condition does not hold. + void check(bool condition, const char* message) + { + if (!condition) + { + fail() << message << '\n'; + } + } + + int errorCount() const + { + return error_count_; + } + + private: + const char* model_; + int error_count_{0}; + }; + } // namespace Utilities +} // namespace GridKit diff --git a/GridKit/Utilities/ParameterReader.hpp b/GridKit/Utilities/ParameterReader.hpp new file mode 100644 index 000000000..3b9955e18 --- /dev/null +++ b/GridKit/Utilities/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 + +namespace GridKit +{ + namespace Utilities + { + /** + * @brief Reads typed parameters out of a model data container. + * + * Every method leaves the target untouched and reports one error through + * the shared checks object when a provided value has the wrong type or is + * not finite; an omitted optional parameter keeps the model default + * silently. Real parameters accept integer values. Parameter names in + * messages come from the parameter enumeration. + * + * @tparam ModelDataT A model data container exposing `RealT`, `IdxT`, + * the `Parameters` enumeration, and the `parameters` map. + */ + template + class ParameterReader + { + public: + using RealT = typename ModelDataT::RealT; + using IdxT = typename ModelDataT::IdxT; + using ParametersT = typename ModelDataT::Parameters; + + ParameterReader(const ModelDataT& data, ConfigurationChecks& checks) + : parameters_(data.parameters), + checks_(checks) + { + } + + /** + * @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 and stored. + */ + bool loadReal(ParametersT key, RealT& target) + { + 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 + { + checks_.fail() << "parameter '" << magic_enum::enum_name(key) + << "' must be numeric\n"; + return false; + } + + if (!std::isfinite(parsed_value)) + { + checks_.fail() << "parameter '" << magic_enum::enum_name(key) + << "' must be finite\n"; + return false; + } + + 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 when provided. + * @return true when the parameter was provided and stored. + */ + bool requireReal(ParametersT key, RealT& target) + { + if (!parameters_.contains(key)) + { + checks_.fail() << "missing required parameter '" + << magic_enum::enum_name(key) << "'\n"; + return false; + } + 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 and stored. + */ + bool loadSwitch(ParametersT key, bool& target) + { + 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; + } + + checks_.fail() << "parameter '" << magic_enum::enum_name(key) + << "' must be boolean\n"; + return false; + } + + /** + * @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 when provided. + * @return true when the parameter was provided and stored. + */ + bool requireSwitch(ParametersT key, bool& target) + { + if (!parameters_.contains(key)) + { + checks_.fail() << "missing required parameter '" + << magic_enum::enum_name(key) << "'\n"; + return false; + } + + 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; + } + + checks_.fail() << "parameter '" << magic_enum::enum_name(key) + << "' must be bool or 0/1\n"; + return false; + } + + /** + * @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 and stored. + */ + bool loadSelector(ParametersT key, IdxT& target) + { + 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; + } + + checks_.fail() << "parameter '" << magic_enum::enum_name(key) + << "' must be an integer selector\n"; + return false; + } + + private: + const std::map>& parameters_; + ConfigurationChecks& checks_; + }; + } // namespace Utilities +} // namespace GridKit From 59c9dc6a27d19db42b3ef1d906f4d053f9cb7980 Mon Sep 17 00:00:00 2001 From: lukelowry Date: Wed, 2 Sep 2026 01:34:08 -0500 Subject: [PATCH 2/3] older model migration and unify with new helper --- .../PhasorDynamics/BusFault/BusFault.hpp | 5 +- .../PhasorDynamics/BusFault/BusFaultImpl.hpp | 20 +-- .../PhasorDynamics/Controller/REECB/Reecb.hpp | 9 - .../Controller/REECB/ReecbImpl.hpp | 154 +++++------------- .../PhasorDynamics/Exciter/IEEET1/Ieeet1.hpp | 3 + .../Exciter/IEEET1/Ieeet1Impl.hpp | 80 +++------ .../Exciter/SEXS-PTI/SexsPtiImpl.hpp | 31 ++-- .../Governor/GASTPTI/GastPti.hpp | 4 - .../Governor/GASTPTI/GastPtiImpl.hpp | 73 ++------- .../Governor/Tgov1/CMakeLists.txt | 3 + .../PhasorDynamics/Governor/Tgov1/Tgov1.hpp | 5 +- .../Governor/Tgov1/Tgov1Impl.hpp | 55 ++----- .../Model/PhasorDynamics/Load/LoadZ/LoadZ.hpp | 5 +- .../PhasorDynamics/Load/LoadZ/LoadZImpl.hpp | 16 +- .../PhasorDynamics/Load/LoadZIP/LoadZIP.hpp | 5 +- .../Load/LoadZIP/LoadZIPImpl.hpp | 26 ++- .../SignalSource/CMakeLists.txt | 2 + .../SignalSource/ConstantSignalSource.hpp | 3 + .../SignalSource/ConstantSignalSourceImpl.hpp | 24 ++- .../Stabilizer/IEEEST/Ieeest.hpp | 3 + .../Stabilizer/IEEEST/IeeestImpl.hpp | 101 ++++-------- .../SynchronousMachine/GENROU/Genrou.hpp | 3 + .../SynchronousMachine/GENROU/GenrouImpl.hpp | 123 ++++---------- .../SynchronousMachine/GENSAL/Gensal.hpp | 3 + .../SynchronousMachine/GENSAL/GensalImpl.hpp | 105 +++--------- .../GenClassical/GenClassical.hpp | 3 + .../GenClassical/GenClassicalImpl.hpp | 49 ++---- 27 files changed, 287 insertions(+), 626 deletions(-) diff --git a/GridKit/Model/PhasorDynamics/BusFault/BusFault.hpp b/GridKit/Model/PhasorDynamics/BusFault/BusFault.hpp index 405f78e6c..343b43f10 100644 --- a/GridKit/Model/PhasorDynamics/BusFault/BusFault.hpp +++ b/GridKit/Model/PhasorDynamics/BusFault/BusFault.hpp @@ -55,7 +55,7 @@ namespace GridKit int verify() const override final { - return 0; + return static_cast(parameter_error_count_); } void updateTime(RealT /* t */, RealT /* a */) override final @@ -124,6 +124,9 @@ namespace GridKit /// Variable monitor std::unique_ptr monitor_; + + /// Count of parameter-loading errors reported through verify() + IdxT parameter_error_count_{0}; }; } // namespace PhasorDynamics diff --git a/GridKit/Model/PhasorDynamics/BusFault/BusFaultImpl.hpp b/GridKit/Model/PhasorDynamics/BusFault/BusFaultImpl.hpp index 89d121246..2037f5254 100644 --- a/GridKit/Model/PhasorDynamics/BusFault/BusFaultImpl.hpp +++ b/GridKit/Model/PhasorDynamics/BusFault/BusFaultImpl.hpp @@ -6,6 +6,8 @@ #include #include #include +#include +#include namespace GridKit { @@ -59,20 +61,14 @@ 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)); - } + Utilities::ConfigurationChecks checks("BusFault"); + Utilities::ParameterReader reader(data, checks); - if (data.parameters.contains(Parameter::X)) - { - X_ = std::get(data.parameters.at(Parameter::X)); - } + reader.loadReal(Parameter::R, R_); + reader.loadReal(Parameter::X, X_); + reader.loadSwitch(Parameter::state0, status_); - if (data.parameters.contains(Parameter::state0)) - { - status_ = std::get(data.parameters.at(Parameter::state0)); - } + parameter_error_count_ = static_cast(checks.errorCount()); if (data.buses.contains(Buses::bus)) { diff --git a/GridKit/Model/PhasorDynamics/Controller/REECB/Reecb.hpp b/GridKit/Model/PhasorDynamics/Controller/REECB/Reecb.hpp index 6ec4bb77c..c2ce1e748 100644 --- a/GridKit/Model/PhasorDynamics/Controller/REECB/Reecb.hpp +++ b/GridKit/Model/PhasorDynamics/Controller/REECB/Reecb.hpp @@ -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(); diff --git a/GridKit/Model/PhasorDynamics/Controller/REECB/ReecbImpl.hpp b/GridKit/Model/PhasorDynamics/Controller/REECB/ReecbImpl.hpp index 7802b57a9..c3e7b5a66 100644 --- a/GridKit/Model/PhasorDynamics/Controller/REECB/ReecbImpl.hpp +++ b/GridKit/Model/PhasorDynamics/Controller/REECB/ReecbImpl.hpp @@ -19,6 +19,7 @@ #include #include #include +#include namespace GridKit { @@ -1165,80 +1166,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 * @@ -1289,52 +1216,47 @@ namespace GridKit using Params = typename ModelDataT::Parameters; parameter_error_count_ = 0; - Vref0_given_ = false; + Vref0_given_ = data.parameters.contains(Params::Vref0); + + Utilities::ConfigurationChecks checks("Reecb"); + Utilities::ParameterReader reader(data, checks); - 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_); + + parameter_error_count_ = static_cast(checks.errorCount()); setDerivedParameters(); } diff --git a/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1.hpp b/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1.hpp index f45eef4f0..1a861389f 100644 --- a/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1.hpp +++ b/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1.hpp @@ -150,6 +150,9 @@ namespace GridKit /// Variable monitor std::unique_ptr monitor_; + /// Count of parameter-loading errors reported through verify() + IdxT parameter_error_count_{0}; + // Parameter initialization function void initModelParams(const ModelDataT& data); void setDerivedParameters(); diff --git a/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1Impl.hpp b/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1Impl.hpp index a38cc0f16..f1a95feec 100644 --- a/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1Impl.hpp +++ b/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1Impl.hpp @@ -19,6 +19,7 @@ #include #include #include +#include namespace GridKit { @@ -156,7 +157,7 @@ namespace GridKit ports_.in.template port().checkOptional(checks, "vuel"); ports_.in.template port().checkOptional(checks, "voel"); - return checks.errorCount(); + return static_cast(parameter_error_count_) + checks.errorCount(); } /** @@ -442,62 +443,27 @@ 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)); - } + parameter_error_count_ = 0; + + Utilities::ConfigurationChecks checks("Ieeet1"); + Utilities::ParameterReader reader(data, checks); + + 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_); + + parameter_error_count_ = static_cast(checks.errorCount()); setDerivedParameters(); } diff --git a/GridKit/Model/PhasorDynamics/Exciter/SEXS-PTI/SexsPtiImpl.hpp b/GridKit/Model/PhasorDynamics/Exciter/SEXS-PTI/SexsPtiImpl.hpp index 27e6fce29..ba327746c 100644 --- a/GridKit/Model/PhasorDynamics/Exciter/SEXS-PTI/SexsPtiImpl.hpp +++ b/GridKit/Model/PhasorDynamics/Exciter/SEXS-PTI/SexsPtiImpl.hpp @@ -16,6 +16,7 @@ #include #include #include +#include namespace GridKit { @@ -275,27 +276,17 @@ namespace GridKit { using Params = typename ModelDataT::Parameters; - missing_param_count_ = 0; + Utilities::ConfigurationChecks checks("SexsPti"); + Utilities::ParameterReader reader(data, checks); - 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_); + + missing_param_count_ = checks.errorCount(); } template diff --git a/GridKit/Model/PhasorDynamics/Governor/GASTPTI/GastPti.hpp b/GridKit/Model/PhasorDynamics/Governor/GASTPTI/GastPti.hpp index 300218d25..b30268dbc 100644 --- a/GridKit/Model/PhasorDynamics/Governor/GASTPTI/GastPti.hpp +++ b/GridKit/Model/PhasorDynamics/Governor/GASTPTI/GastPti.hpp @@ -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(); diff --git a/GridKit/Model/PhasorDynamics/Governor/GASTPTI/GastPtiImpl.hpp b/GridKit/Model/PhasorDynamics/Governor/GASTPTI/GastPtiImpl.hpp index 91e5b3e73..e5705102c 100644 --- a/GridKit/Model/PhasorDynamics/Governor/GASTPTI/GastPtiImpl.hpp +++ b/GridKit/Model/PhasorDynamics/Governor/GASTPTI/GastPtiImpl.hpp @@ -18,6 +18,7 @@ #include #include #include +#include namespace GridKit { @@ -541,45 +542,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 * @@ -624,26 +586,25 @@ namespace GridKit 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)) + Utilities::ConfigurationChecks checks("GastPti"); + Utilities::ParameterReader reader(data, checks); + + 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_; - } + + parameter_error_count_ = static_cast(checks.errorCount()); setDerivedParameters(); } 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..2c8703b24 100644 --- a/GridKit/Model/PhasorDynamics/Governor/Tgov1/Tgov1.hpp +++ b/GridKit/Model/PhasorDynamics/Governor/Tgov1/Tgov1.hpp @@ -110,7 +110,10 @@ namespace GridKit // Input States (which can be parameters) ScalarT pref_set_{0}; - // Component ports + /// Count of parameter-loading errors reported through verify() + IdxT parameter_error_count_{0}; + + /// 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 c99f170f2..d8f9768bf 100644 --- a/GridKit/Model/PhasorDynamics/Governor/Tgov1/Tgov1Impl.hpp +++ b/GridKit/Model/PhasorDynamics/Governor/Tgov1/Tgov1Impl.hpp @@ -17,6 +17,7 @@ #include #include #include +#include namespace GridKit { @@ -91,45 +92,21 @@ 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)); - } + parameter_error_count_ = 0; - 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::Dt)) - { - Dt_ = std::get(data.parameters.at(Parameter::Dt)); - } + Utilities::ConfigurationChecks checks("Tgov1"); + Utilities::ParameterReader reader(data, checks); + + 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_); + + parameter_error_count_ = static_cast(checks.errorCount()); } /** @@ -234,7 +211,7 @@ namespace GridKit ports_.in.template port().checkOptional(checks, "speed"); ports_.in.template port().checkOptional(checks, "pref"); - return checks.errorCount(); + return static_cast(parameter_error_count_) + checks.errorCount(); } /** diff --git a/GridKit/Model/PhasorDynamics/Load/LoadZ/LoadZ.hpp b/GridKit/Model/PhasorDynamics/Load/LoadZ/LoadZ.hpp index c27866a38..a006bc114 100644 --- a/GridKit/Model/PhasorDynamics/Load/LoadZ/LoadZ.hpp +++ b/GridKit/Model/PhasorDynamics/Load/LoadZ/LoadZ.hpp @@ -58,7 +58,7 @@ namespace GridKit virtual int verify() const override final { - return 0; + return static_cast(parameter_error_count_); } public: @@ -116,6 +116,9 @@ namespace GridKit RealT g_; std::unique_ptr monitor_; + + /// Count of parameter-loading errors reported through verify() + IdxT parameter_error_count_{0}; }; } // namespace PhasorDynamics diff --git a/GridKit/Model/PhasorDynamics/Load/LoadZ/LoadZImpl.hpp b/GridKit/Model/PhasorDynamics/Load/LoadZ/LoadZImpl.hpp index b3e24fc8a..6cd0c26ca 100644 --- a/GridKit/Model/PhasorDynamics/Load/LoadZ/LoadZImpl.hpp +++ b/GridKit/Model/PhasorDynamics/Load/LoadZ/LoadZImpl.hpp @@ -7,6 +7,8 @@ #include #include #include +#include +#include namespace GridKit { @@ -46,15 +48,13 @@ 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)); - } + Utilities::ConfigurationChecks checks("LoadZ"); + Utilities::ParameterReader reader(data, checks); + reader.loadReal(Parameter::R, R_); + reader.loadReal(Parameter::X, X_); + + parameter_error_count_ = static_cast(checks.errorCount()); size_ = 2; setDerivedParams(); diff --git a/GridKit/Model/PhasorDynamics/Load/LoadZIP/LoadZIP.hpp b/GridKit/Model/PhasorDynamics/Load/LoadZIP/LoadZIP.hpp index 737cba8c7..3d70ce028 100644 --- a/GridKit/Model/PhasorDynamics/Load/LoadZIP/LoadZIP.hpp +++ b/GridKit/Model/PhasorDynamics/Load/LoadZIP/LoadZIP.hpp @@ -58,7 +58,7 @@ namespace GridKit int verify() const override final { - return 0; + return static_cast(parameter_error_count_); } public: @@ -132,6 +132,9 @@ namespace GridKit RealT alphaZ_{1.0}; std::unique_ptr monitor_; + + /// Count of parameter-loading errors reported through verify() + IdxT parameter_error_count_{0}; }; } // namespace PhasorDynamics diff --git a/GridKit/Model/PhasorDynamics/Load/LoadZIP/LoadZIPImpl.hpp b/GridKit/Model/PhasorDynamics/Load/LoadZIP/LoadZIPImpl.hpp index 929cfb746..3c0661068 100644 --- a/GridKit/Model/PhasorDynamics/Load/LoadZIP/LoadZIPImpl.hpp +++ b/GridKit/Model/PhasorDynamics/Load/LoadZIP/LoadZIPImpl.hpp @@ -4,6 +4,8 @@ #include #include #include +#include +#include namespace GridKit { @@ -56,25 +58,17 @@ 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)); - } + parameter_error_count_ = 0; - if (data.parameters.contains(Parameter::alphaI)) - { - alphaI_ = std::get(data.parameters.at(Parameter::alphaI)); - } + Utilities::ConfigurationChecks checks("LoadZIP"); + Utilities::ParameterReader reader(data, checks); + reader.loadReal(Parameter::Pnom, Pnom_); + reader.loadReal(Parameter::Qnom, Qnom_); + reader.loadReal(Parameter::alphaI, alphaI_); + reader.loadReal(Parameter::alphaP, alphaP_); - if (data.parameters.contains(Parameter::alphaP)) - { - alphaP_ = std::get(data.parameters.at(Parameter::alphaP)); - } + parameter_error_count_ = static_cast(checks.errorCount()); setDerivedParams(); } 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..c95b79335 100644 --- a/GridKit/Model/PhasorDynamics/SignalSource/ConstantSignalSource.hpp +++ b/GridKit/Model/PhasorDynamics/SignalSource/ConstantSignalSource.hpp @@ -70,6 +70,9 @@ namespace GridKit /// Component ports SignalPortsT ports_; + /// Count of parameter-loading errors reported through verify() + IdxT parameter_error_count_{0}; + // Parameter initialization function void initializeParameters(const ModelDataT& data); }; diff --git a/GridKit/Model/PhasorDynamics/SignalSource/ConstantSignalSourceImpl.hpp b/GridKit/Model/PhasorDynamics/SignalSource/ConstantSignalSourceImpl.hpp index 561fbe759..af299220c 100644 --- a/GridKit/Model/PhasorDynamics/SignalSource/ConstantSignalSourceImpl.hpp +++ b/GridKit/Model/PhasorDynamics/SignalSource/ConstantSignalSourceImpl.hpp @@ -3,7 +3,9 @@ #include #include #include +#include #include +#include namespace GridKit { @@ -42,14 +44,26 @@ namespace GridKit void ConstantSignalSource::initializeParameters(const ModelDataT& data) { using Parameters = ModelDataT::Parameters; - if (data.parameters.contains(Parameters::Sr)) + + parameter_error_count_ = 0; + + Utilities::ConfigurationChecks checks("ConstantSignalSource"); + Utilities::ParameterReader reader(data, checks); + + // 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; } + + parameter_error_count_ = static_cast(checks.errorCount()); } /** @@ -86,7 +100,7 @@ namespace GridKit template int ConstantSignalSource::verify() const { - return 0; + return static_cast(parameter_error_count_); } template diff --git a/GridKit/Model/PhasorDynamics/Stabilizer/IEEEST/Ieeest.hpp b/GridKit/Model/PhasorDynamics/Stabilizer/IEEEST/Ieeest.hpp index 448e0144c..d2954c812 100644 --- a/GridKit/Model/PhasorDynamics/Stabilizer/IEEEST/Ieeest.hpp +++ b/GridKit/Model/PhasorDynamics/Stabilizer/IEEEST/Ieeest.hpp @@ -147,6 +147,9 @@ namespace GridKit std::unique_ptr monitor_; + /// Count of parameter-loading errors reported through verify() + IdxT parameter_error_count_{0}; + void initializeParameters(const ModelDataT& data); void initializeMonitor(); }; diff --git a/GridKit/Model/PhasorDynamics/Stabilizer/IEEEST/IeeestImpl.hpp b/GridKit/Model/PhasorDynamics/Stabilizer/IEEEST/IeeestImpl.hpp index 2bc836363..7b2be0db0 100644 --- a/GridKit/Model/PhasorDynamics/Stabilizer/IEEEST/IeeestImpl.hpp +++ b/GridKit/Model/PhasorDynamics/Stabilizer/IEEEST/IeeestImpl.hpp @@ -13,7 +13,9 @@ #include #include #include +#include #include +#include namespace GridKit { @@ -47,78 +49,31 @@ 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)); - } + + parameter_error_count_ = 0; + + Utilities::ConfigurationChecks checks("Ieeest"); + Utilities::ParameterReader reader(data, checks); + 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_); + + parameter_error_count_ = static_cast(checks.errorCount()); a0_ = 1; a1_ = A1_ + A3_; @@ -190,7 +145,7 @@ namespace GridKit template int Ieeest::verify() const { - int ret = 0; + int ret = static_cast(parameter_error_count_); auto input_port = ports_.in.template port(); if (!input_port.connected()) diff --git a/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROU/Genrou.hpp b/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROU/Genrou.hpp index e02704ca5..8436f6011 100644 --- a/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROU/Genrou.hpp +++ b/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROU/Genrou.hpp @@ -222,6 +222,9 @@ namespace GridKit /// Variable monitor std::unique_ptr monitor_; + + /// Count of parameter-loading errors reported through verify() + IdxT parameter_error_count_{0}; }; } // namespace PhasorDynamics diff --git a/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROU/GenrouImpl.hpp b/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROU/GenrouImpl.hpp index c052fec4f..5df33736f 100644 --- a/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROU/GenrouImpl.hpp +++ b/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROU/GenrouImpl.hpp @@ -8,7 +8,9 @@ #include #include #include +#include #include +#include namespace GridKit { @@ -165,100 +167,32 @@ 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)); - } + parameter_error_count_ = 0; + + Utilities::ConfigurationChecks checks("Genrou"); + Utilities::ParameterReader reader(data, checks); + 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_); + + parameter_error_count_ = static_cast(checks.errorCount()); if (data.buses.contains(Buses::bus)) { @@ -351,7 +285,8 @@ namespace GridKit template int Genrou::verify() const { - int ret = 0; + + int ret = static_cast(parameter_error_count_); auto pmech_port = ports_.in.template port(); if (pmech_port.connected() && !pmech_port.linked()) diff --git a/GridKit/Model/PhasorDynamics/SynchronousMachine/GENSAL/Gensal.hpp b/GridKit/Model/PhasorDynamics/SynchronousMachine/GENSAL/Gensal.hpp index cf3d379c6..811bfa9e0 100644 --- a/GridKit/Model/PhasorDynamics/SynchronousMachine/GENSAL/Gensal.hpp +++ b/GridKit/Model/PhasorDynamics/SynchronousMachine/GENSAL/Gensal.hpp @@ -179,6 +179,9 @@ namespace GridKit /// Variable monitor std::unique_ptr monitor_; + + /// Count of parameter-loading errors reported through verify() + IdxT parameter_error_count_{0}; }; } // namespace PhasorDynamics diff --git a/GridKit/Model/PhasorDynamics/SynchronousMachine/GENSAL/GensalImpl.hpp b/GridKit/Model/PhasorDynamics/SynchronousMachine/GENSAL/GensalImpl.hpp index 8435bdb71..084fb6983 100644 --- a/GridKit/Model/PhasorDynamics/SynchronousMachine/GENSAL/GensalImpl.hpp +++ b/GridKit/Model/PhasorDynamics/SynchronousMachine/GENSAL/GensalImpl.hpp @@ -9,7 +9,9 @@ #include #include #include +#include #include +#include namespace GridKit { @@ -43,85 +45,29 @@ 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)); - } + parameter_error_count_ = 0; + + Utilities::ConfigurationChecks checks("Gensal"); + Utilities::ParameterReader reader(data, checks); + 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_); + + parameter_error_count_ = static_cast(checks.errorCount()); } template @@ -227,7 +173,8 @@ namespace GridKit template int Gensal::verify() const { - int ret = 0; + + int ret = static_cast(parameter_error_count_); auto pmech_port = ports_.in.template port(); if (pmech_port.connected() && !pmech_port.linked()) diff --git a/GridKit/Model/PhasorDynamics/SynchronousMachine/GenClassical/GenClassical.hpp b/GridKit/Model/PhasorDynamics/SynchronousMachine/GenClassical/GenClassical.hpp index ced9a652e..e074fd776 100644 --- a/GridKit/Model/PhasorDynamics/SynchronousMachine/GenClassical/GenClassical.hpp +++ b/GridKit/Model/PhasorDynamics/SynchronousMachine/GenClassical/GenClassical.hpp @@ -152,6 +152,9 @@ namespace GridKit /// Variable monitor std::unique_ptr monitor_; + + /// Count of parameter-loading errors reported through verify() + IdxT parameter_error_count_{0}; }; } // namespace PhasorDynamics diff --git a/GridKit/Model/PhasorDynamics/SynchronousMachine/GenClassical/GenClassicalImpl.hpp b/GridKit/Model/PhasorDynamics/SynchronousMachine/GenClassical/GenClassicalImpl.hpp index 0c3b12cf1..ddea99736 100644 --- a/GridKit/Model/PhasorDynamics/SynchronousMachine/GenClassical/GenClassicalImpl.hpp +++ b/GridKit/Model/PhasorDynamics/SynchronousMachine/GenClassical/GenClassicalImpl.hpp @@ -15,6 +15,8 @@ #include #include #include +#include +#include namespace GridKit { @@ -47,40 +49,18 @@ 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)); - } + Utilities::ConfigurationChecks checks("GenClassical"); + Utilities::ParameterReader reader(data, checks); + 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_); + + parameter_error_count_ = static_cast(checks.errorCount()); } template @@ -168,7 +148,8 @@ namespace GridKit template int GenClassical::verify() const { - int ret = 0; + + int ret = static_cast(parameter_error_count_); auto pmech_port = ports_.in.template port(); if (pmech_port.connected() && !pmech_port.linked()) From 6970b6df7f0ba969d1b4b774ce58fa8fec76d863 Mon Sep 17 00:00:00 2001 From: lukelowry Date: Tue, 15 Sep 2026 15:35:57 -0500 Subject: [PATCH 3/3] improved config checks and parameter reader --- GridKit/Model/CMakeLists.txt | 9 +- GridKit/Model/ConfigurationChecks.hpp | 58 ++++++++++++ .../{Utilities => Model}/ParameterReader.hpp | 94 +++++++++---------- .../Model/PhasorDynamics/Branch/Branch.hpp | 18 ++-- .../PhasorDynamics/Branch/BranchImpl.hpp | 15 ++- GridKit/Model/PhasorDynamics/BusBase.hpp | 6 +- .../PhasorDynamics/BusFault/BusFault.hpp | 7 +- .../PhasorDynamics/BusFault/BusFaultImpl.hpp | 9 +- .../BusToSignalAdapter/BusToSignalAdapter.hpp | 16 ++-- .../BusToSignalAdapterImpl.hpp | 22 ++--- GridKit/Model/PhasorDynamics/Component.hpp | 4 +- .../PhasorDynamics/Controller/REECB/Reecb.hpp | 17 ++-- .../Controller/REECB/ReecbImpl.hpp | 67 ++++++------- .../PhasorDynamics/Controller/REPCA/Repca.hpp | 18 ++-- .../Controller/REPCA/RepcaImpl.hpp | 84 ++++++++++------- .../PhasorDynamics/Converter/REGCA/Regca.hpp | 18 ++-- .../Converter/REGCA/RegcaImpl.hpp | 25 +++-- .../PhasorDynamics/Exciter/ESDC1A/Esdc1a.hpp | 18 ++-- .../Exciter/ESDC1A/Esdc1aImpl.hpp | 71 ++++++++------ .../PhasorDynamics/Exciter/IEEET1/Ieeet1.hpp | 19 ++-- .../Exciter/IEEET1/Ieeet1Impl.hpp | 86 ++++++++++++----- .../Exciter/SEXS-PTI/SexsPti.hpp | 18 ++-- .../Exciter/SEXS-PTI/SexsPtiImpl.hpp | 64 +++++++++---- .../Governor/GASTPTI/GastPti.hpp | 42 ++++----- .../Governor/GASTPTI/GastPtiImpl.hpp | 51 +++++----- .../PhasorDynamics/Governor/HYGOV/Hygov.hpp | 20 ++-- .../Governor/HYGOV/HygovImpl.hpp | 65 +++++++------ .../PhasorDynamics/Governor/Tgov1/Tgov1.hpp | 17 ++-- .../Governor/Tgov1/Tgov1Impl.hpp | 31 +++--- .../Model/PhasorDynamics/Load/LoadZ/LoadZ.hpp | 7 +- .../PhasorDynamics/Load/LoadZ/LoadZImpl.hpp | 9 +- .../PhasorDynamics/Load/LoadZIP/LoadZIP.hpp | 7 +- .../Load/LoadZIP/LoadZIPImpl.hpp | 11 +-- GridKit/Model/PhasorDynamics/SignalIn.hpp | 34 ------- .../SignalSource/ConstantSignalSource.hpp | 19 ++-- .../SignalSource/ConstantSignalSourceImpl.hpp | 15 +-- .../Stabilizer/IEEEST/Ieeest.hpp | 19 ++-- .../Stabilizer/IEEEST/IeeestImpl.hpp | 46 ++++----- .../SynchronousMachine/GENROU/Genrou.hpp | 17 ++-- .../SynchronousMachine/GENROU/GenrouImpl.hpp | 34 ++----- .../SynchronousMachine/GENSAL/Gensal.hpp | 17 ++-- .../SynchronousMachine/GENSAL/GensalImpl.hpp | 34 ++----- .../GenClassical/GenClassical.hpp | 17 ++-- .../GenClassical/GenClassicalImpl.hpp | 32 ++----- GridKit/Model/PhasorDynamics/SystemModel.hpp | 6 +- .../Model/PhasorDynamics/SystemModelImpl.hpp | 27 ++++-- GridKit/Testing/Testing.hpp | 16 ++++ GridKit/Utilities/CMakeLists.txt | 2 - GridKit/Utilities/ConfigurationChecks.hpp | 58 ------------ .../UnitTests/PhasorDynamics/BranchTests.hpp | 10 +- .../BusToSignalAdapterTests.hpp | 2 +- .../PhasorDynamics/ControllerReecbTests.hpp | 28 +++--- .../PhasorDynamics/ControllerRepcaTests.hpp | 37 +++++--- .../PhasorDynamics/ConverterRegcaTests.hpp | 31 +++--- .../PhasorDynamics/ExciterEsdc1aTests.hpp | 33 ++++--- .../PhasorDynamics/ExciterIeeet1Tests.hpp | 2 +- .../PhasorDynamics/ExciterSexsPtiTests.hpp | 11 +-- .../PhasorDynamics/GenClassicalTests.hpp | 2 +- .../PhasorDynamics/GovernorGastPtiTests.hpp | 50 +++++----- .../PhasorDynamics/GovernorHygovTests.hpp | 56 ++++++----- .../PhasorDynamics/StabilizerIeeestTests.hpp | 2 +- .../SystemSingleComponentTests.hpp | 4 +- .../UnitTests/PhasorDynamics/SystemTests.hpp | 6 +- 63 files changed, 852 insertions(+), 838 deletions(-) create mode 100644 GridKit/Model/ConfigurationChecks.hpp rename GridKit/{Utilities => Model}/ParameterReader.hpp (57%) delete mode 100644 GridKit/Utilities/ConfigurationChecks.hpp 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/Utilities/ParameterReader.hpp b/GridKit/Model/ParameterReader.hpp similarity index 57% rename from GridKit/Utilities/ParameterReader.hpp rename to GridKit/Model/ParameterReader.hpp index 3b9955e18..30c8dedaf 100644 --- a/GridKit/Utilities/ParameterReader.hpp +++ b/GridKit/Model/ParameterReader.hpp @@ -8,29 +8,31 @@ #include #include +#include +#include #include #include -#include +#include namespace GridKit { - namespace Utilities + namespace Model { /** * @brief Reads typed parameters out of a model data container. * - * Every method leaves the target untouched and reports one error through - * the shared checks object when a provided value has the wrong type or is - * not finite; an omitted optional parameter keeps the model default - * silently. Real parameters accept integer values. Parameter names in - * messages come from the parameter enumeration. + * 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 exposing `RealT`, `IdxT`, - * the `Parameters` enumeration, and the `parameters` map. + * @tparam ModelDataT A model data container satisfying + * PhasorDynamics::ModelData. */ - template + template class ParameterReader { public: @@ -38,9 +40,13 @@ namespace GridKit using IdxT = typename ModelDataT::IdxT; using ParametersT = typename ModelDataT::Parameters; - ParameterReader(const ModelDataT& data, ConfigurationChecks& checks) + /** + * @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), - checks_(checks) + model_(model) { } @@ -49,9 +55,9 @@ namespace GridKit * * @param[in] key Parameter to look up. * @param[out] target Stores the finite numeric value when provided. - * @return true when the parameter was provided and stored. + * @return true when the parameter was provided. */ - bool loadReal(ParametersT key, RealT& target) + bool loadReal(ParametersT key, RealT& target) const { if (!parameters_.contains(key)) { @@ -70,16 +76,12 @@ namespace GridKit } else { - checks_.fail() << "parameter '" << magic_enum::enum_name(key) - << "' must be numeric\n"; - return false; + reject(key, "must be numeric"); } if (!std::isfinite(parsed_value)) { - checks_.fail() << "parameter '" << magic_enum::enum_name(key) - << "' must be finite\n"; - return false; + reject(key, "must be finite"); } target = parsed_value; @@ -90,16 +92,14 @@ namespace GridKit * @brief Load a real parameter that must be provided. * * @param[in] key Parameter to look up. - * @param[out] target Stores the finite numeric value when provided. - * @return true when the parameter was provided and stored. + * @param[out] target Stores the finite numeric value. + * @return true, since a missing parameter is rejected. */ - bool requireReal(ParametersT key, RealT& target) + bool requireReal(ParametersT key, RealT& target) const { if (!parameters_.contains(key)) { - checks_.fail() << "missing required parameter '" - << magic_enum::enum_name(key) << "'\n"; - return false; + reject(key, "is required"); } return loadReal(key, target); } @@ -109,9 +109,9 @@ namespace GridKit * * @param[in] key Parameter to look up. * @param[out] target Stores the boolean value when provided. - * @return true when the parameter was provided and stored. + * @return true when the parameter was provided. */ - bool loadSwitch(ParametersT key, bool& target) + bool loadSwitch(ParametersT key, bool& target) const { if (!parameters_.contains(key)) { @@ -125,9 +125,7 @@ namespace GridKit return true; } - checks_.fail() << "parameter '" << magic_enum::enum_name(key) - << "' must be boolean\n"; - return false; + reject(key, "must be boolean"); } /** @@ -135,16 +133,14 @@ namespace GridKit * integer 0/1 value is accepted. * * @param[in] key Parameter to look up. - * @param[out] target Stores the switch value when provided. - * @return true when the parameter was provided and stored. + * @param[out] target Stores the switch value. + * @return true, since a missing parameter is rejected. */ - bool requireSwitch(ParametersT key, bool& target) + bool requireSwitch(ParametersT key, bool& target) const { if (!parameters_.contains(key)) { - checks_.fail() << "missing required parameter '" - << magic_enum::enum_name(key) << "'\n"; - return false; + reject(key, "is required"); } const auto& value = parameters_.at(key); @@ -160,9 +156,7 @@ namespace GridKit return true; } - checks_.fail() << "parameter '" << magic_enum::enum_name(key) - << "' must be bool or 0/1\n"; - return false; + reject(key, "must be bool or 0/1"); } /** @@ -170,9 +164,9 @@ namespace GridKit * * @param[in] key Parameter to look up. * @param[out] target Stores the integer value when provided. - * @return true when the parameter was provided and stored. + * @return true when the parameter was provided. */ - bool loadSelector(ParametersT key, IdxT& target) + bool loadSelector(ParametersT key, IdxT& target) const { if (!parameters_.contains(key)) { @@ -186,14 +180,20 @@ namespace GridKit return true; } - checks_.fail() << "parameter '" << magic_enum::enum_name(key) - << "' must be an integer selector\n"; - return false; + 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_; - ConfigurationChecks& checks_; + const char* model_; }; - } // namespace Utilities + } // namespace Model } // namespace GridKit diff --git a/GridKit/Model/PhasorDynamics/Branch/Branch.hpp b/GridKit/Model/PhasorDynamics/Branch/Branch.hpp index d9dce3fd5..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) { @@ -204,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 5a59f3594..aeb97bb0f 100644 --- a/GridKit/Model/PhasorDynamics/Branch/BranchImpl.hpp +++ b/GridKit/Model/PhasorDynamics/Branch/BranchImpl.hpp @@ -7,12 +7,12 @@ * */ +#include +#include #include #include #include #include -#include -#include namespace GridKit { @@ -152,9 +152,9 @@ namespace GridKit } template - int Branch::verify() const + Model::ConfigurationChecks Branch::verify() const { - Utilities::ConfigurationChecks checks("Branch"); + Model::ConfigurationChecks checks; checks.check(bus1_ != nullptr, "bus1 pointer is null"); checks.check(bus2_ != nullptr, "bus2 pointer is null"); @@ -170,7 +170,7 @@ namespace GridKit 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 parameter_error_count_ + checks.errorCount(); + return checks; } template @@ -340,8 +340,7 @@ namespace GridKit using Parameter = typename ModelDataT::Parameters; using Buses = typename ModelDataT::Buses; - Utilities::ConfigurationChecks checks("Branch"); - Utilities::ParameterReader reader(data, checks); + Model::ParameterReader reader(data, "Branch"); reader.loadReal(Parameter::R, R_); reader.loadReal(Parameter::X, X_); @@ -352,8 +351,6 @@ namespace GridKit reader.loadReal(Parameter::tap, tap_); reader.loadReal(Parameter::phase, phase_); - parameter_error_count_ = checks.errorCount(); - if (data.buses.contains(Buses::bus1)) { bus1_id_ = data.buses.at(Buses::bus1); 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 343b43f10..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 static_cast(parameter_error_count_); + return {}; } void updateTime(RealT /* t */, RealT /* a */) override final @@ -124,9 +124,6 @@ namespace GridKit /// Variable monitor std::unique_ptr monitor_; - - /// Count of parameter-loading errors reported through verify() - IdxT parameter_error_count_{0}; }; } // namespace PhasorDynamics diff --git a/GridKit/Model/PhasorDynamics/BusFault/BusFaultImpl.hpp b/GridKit/Model/PhasorDynamics/BusFault/BusFaultImpl.hpp index 2037f5254..c6b92b2a8 100644 --- a/GridKit/Model/PhasorDynamics/BusFault/BusFaultImpl.hpp +++ b/GridKit/Model/PhasorDynamics/BusFault/BusFaultImpl.hpp @@ -2,12 +2,12 @@ #include +#include +#include #include #include #include #include -#include -#include namespace GridKit { @@ -61,15 +61,12 @@ namespace GridKit using Parameter = typename ModelDataT::Parameters; using Buses = typename ModelDataT::Buses; - Utilities::ConfigurationChecks checks("BusFault"); - Utilities::ParameterReader reader(data, checks); + Model::ParameterReader reader(data, "BusFault"); reader.loadReal(Parameter::R, R_); reader.loadReal(Parameter::X, X_); reader.loadSwitch(Parameter::state0, status_); - parameter_error_count_ = static_cast(checks.errorCount()); - if (data.buses.contains(Buses::bus)) { bus_id_ = data.buses.at(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 c2ce1e748..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() { @@ -223,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 c3e7b5a66..effbce7b8 100644 --- a/GridKit/Model/PhasorDynamics/Controller/REECB/ReecbImpl.hpp +++ b/GridKit/Model/PhasorDynamics/Controller/REECB/ReecbImpl.hpp @@ -9,17 +9,20 @@ #include #include #include +#include +#include +#include #include +#include +#include #include #include #include #include #include #include -#include #include -#include namespace GridKit { @@ -160,12 +163,12 @@ 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 { - Utilities::ConfigurationChecks checks("Reecb"); + Model::ConfigurationChecks checks; checks.check(bus_ != nullptr, "terminal bus is required"); @@ -180,10 +183,10 @@ namespace GridKit const RealT system_to_component = va_system_base_ / va_component_base_; const RealT component_to_system = va_component_base_ / va_system_base_; 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"); + && 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(Trv_), "Trv must be finite"); @@ -251,13 +254,18 @@ namespace GridKit checks.check(std::isfinite(Imax_) && Imax_ > ZERO, "Imax must be finite and positive"); - ports_.in.template port().checkOptional(checks, "pe"); - ports_.in.template port().checkOptional(checks, "qgen"); - ports_.in.template port().checkOptional(checks, "qext"); - ports_.in.template port().checkOptional(checks, "pfaref"); - ports_.in.template port().checkOptional(checks, "pref"); - - return static_cast(parameter_error_count_) + checks.errorCount(); + 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 checks; } /** @@ -286,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; } @@ -1184,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; @@ -1214,12 +1220,9 @@ 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_ = data.parameters.contains(Params::Vref0); - - Utilities::ConfigurationChecks checks("Reecb"); - Utilities::ParameterReader reader(data, checks); + Model::ParameterReader reader(data, "Reecb"); RealT mva{}; if (reader.requireReal(Params::mva, mva)) @@ -1256,8 +1259,6 @@ namespace GridKit reader.loadReal(Params::Pmin, Pmin_); reader.loadReal(Params::Imax, Imax_); - parameter_error_count_ = static_cast(checks.errorCount()); - 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 e08ec6587..6892f1b9b 100644 --- a/GridKit/Model/PhasorDynamics/Controller/REPCA/RepcaImpl.hpp +++ b/GridKit/Model/PhasorDynamics/Controller/REPCA/RepcaImpl.hpp @@ -7,17 +7,21 @@ #pragma once #include +#include #include +#include +#include +#include +#include +#include #include #include #include #include #include #include -#include #include -#include namespace GridKit { @@ -143,12 +147,12 @@ 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 { - Utilities::ConfigurationChecks checks("Repca"); + Model::ConfigurationChecks checks; checks.check(bus_ != nullptr, "regulated bus is required"); @@ -185,18 +189,33 @@ namespace GridKit "femin <= 0 <= femax is required"); checks.check(Pmin_ <= Pmax_, "Pmin must be less than or equal to Pmax"); - ports_.in.template port().checkRequired(checks, "ir"); - ports_.in.template port().checkRequired(checks, "ii"); - ports_.in.template port().checkRequired(checks, "p"); - ports_.in.template port().checkRequired(checks, "q"); - - ports_.in.template port().checkOptional(checks, "vref"); - ports_.in.template port().checkOptional(checks, "pref"); - ports_.in.template port().checkOptional(checks, "qref"); - ports_.in.template port().checkOptional(checks, "freq"); - ports_.in.template port().checkOptional(checks, "freqref"); - - return static_cast(parameter_error_count_) + checks.errorCount(); + 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; } /** @@ -250,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; } @@ -815,10 +838,7 @@ namespace GridKit { using Params = typename ModelDataT::Parameters; - parameter_error_count_ = 0; - - Utilities::ConfigurationChecks checks("Repca"); - Utilities::ParameterReader reader(data, checks); + Model::ParameterReader reader(data, "Repca"); reader.loadReal(Params::mva, mva_base_); reader.loadSwitch(Params::VcompFlag, VcompFlag_); @@ -852,8 +872,6 @@ namespace GridKit reader.loadReal(Params::Pmin, Pmin_); reader.loadReal(Params::Tlag, Tlag_); - parameter_error_count_ = static_cast(checks.errorCount()); - setDerivedParameters(); } @@ -902,13 +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. - Utilities::ConfigurationChecks checks("Repca"); - checks.check(Tfltr_ >= ZERO, "Tfltr must be non-negative"); - checks.check(Tft_ >= ZERO, "Tft must be non-negative"); - checks.check(Tfv_ >= ZERO, "Tfv must be non-negative"); - checks.check(Tp_ >= ZERO, "Tp must be non-negative"); - checks.check(Tlag_ >= ZERO, "Tlag must be non-negative"); - parameter_error_count_ += static_cast(checks.errorCount()); + const std::array, 5> lags{ + {{Tfltr_, "Tfltr"}, {Tft_, "Tft"}, {Tfv_, "Tfv"}, {Tp_, "Tp"}, {Tlag_, "Tlag"}}}; + for (const auto& [value, name] : lags) + { + if (value < ZERO) + { + throw std::invalid_argument(std::string("Repca: ") + name + " must be non-negative"); + } + } 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 b987fb98c..aae8a89da 100644 --- a/GridKit/Model/PhasorDynamics/Converter/REGCA/RegcaImpl.hpp +++ b/GridKit/Model/PhasorDynamics/Converter/REGCA/RegcaImpl.hpp @@ -10,15 +10,15 @@ #include #include +#include +#include #include #include #include #include #include #include -#include #include -#include namespace GridKit { @@ -169,10 +169,7 @@ namespace GridKit { using Params = typename ModelDataT::Parameters; - parameter_error_count_ = 0; - - Utilities::ConfigurationChecks checks("Regca"); - Utilities::ParameterReader reader(data, checks); + Model::ParameterReader reader(data, "Regca"); reader.requireReal(Params::p0, p0_); reader.requireReal(Params::q0, q0_); @@ -192,8 +189,6 @@ namespace GridKit reader.loadReal(Params::Khv, Khv_); - parameter_error_count_ = static_cast(checks.errorCount()); - setDerivedParameters(); } @@ -318,12 +313,12 @@ 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 { - Utilities::ConfigurationChecks checks("Regca"); + Model::ConfigurationChecks checks; checks.check(bus_ != nullptr, "bus pointer is null"); @@ -338,10 +333,12 @@ namespace GridKit checks.check(std::isfinite(Khv_) && Khv_ >= ZERO, "Khv must be finite and non-negative"); - ports_.in.template port().checkOptional(checks, "ipcmd"); - ports_.in.template port().checkOptional(checks, "iqcmd"); + 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 static_cast(parameter_error_count_) + checks.errorCount(); + 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 2e86ddff5..5fea3deaa 100644 --- a/GridKit/Model/PhasorDynamics/Exciter/ESDC1A/Esdc1aImpl.hpp +++ b/GridKit/Model/PhasorDynamics/Exciter/ESDC1A/Esdc1aImpl.hpp @@ -7,17 +7,22 @@ #pragma once #include +#include +#include #include +#include +#include +#include +#include +#include #include #include #include #include #include #include -#include #include -#include namespace GridKit { @@ -141,12 +146,12 @@ 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 { - Utilities::ConfigurationChecks checks("Esdc1a"); + Model::ConfigurationChecks checks; checks.check(bus_ != nullptr, "bus pointer is null"); @@ -177,12 +182,16 @@ namespace GridKit checks.check(!Spdmlt_ || ports_.in.template port().connected(), "speed signal is required when Spdmlt is enabled"); - ports_.in.template port().checkOptional(checks, "speed"); - ports_.in.template port().checkOptional(checks, "vref"); - ports_.in.template port().checkOptional(checks, "vs"); - ports_.in.template port().checkOptional(checks, "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 static_cast(parameter_error_count_) + checks.errorCount(); + return checks; } /** @@ -219,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. @@ -605,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. @@ -616,10 +629,7 @@ namespace GridKit { using Params = typename ModelDataT::Parameters; - parameter_error_count_ = 0; - - Utilities::ConfigurationChecks checks("Esdc1a"); - Utilities::ParameterReader reader(data, checks); + Model::ParameterReader reader(data, "Esdc1a"); reader.loadReal(Params::Tr, Tr_); reader.loadReal(Params::Ka, Ka_); @@ -640,8 +650,6 @@ namespace GridKit reader.loadSelector(Params::UEL, UEL_); reader.loadSwitch(Params::exclim, exclim_); - parameter_error_count_ = static_cast(checks.errorCount()); - setDerivedParameters(); } @@ -698,15 +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. - Utilities::ConfigurationChecks checks("Esdc1a"); - checks.check(Tr_ >= ZERO, "Tr must be non-negative"); - checks.check(Ta_ >= ZERO, "Ta must be non-negative"); - checks.check(Tb_ >= ZERO, "Tb must be non-negative"); - checks.check(Te_ >= ZERO, "Te must be non-negative"); - checks.check(Tf1_ >= ZERO, "Tf1 must be non-negative"); - parameter_error_count_ += static_cast(checks.errorCount()); + // 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) + { + throw std::invalid_argument(std::string("Esdc1a: ") + name + " must be non-negative"); + } + } 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 1a861389f..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() { @@ -150,9 +150,6 @@ namespace GridKit /// Variable monitor std::unique_ptr monitor_; - /// Count of parameter-loading errors reported through verify() - IdxT parameter_error_count_{0}; - // Parameter initialization function void initModelParams(const ModelDataT& data); void setDerivedParameters(); diff --git a/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1Impl.hpp b/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1Impl.hpp index f1a95feec..3fe974dfd 100644 --- a/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1Impl.hpp +++ b/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1Impl.hpp @@ -11,15 +11,15 @@ #include #include +#include +#include #include #include #include #include #include #include -#include #include -#include namespace GridKit { @@ -128,9 +128,9 @@ namespace GridKit * @brief Verify parameter values and attached signal links */ template - int Ieeet1::verify() const + Model::ConfigurationChecks Ieeet1::verify() const { - Utilities::ConfigurationChecks checks("Ieeet1"); + Model::ConfigurationChecks checks; checks.check(Ka_ > ZERO, "Ka must be positive"); checks.check(Vrmin_ <= Vrmax_, "Vrmin must be less than or equal to Vrmax"); @@ -151,13 +151,18 @@ namespace GridKit checks.check(sat_ordered, "E1/E2 and Se1/Se2 must be ordered consistently"); } - ports_.in.template port().checkOptional(checks, "speed"); - ports_.in.template port().checkOptional(checks, "vref"); - ports_.in.template port().checkOptional(checks, "vs"); - ports_.in.template port().checkOptional(checks, "vuel"); - ports_.in.template port().checkOptional(checks, "voel"); - - return static_cast(parameter_error_count_) + checks.errorCount(); + 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 checks; } /** @@ -187,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; } @@ -409,15 +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* ws_indices = ws_indices_.data(); - ports_.in.template port().refreshWorkspace(omega_set_, ws[static_cast(Ieeet1ExternalVariables::OMEGA)], ws_indices[static_cast(Ieeet1ExternalVariables::OMEGA)]); - ports_.in.template port().refreshWorkspace(vref_set_, ws[static_cast(Ieeet1ExternalVariables::VREF)], ws_indices[static_cast(Ieeet1ExternalVariables::VREF)]); - ports_.in.template port().refreshWorkspace(vs_set_, ws[static_cast(Ieeet1ExternalVariables::VS)], ws_indices[static_cast(Ieeet1ExternalVariables::VS)]); - ports_.in.template port().refreshWorkspace(vuel_set_, ws[static_cast(Ieeet1ExternalVariables::VUEL)], ws_indices[static_cast(Ieeet1ExternalVariables::VUEL)]); - ports_.in.template port().refreshWorkspace(voel_set_, ws[static_cast(Ieeet1ExternalVariables::VOEL)], ws_indices[static_cast(Ieeet1ExternalVariables::VOEL)]); + 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()) + { + 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(); @@ -443,10 +484,7 @@ namespace GridKit { using Parameter = typename ModelDataT::Parameters; - parameter_error_count_ = 0; - - Utilities::ConfigurationChecks checks("Ieeet1"); - Utilities::ParameterReader reader(data, checks); + Model::ParameterReader reader(data, "Ieeet1"); reader.loadReal(Parameter::Tr, Tr_); reader.loadReal(Parameter::Ka, Ka_); @@ -463,8 +501,6 @@ namespace GridKit reader.loadReal(Parameter::Se2, Se2_); reader.loadReal(Parameter::Ispdlim, Ispdlim_); - parameter_error_count_ = static_cast(checks.errorCount()); - 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 ba327746c..707db5a86 100644 --- a/GridKit/Model/PhasorDynamics/Exciter/SEXS-PTI/SexsPtiImpl.hpp +++ b/GridKit/Model/PhasorDynamics/Exciter/SEXS-PTI/SexsPtiImpl.hpp @@ -6,17 +6,18 @@ * @brief Definition of the SEXS-PTI exciter model. */ +#include #include +#include +#include #include #include #include #include #include #include -#include #include -#include namespace GridKit { @@ -93,9 +94,9 @@ namespace GridKit } template - int SexsPti::verify() const + Model::ConfigurationChecks SexsPti::verify() const { - Utilities::ConfigurationChecks checks("SexsPti"); + Model::ConfigurationChecks checks; checks.check(bus_ != nullptr, "bus pointer is null"); checks.check(Ta_ >= 0.0, "Ta must be non-negative"); @@ -107,12 +108,16 @@ namespace GridKit checks.check(ports_.out.template port().connected(), "required EFD signal is not assigned"); - ports_.in.template port().checkOptional(checks, "vref"); - ports_.in.template port().checkOptional(checks, "vs"); - ports_.in.template port().checkOptional(checks, "vuel"); - ports_.in.template port().checkOptional(checks, "voel"); + 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 missing_param_count_ + checks.errorCount(); + return checks; } template @@ -248,14 +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* ws_indices = ws_indices_.data(); - ports_.in.template port().refreshWorkspace(vref_set_, ws[static_cast(SexsPtiExternalVariables::VREF)], ws_indices[static_cast(SexsPtiExternalVariables::VREF)]); - ports_.in.template port().refreshWorkspace(vs_set_, ws[static_cast(SexsPtiExternalVariables::VS)], ws_indices[static_cast(SexsPtiExternalVariables::VS)]); - ports_.in.template port().refreshWorkspace(vuel_set_, ws[static_cast(SexsPtiExternalVariables::VUEL)], ws_indices[static_cast(SexsPtiExternalVariables::VUEL)]); - ports_.in.template port().refreshWorkspace(voel_set_, ws[static_cast(SexsPtiExternalVariables::VOEL)], ws_indices[static_cast(SexsPtiExternalVariables::VOEL)]); + 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()) + { + 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(); @@ -276,8 +307,7 @@ namespace GridKit { using Params = typename ModelDataT::Parameters; - Utilities::ConfigurationChecks checks("SexsPti"); - Utilities::ParameterReader reader(data, checks); + Model::ParameterReader reader(data, "SexsPti"); reader.requireReal(Params::Ta, Ta_); reader.requireReal(Params::Tb, Tb_); @@ -285,8 +315,6 @@ namespace GridKit reader.requireReal(Params::K, K_); reader.requireReal(Params::Efdmax, Efdmax_); reader.requireReal(Params::Efdmin, Efdmin_); - - missing_param_count_ = checks.errorCount(); } template diff --git a/GridKit/Model/PhasorDynamics/Governor/GASTPTI/GastPti.hpp b/GridKit/Model/PhasorDynamics/Governor/GASTPTI/GastPti.hpp index b30268dbc..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() { @@ -120,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 e5705102c..5924a4c39 100644 --- a/GridKit/Model/PhasorDynamics/Governor/GASTPTI/GastPtiImpl.hpp +++ b/GridKit/Model/PhasorDynamics/Governor/GASTPTI/GastPtiImpl.hpp @@ -9,16 +9,18 @@ #include #include #include +#include +#include #include +#include +#include #include #include #include #include #include -#include #include -#include namespace GridKit { @@ -130,14 +132,14 @@ 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); - Utilities::ConfigurationChecks checks("GastPti"); + Model::ConfigurationChecks checks; checks.check(std::isfinite(R_) && R_ > ZERO, "R must be finite and positive"); checks.check(std::isfinite(At_) && At_ >= ZERO, @@ -153,7 +155,7 @@ namespace GridKit } checks.check(std::isfinite(Dturb_) && Dturb_ >= ZERO, - "Dturb must be finite and non-negative"); + "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_) @@ -166,17 +168,19 @@ namespace GridKit const RealT system_to_component = va_system_base_ / va_component_base_; const RealT component_to_system = va_component_base_ / va_system_base_; 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"); + && 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(ports_.out.template port().connected(), "pmech output must be assigned"); - ports_.in.template port().checkOptional(checks, "speed"); - ports_.in.template port().checkOptional(checks, "pref"); + 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 bool omega_linked = ports_.in.template port().connected() @@ -217,7 +221,7 @@ namespace GridKit } } - return static_cast(parameter_error_count_) + checks.errorCount(); + return checks; } /** @@ -258,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; } @@ -559,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; @@ -584,10 +588,7 @@ namespace GridKit { using Params = typename ModelDataT::Parameters; - parameter_error_count_ = 0; - - Utilities::ConfigurationChecks checks("GastPti"); - Utilities::ParameterReader reader(data, checks); + Model::ParameterReader reader(data, "GastPti"); reader.loadReal(Params::R, R_); reader.loadReal(Params::T1, T1_); @@ -604,8 +605,6 @@ namespace GridKit this->setComponentBase(trate * static_cast(1.0e6)); } - parameter_error_count_ = static_cast(checks.errorCount()); - 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 824b76422..7db081344 100644 --- a/GridKit/Model/PhasorDynamics/Governor/HYGOV/HygovImpl.hpp +++ b/GridKit/Model/PhasorDynamics/Governor/HYGOV/HygovImpl.hpp @@ -7,17 +7,23 @@ #pragma once #include +#include +#include +#include #include #include +#include +#include +#include +#include +#include #include #include #include #include #include -#include #include -#include namespace GridKit { @@ -125,12 +131,12 @@ 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 { - Utilities::ConfigurationChecks checks("Hygov"); + Model::ConfigurationChecks checks; const bool valid_component_base = std::isfinite(va_component_base_) && va_component_base_ > ZERO; @@ -208,11 +214,14 @@ namespace GridKit checks.check(ports_.out.template port().connected(), "pmech output signal must be assigned"); - ports_.in.template port().checkOptional(checks, "speed"); - ports_.in.template port().checkOptional(checks, "pref"); - ports_.in.template port().checkOptional(checks, "paux"); + 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"); - return static_cast(parameter_error_count_) + checks.errorCount(); + return checks; } /** @@ -254,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. @@ -615,10 +628,7 @@ namespace GridKit { using Params = typename ModelDataT::Parameters; - parameter_error_count_ = 0; - - Utilities::ConfigurationChecks checks("Hygov"); - Utilities::ParameterReader reader(data, checks); + Model::ParameterReader reader(data, "Hygov"); RealT trate{}; if (reader.requireReal(Params::Trate, trate)) @@ -659,8 +669,6 @@ namespace GridKit reader.loadReal(Params::Pgv4, Pgv_[4]); reader.loadReal(Params::Pgv5, Pgv_[5]); - parameter_error_count_ = static_cast(checks.errorCount()); - setDerivedParameters(); } @@ -735,15 +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. - Utilities::ConfigurationChecks checks("Hygov"); - checks.check(Tr_ >= ZERO, "Tr must be non-negative"); - checks.check(Tf_ >= ZERO, "Tf must be non-negative"); - checks.check(Tg_ >= ZERO, "Tg must be non-negative"); - checks.check(Tw_ >= ZERO, "Tw must be non-negative"); - checks.check(Tnp_ >= ZERO, "Tnp must be non-negative"); - parameter_error_count_ += static_cast(checks.errorCount()); + // 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) + { + throw std::invalid_argument(std::string("Hygov: ") + name + " must be non-negative"); + } + } 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/Tgov1.hpp b/GridKit/Model/PhasorDynamics/Governor/Tgov1/Tgov1.hpp index 2c8703b24..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,9 +110,6 @@ namespace GridKit // Input States (which can be parameters) ScalarT pref_set_{0}; - /// Count of parameter-loading errors reported through verify() - IdxT parameter_error_count_{0}; - /// Component ports SignalPortsT ports_; diff --git a/GridKit/Model/PhasorDynamics/Governor/Tgov1/Tgov1Impl.hpp b/GridKit/Model/PhasorDynamics/Governor/Tgov1/Tgov1Impl.hpp index d8f9768bf..70b934add 100644 --- a/GridKit/Model/PhasorDynamics/Governor/Tgov1/Tgov1Impl.hpp +++ b/GridKit/Model/PhasorDynamics/Governor/Tgov1/Tgov1Impl.hpp @@ -11,13 +11,13 @@ #include #include +#include +#include #include #include #include #include -#include #include -#include namespace GridKit { @@ -92,10 +92,7 @@ namespace GridKit { using Parameter = typename ModelDataT::Parameters; - parameter_error_count_ = 0; - - Utilities::ConfigurationChecks checks("Tgov1"); - Utilities::ParameterReader reader(data, checks); + Model::ParameterReader reader(data, "Tgov1"); reader.loadReal(Parameter::Trate, Trate_); reader.loadReal(Parameter::R, R_); @@ -105,8 +102,6 @@ namespace GridKit reader.loadReal(Parameter::T2, T2_); reader.loadReal(Parameter::T3, T3_); reader.loadReal(Parameter::Dt, Dt_); - - parameter_error_count_ = static_cast(checks.errorCount()); } /** @@ -196,10 +191,10 @@ namespace GridKit * @brief verify method checks that attached signals are also linked */ template - int Tgov1::verify() const + Model::ConfigurationChecks Tgov1::verify() const { - Utilities::ConfigurationChecks checks("Tgov1"); + Model::ConfigurationChecks checks; checks.check(Trate_ > ZERO, "Trate must be positive"); checks.check(va_system_base_ > ZERO, "system power base must be positive"); @@ -208,10 +203,12 @@ namespace GridKit checks.check(ports_.out.template port().connected(), "pmech output signal must be assigned"); - ports_.in.template port().checkOptional(checks, "speed"); - ports_.in.template port().checkOptional(checks, "pref"); + 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 static_cast(parameter_error_count_) + checks.errorCount(); + return checks; } /** @@ -221,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 a006bc114..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 static_cast(parameter_error_count_); + return {}; } public: @@ -116,9 +116,6 @@ namespace GridKit RealT g_; std::unique_ptr monitor_; - - /// Count of parameter-loading errors reported through verify() - IdxT parameter_error_count_{0}; }; } // namespace PhasorDynamics diff --git a/GridKit/Model/PhasorDynamics/Load/LoadZ/LoadZImpl.hpp b/GridKit/Model/PhasorDynamics/Load/LoadZ/LoadZImpl.hpp index 6cd0c26ca..d8c96d154 100644 --- a/GridKit/Model/PhasorDynamics/Load/LoadZ/LoadZImpl.hpp +++ b/GridKit/Model/PhasorDynamics/Load/LoadZ/LoadZImpl.hpp @@ -3,12 +3,12 @@ #include #include +#include +#include #include #include #include #include -#include -#include namespace GridKit { @@ -49,13 +49,10 @@ namespace GridKit { using Parameter = typename ModelDataT::Parameters; - Utilities::ConfigurationChecks checks("LoadZ"); - Utilities::ParameterReader reader(data, checks); + Model::ParameterReader reader(data, "LoadZ"); reader.loadReal(Parameter::R, R_); reader.loadReal(Parameter::X, X_); - parameter_error_count_ = static_cast(checks.errorCount()); - size_ = 2; setDerivedParams(); initializeMonitor(); diff --git a/GridKit/Model/PhasorDynamics/Load/LoadZIP/LoadZIP.hpp b/GridKit/Model/PhasorDynamics/Load/LoadZIP/LoadZIP.hpp index 3d70ce028..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 static_cast(parameter_error_count_); + return {}; } public: @@ -132,9 +132,6 @@ namespace GridKit RealT alphaZ_{1.0}; std::unique_ptr monitor_; - - /// Count of parameter-loading errors reported through verify() - IdxT parameter_error_count_{0}; }; } // namespace PhasorDynamics diff --git a/GridKit/Model/PhasorDynamics/Load/LoadZIP/LoadZIPImpl.hpp b/GridKit/Model/PhasorDynamics/Load/LoadZIP/LoadZIPImpl.hpp index 3c0661068..a26d69608 100644 --- a/GridKit/Model/PhasorDynamics/Load/LoadZIP/LoadZIPImpl.hpp +++ b/GridKit/Model/PhasorDynamics/Load/LoadZIP/LoadZIPImpl.hpp @@ -1,11 +1,11 @@ #pragma once +#include +#include #include #include #include #include -#include -#include namespace GridKit { @@ -59,17 +59,12 @@ namespace GridKit { using Parameter = typename ModelDataT::Parameters; - parameter_error_count_ = 0; - - Utilities::ConfigurationChecks checks("LoadZIP"); - Utilities::ParameterReader reader(data, checks); + 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_); - parameter_error_count_ = static_cast(checks.errorCount()); - setDerivedParams(); } diff --git a/GridKit/Model/PhasorDynamics/SignalIn.hpp b/GridKit/Model/PhasorDynamics/SignalIn.hpp index e4db724cf..549839cb2 100644 --- a/GridKit/Model/PhasorDynamics/SignalIn.hpp +++ b/GridKit/Model/PhasorDynamics/SignalIn.hpp @@ -6,9 +6,7 @@ #pragma once -#include #include -#include namespace GridKit { @@ -36,26 +34,6 @@ namespace GridKit return this->signal_node_->getVariableIndex(); } - /// Verify an optional input is linked whenever it is connected. - void checkOptional(Utilities::ConfigurationChecks& checks, const char* name) const - { - if (this->connected() && !this->linked()) - { - checks.fail() << name << " signal attached with no linked source\n"; - } - } - - /// Verify a required input is connected and linked. - void checkRequired(Utilities::ConfigurationChecks& checks, const char* name) const - { - if (!this->connected()) - { - checks.fail() << name << " signal is required\n"; - return; - } - checkOptional(checks, name); - } - /// Read the connected signal, or the fallback when disconnected. ScalarT readOrDefault(ScalarT fallback) const { @@ -66,18 +44,6 @@ namespace GridKit return fallback; } - /// Refresh an explicitly selected workspace value and global index. - void refreshWorkspace(ScalarT fallback, ScalarT& value, IdxT& index) const - { - value = fallback; - index = INVALID_INDEX; - if (this->connected()) - { - value = readSignal(); - index = signalVariableIndex(); - } - } - /// Write a value to the connected signal node. /// /// @warning Use only during initialization as this violates assumptions. diff --git a/GridKit/Model/PhasorDynamics/SignalSource/ConstantSignalSource.hpp b/GridKit/Model/PhasorDynamics/SignalSource/ConstantSignalSource.hpp index c95b79335..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() { @@ -70,9 +70,6 @@ namespace GridKit /// Component ports SignalPortsT ports_; - /// Count of parameter-loading errors reported through verify() - IdxT parameter_error_count_{0}; - // Parameter initialization function void initializeParameters(const ModelDataT& data); }; diff --git a/GridKit/Model/PhasorDynamics/SignalSource/ConstantSignalSourceImpl.hpp b/GridKit/Model/PhasorDynamics/SignalSource/ConstantSignalSourceImpl.hpp index af299220c..3fb169794 100644 --- a/GridKit/Model/PhasorDynamics/SignalSource/ConstantSignalSourceImpl.hpp +++ b/GridKit/Model/PhasorDynamics/SignalSource/ConstantSignalSourceImpl.hpp @@ -1,11 +1,11 @@ +#include +#include #include #include #include #include -#include #include -#include namespace GridKit { @@ -45,10 +45,7 @@ namespace GridKit { using Parameters = ModelDataT::Parameters; - parameter_error_count_ = 0; - - Utilities::ConfigurationChecks checks("ConstantSignalSource"); - Utilities::ParameterReader reader(data, checks); + Model::ParameterReader reader(data, "ConstantSignalSource"); // The signal values are differentiable scalars, so each is loaded // through a real intermediate. @@ -62,8 +59,6 @@ namespace GridKit { s_imag_ = s_imag; } - - parameter_error_count_ = static_cast(checks.errorCount()); } /** @@ -98,9 +93,9 @@ namespace GridKit } template - int ConstantSignalSource::verify() const + Model::ConfigurationChecks ConstantSignalSource::verify() const { - return static_cast(parameter_error_count_); + return {}; } template diff --git a/GridKit/Model/PhasorDynamics/Stabilizer/IEEEST/Ieeest.hpp b/GridKit/Model/PhasorDynamics/Stabilizer/IEEEST/Ieeest.hpp index d2954c812..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() { @@ -147,9 +147,6 @@ namespace GridKit std::unique_ptr monitor_; - /// Count of parameter-loading errors reported through verify() - IdxT parameter_error_count_{0}; - void initializeParameters(const ModelDataT& data); void initializeMonitor(); }; diff --git a/GridKit/Model/PhasorDynamics/Stabilizer/IEEEST/IeeestImpl.hpp b/GridKit/Model/PhasorDynamics/Stabilizer/IEEEST/IeeestImpl.hpp index 7b2be0db0..5ccc70ed0 100644 --- a/GridKit/Model/PhasorDynamics/Stabilizer/IEEEST/IeeestImpl.hpp +++ b/GridKit/Model/PhasorDynamics/Stabilizer/IEEEST/IeeestImpl.hpp @@ -8,14 +8,14 @@ #include +#include +#include #include #include #include #include #include -#include #include -#include namespace GridKit { @@ -50,10 +50,7 @@ namespace GridKit { using Parameter = typename ModelDataT::Parameters; - parameter_error_count_ = 0; - - Utilities::ConfigurationChecks checks("Ieeest"); - Utilities::ParameterReader reader(data, checks); + Model::ParameterReader reader(data, "Ieeest"); reader.loadReal(Parameter::A1, A1_); reader.loadReal(Parameter::A2, A2_); reader.loadReal(Parameter::A3, A3_); @@ -73,8 +70,6 @@ namespace GridKit reader.loadReal(Parameter::Vcu, Vcu_); reader.loadReal(Parameter::Tdelay, Tdelay_); - parameter_error_count_ = static_cast(checks.errorCount()); - a0_ = 1; a1_ = A1_ + A3_; a2_ = A2_ + A4_ + A1_ * A3_; @@ -143,37 +138,30 @@ namespace GridKit } template - int Ieeest::verify() const + Model::ConfigurationChecks Ieeest::verify() const { - int ret = static_cast(parameter_error_count_); + 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 8436f6011..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; @@ -222,9 +222,6 @@ namespace GridKit /// Variable monitor std::unique_ptr monitor_; - - /// Count of parameter-loading errors reported through verify() - IdxT parameter_error_count_{0}; }; } // namespace PhasorDynamics diff --git a/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROU/GenrouImpl.hpp b/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROU/GenrouImpl.hpp index 5df33736f..c2ec0863d 100644 --- a/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROU/GenrouImpl.hpp +++ b/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROU/GenrouImpl.hpp @@ -2,15 +2,15 @@ #include +#include +#include #include #include #include #include #include #include -#include #include -#include namespace GridKit { @@ -168,10 +168,7 @@ namespace GridKit using Parameter = typename ModelDataT::Parameters; using Buses = typename ModelDataT::Buses; - parameter_error_count_ = 0; - - Utilities::ConfigurationChecks checks("Genrou"); - Utilities::ParameterReader reader(data, checks); + Model::ParameterReader reader(data, "Genrou"); reader.loadReal(Parameter::p0, p0_); reader.loadReal(Parameter::q0, q0_); reader.loadReal(Parameter::H, H_); @@ -192,8 +189,6 @@ namespace GridKit reader.loadReal(Parameter::S12, S12_); reader.loadReal(Parameter::mva, mva_base_); - parameter_error_count_ = static_cast(checks.errorCount()); - if (data.buses.contains(Buses::bus)) { bus_id_ = data.buses.at(Buses::bus); @@ -283,26 +278,17 @@ namespace GridKit * @brief verify method checks that attached signals are also linked */ template - int Genrou::verify() const + Model::ConfigurationChecks Genrou::verify() const { + Model::ConfigurationChecks checks; - int ret = static_cast(parameter_error_count_); + const auto pmech_port = ports_.in.template port(); + checks.check(!pmech_port.connected() || pmech_port.linked(), "pmech signal attached with no linked governor"); - 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; - } - - 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 811bfa9e0..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; @@ -179,9 +179,6 @@ namespace GridKit /// Variable monitor std::unique_ptr monitor_; - - /// Count of parameter-loading errors reported through verify() - IdxT parameter_error_count_{0}; }; } // namespace PhasorDynamics diff --git a/GridKit/Model/PhasorDynamics/SynchronousMachine/GENSAL/GensalImpl.hpp b/GridKit/Model/PhasorDynamics/SynchronousMachine/GENSAL/GensalImpl.hpp index 084fb6983..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 @@ -9,9 +11,7 @@ #include #include #include -#include #include -#include namespace GridKit { @@ -46,10 +46,7 @@ namespace GridKit { using Parameter = typename ModelDataT::Parameters; - parameter_error_count_ = 0; - - Utilities::ConfigurationChecks checks("Gensal"); - Utilities::ParameterReader reader(data, checks); + Model::ParameterReader reader(data, "Gensal"); reader.loadReal(Parameter::p0, p0_); reader.loadReal(Parameter::q0, q0_); reader.loadReal(Parameter::H, H_); @@ -66,8 +63,6 @@ namespace GridKit reader.loadReal(Parameter::S10, S10_); reader.loadReal(Parameter::S12, S12_); reader.loadReal(Parameter::mva, mva_base_); - - parameter_error_count_ = static_cast(checks.errorCount()); } template @@ -171,26 +166,17 @@ namespace GridKit * @brief verify method checks that attached signals are also linked */ template - int Gensal::verify() const + Model::ConfigurationChecks Gensal::verify() const { + Model::ConfigurationChecks checks; - int ret = static_cast(parameter_error_count_); + const auto pmech_port = ports_.in.template port(); + checks.check(!pmech_port.connected() || pmech_port.linked(), "pmech signal attached with no linked governor"); - 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; - } - - 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 e074fd776..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; @@ -152,9 +152,6 @@ namespace GridKit /// Variable monitor std::unique_ptr monitor_; - - /// Count of parameter-loading errors reported through verify() - IdxT parameter_error_count_{0}; }; } // namespace PhasorDynamics diff --git a/GridKit/Model/PhasorDynamics/SynchronousMachine/GenClassical/GenClassicalImpl.hpp b/GridKit/Model/PhasorDynamics/SynchronousMachine/GenClassical/GenClassicalImpl.hpp index ddea99736..acede05af 100644 --- a/GridKit/Model/PhasorDynamics/SynchronousMachine/GenClassical/GenClassicalImpl.hpp +++ b/GridKit/Model/PhasorDynamics/SynchronousMachine/GenClassical/GenClassicalImpl.hpp @@ -10,13 +10,13 @@ #include +#include +#include #include #include #include #include #include -#include -#include namespace GridKit { @@ -50,8 +50,7 @@ namespace GridKit { using Parameter = typename ModelDataT::Parameters; - Utilities::ConfigurationChecks checks("GenClassical"); - Utilities::ParameterReader reader(data, checks); + Model::ParameterReader reader(data, "GenClassical"); reader.loadReal(Parameter::p0, p0_); reader.loadReal(Parameter::q0, q0_); reader.loadReal(Parameter::H, H_); @@ -59,8 +58,6 @@ namespace GridKit reader.loadReal(Parameter::Ra, Ra_); reader.loadReal(Parameter::Xdp, Xdp_); reader.loadReal(Parameter::mva, mva_base_); - - parameter_error_count_ = static_cast(checks.errorCount()); } template @@ -146,26 +143,17 @@ namespace GridKit * @brief verify method checks that attached signals are also linked */ template - int GenClassical::verify() const + Model::ConfigurationChecks GenClassical::verify() const { + Model::ConfigurationChecks checks; - int ret = static_cast(parameter_error_count_); - - 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/GridKit/Utilities/CMakeLists.txt b/GridKit/Utilities/CMakeLists.txt index f0d02a1d0..ccc633074 100644 --- a/GridKit/Utilities/CMakeLists.txt +++ b/GridKit/Utilities/CMakeLists.txt @@ -15,12 +15,10 @@ add_subdirectory(CliArgs) install(TARGETS Utilities EXPORT gridkit-targets) install( FILES Colors.hpp - ConfigurationChecks.hpp Enum.hpp Errors.hpp FileIO.hpp MapFromCsr.hpp - ParameterReader.hpp Stream.hpp String.hpp DESTINATION include/GridKit/Utilities) diff --git a/GridKit/Utilities/ConfigurationChecks.hpp b/GridKit/Utilities/ConfigurationChecks.hpp deleted file mode 100644 index 141c80281..000000000 --- a/GridKit/Utilities/ConfigurationChecks.hpp +++ /dev/null @@ -1,58 +0,0 @@ -/** - * @file ConfigurationChecks.hpp - * @author Luke Lowery (lukel@tamu.edu) - * @brief Error accumulator for model configuration validation. - */ - -#pragma once - -#include - -#include - -namespace GridKit -{ - namespace Utilities - { - /** - * @brief Error accumulator for model configuration validation. - * - * One instance collects the errors found while loading parameters or - * verifying a model, logging each with the model-name prefix. The - * caller reports errorCount() as its error total. - */ - class ConfigurationChecks - { - public: - explicit ConfigurationChecks(const char* model) - : model_(model) - { - } - - /// Log one error against this model and count it. - std::ostream& fail() - { - ++error_count_; - return Logger::error() << model_ << ": "; - } - - /// Log and count one error when the condition does not hold. - void check(bool condition, const char* message) - { - if (!condition) - { - fail() << message << '\n'; - } - } - - int errorCount() const - { - return error_count_; - } - - private: - const char* model_; - int error_count_{0}; - }; - } // namespace Utilities -} // namespace GridKit 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);