From 9134b9470fe37f8455d800abd07b28ddb7cf69be Mon Sep 17 00:00:00 2001 From: lukelowry Date: Wed, 2 Sep 2026 01:03:42 -0500 Subject: [PATCH 01/11] initial lambda extraction --- .../Model/PhasorDynamics/Branch/Branch.hpp | 3 - .../PhasorDynamics/Branch/BranchImpl.hpp | 99 +++---- .../Controller/REECB/ReecbImpl.hpp | 92 +++---- .../Controller/REPCA/RepcaImpl.hpp | 246 ++++++------------ .../Converter/REGCA/RegcaImpl.hpp | 148 +++-------- .../Exciter/ESDC1A/Esdc1aImpl.hpp | 208 ++++----------- .../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 | 40 +-- GridKit/Model/PhasorDynamics/SignalIn.hpp | 44 ++++ GridKit/Utilities/CMakeLists.txt | 2 + GridKit/Utilities/ConfigurationChecks.hpp | 58 +++++ GridKit/Utilities/ParameterReader.hpp | 199 ++++++++++++++ 15 files changed, 712 insertions(+), 946 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 8588f7869f..5719ade7f9 100644 --- a/GridKit/Model/PhasorDynamics/Branch/Branch.hpp +++ b/GridKit/Model/PhasorDynamics/Branch/Branch.hpp @@ -120,9 +120,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 90d9ea5e28..1d8e18fb4a 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(); } /// Put the branch in or out of service; out of service, it injects no current. @@ -367,14 +356,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)) { @@ -387,35 +381,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 5917d14b22..3acbca888f 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 1e266055db..87bb769e3f 100644 --- a/GridKit/Model/PhasorDynamics/Controller/REPCA/RepcaImpl.hpp +++ b/GridKit/Model/PhasorDynamics/Controller/REPCA/RepcaImpl.hpp @@ -16,7 +16,9 @@ #include #include #include +#include #include +#include namespace GridKit { @@ -147,18 +149,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 +159,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 +818,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 +903,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 cf585b6bd0..638a37702a 100644 --- a/GridKit/Model/PhasorDynamics/Converter/REGCA/RegcaImpl.hpp +++ b/GridKit/Model/PhasorDynamics/Converter/REGCA/RegcaImpl.hpp @@ -17,7 +17,9 @@ #include #include #include +#include #include +#include namespace GridKit { @@ -172,76 +174,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(); } @@ -372,51 +326,25 @@ namespace GridKit template int Regca::verify() const { - int ret = static_cast(parameter_error_count_); - - auto check = [&](bool condition, const char* message) - { - if (!condition) - { - Log::error() << "Regca: " << message << '\n'; - ret += 1; - } - }; + Utilities::ConfigurationChecks checks("Regca"); - 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 505fc83d7f..af1849738d 100644 --- a/GridKit/Model/PhasorDynamics/Exciter/ESDC1A/Esdc1aImpl.hpp +++ b/GridKit/Model/PhasorDynamics/Exciter/ESDC1A/Esdc1aImpl.hpp @@ -16,7 +16,9 @@ #include #include #include +#include #include +#include namespace GridKit { @@ -145,27 +147,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 +163,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 +619,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 +701,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 0074d0e33b..89fa80ee5b 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 5c9b035d21..27e6fce29e 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 92aa4f1e0b..675d7f8ba3 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 1ad51b4240..43b8a74ca6 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 a50c3a2748..4aed5aaa13 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 @@ -222,39 +223,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"); + Utilities::ConfigurationChecks checks("Tgov1"); - 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; - } + 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"); - 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; - } + 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 09636114e7..0e9244fb79 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 ccc6330742..f0d02a1d01 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 0000000000..141c802819 --- /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 0000000000..3b9955e18c --- /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 1ef253e2002ac51273bb6baa8cab0fb5730020bd Mon Sep 17 00:00:00 2001 From: lukelowry Date: Wed, 2 Sep 2026 01:34:08 -0500 Subject: [PATCH 02/11] older model migration and unify with new helper --- .../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 ++---- 25 files changed, 275 insertions(+), 613 deletions(-) diff --git a/GridKit/Model/PhasorDynamics/Controller/REECB/Reecb.hpp b/GridKit/Model/PhasorDynamics/Controller/REECB/Reecb.hpp index 6ec4bb77cf..c2ce1e748c 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 3acbca888f..48c99f5308 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 f45eef4f00..1a861389f2 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 89fa80ee5b..9f81c668f3 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 27e6fce29e..ba327746cb 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 300218d25c..b30268dbcc 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 675d7f8ba3..533710d87a 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 d4647a8261..2a41f7157f 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 dcc5e147e2..2c8703b24a 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 4aed5aaa13..b4320e94f1 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()); } /** @@ -236,7 +213,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 008abceeb7..69155c36eb 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 b3e24fc8aa..6cd0c26caf 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 737cba8c7b..3d70ce0280 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 929cfb746e..3c06610681 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 11fbc8be29..b43013a977 100644 --- a/GridKit/Model/PhasorDynamics/SignalSource/CMakeLists.txt +++ b/GridKit/Model/PhasorDynamics/SignalSource/CMakeLists.txt @@ -8,11 +8,13 @@ gridkit_add_library( phasor_dynamics_signalsource SOURCES ConstantSignalSource.cpp FunctionSignalSource.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 FunctionSignalSourceDependencyTracking.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 6adf70824f..c95b793352 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 561fbe759b..af299220c4 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 448e0144c9..d2954c8121 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 2bc836363e..7b2be0db06 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 8241aac801..1461198fab 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 c052fec4f2..5df33736f2 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 cf3d379c6d..811bfa9e0f 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 8435bdb713..084fb69833 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 ced9a652e3..e074fd7765 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 0c3b12cf18..ddea99736e 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 af676adab997418258de45bf8b7b32c712aa5f8d Mon Sep 17 00:00:00 2001 From: lukelowry Date: Tue, 15 Sep 2026 15:35:57 -0500 Subject: [PATCH 03/11] improved config checks and parameter reader --- GridKit/Model/CMakeLists.txt | 9 +- GridKit/Model/ConfigurationChecks.hpp | 58 +++++++++++ .../{Utilities => Model}/ParameterReader.hpp | 96 +++++++++---------- .../Model/PhasorDynamics/Branch/Branch.hpp | 18 ++-- .../PhasorDynamics/Branch/BranchImpl.hpp | 15 ++- .../BusSignalVoltageIn/BusSignalVoltageIn.hpp | 2 +- .../BusSignalVoltageInImpl.hpp | 52 +++------- .../BusSignalVoltageOut.hpp | 2 +- .../BusSignalVoltageOutImpl.hpp | 52 +++------- GridKit/Model/PhasorDynamics/BusBase.hpp | 6 +- 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 | 73 +++++++------- .../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 +-- .../SignalSource/FunctionSignalSource.hpp | 2 +- .../SignalSource/FunctionSignalSourceImpl.hpp | 4 +- .../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 +- .../BusSignalVoltageInTests.hpp | 34 +++---- .../BusSignalVoltageOutTests.hpp | 28 ++---- .../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 +- 66 files changed, 887 insertions(+), 927 deletions(-) create mode 100644 GridKit/Model/ConfigurationChecks.hpp rename GridKit/{Utilities => Model}/ParameterReader.hpp (56%) delete mode 100644 GridKit/Utilities/ConfigurationChecks.hpp diff --git a/GridKit/Model/CMakeLists.txt b/GridKit/Model/CMakeLists.txt index af94b489f4..a28d08def9 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 0000000000..aded652e3c --- /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 56% rename from GridKit/Utilities/ParameterReader.hpp rename to GridKit/Model/ParameterReader.hpp index 3b9955e18c..c55826e729 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: - const std::map>& parameters_; - ConfigurationChecks& checks_; + /// Reject the model data with a message naming the model and parameter. + [[noreturn]] void reject(ParametersT key, const char* reason) const + { + throw std::invalid_argument(std::string(model_) + ": parameter '" + + std::string(magic_enum::enum_name(key)) + + "' " + reason); + } + + const std::map>& parameters_; + const char* model_; }; - } // namespace Utilities + } // namespace Model } // namespace GridKit diff --git a/GridKit/Model/PhasorDynamics/Branch/Branch.hpp b/GridKit/Model/PhasorDynamics/Branch/Branch.hpp index 5719ade7f9..7393807b5a 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; int setInService(bool in_service); @@ -207,8 +207,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 1d8e18fb4a..186488669c 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; } /// Put the branch in or out of service; out of service, it injects no current. @@ -356,8 +356,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_); @@ -368,8 +367,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/Bus/BusSignalVoltageIn/BusSignalVoltageIn.hpp b/GridKit/Model/PhasorDynamics/Bus/BusSignalVoltageIn/BusSignalVoltageIn.hpp index eb39bad52e..01ddb2b081 100644 --- a/GridKit/Model/PhasorDynamics/Bus/BusSignalVoltageIn/BusSignalVoltageIn.hpp +++ b/GridKit/Model/PhasorDynamics/Bus/BusSignalVoltageIn/BusSignalVoltageIn.hpp @@ -70,7 +70,7 @@ namespace GridKit virtual int setBusID(IdxT) override final; virtual int allocate() override final; - virtual int verify() const override final; + virtual Model::ConfigurationChecks verify() const override final; virtual int tagDifferentiable() override final; virtual int setAbsoluteTolerance(RealT rel_tol) override final; virtual int initialize() override final; diff --git a/GridKit/Model/PhasorDynamics/Bus/BusSignalVoltageIn/BusSignalVoltageInImpl.hpp b/GridKit/Model/PhasorDynamics/Bus/BusSignalVoltageIn/BusSignalVoltageInImpl.hpp index 0ca026ebb9..c0e91895e8 100644 --- a/GridKit/Model/PhasorDynamics/Bus/BusSignalVoltageIn/BusSignalVoltageInImpl.hpp +++ b/GridKit/Model/PhasorDynamics/Bus/BusSignalVoltageIn/BusSignalVoltageInImpl.hpp @@ -106,60 +106,34 @@ namespace GridKit } /** - * @brief Check that the voltage inlets are connected and linked, and - * that connected outlets are linked. - * - * Both voltage inlets `vr` and `vi` are mandatory, since the bus has no - * voltage of its own and no default value is allowed. - * - * @throws std::runtime_error if any port fails the check. Each problem - * is logged before throwing. - * - * @return 0 (an error is reported by throwing). + * @brief Collect missing voltage/current inlet and unlinked outlet errors. */ template - int BusSignalVoltageIn::verify() const + Model::ConfigurationChecks BusSignalVoltageIn::verify() const { - int errors = 0; - + Model::ConfigurationChecks checks; auto check_input = [&](const char* name) { const auto& port = ports_.in.template port(); - if (!port.connected()) + checks.check(port.connected(), std::string("BusSignalVoltageIn: ") + name + " signal inlet is not connected"); + if (port.connected()) { - Log::error() << "BusSignalVoltageIn: " << name - << " signal inlet is not connected; a default voltage is not allowed\n"; - errors += 1; - } - else if (!port.linked()) - { - Log::error() << "BusSignalVoltageIn: " << name << " signal attached with no linked source\n"; - errors += 1; + checks.check(port.linked(), std::string("BusSignalVoltageIn: ") + name + " signal attached with no linked source"); } }; - auto check_output = [&](const char* name) { const auto& port = ports_.out.template port(); - if (port.connected() && !port.linked()) + if (port.connected()) { - Log::error() << "BusSignalVoltageIn: " << name - << " signal attached but not linked; connect signal ports before allocate()\n"; - errors += 1; + checks.check(port.linked(), std::string("BusSignalVoltageIn: ") + name + " signal attached but not linked; connect before allocate()"); } }; - - check_input.template operator()("Vr"); - check_input.template operator()("Vi"); - check_output.template operator()("Ir"); - check_output.template operator()("Ii"); - - if (errors > 0) - { - throw std::runtime_error("BusSignalVoltageIn: signal ports are not correctly connected"); - } - - return 0; + check_input.template operator()("vr"); + check_input.template operator()("vi"); + check_output.template operator()("ir"); + check_output.template operator()("ii"); + return checks; } /** diff --git a/GridKit/Model/PhasorDynamics/Bus/BusSignalVoltageOut/BusSignalVoltageOut.hpp b/GridKit/Model/PhasorDynamics/Bus/BusSignalVoltageOut/BusSignalVoltageOut.hpp index f3c9855c8d..c57fb276d9 100644 --- a/GridKit/Model/PhasorDynamics/Bus/BusSignalVoltageOut/BusSignalVoltageOut.hpp +++ b/GridKit/Model/PhasorDynamics/Bus/BusSignalVoltageOut/BusSignalVoltageOut.hpp @@ -67,7 +67,7 @@ namespace GridKit virtual int setBusID(IdxT) override final; virtual int allocate() override final; - virtual int verify() const override final; + virtual Model::ConfigurationChecks verify() const override final; virtual int tagDifferentiable() override final; virtual int setAbsoluteTolerance(RealT rel_tol) override final; virtual int initialize() override final; diff --git a/GridKit/Model/PhasorDynamics/Bus/BusSignalVoltageOut/BusSignalVoltageOutImpl.hpp b/GridKit/Model/PhasorDynamics/Bus/BusSignalVoltageOut/BusSignalVoltageOutImpl.hpp index 105526ae51..473f2285de 100644 --- a/GridKit/Model/PhasorDynamics/Bus/BusSignalVoltageOut/BusSignalVoltageOutImpl.hpp +++ b/GridKit/Model/PhasorDynamics/Bus/BusSignalVoltageOut/BusSignalVoltageOutImpl.hpp @@ -125,60 +125,34 @@ namespace GridKit } /** - * @brief Check that the current inlets are connected and linked, and - * that connected outlets are linked. - * - * Both current inlets `ir` and `ii` are mandatory, since the residuals - * are set from them and no default value is allowed. - * - * @throws std::runtime_error if any port fails the check. Each problem - * is logged before throwing. - * - * @return 0 (an error is reported by throwing). + * @brief Collect missing voltage/current inlet and unlinked outlet errors. */ template - int BusSignalVoltageOut::verify() const + Model::ConfigurationChecks BusSignalVoltageOut::verify() const { - int errors = 0; - + Model::ConfigurationChecks checks; auto check_input = [&](const char* name) { const auto& port = ports_.in.template port(); - if (!port.connected()) + checks.check(port.connected(), std::string("BusSignalVoltageOut: ") + name + " signal inlet is not connected"); + if (port.connected()) { - Log::error() << "BusSignalVoltageOut: " << name - << " signal inlet is not connected; a default current is not allowed\n"; - errors += 1; - } - else if (!port.linked()) - { - Log::error() << "BusSignalVoltageOut: " << name << " signal attached with no linked source\n"; - errors += 1; + checks.check(port.linked(), std::string("BusSignalVoltageOut: ") + name + " signal attached with no linked source"); } }; - auto check_output = [&](const char* name) { const auto& port = ports_.out.template port(); - if (port.connected() && !port.linked()) + if (port.connected()) { - Log::error() << "BusSignalVoltageOut: " << name - << " signal attached but not linked; connect signal ports before allocate()\n"; - errors += 1; + checks.check(port.linked(), std::string("BusSignalVoltageOut: ") + name + " signal attached but not linked; connect before allocate()"); } }; - - check_input.template operator()("Ir"); - check_input.template operator()("Ii"); - check_output.template operator()("Vr"); - check_output.template operator()("Vi"); - - if (errors > 0) - { - throw std::runtime_error("BusSignalVoltageOut: signal ports are not correctly connected"); - } - - return 0; + check_input.template operator()("ir"); + check_input.template operator()("ii"); + check_output.template operator()("vr"); + check_output.template operator()("vi"); + return checks; } /** diff --git a/GridKit/Model/PhasorDynamics/BusBase.hpp b/GridKit/Model/PhasorDynamics/BusBase.hpp index cd447b6597..6dec007193 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/Component.hpp b/GridKit/Model/PhasorDynamics/Component.hpp index 4bc4051ad6..265e55d449 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 c2ce1e748c..438a9de953 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 48c99f5308..90e9ecfb26 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 16f9af8de6..b90d0516e7 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 87bb769e3f..d3f5ced3e2 100644 --- a/GridKit/Model/PhasorDynamics/Controller/REPCA/RepcaImpl.hpp +++ b/GridKit/Model/PhasorDynamics/Controller/REPCA/RepcaImpl.hpp @@ -9,16 +9,20 @@ #include #include #include +#include +#include +#include +#include +#include +#include #include #include #include #include #include #include -#include #include -#include namespace GridKit { @@ -144,12 +148,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"); @@ -186,18 +190,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; } /** @@ -251,9 +270,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; } @@ -816,10 +839,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_); @@ -853,8 +873,6 @@ namespace GridKit reader.loadReal(Params::Pmin, Pmin_); reader.loadReal(Params::Tlag, Tlag_); - parameter_error_count_ = static_cast(checks.errorCount()); - setDerivedParameters(); } @@ -903,13 +921,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 bb3ab989c4..f3c9be9d77 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 638a37702a..a4a63da2df 100644 --- a/GridKit/Model/PhasorDynamics/Converter/REGCA/RegcaImpl.hpp +++ b/GridKit/Model/PhasorDynamics/Converter/REGCA/RegcaImpl.hpp @@ -11,15 +11,15 @@ #include #include +#include +#include #include #include #include #include #include #include -#include #include -#include namespace GridKit { @@ -172,10 +172,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_); @@ -195,8 +192,6 @@ namespace GridKit reader.loadReal(Params::Khv, Khv_); - parameter_error_count_ = static_cast(checks.errorCount()); - setDerivedParameters(); } @@ -321,12 +316,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"); @@ -341,10 +336,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 6d70cb1a94..1323e972d3 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 af1849738d..5024749c0a 100644 --- a/GridKit/Model/PhasorDynamics/Exciter/ESDC1A/Esdc1aImpl.hpp +++ b/GridKit/Model/PhasorDynamics/Exciter/ESDC1A/Esdc1aImpl.hpp @@ -9,16 +9,21 @@ #include #include #include - +#include +#include +#include +#include +#include + +#include +#include #include #include #include #include #include #include -#include #include -#include namespace GridKit { @@ -142,12 +147,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"); @@ -178,12 +183,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; } /** @@ -220,14 +229,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. @@ -606,8 +619,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. @@ -617,10 +630,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_); @@ -641,8 +651,6 @@ namespace GridKit reader.loadSelector(Params::UEL, UEL_); reader.loadSwitch(Params::exclim, exclim_); - parameter_error_count_ = static_cast(checks.errorCount()); - setDerivedParameters(); } @@ -699,15 +707,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 1a861389f2..c17e8d1a21 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 9f81c668f3..a79a9db2f9 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 fbc3687e1e..c458bb8363 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 ba327746cb..707db5a86e 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 b30268dbcc..69e9020823 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 533710d87a..9f92978cf6 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 10ed07c265..679c059f52 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 43b8a74ca6..e9858cf65f 100644 --- a/GridKit/Model/PhasorDynamics/Governor/HYGOV/HygovImpl.hpp +++ b/GridKit/Model/PhasorDynamics/Governor/HYGOV/HygovImpl.hpp @@ -8,16 +8,22 @@ #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 2c8703b24a..6d4b16c402 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 b4320e94f1..fe5fafc870 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()); } /** @@ -198,10 +193,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"); @@ -210,10 +205,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; } /** @@ -223,9 +220,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 69155c36eb..05cf61021d 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 6cd0c26caf..d8c96d154d 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 3d70ce0280..86090bca76 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 3c06610681..a26d696088 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 0e9244fb79..18f30ff451 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 c95b793352..cbf8c3600f 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 af299220c4..3fb1697949 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/SignalSource/FunctionSignalSource.hpp b/GridKit/Model/PhasorDynamics/SignalSource/FunctionSignalSource.hpp index c172e1ce2b..2c36c03077 100644 --- a/GridKit/Model/PhasorDynamics/SignalSource/FunctionSignalSource.hpp +++ b/GridKit/Model/PhasorDynamics/SignalSource/FunctionSignalSource.hpp @@ -48,7 +48,7 @@ namespace GridKit int setGridKitComponentID(IdxT) override final; int allocate() override final; - int verify() const override final; + Model::ConfigurationChecks verify() const override final; int initialize() override final; int tagDifferentiable() override final; int setAbsoluteTolerance(RealT) override final; diff --git a/GridKit/Model/PhasorDynamics/SignalSource/FunctionSignalSourceImpl.hpp b/GridKit/Model/PhasorDynamics/SignalSource/FunctionSignalSourceImpl.hpp index af2dfcf732..e0efa0e5f9 100644 --- a/GridKit/Model/PhasorDynamics/SignalSource/FunctionSignalSourceImpl.hpp +++ b/GridKit/Model/PhasorDynamics/SignalSource/FunctionSignalSourceImpl.hpp @@ -110,9 +110,9 @@ namespace GridKit } template - int FunctionSignalSource::verify() const + Model::ConfigurationChecks FunctionSignalSource::verify() const { - return 0; + return {}; } template diff --git a/GridKit/Model/PhasorDynamics/Stabilizer/IEEEST/Ieeest.hpp b/GridKit/Model/PhasorDynamics/Stabilizer/IEEEST/Ieeest.hpp index d2954c8121..2c5679a4f5 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 7b2be0db06..5ccc70ed07 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 1461198fab..5f7b0929a3 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 5df33736f2..c2ec0863d2 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 811bfa9e0f..1defcff89f 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 084fb69833..0d86c563c3 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 e074fd7765..6192267f1f 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 ddea99736e..acede05af0 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 9d8c938ce7..3828763372 100644 --- a/GridKit/Model/PhasorDynamics/SystemModel.hpp +++ b/GridKit/Model/PhasorDynamics/SystemModel.hpp @@ -78,9 +78,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 0d7e1859b6..cccdc3f271 100644 --- a/GridKit/Model/PhasorDynamics/SystemModelImpl.hpp +++ b/GridKit/Model/PhasorDynamics/SystemModelImpl.hpp @@ -1,5 +1,6 @@ #include #include +#include #include #include @@ -458,10 +459,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"); } @@ -493,21 +497,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 ba6839f4fd..fbf565292e 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 f0d02a1d01..ccc6330742 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 141c802819..0000000000 --- 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 5dea2270af..b3d003e688 100644 --- a/tests/UnitTests/PhasorDynamics/BranchTests.hpp +++ b/tests/UnitTests/PhasorDynamics/BranchTests.hpp @@ -373,7 +373,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. @@ -381,17 +381,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/BusSignalVoltageInTests.hpp b/tests/UnitTests/PhasorDynamics/BusSignalVoltageInTests.hpp index 2d7fa6a931..762802ce69 100644 --- a/tests/UnitTests/PhasorDynamics/BusSignalVoltageInTests.hpp +++ b/tests/UnitTests/PhasorDynamics/BusSignalVoltageInTests.hpp @@ -32,19 +32,11 @@ namespace GridKit BusSignalVoltageInTests() = default; ~BusSignalVoltageInTests() = default; - /// True if verify() throws, as it must for a misconnected bus + /// Invalid configurations are reported without throwing. template - static bool verifyThrows(const BusLike& bus) + static bool verifyFails(const BusLike& bus) { - try - { - bus.verify(); - } - catch (const std::runtime_error&) - { - return true; - } - return false; + return !bus.verify().passed(); } /// Constructor, allocation, and initialization checks @@ -69,14 +61,14 @@ namespace GridKit success *= isEqual(bus->Ir(), 0.0); success *= isEqual(bus->Ii(), 0.0); // Voltage inlets are mandatory: an unconnected bus fails verification - success *= verifyThrows(*bus); + success *= verifyFails(*bus); delete bus; // Initial voltage arguments are accepted for interface uniformity but not used bus = new BusT(Vr, Vi); bus->allocate(); bus->initialize(); - success *= verifyThrows(*bus); + success *= verifyFails(*bus); delete bus; bus = nullptr; @@ -110,7 +102,7 @@ namespace GridKit bus.getPorts().in.template port().connect(&vi_node); bus.allocate(); bus.initialize(); - success *= (bus.verify() == 0); + success *= bus.verify().passed(); // Voltage is read straight from the signals, no evaluation needed success *= isEqual(bus.Vr(), Vr); @@ -131,7 +123,7 @@ namespace GridKit BusT plain; plain.allocate(); plain.initialize(); - success *= verifyThrows(plain); + success *= verifyFails(plain); bool threw = false; try { @@ -177,7 +169,7 @@ namespace GridKit bus.allocate(); bus.initialize(); - success *= (bus.verify() == 0); + success *= bus.verify().passed(); success *= ir_node.linked(); success *= ii_node.linked(); success *= (ir_node.getVariableIndex() == INVALID_INDEX); @@ -203,7 +195,7 @@ namespace GridKit return success.report(__func__); } - /// verify() throws for voltage inlets that are unconnected or unlinked + /// verify() reports errors for voltage inlets that are unconnected or unlinked TestOutcome verifyUnlinked() { TestStatus success = true; @@ -221,20 +213,20 @@ namespace GridKit bus.initialize(); // vr connected but unlinked, vi not connected - success *= verifyThrows(bus); + success *= verifyFails(bus); ScalarT Vr{0.1}; IdxT vr_index{0}; vr_node.link(&Vr, &vr_index); - success *= verifyThrows(bus); + success *= verifyFails(bus); bus.getPorts().in.template port().connect(&vi_node); - success *= verifyThrows(bus); + success *= verifyFails(bus); ScalarT Vi{0.2}; IdxT vi_index{1}; vi_node.link(&Vi, &vi_index); - success *= (bus.verify() == 0); + success *= bus.verify().passed(); Log::setVerbosity(previous_verbosity); diff --git a/tests/UnitTests/PhasorDynamics/BusSignalVoltageOutTests.hpp b/tests/UnitTests/PhasorDynamics/BusSignalVoltageOutTests.hpp index 4ca19516ff..27ae295f90 100644 --- a/tests/UnitTests/PhasorDynamics/BusSignalVoltageOutTests.hpp +++ b/tests/UnitTests/PhasorDynamics/BusSignalVoltageOutTests.hpp @@ -33,19 +33,11 @@ namespace GridKit BusSignalVoltageOutTests() = default; ~BusSignalVoltageOutTests() = default; - /// True if verify() throws, as it must for a misconnected bus + /// Invalid configurations are reported without throwing. template - static bool verifyThrows(const BusLike& bus) + static bool verifyFails(const BusLike& bus) { - try - { - bus.verify(); - } - catch (const std::runtime_error&) - { - return true; - } - return false; + return !bus.verify().passed(); } /// Constructor, allocation, and initialization checks @@ -117,7 +109,7 @@ namespace GridKit bus.setVariableIndex(1, vi_index); bus.initialize(); - success *= (bus.verify() == 0); + success *= bus.verify().passed(); success *= vr_node.linked(); success *= vi_node.linked(); success *= isEqual(vr_node.read(), Vr); @@ -153,7 +145,7 @@ namespace GridKit bus.initialize(); const auto previous_verbosity = Log::verbosity(); Log::setVerbosity(Log::Verbosity::NONE); // expected errors - success *= verifyThrows(bus); + success *= verifyFails(bus); Log::setVerbosity(previous_verbosity); } @@ -167,7 +159,7 @@ namespace GridKit bus.getPorts().in.template port().connect(&ii_node); bus.allocate(); bus.initialize(); - success *= (bus.verify() == 0); + success *= bus.verify().passed(); bus.evaluateResidual(); success *= isEqual(bus.Ir(), Ir); @@ -191,7 +183,7 @@ namespace GridKit return success.report(__func__); } - /// verify() throws for current inlets that are unconnected or unlinked + /// verify() reports errors for current inlets that are unconnected or unlinked TestOutcome verifyUnlinked() { TestStatus success = true; @@ -209,17 +201,17 @@ namespace GridKit bus.allocate(); bus.initialize(); - success *= verifyThrows(bus); + success *= verifyFails(bus); ScalarT Ir{0.1}; IdxT ir_index{0}; ir_node.link(&Ir, &ir_index); - success *= verifyThrows(bus); + success *= verifyFails(bus); ScalarT Ii{0.2}; IdxT ii_index{1}; ii_node.link(&Ii, &ii_index); - success *= (bus.verify() == 0); + success *= bus.verify().passed(); Log::setVerbosity(previous_verbosity); diff --git a/tests/UnitTests/PhasorDynamics/ControllerReecbTests.hpp b/tests/UnitTests/PhasorDynamics/ControllerReecbTests.hpp index 65568cecb7..ad1d3b5ad3 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 e6f90ca2e7..521c00e0ee 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 1288eee310..c39ecc208e 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 05fc850b01..39cb771945 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 2fbb230660..757b2dcf92 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 19ad86a58c..f15873e30e 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 e0d2ed5488..a089cf1ccb 100644 --- a/tests/UnitTests/PhasorDynamics/GenClassicalTests.hpp +++ b/tests/UnitTests/PhasorDynamics/GenClassicalTests.hpp @@ -328,7 +328,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 3231b05611..ba383180a9 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 f72a8ba1a8..825733547a 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 155a88a164..17ab0f79cb 100644 --- a/tests/UnitTests/PhasorDynamics/StabilizerIeeestTests.hpp +++ b/tests/UnitTests/PhasorDynamics/StabilizerIeeestTests.hpp @@ -74,7 +74,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 1d78f3ce0e..aee34b8ffe 100644 --- a/tests/UnitTests/PhasorDynamics/SystemSingleComponentTests.hpp +++ b/tests/UnitTests/PhasorDynamics/SystemSingleComponentTests.hpp @@ -160,7 +160,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__); @@ -478,7 +478,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 e0fd40ae85..4d9902c71a 100644 --- a/tests/UnitTests/PhasorDynamics/SystemTests.hpp +++ b/tests/UnitTests/PhasorDynamics/SystemTests.hpp @@ -67,9 +67,9 @@ namespace GridKit return 0; } - int verify() const override final + Model::ConfigurationChecks verify() const override final { - return 0; + return {}; } int initialize() override final @@ -450,7 +450,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); From 586783a05c8361aa50974fbf12f2c8d8936219df Mon Sep 17 00:00:00 2001 From: lukelowry Date: Tue, 6 Oct 2026 18:23:45 +0000 Subject: [PATCH 04/11] Apply pre-commit fixes --- .../BusSignalVoltageIn/BusSignalVoltageIn.hpp | 16 ++++++++-------- .../BusSignalVoltageInImpl.hpp | 6 +++--- .../Bus/BusSignalVoltageIn/README.md | 3 +-- .../BusSignalVoltageOut/BusSignalVoltageOut.hpp | 14 +++++++------- .../BusSignalVoltageOutImpl.hpp | 6 +++--- .../Bus/BusSignalVoltageOut/README.md | 3 +-- .../Controller/REPCA/RepcaImpl.hpp | 4 ++-- .../PhasorDynamics/Exciter/ESDC1A/Esdc1aImpl.hpp | 4 ++-- .../PhasorDynamics/Governor/HYGOV/HygovImpl.hpp | 2 +- .../PhasorDynamics/BusSignalVoltageInTests.hpp | 6 +++--- .../PhasorDynamics/BusSignalVoltageOutTests.hpp | 2 +- 11 files changed, 32 insertions(+), 34 deletions(-) diff --git a/GridKit/Model/PhasorDynamics/Bus/BusSignalVoltageIn/BusSignalVoltageIn.hpp b/GridKit/Model/PhasorDynamics/Bus/BusSignalVoltageIn/BusSignalVoltageIn.hpp index 01ddb2b081..1580a8ab43 100644 --- a/GridKit/Model/PhasorDynamics/Bus/BusSignalVoltageIn/BusSignalVoltageIn.hpp +++ b/GridKit/Model/PhasorDynamics/Bus/BusSignalVoltageIn/BusSignalVoltageIn.hpp @@ -24,7 +24,7 @@ namespace GridKit * components _Vr_ and _Vi_ are read directly from signal inlets * `vr` and `vi` whenever Vr() or Vi() is called; the bus stores no * voltage of its own and never modifies it. Both voltage inlets are - * mandatory: verify() logs an error and throws for an inlet that is not + * mandatory: verify() reports an error for an inlet that is not * connected to a linked signal, and reading the voltage through an * unlinked inlet throws. No default voltage is ever used. The bus has * no unknowns and no equations (size() == 0, like @ref BusInfinite). @@ -68,14 +68,14 @@ namespace GridKit BusSignalVoltageIn(const ModelDataT& data); virtual ~BusSignalVoltageIn(); - virtual int setBusID(IdxT) override final; - virtual int allocate() override final; + virtual int setBusID(IdxT) override final; + virtual int allocate() override final; virtual Model::ConfigurationChecks verify() const override final; - virtual int tagDifferentiable() override final; - virtual int setAbsoluteTolerance(RealT rel_tol) override final; - virtual int initialize() override final; - virtual int evaluateResidual() override final; - virtual int evaluateJacobian() override final; + virtual int tagDifferentiable() override final; + virtual int setAbsoluteTolerance(RealT rel_tol) override final; + virtual int initialize() override final; + virtual int evaluateResidual() override final; + virtual int evaluateJacobian() override final; virtual BusTypeT BusType() const override final { diff --git a/GridKit/Model/PhasorDynamics/Bus/BusSignalVoltageIn/BusSignalVoltageInImpl.hpp b/GridKit/Model/PhasorDynamics/Bus/BusSignalVoltageIn/BusSignalVoltageInImpl.hpp index c0e91895e8..98d753f0ef 100644 --- a/GridKit/Model/PhasorDynamics/Bus/BusSignalVoltageIn/BusSignalVoltageInImpl.hpp +++ b/GridKit/Model/PhasorDynamics/Bus/BusSignalVoltageIn/BusSignalVoltageInImpl.hpp @@ -112,7 +112,7 @@ namespace GridKit Model::ConfigurationChecks BusSignalVoltageIn::verify() const { Model::ConfigurationChecks checks; - auto check_input = [&](const char* name) + auto check_input = [&](const char* name) { const auto& port = ports_.in.template port(); checks.check(port.connected(), std::string("BusSignalVoltageIn: ") + name + " signal inlet is not connected"); @@ -129,8 +129,8 @@ namespace GridKit checks.check(port.linked(), std::string("BusSignalVoltageIn: ") + name + " signal attached but not linked; connect before allocate()"); } }; - check_input.template operator()("vr"); - check_input.template operator()("vi"); + check_input.template operator()("vr"); + check_input.template operator()("vi"); check_output.template operator()("ir"); check_output.template operator()("ii"); return checks; diff --git a/GridKit/Model/PhasorDynamics/Bus/BusSignalVoltageIn/README.md b/GridKit/Model/PhasorDynamics/Bus/BusSignalVoltageIn/README.md index ce228b85c0..c35d111d2e 100644 --- a/GridKit/Model/PhasorDynamics/Bus/BusSignalVoltageIn/README.md +++ b/GridKit/Model/PhasorDynamics/Bus/BusSignalVoltageIn/README.md @@ -9,8 +9,7 @@ The bus stores no voltage of its own and has no unknowns and no equations. - Signal ports must be connected before `allocate()` is called. The signal outlets are linked to the current sums in `allocate()`. -- Both voltage inlets are mandatory. `verify()` logs each problem and - throws if a voltage inlet is not connected or not linked, or if a +- Both voltage inlets are mandatory. `verify()` returns configuration errors if a voltage inlet is not connected or not linked, or if a connected outlet is not linked. Reading the voltage through an unlinked inlet throws. No default voltage is ever used. - The current sums are complete only after all attached components have diff --git a/GridKit/Model/PhasorDynamics/Bus/BusSignalVoltageOut/BusSignalVoltageOut.hpp b/GridKit/Model/PhasorDynamics/Bus/BusSignalVoltageOut/BusSignalVoltageOut.hpp index c57fb276d9..f6f47275b5 100644 --- a/GridKit/Model/PhasorDynamics/Bus/BusSignalVoltageOut/BusSignalVoltageOut.hpp +++ b/GridKit/Model/PhasorDynamics/Bus/BusSignalVoltageOut/BusSignalVoltageOut.hpp @@ -65,14 +65,14 @@ namespace GridKit BusSignalVoltageOut(const ModelDataT& data); virtual ~BusSignalVoltageOut(); - virtual int setBusID(IdxT) override final; - virtual int allocate() override final; + virtual int setBusID(IdxT) override final; + virtual int allocate() override final; virtual Model::ConfigurationChecks verify() const override final; - virtual int tagDifferentiable() override final; - virtual int setAbsoluteTolerance(RealT rel_tol) override final; - virtual int initialize() override final; - virtual int evaluateResidual() override final; - virtual int evaluateJacobian() override final; + virtual int tagDifferentiable() override final; + virtual int setAbsoluteTolerance(RealT rel_tol) override final; + virtual int initialize() override final; + virtual int evaluateResidual() override final; + virtual int evaluateJacobian() override final; virtual BusTypeT BusType() const override final { diff --git a/GridKit/Model/PhasorDynamics/Bus/BusSignalVoltageOut/BusSignalVoltageOutImpl.hpp b/GridKit/Model/PhasorDynamics/Bus/BusSignalVoltageOut/BusSignalVoltageOutImpl.hpp index 473f2285de..46fd6e1c95 100644 --- a/GridKit/Model/PhasorDynamics/Bus/BusSignalVoltageOut/BusSignalVoltageOutImpl.hpp +++ b/GridKit/Model/PhasorDynamics/Bus/BusSignalVoltageOut/BusSignalVoltageOutImpl.hpp @@ -131,7 +131,7 @@ namespace GridKit Model::ConfigurationChecks BusSignalVoltageOut::verify() const { Model::ConfigurationChecks checks; - auto check_input = [&](const char* name) + auto check_input = [&](const char* name) { const auto& port = ports_.in.template port(); checks.check(port.connected(), std::string("BusSignalVoltageOut: ") + name + " signal inlet is not connected"); @@ -148,8 +148,8 @@ namespace GridKit checks.check(port.linked(), std::string("BusSignalVoltageOut: ") + name + " signal attached but not linked; connect before allocate()"); } }; - check_input.template operator()("ir"); - check_input.template operator()("ii"); + check_input.template operator()("ir"); + check_input.template operator()("ii"); check_output.template operator()("vr"); check_output.template operator()("vi"); return checks; diff --git a/GridKit/Model/PhasorDynamics/Bus/BusSignalVoltageOut/README.md b/GridKit/Model/PhasorDynamics/Bus/BusSignalVoltageOut/README.md index 5224654e2a..754147eb08 100644 --- a/GridKit/Model/PhasorDynamics/Bus/BusSignalVoltageOut/README.md +++ b/GridKit/Model/PhasorDynamics/Bus/BusSignalVoltageOut/README.md @@ -11,8 +11,7 @@ the bus residual is evaluated. - Signal ports must be connected before `allocate()` is called. The signal outlets are linked to the bus voltage variables and their system indices in `allocate()`. -- Both current inlets are mandatory. `verify()` logs each problem and - throws if a current inlet is not connected or not linked, or if a +- Both current inlets are mandatory. `verify()` returns configuration errors if a current inlet is not connected or not linked, or if a connected outlet is not linked. No default current is ever used. - Current entering the bus has positive sign. diff --git a/GridKit/Model/PhasorDynamics/Controller/REPCA/RepcaImpl.hpp b/GridKit/Model/PhasorDynamics/Controller/REPCA/RepcaImpl.hpp index d3f5ced3e2..0ffeb71584 100644 --- a/GridKit/Model/PhasorDynamics/Controller/REPCA/RepcaImpl.hpp +++ b/GridKit/Model/PhasorDynamics/Controller/REPCA/RepcaImpl.hpp @@ -7,12 +7,12 @@ #pragma once #include -#include -#include #include +#include #include #include #include +#include #include #include diff --git a/GridKit/Model/PhasorDynamics/Exciter/ESDC1A/Esdc1aImpl.hpp b/GridKit/Model/PhasorDynamics/Exciter/ESDC1A/Esdc1aImpl.hpp index 5024749c0a..f334058eb9 100644 --- a/GridKit/Model/PhasorDynamics/Exciter/ESDC1A/Esdc1aImpl.hpp +++ b/GridKit/Model/PhasorDynamics/Exciter/ESDC1A/Esdc1aImpl.hpp @@ -7,13 +7,13 @@ #pragma once #include -#include -#include #include +#include #include #include #include #include +#include #include #include diff --git a/GridKit/Model/PhasorDynamics/Governor/HYGOV/HygovImpl.hpp b/GridKit/Model/PhasorDynamics/Governor/HYGOV/HygovImpl.hpp index e9858cf65f..132a2fd0cb 100644 --- a/GridKit/Model/PhasorDynamics/Governor/HYGOV/HygovImpl.hpp +++ b/GridKit/Model/PhasorDynamics/Governor/HYGOV/HygovImpl.hpp @@ -7,8 +7,8 @@ #pragma once #include -#include #include +#include #include #include #include diff --git a/tests/UnitTests/PhasorDynamics/BusSignalVoltageInTests.hpp b/tests/UnitTests/PhasorDynamics/BusSignalVoltageInTests.hpp index 762802ce69..bfd5f830a7 100644 --- a/tests/UnitTests/PhasorDynamics/BusSignalVoltageInTests.hpp +++ b/tests/UnitTests/PhasorDynamics/BusSignalVoltageInTests.hpp @@ -44,7 +44,7 @@ namespace GridKit { TestStatus success = true; - // This test triggers error messages on purpose; silence them. + // Keep expected invalid-configuration diagnostics quiet. const auto previous_verbosity = Log::verbosity(); Log::setVerbosity(Log::Verbosity::NONE); @@ -83,7 +83,7 @@ namespace GridKit { TestStatus success = true; - // This test triggers error messages on purpose; silence them. + // Keep expected invalid-configuration diagnostics quiet. const auto previous_verbosity = Log::verbosity(); Log::setVerbosity(Log::Verbosity::NONE); @@ -200,7 +200,7 @@ namespace GridKit { TestStatus success = true; - // This test triggers error messages on purpose; silence them. + // Keep expected invalid-configuration diagnostics quiet. const auto previous_verbosity = Log::verbosity(); Log::setVerbosity(Log::Verbosity::NONE); diff --git a/tests/UnitTests/PhasorDynamics/BusSignalVoltageOutTests.hpp b/tests/UnitTests/PhasorDynamics/BusSignalVoltageOutTests.hpp index 27ae295f90..0b6854be06 100644 --- a/tests/UnitTests/PhasorDynamics/BusSignalVoltageOutTests.hpp +++ b/tests/UnitTests/PhasorDynamics/BusSignalVoltageOutTests.hpp @@ -188,7 +188,7 @@ namespace GridKit { TestStatus success = true; - // This test triggers error messages on purpose; silence them. + // Keep expected invalid-configuration diagnostics quiet. const auto previous_verbosity = Log::verbosity(); Log::setVerbosity(Log::Verbosity::NONE); From 7c932a9bdd547bfda323d0b12ad00f32807498c8 Mon Sep 17 00:00:00 2001 From: lukelowry Date: Fri, 9 Oct 2026 17:22:53 -0500 Subject: [PATCH 05/11] misc formatting changes per reviewers --- .../Controller/REPCA/RepcaImpl.hpp | 3 +- .../PhasorDynamics/Exciter/IEEET1/Ieeet1.hpp | 4 ++ .../Exciter/IEEET1/Ieeet1Impl.hpp | 54 +++++++------------ .../Model/PhasorDynamics/SystemModelImpl.hpp | 3 +- 4 files changed, 27 insertions(+), 37 deletions(-) diff --git a/GridKit/Model/PhasorDynamics/Controller/REPCA/RepcaImpl.hpp b/GridKit/Model/PhasorDynamics/Controller/REPCA/RepcaImpl.hpp index 0ffeb71584..0131e08755 100644 --- a/GridKit/Model/PhasorDynamics/Controller/REPCA/RepcaImpl.hpp +++ b/GridKit/Model/PhasorDynamics/Controller/REPCA/RepcaImpl.hpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -927,7 +928,7 @@ namespace GridKit { if (value < ZERO) { - throw std::invalid_argument(std::string("Repca: ") + name + " must be non-negative"); + throw std::invalid_argument(std::format("Repca: {} must be non-negative", name)); } } diff --git a/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1.hpp b/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1.hpp index c17e8d1a21..c0917cca55 100644 --- a/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1.hpp +++ b/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1.hpp @@ -154,6 +154,10 @@ namespace GridKit void initModelParams(const ModelDataT& data); void setDerivedParameters(); + /// Refresh one signal input and its global variable index. + template + void readSignal(const ScalarT& fallback); + /// Associate variable getter functions with enum values void initializeMonitor(); }; diff --git a/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1Impl.hpp b/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1Impl.hpp index a79a9db2f9..b329ae00f8 100644 --- a/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1Impl.hpp +++ b/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1Impl.hpp @@ -411,6 +411,18 @@ namespace GridKit return 0; } + template + template + void Ieeet1::readSignal(const ScalarT& fallback) + { + constexpr auto index = static_cast(variable); + const auto& port = ports_.in.template port(); + + ws_.getData()[index] = port.readOrDefault(fallback); + ws_indices_[index] = port ? port.signalVariableIndex() + : INVALID_INDEX; + } + /** * @brief Residual evaluation * @@ -418,47 +430,19 @@ 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); + using Input = Ieeet1SignalInputs; + using Variable = Ieeet1ExternalVariables; auto* ws = ws_.getData(); // Attached signals are read live; unattached ones keep the latched value. - 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(); - } + readSignal(omega_set_); + readSignal(vref_set_); + readSignal(vs_set_); + readSignal(vuel_set_); + readSignal(voel_set_); // Bus voltages auto* wb = wb_.getData(); diff --git a/GridKit/Model/PhasorDynamics/SystemModelImpl.hpp b/GridKit/Model/PhasorDynamics/SystemModelImpl.hpp index cccdc3f271..cc41c11419 100644 --- a/GridKit/Model/PhasorDynamics/SystemModelImpl.hpp +++ b/GridKit/Model/PhasorDynamics/SystemModelImpl.hpp @@ -1,4 +1,5 @@ #include +#include #include #include @@ -510,7 +511,7 @@ namespace GridKit const auto component_checks = component->verify(); for (const auto& error : component_checks.errors()) { - checks.fail("component " + std::to_string(component->getGridKitComponentID()) + ": " + error); + checks.fail(std::format("component {}: {}", component->getGridKitComponentID(), error)); } } From d2a45f24b13810bde7a8bebf06539d10dbadf2a1 Mon Sep 17 00:00:00 2001 From: lukelowry Date: Sat, 10 Oct 2026 21:02:37 +0000 Subject: [PATCH 06/11] Apply pre-commit fixes --- .../SignalSource/FunctionSignalSource.hpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/GridKit/Model/PhasorDynamics/SignalSource/FunctionSignalSource.hpp b/GridKit/Model/PhasorDynamics/SignalSource/FunctionSignalSource.hpp index 2c36c03077..3df58dbdf5 100644 --- a/GridKit/Model/PhasorDynamics/SignalSource/FunctionSignalSource.hpp +++ b/GridKit/Model/PhasorDynamics/SignalSource/FunctionSignalSource.hpp @@ -46,14 +46,14 @@ namespace GridKit FunctionSignalSource(const ModelDataT& data); ~FunctionSignalSource(); - int setGridKitComponentID(IdxT) override final; - int allocate() 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; + int initialize() override final; + int tagDifferentiable() override final; + int setAbsoluteTolerance(RealT) override final; + int evaluateResidual() override final; + int evaluateJacobian() override final; void updateTime(RealT t, RealT a) override; From cb6b63caf6696d673801682019ce0ecef121dea0 Mon Sep 17 00:00:00 2001 From: lukelowry Date: Sun, 11 Oct 2026 09:10:01 -0500 Subject: [PATCH 07/11] reduce scope --- GridKit/Model/ConfigurationChecks.hpp | 20 +- GridKit/Model/ParameterReader.hpp | 61 +---- .../Model/PhasorDynamics/Branch/Branch.hpp | 20 +- .../PhasorDynamics/Branch/BranchImpl.hpp | 57 +++-- .../BusSignalVoltageIn/BusSignalVoltageIn.hpp | 15 +- .../BusSignalVoltageInImpl.hpp | 38 +++- .../Bus/BusSignalVoltageIn/README.md | 3 +- .../BusSignalVoltageOut.hpp | 17 +- .../BusSignalVoltageOutImpl.hpp | 40 ++-- .../Bus/BusSignalVoltageOut/README.md | 3 +- GridKit/Model/PhasorDynamics/Component.hpp | 15 ++ .../PhasorDynamics/Controller/REECB/Reecb.hpp | 25 ++- .../Controller/REECB/ReecbImpl.hpp | 202 ++++++++++++----- .../PhasorDynamics/Controller/REPCA/Repca.hpp | 17 +- .../Controller/REPCA/RepcaImpl.hpp | 208 +++++++++++------- .../PhasorDynamics/Converter/REGCA/Regca.hpp | 17 +- .../Converter/REGCA/RegcaImpl.hpp | 125 ++++++++--- .../PhasorDynamics/Exciter/ESDC1A/Esdc1a.hpp | 15 +- .../Exciter/ESDC1A/Esdc1aImpl.hpp | 90 ++++---- .../PhasorDynamics/Exciter/IEEET1/Ieeet1.hpp | 19 +- .../Exciter/IEEET1/Ieeet1Impl.hpp | 172 ++++++++++----- .../Exciter/SEXS-PTI/SexsPti.hpp | 17 +- .../Exciter/SEXS-PTI/SexsPtiImpl.hpp | 165 ++++++++------ .../Governor/GASTPTI/GastPti.hpp | 45 ++-- .../Governor/GASTPTI/GastPtiImpl.hpp | 137 ++++++++---- .../PhasorDynamics/Governor/HYGOV/Hygov.hpp | 19 +- .../Governor/HYGOV/HygovImpl.hpp | 181 +++++++++------ .../Governor/Tgov1/CMakeLists.txt | 3 - .../PhasorDynamics/Governor/Tgov1/Tgov1.hpp | 15 +- .../Governor/Tgov1/Tgov1Impl.hpp | 75 +++++-- .../PhasorDynamics/Load/LoadZ/LoadZImpl.hpp | 13 +- .../Load/LoadZIP/LoadZIPImpl.hpp | 25 ++- GridKit/Model/PhasorDynamics/SignalIn.hpp | 10 - .../SignalSource/CMakeLists.txt | 2 - .../SignalSource/ConstantSignalSource.hpp | 15 +- .../SignalSource/ConstantSignalSourceImpl.hpp | 17 +- .../SignalSource/FunctionSignalSource.hpp | 15 +- .../Stabilizer/IEEEST/Ieeest.hpp | 15 +- .../Stabilizer/IEEEST/IeeestImpl.hpp | 122 +++++++--- .../SynchronousMachine/GENROU/Genrou.hpp | 13 +- .../SynchronousMachine/GENROU/GenrouImpl.hpp | 131 ++++++++--- .../SynchronousMachine/GENSAL/Gensal.hpp | 13 +- .../SynchronousMachine/GENSAL/GensalImpl.hpp | 113 ++++++++-- .../GenClassical/GenClassical.hpp | 13 +- .../GenClassical/GenClassicalImpl.hpp | 59 +++-- GridKit/Model/PhasorDynamics/SystemModel.hpp | 5 +- .../Model/PhasorDynamics/SystemModelImpl.hpp | 9 +- GridKit/Testing/Testing.hpp | 16 -- .../BusSignalVoltageInTests.hpp | 14 +- .../BusSignalVoltageOutTests.hpp | 10 +- .../PhasorDynamics/ControllerReecbTests.hpp | 14 +- .../PhasorDynamics/ControllerRepcaTests.hpp | 15 +- .../PhasorDynamics/ConverterRegcaTests.hpp | 21 +- .../PhasorDynamics/ExciterEsdc1aTests.hpp | 1 + .../PhasorDynamics/ExciterSexsPtiTests.hpp | 3 +- .../PhasorDynamics/GovernorGastPtiTests.hpp | 30 +-- .../PhasorDynamics/GovernorHygovTests.hpp | 30 +-- 57 files changed, 1637 insertions(+), 943 deletions(-) diff --git a/GridKit/Model/ConfigurationChecks.hpp b/GridKit/Model/ConfigurationChecks.hpp index aded652e3c..f4b874b5fc 100644 --- a/GridKit/Model/ConfigurationChecks.hpp +++ b/GridKit/Model/ConfigurationChecks.hpp @@ -6,8 +6,9 @@ #pragma once +#include #include -#include +#include #include namespace GridKit @@ -24,18 +25,26 @@ namespace GridKit class ConfigurationChecks { public: + ConfigurationChecks() = default; + + /// @param[in] model Model name that prefixes every recorded message. + explicit ConfigurationChecks(std::string_view model) + : prefix_(std::format("{}: ", model)) + { + } + /// Record a problem. - void fail(std::string message) + void fail(std::string_view message) { - errors_.push_back(std::move(message)); + errors_.push_back(prefix_ + std::string(message)); } /// Record the message when the condition does not hold. - void check(bool condition, std::string message) + void check(bool condition, std::string_view message) { if (!condition) { - fail(std::move(message)); + fail(message); } } @@ -52,6 +61,7 @@ namespace GridKit } private: + std::string prefix_; std::vector errors_; }; } // namespace Model diff --git a/GridKit/Model/ParameterReader.hpp b/GridKit/Model/ParameterReader.hpp index c55826e729..47d3f771c0 100644 --- a/GridKit/Model/ParameterReader.hpp +++ b/GridKit/Model/ParameterReader.hpp @@ -7,9 +7,11 @@ #pragma once #include +#include #include #include #include +#include #include #include @@ -26,8 +28,9 @@ namespace GridKit * 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. + * parameter. An omitted parameter leaves the target untouched, so the + * target's initial value is the default. Real parameters accept integer + * values. * * @tparam ModelDataT A model data container satisfying * PhasorDynamics::ModelData. @@ -88,22 +91,6 @@ namespace GridKit return true; } - /** - * @brief Load a real parameter that must be provided. - * - * @param[in] key Parameter to look up. - * @param[out] target Stores the finite numeric value. - * @return true, since a missing parameter is rejected. - */ - bool requireReal(ParametersT key, RealT& target) const - { - if (!parameters_.contains(key)) - { - reject(key, "is required"); - } - return loadReal(key, target); - } - /** * @brief Load an optional boolean switch parameter. * @@ -128,37 +115,6 @@ namespace GridKit reject(key, "must be boolean"); } - /** - * @brief Load a switch parameter that must be provided. A boolean or an - * integer 0/1 value is accepted. - * - * @param[in] key Parameter to look up. - * @param[out] target Stores the switch value. - * @return true, since a missing parameter is rejected. - */ - bool requireSwitch(ParametersT key, bool& target) const - { - if (!parameters_.contains(key)) - { - reject(key, "is required"); - } - - const auto& value = parameters_.at(key); - if (const auto* bool_value = std::get_if(&value)) - { - target = *bool_value; - return true; - } - if (const auto* index_value = std::get_if(&value); - index_value && (*index_value == 0 || *index_value == 1)) - { - target = (*index_value == 1); - return true; - } - - reject(key, "must be bool or 0/1"); - } - /** * @brief Load an optional integer selector parameter. * @@ -185,11 +141,10 @@ namespace GridKit private: /// Reject the model data with a message naming the model and parameter. - [[noreturn]] void reject(ParametersT key, const char* reason) const + [[noreturn]] void reject(ParametersT key, std::string_view reason) const { - throw std::invalid_argument(std::string(model_) + ": parameter '" - + std::string(magic_enum::enum_name(key)) - + "' " + reason); + throw std::invalid_argument( + std::format("{}: parameter '{}' {}", model_, magic_enum::enum_name(key), reason)); } const std::map>& parameters_; diff --git a/GridKit/Model/PhasorDynamics/Branch/Branch.hpp b/GridKit/Model/PhasorDynamics/Branch/Branch.hpp index 7393807b5a..69c50eab36 100644 --- a/GridKit/Model/PhasorDynamics/Branch/Branch.hpp +++ b/GridKit/Model/PhasorDynamics/Branch/Branch.hpp @@ -65,13 +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 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; int setInService(bool in_service); @@ -120,6 +121,9 @@ namespace GridKit void setDerivedParams(); void terminalCurrent1(ScalarT& Ir, ScalarT& Ii); void terminalCurrent2(ScalarT& Ir, ScalarT& Ii); + bool readRealParameter(const ModelDataT& data, + typename ModelDataT::Parameters parameter, + RealT& target); static __attribute__((always_inline)) inline void addAdmittanceContribution(const RealT G, const RealT B, @@ -207,6 +211,8 @@ 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 186488669c..0cd41a6867 100644 --- a/GridKit/Model/PhasorDynamics/Branch/BranchImpl.hpp +++ b/GridKit/Model/PhasorDynamics/Branch/BranchImpl.hpp @@ -7,8 +7,10 @@ * */ -#include -#include +#include + +#include + #include #include #include @@ -154,7 +156,9 @@ namespace GridKit template Model::ConfigurationChecks Branch::verify() const { - Model::ConfigurationChecks checks; + Model::ConfigurationChecks checks("Branch"); + + checks.check(parameter_error_count_ == 0, "model data has invalid parameters"); checks.check(bus1_ != nullptr, "bus1 pointer is null"); checks.check(bus2_ != nullptr, "bus2 pointer is null"); @@ -356,16 +360,14 @@ namespace GridKit using Parameter = typename ModelDataT::Parameters; using Buses = typename ModelDataT::Buses; - Model::ParameterReader reader(data, "Branch"); - - reader.loadReal(Parameter::R, R_); - reader.loadReal(Parameter::X, X_); - reader.loadReal(Parameter::G, G_); - reader.loadReal(Parameter::B, B_); - reader.loadReal(Parameter::Gmag, Gmag_); - reader.loadReal(Parameter::Bmag, Bmag_); - reader.loadReal(Parameter::tap, tap_); - reader.loadReal(Parameter::phase, phase_); + 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_); if (data.buses.contains(Buses::bus1)) { @@ -378,6 +380,35 @@ 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/Bus/BusSignalVoltageIn/BusSignalVoltageIn.hpp b/GridKit/Model/PhasorDynamics/Bus/BusSignalVoltageIn/BusSignalVoltageIn.hpp index 1580a8ab43..ac0f7ee800 100644 --- a/GridKit/Model/PhasorDynamics/Bus/BusSignalVoltageIn/BusSignalVoltageIn.hpp +++ b/GridKit/Model/PhasorDynamics/Bus/BusSignalVoltageIn/BusSignalVoltageIn.hpp @@ -68,14 +68,15 @@ namespace GridKit BusSignalVoltageIn(const ModelDataT& data); virtual ~BusSignalVoltageIn(); - virtual int setBusID(IdxT) override final; - virtual int allocate() override final; + virtual int setBusID(IdxT) override final; + virtual int allocate() override final; + virtual int tagDifferentiable() override final; + virtual int setAbsoluteTolerance(RealT rel_tol) override final; + virtual int initialize() override final; + virtual int evaluateResidual() override final; + virtual int evaluateJacobian() override final; + virtual Model::ConfigurationChecks verify() const override final; - virtual int tagDifferentiable() override final; - virtual int setAbsoluteTolerance(RealT rel_tol) override final; - virtual int initialize() override final; - virtual int evaluateResidual() override final; - virtual int evaluateJacobian() override final; virtual BusTypeT BusType() const override final { diff --git a/GridKit/Model/PhasorDynamics/Bus/BusSignalVoltageIn/BusSignalVoltageInImpl.hpp b/GridKit/Model/PhasorDynamics/Bus/BusSignalVoltageIn/BusSignalVoltageInImpl.hpp index 98d753f0ef..ac74072e3b 100644 --- a/GridKit/Model/PhasorDynamics/Bus/BusSignalVoltageIn/BusSignalVoltageInImpl.hpp +++ b/GridKit/Model/PhasorDynamics/Bus/BusSignalVoltageIn/BusSignalVoltageInImpl.hpp @@ -5,6 +5,7 @@ */ #include +#include #include #include @@ -106,33 +107,46 @@ namespace GridKit } /** - * @brief Collect missing voltage/current inlet and unlinked outlet errors. + * @brief Check that the voltage inlets are connected and linked, and + * that connected outlets are linked. + * + * Both voltage inlets `vr` and `vi` are mandatory, since the bus has no + * voltage of its own and no default value is allowed. + * + * @return The configuration checks; passed() when valid. */ template Model::ConfigurationChecks BusSignalVoltageIn::verify() const { - Model::ConfigurationChecks checks; - auto check_input = [&](const char* name) + Model::ConfigurationChecks checks("BusSignalVoltageIn"); + + auto check_input = [&](const char* name) { const auto& port = ports_.in.template port(); - checks.check(port.connected(), std::string("BusSignalVoltageIn: ") + name + " signal inlet is not connected"); - if (port.connected()) + if (!port.connected()) + { + checks.fail(std::format("{} signal inlet is not connected; a default voltage is not allowed", name)); + } + else if (!port.linked()) { - checks.check(port.linked(), std::string("BusSignalVoltageIn: ") + name + " signal attached with no linked source"); + checks.fail(std::format("{} signal attached with no linked source", name)); } }; + auto check_output = [&](const char* name) { const auto& port = ports_.out.template port(); - if (port.connected()) + if (port.connected() && !port.linked()) { - checks.check(port.linked(), std::string("BusSignalVoltageIn: ") + name + " signal attached but not linked; connect before allocate()"); + checks.fail(std::format("{} signal attached but not linked; connect signal ports before allocate()", name)); } }; - check_input.template operator()("vr"); - check_input.template operator()("vi"); - check_output.template operator()("ir"); - check_output.template operator()("ii"); + + check_input.template operator()("Vr"); + check_input.template operator()("Vi"); + check_output.template operator()("Ir"); + check_output.template operator()("Ii"); + return checks; } diff --git a/GridKit/Model/PhasorDynamics/Bus/BusSignalVoltageIn/README.md b/GridKit/Model/PhasorDynamics/Bus/BusSignalVoltageIn/README.md index c35d111d2e..670e40c77e 100644 --- a/GridKit/Model/PhasorDynamics/Bus/BusSignalVoltageIn/README.md +++ b/GridKit/Model/PhasorDynamics/Bus/BusSignalVoltageIn/README.md @@ -9,7 +9,8 @@ The bus stores no voltage of its own and has no unknowns and no equations. - Signal ports must be connected before `allocate()` is called. The signal outlets are linked to the current sums in `allocate()`. -- Both voltage inlets are mandatory. `verify()` returns configuration errors if a voltage inlet is not connected or not linked, or if a +- Both voltage inlets are mandatory. `verify()` reports an error if a + voltage inlet is not connected or not linked, or if a connected outlet is not linked. Reading the voltage through an unlinked inlet throws. No default voltage is ever used. - The current sums are complete only after all attached components have diff --git a/GridKit/Model/PhasorDynamics/Bus/BusSignalVoltageOut/BusSignalVoltageOut.hpp b/GridKit/Model/PhasorDynamics/Bus/BusSignalVoltageOut/BusSignalVoltageOut.hpp index f6f47275b5..553fb73b65 100644 --- a/GridKit/Model/PhasorDynamics/Bus/BusSignalVoltageOut/BusSignalVoltageOut.hpp +++ b/GridKit/Model/PhasorDynamics/Bus/BusSignalVoltageOut/BusSignalVoltageOut.hpp @@ -23,7 +23,7 @@ namespace GridKit * - publishes _Vr_ and _Vi_ on signal outlets `vr` and `vi`, and * - sets its residuals f[0] and f[1] to the current injections read from * signal inlets `ir` and `ii`, respectively. Both inlets are - * mandatory: verify() throws if either is not connected to a linked + * mandatory: verify() reports an error if either is not connected to a linked * signal, and no default value is ever used. * * Components attached to the bus directly (without signals) keep adding @@ -65,14 +65,15 @@ namespace GridKit BusSignalVoltageOut(const ModelDataT& data); virtual ~BusSignalVoltageOut(); - virtual int setBusID(IdxT) override final; - virtual int allocate() override final; + virtual int setBusID(IdxT) override final; + virtual int allocate() override final; + virtual int tagDifferentiable() override final; + virtual int setAbsoluteTolerance(RealT rel_tol) override final; + virtual int initialize() override final; + virtual int evaluateResidual() override final; + virtual int evaluateJacobian() override final; + virtual Model::ConfigurationChecks verify() const override final; - virtual int tagDifferentiable() override final; - virtual int setAbsoluteTolerance(RealT rel_tol) override final; - virtual int initialize() override final; - virtual int evaluateResidual() override final; - virtual int evaluateJacobian() override final; virtual BusTypeT BusType() const override final { diff --git a/GridKit/Model/PhasorDynamics/Bus/BusSignalVoltageOut/BusSignalVoltageOutImpl.hpp b/GridKit/Model/PhasorDynamics/Bus/BusSignalVoltageOut/BusSignalVoltageOutImpl.hpp index 46fd6e1c95..d1e3e5b88c 100644 --- a/GridKit/Model/PhasorDynamics/Bus/BusSignalVoltageOut/BusSignalVoltageOutImpl.hpp +++ b/GridKit/Model/PhasorDynamics/Bus/BusSignalVoltageOut/BusSignalVoltageOutImpl.hpp @@ -5,6 +5,7 @@ */ #include +#include #include #include @@ -125,33 +126,46 @@ namespace GridKit } /** - * @brief Collect missing voltage/current inlet and unlinked outlet errors. + * @brief Check that the current inlets are connected and linked, and + * that connected outlets are linked. + * + * Both current inlets `ir` and `ii` are mandatory, since the residuals + * are set from them and no default value is allowed. + * + * @return The configuration checks; passed() when valid. */ template Model::ConfigurationChecks BusSignalVoltageOut::verify() const { - Model::ConfigurationChecks checks; - auto check_input = [&](const char* name) + Model::ConfigurationChecks checks("BusSignalVoltageOut"); + + auto check_input = [&](const char* name) { const auto& port = ports_.in.template port(); - checks.check(port.connected(), std::string("BusSignalVoltageOut: ") + name + " signal inlet is not connected"); - if (port.connected()) + if (!port.connected()) + { + checks.fail(std::format("{} signal inlet is not connected; a default current is not allowed", name)); + } + else if (!port.linked()) { - checks.check(port.linked(), std::string("BusSignalVoltageOut: ") + name + " signal attached with no linked source"); + checks.fail(std::format("{} signal attached with no linked source", name)); } }; + auto check_output = [&](const char* name) { const auto& port = ports_.out.template port(); - if (port.connected()) + if (port.connected() && !port.linked()) { - checks.check(port.linked(), std::string("BusSignalVoltageOut: ") + name + " signal attached but not linked; connect before allocate()"); + checks.fail(std::format("{} signal attached but not linked; connect signal ports before allocate()", name)); } }; - check_input.template operator()("ir"); - check_input.template operator()("ii"); - check_output.template operator()("vr"); - check_output.template operator()("vi"); + + check_input.template operator()("Ir"); + check_input.template operator()("Ii"); + check_output.template operator()("Vr"); + check_output.template operator()("Vi"); + return checks; } @@ -221,7 +235,7 @@ namespace GridKit * * Residuals f[0] and f[1] are set to the values read from the `ir` and * `ii` signal inlets, respectively. Both inlets are mandatory; verify() - * throws if either is not connected to a linked signal, and no default + * reports an error if either is not connected to a linked signal, and no default * value is used here. Components attached to the bus add their currents * afterwards. * diff --git a/GridKit/Model/PhasorDynamics/Bus/BusSignalVoltageOut/README.md b/GridKit/Model/PhasorDynamics/Bus/BusSignalVoltageOut/README.md index 754147eb08..83642e8d0d 100644 --- a/GridKit/Model/PhasorDynamics/Bus/BusSignalVoltageOut/README.md +++ b/GridKit/Model/PhasorDynamics/Bus/BusSignalVoltageOut/README.md @@ -11,7 +11,8 @@ the bus residual is evaluated. - Signal ports must be connected before `allocate()` is called. The signal outlets are linked to the bus voltage variables and their system indices in `allocate()`. -- Both current inlets are mandatory. `verify()` returns configuration errors if a current inlet is not connected or not linked, or if a +- Both current inlets are mandatory. `verify()` reports an error if a + current inlet is not connected or not linked, or if a connected outlet is not linked. No default current is ever used. - Current entering the bus has positive sign. diff --git a/GridKit/Model/PhasorDynamics/Component.hpp b/GridKit/Model/PhasorDynamics/Component.hpp index 265e55d449..5c70cf2be6 100644 --- a/GridKit/Model/PhasorDynamics/Component.hpp +++ b/GridKit/Model/PhasorDynamics/Component.hpp @@ -282,6 +282,21 @@ namespace GridKit } protected: + /** + * @brief Log every problem verify() reports. + * + * @return true when the configuration has no problems. + */ + bool verifyAndLog() const + { + const auto checks = verify(); + for (const auto& error : checks.errors()) + { + Log::error() << error << '\n'; + } + return checks.passed(); + } + void setComponentBase(RealT va_component_base) { va_component_base_ = va_component_base; diff --git a/GridKit/Model/PhasorDynamics/Controller/REECB/Reecb.hpp b/GridKit/Model/PhasorDynamics/Controller/REECB/Reecb.hpp index 438a9de953..20e9e618be 100644 --- a/GridKit/Model/PhasorDynamics/Controller/REECB/Reecb.hpp +++ b/GridKit/Model/PhasorDynamics/Controller/REECB/Reecb.hpp @@ -114,14 +114,15 @@ namespace GridKit Reecb(BusT* bus, const ModelDataT& data); ~Reecb(); - int setGridKitComponentID(IdxT component_id) override final; - int allocate() override final; + int setGridKitComponentID(IdxT component_id) override final; + int allocate() 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; + 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,6 +174,15 @@ 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(); @@ -223,6 +233,7 @@ 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 90e9ecfb26..bec915f642 100644 --- a/GridKit/Model/PhasorDynamics/Controller/REECB/ReecbImpl.hpp +++ b/GridKit/Model/PhasorDynamics/Controller/REECB/ReecbImpl.hpp @@ -9,13 +9,9 @@ #include #include #include -#include -#include -#include +#include #include -#include -#include #include #include #include @@ -168,7 +164,9 @@ namespace GridKit template Model::ConfigurationChecks Reecb::verify() const { - Model::ConfigurationChecks checks; + Model::ConfigurationChecks checks("Reecb"); + + checks.check(parameter_error_count_ == 0, "model data has invalid parameters"); checks.check(bus_ != nullptr, "terminal bus is required"); @@ -254,16 +252,20 @@ namespace GridKit checks.check(std::isfinite(Imax_) && Imax_ > ZERO, "Imax must be finite and positive"); - 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"); + auto check_optional_signal = [&](const char* name) + { + if (ports_.in.template port().connected() + && !ports_.in.template port().linked()) + { + checks.fail(std::format("{} signal attached with no linked source", name)); + } + }; + + 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"); return checks; } @@ -294,13 +296,9 @@ namespace GridKit return 1; } - const auto checks = verify(); - for (const auto& error : checks.errors()) - { - Log::error() << "Reecb: " << error << '\n'; - } - if (!checks.passed()) + if (!this->verifyAndLog()) { + Log::error() << "Reecb: cannot initialize with invalid configuration\n"; return 1; } @@ -1178,6 +1176,80 @@ 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 * @@ -1196,11 +1268,17 @@ namespace GridKit { if (!std::isfinite(value)) { - throw std::invalid_argument(std::string("Reecb: ") + name + " must be finite"); + Log::error() << "Reecb: " << name << " must be finite\n"; + ++parameter_error_count_; + value = TIME_CONSTANT_MINIMUM; + return false; } if (value < ZERO) { - throw std::invalid_argument(std::string("Reecb: ") + name + " must be non-negative"); + Log::error() << "Reecb: " << name << " must be non-negative\n"; + ++parameter_error_count_; + value = TIME_CONSTANT_MINIMUM; + return false; } const bool raised = value < TIME_CONSTANT_MINIMUM; @@ -1220,44 +1298,54 @@ namespace GridKit void Reecb::initializeParameters(const ModelDataT& data) { using Params = typename ModelDataT::Parameters; - Vref0_given_ = data.parameters.contains(Params::Vref0); - Model::ParameterReader reader(data, "Reecb"); + parameter_error_count_ = 0; + Vref0_given_ = false; - RealT mva{}; - if (reader.requireReal(Params::mva, mva)) + if (data.parameters.contains(Params::mva)) { + RealT mva{}; + loadRealParameter(data, Params::mva, mva, "mva"); this->setComponentBase(mva * static_cast(1.0e6)); } - 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_); + 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"); setDerivedParameters(); } diff --git a/GridKit/Model/PhasorDynamics/Controller/REPCA/Repca.hpp b/GridKit/Model/PhasorDynamics/Controller/REPCA/Repca.hpp index b90d0516e7..78a7bae669 100644 --- a/GridKit/Model/PhasorDynamics/Controller/REPCA/Repca.hpp +++ b/GridKit/Model/PhasorDynamics/Controller/REPCA/Repca.hpp @@ -119,14 +119,15 @@ namespace GridKit Repca(BusT* bus, const ModelDataT& data); ~Repca(); - int setGridKitComponentID(IdxT component_id) override final; - int allocate() override final; + int setGridKitComponentID(IdxT component_id) override final; + int allocate() 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; + 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,6 +198,8 @@ 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 0131e08755..e61dc0d579 100644 --- a/GridKit/Model/PhasorDynamics/Controller/REPCA/RepcaImpl.hpp +++ b/GridKit/Model/PhasorDynamics/Controller/REPCA/RepcaImpl.hpp @@ -7,16 +7,10 @@ #pragma once #include -#include #include #include -#include -#include -#include #include -#include -#include #include #include #include @@ -154,7 +148,9 @@ namespace GridKit template Model::ConfigurationChecks Repca::verify() const { - Model::ConfigurationChecks checks; + Model::ConfigurationChecks checks("Repca"); + + checks.check(parameter_error_count_ == 0, "model data has invalid parameters"); checks.check(bus_ != nullptr, "regulated bus is required"); @@ -191,31 +187,37 @@ namespace GridKit "femin <= 0 <= femax is required"); checks.check(Pmin_ <= Pmax_, "Pmin must be less than or equal to Pmax"); - const auto ir_port = ports_.in.template port(); - - checks.check(ir_port.connected(), "ir signal is required"); - - checks.check(!ir_port.connected() || ir_port.linked(), "ir signal attached with no linked source"); - const auto ii_port = ports_.in.template port(); - checks.check(ii_port.connected(), "ii signal is required"); - checks.check(!ii_port.connected() || ii_port.linked(), "ii signal attached with no linked source"); - const auto p_port = ports_.in.template port(); - checks.check(p_port.connected(), "p signal is required"); - checks.check(!p_port.connected() || p_port.linked(), "p signal attached with no linked source"); - const auto q_port = ports_.in.template port(); - checks.check(q_port.connected(), "q signal is required"); - checks.check(!q_port.connected() || q_port.linked(), "q signal attached with no linked source"); - - const auto vref_port = ports_.in.template port(); - checks.check(!vref_port.connected() || vref_port.linked(), "vref signal attached with no linked source"); - const auto pref_port = ports_.in.template port(); - checks.check(!pref_port.connected() || pref_port.linked(), "pref signal attached with no linked source"); - const auto qref_port = ports_.in.template port(); - checks.check(!qref_port.connected() || qref_port.linked(), "qref signal attached with no linked source"); - const auto freq_port = ports_.in.template port(); - checks.check(!freq_port.connected() || freq_port.linked(), "freq signal attached with no linked source"); - const auto freqref_port = ports_.in.template port(); - checks.check(!freqref_port.connected() || freqref_port.linked(), "freqref signal attached with no linked source"); + auto check_required_signal = [&](const char* name) + { + if (!ports_.in.template port().connected()) + { + checks.fail(std::format("{} signal is required", name)); + } + else if (!ports_.in.template port().linked()) + { + checks.fail(std::format("{} signal attached with no linked source", name)); + } + }; + + 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()) + { + checks.fail(std::format("{} signal attached with no linked source", name)); + } + }; + + 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 checks; } @@ -271,13 +273,9 @@ namespace GridKit return 1; } - const auto checks = verify(); - for (const auto& error : checks.errors()) - { - Log::error() << "Repca: " << error << '\n'; - } - if (!checks.passed()) + if (!this->verifyAndLog()) { + Log::error() << "Repca: cannot initialize with invalid configuration\n"; return 1; } @@ -840,39 +838,92 @@ namespace GridKit { using Params = typename ModelDataT::Parameters; - Model::ParameterReader reader(data, "Repca"); - - reader.loadReal(Params::mva, mva_base_); - reader.loadSwitch(Params::VcompFlag, VcompFlag_); - reader.loadSwitch(Params::RefFlag, RefFlag_); - reader.loadSwitch(Params::Freqflag, Freqflag_); - reader.loadReal(Params::Tfltr, Tfltr_); - reader.loadReal(Params::Vfrz, Vfrz_); - reader.loadReal(Params::Rc, Rc_); - reader.loadReal(Params::Xc, Xc_); - reader.loadReal(Params::Kc, Kc_); - reader.loadReal(Params::dbdlow, dbdlow_); - reader.loadReal(Params::dbdupper, dbdupper_); - reader.loadReal(Params::emax, emax_); - reader.loadReal(Params::emin, emin_); - reader.loadReal(Params::Kp, Kp_); - reader.loadReal(Params::Ki, Ki_); - reader.loadReal(Params::Qmax, Qmax_); - reader.loadReal(Params::Qmin, Qmin_); - reader.loadReal(Params::Tft, Tft_); - reader.loadReal(Params::Tfv, Tfv_); - reader.loadReal(Params::Tp, Tp_); - reader.loadReal(Params::fdbd1, fdbd1_); - reader.loadReal(Params::fdbd2, fdbd2_); - reader.loadReal(Params::Ddn, Ddn_); - reader.loadReal(Params::Dup, Dup_); - reader.loadReal(Params::femax, femax_); - reader.loadReal(Params::femin, femin_); - reader.loadReal(Params::Kpg, Kpg_); - reader.loadReal(Params::Kig, Kig_); - reader.loadReal(Params::Pmax, Pmax_); - reader.loadReal(Params::Pmin, Pmin_); - reader.loadReal(Params::Tlag, Tlag_); + 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"); setDerivedParameters(); } @@ -922,15 +973,20 @@ 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. - const std::array, 5> lags{ - {{Tfltr_, "Tfltr"}, {Tft_, "Tft"}, {Tfv_, "Tfv"}, {Tp_, "Tp"}, {Tlag_, "Tlag"}}}; - for (const auto& [value, name] : lags) + auto check_non_negative = [&](RealT value, const char* name) { if (value < ZERO) { - throw std::invalid_argument(std::format("Repca: {} must be non-negative", name)); + 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"); 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 f3c9be9d77..61f23966d6 100644 --- a/GridKit/Model/PhasorDynamics/Converter/REGCA/Regca.hpp +++ b/GridKit/Model/PhasorDynamics/Converter/REGCA/Regca.hpp @@ -102,14 +102,15 @@ namespace GridKit Regca(BusT* bus, const ModelDataT& data); ~Regca(); - int setGridKitComponentID(IdxT component_id) override final; - int allocate() override final; + int setGridKitComponentID(IdxT component_id) override final; + int allocate() 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; + 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,6 +219,8 @@ 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 a4a63da2df..43d5b6a144 100644 --- a/GridKit/Model/PhasorDynamics/Converter/REGCA/RegcaImpl.hpp +++ b/GridKit/Model/PhasorDynamics/Converter/REGCA/RegcaImpl.hpp @@ -11,8 +11,6 @@ #include #include -#include -#include #include #include #include @@ -172,25 +170,78 @@ namespace GridKit { using Params = typename ModelDataT::Parameters; - Model::ParameterReader reader(data, "Regca"); - - reader.requireReal(Params::p0, p0_); - reader.requireReal(Params::q0, q0_); - reader.requireReal(Params::mva, mva_base_); - reader.requireReal(Params::Tg, Tg_); - reader.requireReal(Params::TM, TM_); - reader.requireReal(Params::Rqmax, Rqmax_); - reader.requireReal(Params::Rqmin, Rqmin_); - reader.requireReal(Params::Rpmax, Rpmax_); - reader.requireSwitch(Params::sL, sL_); - reader.requireReal(Params::IL1, IL1_); - reader.requireReal(Params::VL0, VL0_); - reader.requireReal(Params::VL1, VL1_); - reader.requireReal(Params::VA0, VA0_); - reader.requireReal(Params::VA1, VA1_); - reader.requireReal(Params::Vhvmax, Vhvmax_); - - reader.loadReal(Params::Khv, Khv_); + 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"); + } setDerivedParameters(); } @@ -321,25 +372,39 @@ namespace GridKit template Model::ConfigurationChecks Regca::verify() const { - Model::ConfigurationChecks checks; + Model::ConfigurationChecks checks("Regca"); - checks.check(bus_ != nullptr, "bus pointer is null"); + checks.check(parameter_error_count_ == 0, "model data has invalid parameters"); + + if (bus_ == nullptr) + { + checks.fail("bus pointer is null"); + } 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(std::isfinite(Khv_) && Khv_ >= ZERO, + "Khv must be finite and non-negative"); 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"); - 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()) + { + checks.fail("ipcmd signal attached with no linked source"); + } + } - 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"); + if (ports_.in.template port()) + { + if (!ports_.in.template port().linked()) + { + checks.fail("iqcmd signal attached with no linked source"); + } + } return checks; } diff --git a/GridKit/Model/PhasorDynamics/Exciter/ESDC1A/Esdc1a.hpp b/GridKit/Model/PhasorDynamics/Exciter/ESDC1A/Esdc1a.hpp index 1323e972d3..7e9fba6328 100644 --- a/GridKit/Model/PhasorDynamics/Exciter/ESDC1A/Esdc1a.hpp +++ b/GridKit/Model/PhasorDynamics/Exciter/ESDC1A/Esdc1a.hpp @@ -97,14 +97,15 @@ namespace GridKit Esdc1a(BusT* bus, const ModelDataT& data); ~Esdc1a(); - int setGridKitComponentID(IdxT component_id) override final; - int allocate() override final; + int setGridKitComponentID(IdxT component_id) override final; + int allocate() 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; + Model::ConfigurationChecks verify() const override final; - int initialize() override final; - int tagDifferentiable() override final; - int setAbsoluteTolerance(RealT rel_tol) override final; - int evaluateResidual() override final; - int evaluateJacobian() override final; SignalPortsT& getPorts() { diff --git a/GridKit/Model/PhasorDynamics/Exciter/ESDC1A/Esdc1aImpl.hpp b/GridKit/Model/PhasorDynamics/Exciter/ESDC1A/Esdc1aImpl.hpp index f334058eb9..f6f5187556 100644 --- a/GridKit/Model/PhasorDynamics/Exciter/ESDC1A/Esdc1aImpl.hpp +++ b/GridKit/Model/PhasorDynamics/Exciter/ESDC1A/Esdc1aImpl.hpp @@ -7,12 +7,9 @@ #pragma once #include -#include #include -#include +#include #include -#include -#include #include #include @@ -142,19 +139,22 @@ namespace GridKit /** * @brief Validate the ESDC1A configuration * - * Checks parameter-loading errors, static parameter relationships, - * terminal-bus association, the required field-voltage output, and - * attached external signals. Seed feasibility is operating-point - * dependent and is checked by initialize(). + * Checks static parameter relationships, terminal-bus association, the + * required field-voltage output, and attached external signals. Seed + * feasibility is operating-point dependent and is checked by + * initialize(). * * @return The configuration checks; passed() when valid. */ template Model::ConfigurationChecks Esdc1a::verify() const { - Model::ConfigurationChecks checks; + Model::ConfigurationChecks checks("Esdc1a"); - checks.check(bus_ != nullptr, "bus pointer is null"); + if (bus_ == nullptr) + { + checks.fail("bus pointer is null"); + } checks.check(Ka_ > ZERO, "Ka must be positive"); checks.check(Vrmin_ <= Vrmax_, "Vrmin must be less than or equal to Vrmax"); @@ -177,20 +177,32 @@ namespace GridKit checks.check(sat_ordered, "E1/E2 and Se1/Se2 must be ordered consistently"); } - checks.check(ports_.out.template port().connected(), - "required efd output signal is not assigned"); + if (!ports_.out.template port().connected()) + { + checks.fail("required efd output signal is not assigned"); + } - checks.check(!Spdmlt_ || ports_.in.template port().connected(), - "speed signal is required when Spdmlt is enabled"); + if (Spdmlt_ && !ports_.in.template port().connected()) + { + checks.fail("speed signal is required when Spdmlt is enabled"); + } - 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"); + // 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()) + { + checks.fail(std::format("{} signal attached with no linked source", name)); + } + }; + + 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 checks; } @@ -229,18 +241,14 @@ namespace GridKit const auto VFE = static_cast(Esdc1aInternalVariables::VFE); const auto EFD = static_cast(Esdc1aInternalVariables::EFD); - const auto checks = verify(); - for (const auto& error : checks.errors()) - { - Log::error() << "Esdc1a: " << error << '\n'; - } - if (!checks.passed()) + bool ret = this->verifyAndLog(); + if (!ret) { + Log::error() << "Esdc1a: cannot initialize with invalid configuration\n"; return 1; } - bool ret = true; - auto* y = y_.getData(); + auto* y = y_.getData(); // The assigned efd node aliases this entry after allocate(). Its // seeded value remains untouched throughout initialization. @@ -618,9 +626,9 @@ namespace GridKit * @brief Read the parameters out of the model data * * 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$, a non-integer selector, or a negative - * lag is rejected with std::invalid_argument. Integer JSON values are + * documented in the model README when omitted. A non-numeric or + * non-finite real, a non-Boolean switch, a non-integer selector, or a + * negative lag throws std::invalid_argument. Integer JSON values are * accepted for real parameters. * * @param[in] data Parameters and monitored-variable selections. @@ -650,7 +658,6 @@ namespace GridKit reader.loadReal(Params::Se2, Se2_); reader.loadSelector(Params::UEL, UEL_); reader.loadSwitch(Params::exclim, exclim_); - setDerivedParameters(); } @@ -708,15 +715,20 @@ 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. - const std::array, 5> lags{ - {{Tr_, "Tr"}, {Ta_, "Ta"}, {Tb_, "Tb"}, {Te_, "Te"}, {Tf1_, "Tf1"}}}; - for (const auto& [value, name] : lags) + auto check_non_negative = [&](RealT value, const char* name) { if (value < ZERO) { - throw std::invalid_argument(std::string("Esdc1a: ") + name + " must be non-negative"); + throw std::invalid_argument( + std::format("Esdc1a: parameter '{}' must be non-negative", name)); } - } + }; + + check_non_negative(Tr_, "Tr"); + check_non_negative(Ta_, "Ta"); + check_non_negative(Tb_, "Tb"); + check_non_negative(Te_, "Te"); + check_non_negative(Tf1_, "Tf1"); if (Tr_ < TIME_CONSTANT_MINIMUM || Ta_ < TIME_CONSTANT_MINIMUM || Tb_ < TIME_CONSTANT_MINIMUM || Te_ < TIME_CONSTANT_MINIMUM diff --git a/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1.hpp b/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1.hpp index c0917cca55..66a4057626 100644 --- a/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1.hpp +++ b/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1.hpp @@ -85,14 +85,15 @@ namespace GridKit Ieeet1(BusT* bus, const ModelDataT& data); ~Ieeet1(); - int setGridKitComponentID(IdxT) override final; - int allocate() override final; + int setGridKitComponentID(IdxT) override final; + int allocate() 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; + 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() { @@ -154,10 +155,6 @@ namespace GridKit void initModelParams(const ModelDataT& data); void setDerivedParameters(); - /// Refresh one signal input and its global variable index. - template - void readSignal(const ScalarT& fallback); - /// Associate variable getter functions with enum values void initializeMonitor(); }; diff --git a/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1Impl.hpp b/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1Impl.hpp index b329ae00f8..798aa18e35 100644 --- a/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1Impl.hpp +++ b/GridKit/Model/PhasorDynamics/Exciter/IEEET1/Ieeet1Impl.hpp @@ -9,10 +9,9 @@ #include #include +#include #include -#include -#include #include #include #include @@ -130,7 +129,7 @@ namespace GridKit template Model::ConfigurationChecks Ieeet1::verify() const { - Model::ConfigurationChecks checks; + Model::ConfigurationChecks checks("Ieeet1"); checks.check(Ka_ > ZERO, "Ka must be positive"); checks.check(Vrmin_ <= Vrmax_, "Vrmin must be less than or equal to Vrmax"); @@ -151,16 +150,21 @@ namespace GridKit checks.check(sat_ordered, "E1/E2 and Se1/Se2 must be ordered consistently"); } - 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"); + auto check_attached_signal = + [&](const char* name) + { + auto port = ports_.in.template port(); + if (port.connected() && !port.linked()) + { + checks.fail(std::format("{} signal attached with no linked source", name)); + } + }; + + 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"); return checks; } @@ -192,13 +196,9 @@ namespace GridKit template int Ieeet1::initialize() { - const auto checks = verify(); - for (const auto& error : checks.errors()) - { - Log::error() << "Ieeet1: " << error << '\n'; - } - if (!checks.passed()) + if (!this->verifyAndLog()) { + Log::error() << "Ieeet1: cannot initialize with invalid configuration\n"; return 1; } @@ -219,10 +219,19 @@ namespace GridKit } // Setpoint members provide the defaults for unattached signals. - 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_); + 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_); uel_on_ = ZERO; if (ports_.in.template port()) @@ -411,18 +420,6 @@ namespace GridKit return 0; } - template - template - void Ieeet1::readSignal(const ScalarT& fallback) - { - constexpr auto index = static_cast(variable); - const auto& port = ports_.in.template port(); - - ws_.getData()[index] = port.readOrDefault(fallback); - ws_indices_[index] = port ? port.signalVariableIndex() - : INVALID_INDEX; - } - /** * @brief Residual evaluation * @@ -430,19 +427,32 @@ namespace GridKit template int Ieeet1::evaluateResidual() { - using Input = Ieeet1SignalInputs; - using Variable = Ieeet1ExternalVariables; - auto* ws = ws_.getData(); // Attached signals are read live; unattached ones keep the latched value. - std::fill(ws_indices_.begin(), ws_indices_.end(), INVALID_INDEX); - - readSignal(omega_set_); - readSignal(vref_set_); - readSignal(vs_set_); - readSignal(vuel_set_); - readSignal(voel_set_); + 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_); // Bus voltages auto* wb = wb_.getData(); @@ -468,22 +478,62 @@ namespace GridKit { using Parameter = typename ModelDataT::Parameters; - Model::ParameterReader reader(data, "Ieeet1"); - - reader.loadReal(Parameter::Tr, Tr_); - reader.loadReal(Parameter::Ka, Ka_); - reader.loadReal(Parameter::Ta, Ta_); - reader.loadReal(Parameter::Ke, Ke_); - reader.loadReal(Parameter::Te, Te_); - reader.loadReal(Parameter::Kf, Kf_); - reader.loadReal(Parameter::Tf, Tf_); - reader.loadReal(Parameter::Vrmin, Vrmin_); - reader.loadReal(Parameter::Vrmax, Vrmax_); - reader.loadReal(Parameter::E1, E1_); - reader.loadReal(Parameter::E2, E2_); - reader.loadReal(Parameter::Se1, Se1_); - reader.loadReal(Parameter::Se2, Se2_); - reader.loadReal(Parameter::Ispdlim, Ispdlim_); + 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)); + } setDerivedParameters(); } diff --git a/GridKit/Model/PhasorDynamics/Exciter/SEXS-PTI/SexsPti.hpp b/GridKit/Model/PhasorDynamics/Exciter/SEXS-PTI/SexsPti.hpp index c458bb8363..30801c1acd 100644 --- a/GridKit/Model/PhasorDynamics/Exciter/SEXS-PTI/SexsPti.hpp +++ b/GridKit/Model/PhasorDynamics/Exciter/SEXS-PTI/SexsPti.hpp @@ -72,14 +72,15 @@ namespace GridKit SexsPti(BusT* bus, const ModelDataT& data); ~SexsPti(); - int setGridKitComponentID(IdxT) override final; - int allocate() override final; + int setGridKitComponentID(IdxT) override final; + int allocate() 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; + 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,6 +102,8 @@ 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 707db5a86e..579ec9f792 100644 --- a/GridKit/Model/PhasorDynamics/Exciter/SEXS-PTI/SexsPtiImpl.hpp +++ b/GridKit/Model/PhasorDynamics/Exciter/SEXS-PTI/SexsPtiImpl.hpp @@ -6,11 +6,9 @@ * @brief Definition of the SEXS-PTI exciter model. */ -#include +#include #include -#include -#include #include #include #include @@ -96,26 +94,55 @@ namespace GridKit template Model::ConfigurationChecks SexsPti::verify() const { - Model::ConfigurationChecks checks; - - checks.check(bus_ != nullptr, "bus pointer is null"); - checks.check(Ta_ >= 0.0, "Ta must be non-negative"); - checks.check(Tb_ > 0.0, "Tb must be positive"); - checks.check(Te_ > 0.0, "Te must be positive"); - checks.check(K_ > 0.0, "K must be positive"); - checks.check(Efdmin_ < Efdmax_, "Efdmin must be less than Efdmax"); - - checks.check(ports_.out.template port().connected(), - "required EFD signal is not assigned"); - - const auto vref_port = ports_.in.template port(); - checks.check(!vref_port.connected() || vref_port.linked(), "vref signal attached with no linked source"); - const auto vs_port = ports_.in.template port(); - checks.check(!vs_port.connected() || vs_port.linked(), "vs signal attached with no linked source"); - const auto vuel_port = ports_.in.template port(); - checks.check(!vuel_port.connected() || vuel_port.linked(), "vuel signal attached with no linked source"); - const auto voel_port = ports_.in.template port(); - checks.check(!voel_port.connected() || voel_port.linked(), "voel signal attached with no linked source"); + Model::ConfigurationChecks checks("SexsPti"); + + checks.check(missing_param_count_ == 0, "model data is missing required parameters"); + + if (bus_ == nullptr) + { + checks.fail("bus pointer is null"); + } + if (Ta_ < 0.0) + { + checks.fail("Ta must be non-negative"); + } + if (Tb_ <= 0.0) + { + checks.fail("Tb must be positive"); + } + if (Te_ <= 0.0) + { + checks.fail("Te must be positive"); + } + if (K_ <= 0.0) + { + checks.fail("K must be positive"); + } + if (Efdmin_ >= Efdmax_) + { + checks.fail("Efdmin must be less than Efdmax"); + } + + auto efd_port = ports_.out.template port(); + if (!efd_port.connected() || !efd_port.linked()) + { + checks.fail("required EFD signal is not assigned"); + } + + auto check_attached_signal = + [&](const char* name) + { + auto port = ports_.in.template port(); + if (port.connected() && !port.linked()) + { + checks.fail(std::format("{} signal attached with no linked source", name)); + } + }; + + check_attached_signal.template operator()("vref"); + check_attached_signal.template operator()("vs"); + check_attached_signal.template operator()("vuel"); + check_attached_signal.template operator()("voel"); return checks; } @@ -133,9 +160,18 @@ namespace GridKit } // Setpoint members provide the defaults for unattached signals. - 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_); + 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_); uel_on_ = ZERO; if (ports_.in.template port()) @@ -253,40 +289,30 @@ 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. - 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()) + auto read_signal = [&](const ScalarT& latched) { - 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(); - } + 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* wb = wb_.getData(); wb[0] = bus_->Vr(); @@ -307,14 +333,27 @@ namespace GridKit { using Params = typename ModelDataT::Parameters; - Model::ParameterReader reader(data, "SexsPti"); + missing_param_count_ = 0; - 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_); + 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"); } template diff --git a/GridKit/Model/PhasorDynamics/Governor/GASTPTI/GastPti.hpp b/GridKit/Model/PhasorDynamics/Governor/GASTPTI/GastPti.hpp index 69e9020823..074bb2caa1 100644 --- a/GridKit/Model/PhasorDynamics/Governor/GASTPTI/GastPti.hpp +++ b/GridKit/Model/PhasorDynamics/Governor/GASTPTI/GastPti.hpp @@ -86,14 +86,15 @@ namespace GridKit explicit GastPti(const ModelDataT& data); ~GastPti(); - int setGridKitComponentID(IdxT component_id) override final; - int allocate() override final; + int setGridKitComponentID(IdxT component_id) override final; + int allocate() 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; + Model::ConfigurationChecks verify() const override final; - int initialize() override final; - int tagDifferentiable() override final; - int setAbsoluteTolerance(RealT rel_tol) override final; - int evaluateResidual() override final; - int evaluateJacobian() override final; SignalPortsT& getPorts() { @@ -110,6 +111,10 @@ 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(); @@ -120,18 +125,20 @@ 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}; + 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}; 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 9f92978cf6..d236370374 100644 --- a/GridKit/Model/PhasorDynamics/Governor/GASTPTI/GastPtiImpl.hpp +++ b/GridKit/Model/PhasorDynamics/Governor/GASTPTI/GastPtiImpl.hpp @@ -9,12 +9,9 @@ #include #include #include -#include -#include +#include #include -#include -#include #include #include #include @@ -139,7 +136,9 @@ namespace GridKit { const auto PMECH = static_cast(GastPtiInternalVariables::PMECH); - Model::ConfigurationChecks checks; + Model::ConfigurationChecks checks("GastPti"); + + checks.check(parameter_error_count_ == 0, "model data has invalid parameters"); checks.check(std::isfinite(R_) && R_ > ZERO, "R must be finite and positive"); checks.check(std::isfinite(At_) && At_ >= ZERO, @@ -177,17 +176,32 @@ namespace GridKit checks.check(ports_.out.template port().connected(), "pmech output must be assigned"); - 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"); + // 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()) + { + checks.fail(std::format("{} port attached with no linked source", name)); + } + else + { + return true; + } + } + return false; + }; - 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(); + const auto omega_linked = + check_attached_signal + .template operator()("speed"); + const auto pref_linked = + check_attached_signal + .template operator()("pref"); if (variable_indices_.size() == static_cast(size_)) { @@ -195,14 +209,14 @@ namespace GridKit if (omega_linked) { - checks.check(ports_.in.template port().signalVariableIndex() - != pmech_index, + auto omega = ports_.in.template port(); + checks.check(omega.signalVariableIndex() != pmech_index, "speed and pmech ports must use distinct signals"); } if (pref_linked) { - checks.check(ports_.in.template port().signalVariableIndex() - != pmech_index, + auto pref = ports_.in.template port(); + checks.check(pref.signalVariableIndex() != pmech_index, "pref and pmech ports must use distinct signals"); } if (omega_linked && pref_linked) @@ -262,13 +276,9 @@ namespace GridKit return 1; } - const auto checks = verify(); - for (const auto& error : checks.errors()) - { - Log::error() << "GastPti: " << error << '\n'; - } - if (!checks.passed()) + if (!this->verifyAndLog()) { + Log::error() << "GastPti: cannot initialize with invalid configuration\n"; return 1; } @@ -550,6 +560,45 @@ 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 * @@ -567,7 +616,11 @@ namespace GridKit { if (!std::isfinite(value) || value < ZERO) { - throw std::invalid_argument(std::string("GastPti: ") + name + " must be finite and non-negative"); + Log::error() << "GastPti: " << name + << " must be finite and non-negative\n"; + ++parameter_error_count_; + value = TIME_CONSTANT_MINIMUM; + return false; } const bool raised = value < TIME_CONSTANT_MINIMUM; @@ -588,22 +641,28 @@ namespace GridKit { using Params = typename ModelDataT::Parameters; - Model::ParameterReader reader(data, "GastPti"); - - reader.loadReal(Params::R, R_); - reader.loadReal(Params::T1, T1_); - reader.loadReal(Params::T2, T2_); - reader.loadReal(Params::T3, T3_); - reader.loadReal(Params::At, At_); - reader.loadReal(Params::Kt, Kt_); - reader.loadReal(Params::Vmax, Vmax_); - reader.loadReal(Params::Vmin, Vmin_); - reader.loadReal(Params::Dturb, Dturb_); - RealT trate{}; - if (reader.requireReal(Params::Trate, trate)) + 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)) { + RealT trate{}; + loadRealParameter(data, Params::Trate, trate, "Trate"); this->setComponentBase(trate * static_cast(1.0e6)); } + else + { + Log::error() << "GastPti: missing required parameter 'Trate'\n"; + ++parameter_error_count_; + } setDerivedParameters(); } diff --git a/GridKit/Model/PhasorDynamics/Governor/HYGOV/Hygov.hpp b/GridKit/Model/PhasorDynamics/Governor/HYGOV/Hygov.hpp index 679c059f52..108f4972d8 100644 --- a/GridKit/Model/PhasorDynamics/Governor/HYGOV/Hygov.hpp +++ b/GridKit/Model/PhasorDynamics/Governor/HYGOV/Hygov.hpp @@ -97,14 +97,15 @@ namespace GridKit explicit Hygov(const ModelDataT& data); ~Hygov(); - int setGridKitComponentID(IdxT component_id) override final; - int allocate() override final; + int setGridKitComponentID(IdxT component_id) override final; + int allocate() 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; + 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,7 +174,9 @@ namespace GridKit std::array Gv_{}; std::array Pgv_{}; - RealT leadlag_gain_{ZERO}; + RealT leadlag_gain_{ZERO}; + + IdxT parameter_error_count_{0}; 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 132a2fd0cb..a5bd471af0 100644 --- a/GridKit/Model/PhasorDynamics/Governor/HYGOV/HygovImpl.hpp +++ b/GridKit/Model/PhasorDynamics/Governor/HYGOV/HygovImpl.hpp @@ -7,17 +7,11 @@ #pragma once #include -#include #include -#include -#include +#include #include -#include -#include -#include +#include -#include -#include #include #include #include @@ -136,7 +130,9 @@ namespace GridKit template Model::ConfigurationChecks Hygov::verify() const { - Model::ConfigurationChecks checks; + Model::ConfigurationChecks checks("Hygov"); + + checks.check(parameter_error_count_ == 0, "model data has invalid parameters"); const bool valid_component_base = std::isfinite(va_component_base_) && va_component_base_ > ZERO; @@ -214,12 +210,21 @@ namespace GridKit checks.check(ports_.out.template port().connected(), "pmech output signal must be assigned"); - 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"); + // 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()) + { + checks.fail(std::format("{} signal attached with no linked source", name)); + } + }; + + check_attached_signal.template operator()("speed"); + check_attached_signal.template operator()("pref"); + check_attached_signal.template operator()("paux"); return checks; } @@ -263,18 +268,14 @@ namespace GridKit const auto H = static_cast(HygovInternalVariables::H); const auto PMECH = static_cast(HygovInternalVariables::PMECH); - const auto checks = verify(); - for (const auto& error : checks.errors()) - { - Log::error() << "Hygov: " << error << '\n'; - } - if (!checks.passed()) + bool ret = this->verifyAndLog(); + if (!ret) { + Log::error() << "Hygov: cannot initialize with invalid configuration\n"; return 1; } - bool ret = true; - auto* y = y_.getData(); + auto* y = y_.getData(); // The assigned pmech node aliases this entry after allocate(). Its // system-base value remains untouched throughout initialization. @@ -628,46 +629,90 @@ namespace GridKit { using Params = typename ModelDataT::Parameters; - Model::ParameterReader reader(data, "Hygov"); - - RealT trate{}; - if (reader.requireReal(Params::Trate, trate)) - { - this->setComponentBase(trate * static_cast(1.0e6)); - } - reader.loadReal(Params::Rperm, Rperm_); - reader.loadReal(Params::Rtemp, Rtemp_); - reader.loadReal(Params::Tr, Tr_); - reader.loadReal(Params::Tf, Tf_); - reader.loadReal(Params::Tg, Tg_); - reader.loadReal(Params::Velm, Velm_); - reader.loadReal(Params::Gmax, Gmax_); - reader.loadReal(Params::Gmin, Gmin_); - reader.loadReal(Params::Tw, Tw_); - reader.loadReal(Params::At, At_); - reader.loadReal(Params::Dturb, Dturb_); - reader.loadReal(Params::Qnl, Qnl_); - reader.loadReal(Params::Tn, Tn_); - reader.loadReal(Params::Tnp, Tnp_); - reader.loadReal(Params::db1, db1_); - if (reader.loadReal(Params::db2, db2_) && db2_ != ZERO) + 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) { Log::warning() << "Hygov: nonzero db2 requests mechanical backlash, " "but backlash is not implemented and db2 is ignored\n"; } - 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]); + 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"); setDerivedParameters(); } @@ -743,16 +788,22 @@ 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. - const std::array, 5> lags{ - {{Tr_, "Tr"}, {Tf_, "Tf"}, {Tg_, "Tg"}, {Tw_, "Tw"}, {Tnp_, "Tnp"}}}; - for (const auto& [value, name] : lags) + // 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) { - throw std::invalid_argument(std::string("Hygov: ") + name + " must be non-negative"); + 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"); if (Tr_ < TIME_CONSTANT_MINIMUM || Tf_ < TIME_CONSTANT_MINIMUM || Tg_ < TIME_CONSTANT_MINIMUM || Tw_ < TIME_CONSTANT_MINIMUM diff --git a/GridKit/Model/PhasorDynamics/Governor/Tgov1/CMakeLists.txt b/GridKit/Model/PhasorDynamics/Governor/Tgov1/CMakeLists.txt index 2a41f7157f..d4647a8261 100644 --- a/GridKit/Model/PhasorDynamics/Governor/Tgov1/CMakeLists.txt +++ b/GridKit/Model/PhasorDynamics/Governor/Tgov1/CMakeLists.txt @@ -11,7 +11,6 @@ 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 @@ -29,14 +28,12 @@ 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 6d4b16c402..84b7f260dc 100644 --- a/GridKit/Model/PhasorDynamics/Governor/Tgov1/Tgov1.hpp +++ b/GridKit/Model/PhasorDynamics/Governor/Tgov1/Tgov1.hpp @@ -76,13 +76,14 @@ namespace GridKit Tgov1(const ModelDataT&); ~Tgov1() = default; - int setGridKitComponentID(IdxT) override final; - int allocate() override final; + int setGridKitComponentID(IdxT) override final; + int allocate() override final; + int initialize() override final; + int tagDifferentiable() override final; + int setAbsoluteTolerance(RealT) override final; + int evaluateResidual() override final; + Model::ConfigurationChecks verify() const override final; - int initialize() override final; - int tagDifferentiable() override final; - int setAbsoluteTolerance(RealT) override final; - int evaluateResidual() override final; // Still to be implemented int evaluateJacobian() override final; @@ -110,7 +111,7 @@ namespace GridKit // Input States (which can be parameters) ScalarT pref_set_{0}; - /// Component ports + // Component ports SignalPortsT ports_; // Parameter initialization function diff --git a/GridKit/Model/PhasorDynamics/Governor/Tgov1/Tgov1Impl.hpp b/GridKit/Model/PhasorDynamics/Governor/Tgov1/Tgov1Impl.hpp index fe5fafc870..06b400682b 100644 --- a/GridKit/Model/PhasorDynamics/Governor/Tgov1/Tgov1Impl.hpp +++ b/GridKit/Model/PhasorDynamics/Governor/Tgov1/Tgov1Impl.hpp @@ -11,8 +11,6 @@ #include #include -#include -#include #include #include #include @@ -92,16 +90,45 @@ namespace GridKit { using Parameter = typename ModelDataT::Parameters; - Model::ParameterReader reader(data, "Tgov1"); + if (data.parameters.contains(Parameter::Trate)) + { + Trate_ = std::get(data.parameters.at(Parameter::Trate)); + } + + if (data.parameters.contains(Parameter::R)) + { + R_ = std::get(data.parameters.at(Parameter::R)); + } + + if (data.parameters.contains(Parameter::Pvmin)) + { + Pvmin_ = std::get(data.parameters.at(Parameter::Pvmin)); + } + + if (data.parameters.contains(Parameter::Pvmax)) + { + Pvmax_ = std::get(data.parameters.at(Parameter::Pvmax)); + } + + if (data.parameters.contains(Parameter::T1)) + { + T1_ = std::get(data.parameters.at(Parameter::T1)); + } + + if (data.parameters.contains(Parameter::T2)) + { + T2_ = std::get(data.parameters.at(Parameter::T2)); + } + + if (data.parameters.contains(Parameter::T3)) + { + T3_ = std::get(data.parameters.at(Parameter::T3)); + } - 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_); + if (data.parameters.contains(Parameter::Dt)) + { + Dt_ = std::get(data.parameters.at(Parameter::Dt)); + } } /** @@ -195,8 +222,7 @@ namespace GridKit template Model::ConfigurationChecks Tgov1::verify() const { - - Model::ConfigurationChecks checks; + Model::ConfigurationChecks checks("Tgov1"); checks.check(Trate_ > ZERO, "Trate must be positive"); checks.check(va_system_base_ > ZERO, "system power base must be positive"); @@ -205,10 +231,17 @@ namespace GridKit checks.check(ports_.out.template port().connected(), "pmech output signal must be assigned"); - 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"); + auto speed_port = ports_.in.template port(); + if (speed_port.connected() && !speed_port.linked()) + { + checks.fail("speed signal attached with no linked generator"); + } + + auto pref_port = ports_.in.template port(); + if (pref_port.connected() && !pref_port.linked()) + { + checks.fail("pref signal attached with no linked source"); + } return checks; } @@ -220,13 +253,9 @@ namespace GridKit template int Tgov1::initialize() { - const auto checks = verify(); - for (const auto& error : checks.errors()) - { - Log::error() << "Tgov1: " << error << '\n'; - } - if (!checks.passed()) + if (!this->verifyAndLog()) { + Log::error() << "Tgov1: cannot initialize with invalid configuration\n"; return 1; } diff --git a/GridKit/Model/PhasorDynamics/Load/LoadZ/LoadZImpl.hpp b/GridKit/Model/PhasorDynamics/Load/LoadZ/LoadZImpl.hpp index d8c96d154d..b3e24fc8aa 100644 --- a/GridKit/Model/PhasorDynamics/Load/LoadZ/LoadZImpl.hpp +++ b/GridKit/Model/PhasorDynamics/Load/LoadZ/LoadZImpl.hpp @@ -3,8 +3,6 @@ #include #include -#include -#include #include #include #include @@ -48,10 +46,15 @@ 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)); + } - Model::ParameterReader reader(data, "LoadZ"); - reader.loadReal(Parameter::R, R_); - reader.loadReal(Parameter::X, X_); + if (data.parameters.contains(Parameter::X)) + { + X_ = std::get(data.parameters.at(Parameter::X)); + } size_ = 2; setDerivedParams(); diff --git a/GridKit/Model/PhasorDynamics/Load/LoadZIP/LoadZIPImpl.hpp b/GridKit/Model/PhasorDynamics/Load/LoadZIP/LoadZIPImpl.hpp index a26d696088..929cfb746e 100644 --- a/GridKit/Model/PhasorDynamics/Load/LoadZIP/LoadZIPImpl.hpp +++ b/GridKit/Model/PhasorDynamics/Load/LoadZIP/LoadZIPImpl.hpp @@ -1,7 +1,5 @@ #pragma once -#include -#include #include #include #include @@ -58,12 +56,25 @@ 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)); + } - 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_); + if (data.parameters.contains(Parameter::alphaI)) + { + alphaI_ = std::get(data.parameters.at(Parameter::alphaI)); + } + + if (data.parameters.contains(Parameter::alphaP)) + { + alphaP_ = std::get(data.parameters.at(Parameter::alphaP)); + } setDerivedParams(); } diff --git a/GridKit/Model/PhasorDynamics/SignalIn.hpp b/GridKit/Model/PhasorDynamics/SignalIn.hpp index 18f30ff451..09636114e7 100644 --- a/GridKit/Model/PhasorDynamics/SignalIn.hpp +++ b/GridKit/Model/PhasorDynamics/SignalIn.hpp @@ -34,16 +34,6 @@ namespace GridKit return this->signal_node_->getVariableIndex(); } - /// Read the connected signal, or the fallback when disconnected. - ScalarT readOrDefault(ScalarT fallback) const - { - if (this->connected()) - { - return readSignal(); - } - return fallback; - } - /// Write a value to the connected signal node. /// /// @warning Use only during initialization as this violates assumptions. diff --git a/GridKit/Model/PhasorDynamics/SignalSource/CMakeLists.txt b/GridKit/Model/PhasorDynamics/SignalSource/CMakeLists.txt index b43013a977..11fbc8be29 100644 --- a/GridKit/Model/PhasorDynamics/SignalSource/CMakeLists.txt +++ b/GridKit/Model/PhasorDynamics/SignalSource/CMakeLists.txt @@ -8,13 +8,11 @@ gridkit_add_library( phasor_dynamics_signalsource SOURCES ConstantSignalSource.cpp FunctionSignalSource.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 FunctionSignalSourceDependencyTracking.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 cbf8c3600f..d3f1023f0a 100644 --- a/GridKit/Model/PhasorDynamics/SignalSource/ConstantSignalSource.hpp +++ b/GridKit/Model/PhasorDynamics/SignalSource/ConstantSignalSource.hpp @@ -43,14 +43,15 @@ namespace GridKit ConstantSignalSource(const ModelDataT& data); ~ConstantSignalSource(); - int setGridKitComponentID(IdxT) override final; - int allocate() override final; + int setGridKitComponentID(IdxT) override final; + int allocate() override final; + int initialize() override final; + int tagDifferentiable() override final; + int setAbsoluteTolerance(RealT) override final; + int evaluateResidual() override final; + int evaluateJacobian() override final; + Model::ConfigurationChecks verify() const override final; - int initialize() override final; - int tagDifferentiable() override final; - int setAbsoluteTolerance(RealT) override final; - int evaluateResidual() override final; - int evaluateJacobian() override final; SignalPortsT& getPorts() { diff --git a/GridKit/Model/PhasorDynamics/SignalSource/ConstantSignalSourceImpl.hpp b/GridKit/Model/PhasorDynamics/SignalSource/ConstantSignalSourceImpl.hpp index 3fb1697949..b87c77bbbb 100644 --- a/GridKit/Model/PhasorDynamics/SignalSource/ConstantSignalSourceImpl.hpp +++ b/GridKit/Model/PhasorDynamics/SignalSource/ConstantSignalSourceImpl.hpp @@ -1,6 +1,4 @@ -#include -#include #include #include #include @@ -44,20 +42,13 @@ namespace GridKit void ConstantSignalSource::initializeParameters(const ModelDataT& data) { using Parameters = ModelDataT::Parameters; - - Model::ParameterReader reader(data, "ConstantSignalSource"); - - // The signal values are differentiable scalars, so each is loaded - // through a real intermediate. - RealT s_real{}; - if (reader.loadReal(Parameters::Sr, s_real)) + if (data.parameters.contains(Parameters::Sr)) { - s_real_ = s_real; + s_real_ = std::get(data.parameters.at(Parameters::Sr)); } - RealT s_imag{}; - if (reader.loadReal(Parameters::Si, s_imag)) + if (data.parameters.contains(Parameters::Si)) { - s_imag_ = s_imag; + s_imag_ = std::get(data.parameters.at(Parameters::Si)); } } diff --git a/GridKit/Model/PhasorDynamics/SignalSource/FunctionSignalSource.hpp b/GridKit/Model/PhasorDynamics/SignalSource/FunctionSignalSource.hpp index 3df58dbdf5..085bf25590 100644 --- a/GridKit/Model/PhasorDynamics/SignalSource/FunctionSignalSource.hpp +++ b/GridKit/Model/PhasorDynamics/SignalSource/FunctionSignalSource.hpp @@ -46,14 +46,15 @@ namespace GridKit FunctionSignalSource(const ModelDataT& data); ~FunctionSignalSource(); - int setGridKitComponentID(IdxT) override final; - int allocate() override final; + int setGridKitComponentID(IdxT) override final; + int allocate() override final; + int initialize() override final; + int tagDifferentiable() override final; + int setAbsoluteTolerance(RealT) override final; + int evaluateResidual() override final; + int evaluateJacobian() 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; void updateTime(RealT t, RealT a) override; diff --git a/GridKit/Model/PhasorDynamics/Stabilizer/IEEEST/Ieeest.hpp b/GridKit/Model/PhasorDynamics/Stabilizer/IEEEST/Ieeest.hpp index 2c5679a4f5..9d7bc8c54f 100644 --- a/GridKit/Model/PhasorDynamics/Stabilizer/IEEEST/Ieeest.hpp +++ b/GridKit/Model/PhasorDynamics/Stabilizer/IEEEST/Ieeest.hpp @@ -78,14 +78,15 @@ namespace GridKit Ieeest(const ModelDataT& data); ~Ieeest(); - int setGridKitComponentID(IdxT) override final; - int allocate() override final; + int setGridKitComponentID(IdxT) override final; + int allocate() 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; + Model::ConfigurationChecks verify() const override final; - int initialize() override final; - int tagDifferentiable() override final; - int setAbsoluteTolerance(RealT rel_tol) override final; - int evaluateResidual() override final; - int evaluateJacobian() override final; SignalPortsT& getPorts() { diff --git a/GridKit/Model/PhasorDynamics/Stabilizer/IEEEST/IeeestImpl.hpp b/GridKit/Model/PhasorDynamics/Stabilizer/IEEEST/IeeestImpl.hpp index 5ccc70ed07..2bd419b985 100644 --- a/GridKit/Model/PhasorDynamics/Stabilizer/IEEEST/IeeestImpl.hpp +++ b/GridKit/Model/PhasorDynamics/Stabilizer/IEEEST/IeeestImpl.hpp @@ -8,8 +8,6 @@ #include -#include -#include #include #include #include @@ -49,26 +47,78 @@ namespace GridKit void Ieeest::initializeParameters(const ModelDataT& data) { using Parameter = typename ModelDataT::Parameters; - - Model::ParameterReader reader(data, "Ieeest"); - reader.loadReal(Parameter::A1, A1_); - reader.loadReal(Parameter::A2, A2_); - reader.loadReal(Parameter::A3, A3_); - reader.loadReal(Parameter::A4, A4_); - reader.loadReal(Parameter::A5, A5_); - reader.loadReal(Parameter::A6, A6_); - reader.loadReal(Parameter::T1, T1_); - reader.loadReal(Parameter::T2, T2_); - reader.loadReal(Parameter::T3, T3_); - reader.loadReal(Parameter::T4, T4_); - reader.loadReal(Parameter::T5, T5_); - reader.loadReal(Parameter::T6, T6_); - reader.loadReal(Parameter::Ks, Ks_); - reader.loadReal(Parameter::Lsmin, Lsmin_); - reader.loadReal(Parameter::Lsmax, Lsmax_); - reader.loadReal(Parameter::Vcl, Vcl_); - reader.loadReal(Parameter::Vcu, Vcu_); - reader.loadReal(Parameter::Tdelay, Tdelay_); + 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)); + } a0_ = 1; a1_ = A1_ + A3_; @@ -140,14 +190,22 @@ namespace GridKit template Model::ConfigurationChecks Ieeest::verify() const { - Model::ConfigurationChecks checks; + Model::ConfigurationChecks checks("Ieeest"); - 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"); + auto input_port = ports_.in.template port(); + if (!input_port.connected()) + { + checks.fail("required input signal U is not attached"); + } + if (input_port.connected() && !input_port.linked()) + { + checks.fail("input signal U attached with no linked source"); + } - checks.check(!(a4_ == 0 && a3_ == 0 && a2_ == 0 && a1_ != 0), - "a2, a3, and a4 are all zero - no valid notch filter"); + if (a4_ == 0 && a3_ == 0 && a2_ == 0 && a1_ != 0) + { + checks.fail("a2, a3, and a4 are all zero - no valid notch filter"); + } return checks; } @@ -155,13 +213,9 @@ namespace GridKit template int Ieeest::initialize() { - const auto checks = verify(); - for (const auto& error : checks.errors()) - { - Log::error() << "Ieeest: " << error << '\n'; - } - if (!checks.passed()) + if (!this->verifyAndLog()) { + 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 5f7b0929a3..f8fd7ec340 100644 --- a/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROU/Genrou.hpp +++ b/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROU/Genrou.hpp @@ -119,13 +119,14 @@ namespace GridKit RealT S12); ~Genrou(); - int setGridKitComponentID(IdxT) override final; - int allocate() override final; + int setGridKitComponentID(IdxT) override final; + int allocate() override final; + int initialize() override final; + int tagDifferentiable() override final; + int setAbsoluteTolerance(RealT) override final; + int evaluateResidual() override final; + Model::ConfigurationChecks verify() const override final; - int initialize() override final; - int tagDifferentiable() override final; - int setAbsoluteTolerance(RealT) override final; - int evaluateResidual() override final; // Still to be implemented int evaluateJacobian() override final; diff --git a/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROU/GenrouImpl.hpp b/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROU/GenrouImpl.hpp index c2ec0863d2..cfad1a9ffc 100644 --- a/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROU/GenrouImpl.hpp +++ b/GridKit/Model/PhasorDynamics/SynchronousMachine/GENROU/GenrouImpl.hpp @@ -2,8 +2,6 @@ #include -#include -#include #include #include #include @@ -167,27 +165,100 @@ 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)); + } - Model::ParameterReader reader(data, "Genrou"); - reader.loadReal(Parameter::p0, p0_); - reader.loadReal(Parameter::q0, q0_); - reader.loadReal(Parameter::H, H_); - reader.loadReal(Parameter::D, D_); - reader.loadReal(Parameter::Ra, Ra_); - reader.loadReal(Parameter::Tdop, Tdop_); - reader.loadReal(Parameter::Tdopp, Tdopp_); - reader.loadReal(Parameter::Tqopp, Tqopp_); - reader.loadReal(Parameter::Tqop, Tqop_); - reader.loadReal(Parameter::Xd, Xd_); - reader.loadReal(Parameter::Xdp, Xdp_); - reader.loadReal(Parameter::Xdpp, Xdpp_); - reader.loadReal(Parameter::Xq, Xq_); - reader.loadReal(Parameter::Xqp, Xqp_); - reader.loadReal(Parameter::Xqpp, Xqpp_); - reader.loadReal(Parameter::Xl, Xl_); - reader.loadReal(Parameter::S10, S10_); - reader.loadReal(Parameter::S12, S12_); - reader.loadReal(Parameter::mva, mva_base_); + if (data.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)); + } if (data.buses.contains(Buses::bus)) { @@ -280,13 +351,19 @@ namespace GridKit template Model::ConfigurationChecks Genrou::verify() const { - Model::ConfigurationChecks checks; + Model::ConfigurationChecks checks("Genrou"); - 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()) + { + checks.fail("pmech signal attached with no linked governor"); + } - const auto efd_port = ports_.in.template port(); - checks.check(!efd_port.connected() || efd_port.linked(), "efd signal attached with no linked exciter"); + auto efd_port = ports_.in.template port(); + if (efd_port.connected() && !efd_port.linked()) + { + checks.fail("efd signal attached with no linked exciter"); + } return checks; } diff --git a/GridKit/Model/PhasorDynamics/SynchronousMachine/GENSAL/Gensal.hpp b/GridKit/Model/PhasorDynamics/SynchronousMachine/GENSAL/Gensal.hpp index 1defcff89f..9dff277405 100644 --- a/GridKit/Model/PhasorDynamics/SynchronousMachine/GENSAL/Gensal.hpp +++ b/GridKit/Model/PhasorDynamics/SynchronousMachine/GENSAL/Gensal.hpp @@ -85,13 +85,14 @@ namespace GridKit Gensal(BusT* bus, const ModelDataT& data); ~Gensal(); - int setGridKitComponentID(IdxT) override final; - int allocate() override final; + int setGridKitComponentID(IdxT) override final; + int allocate() override final; + int initialize() override final; + int tagDifferentiable() override final; + int setAbsoluteTolerance(RealT rel_tol) override final; + int evaluateResidual() override final; + Model::ConfigurationChecks verify() const override final; - int initialize() override final; - int tagDifferentiable() override final; - int setAbsoluteTolerance(RealT rel_tol) override final; - int evaluateResidual() override final; // Still to be implemented int evaluateJacobian() override final; diff --git a/GridKit/Model/PhasorDynamics/SynchronousMachine/GENSAL/GensalImpl.hpp b/GridKit/Model/PhasorDynamics/SynchronousMachine/GENSAL/GensalImpl.hpp index 0d86c563c3..9bfa40b916 100644 --- a/GridKit/Model/PhasorDynamics/SynchronousMachine/GENSAL/GensalImpl.hpp +++ b/GridKit/Model/PhasorDynamics/SynchronousMachine/GENSAL/GensalImpl.hpp @@ -2,8 +2,6 @@ #include -#include -#include #include #include #include @@ -45,24 +43,85 @@ 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)); + } - Model::ParameterReader reader(data, "Gensal"); - reader.loadReal(Parameter::p0, p0_); - reader.loadReal(Parameter::q0, q0_); - reader.loadReal(Parameter::H, H_); - reader.loadReal(Parameter::D, D_); - reader.loadReal(Parameter::Ra, Ra_); - reader.loadReal(Parameter::Tdop, Tdop_); - reader.loadReal(Parameter::Tdopp, Tdopp_); - reader.loadReal(Parameter::Tqopp, Tqopp_); - reader.loadReal(Parameter::Xd, Xd_); - reader.loadReal(Parameter::Xdp, Xdp_); - reader.loadReal(Parameter::Xdpp, Xdpp_); - reader.loadReal(Parameter::Xq, Xq_); - reader.loadReal(Parameter::Xl, Xl_); - reader.loadReal(Parameter::S10, S10_); - reader.loadReal(Parameter::S12, S12_); - reader.loadReal(Parameter::mva, mva_base_); + 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)); + } } template @@ -168,13 +227,19 @@ namespace GridKit template Model::ConfigurationChecks Gensal::verify() const { - Model::ConfigurationChecks checks; + Model::ConfigurationChecks checks("Gensal"); - 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()) + { + checks.fail("pmech signal attached with no linked governor"); + } - const auto efd_port = ports_.in.template port(); - checks.check(!efd_port.connected() || efd_port.linked(), "efd signal attached with no linked exciter"); + auto efd_port = ports_.in.template port(); + if (efd_port.connected() && !efd_port.linked()) + { + checks.fail("efd signal attached with no linked exciter"); + } return checks; } diff --git a/GridKit/Model/PhasorDynamics/SynchronousMachine/GenClassical/GenClassical.hpp b/GridKit/Model/PhasorDynamics/SynchronousMachine/GenClassical/GenClassical.hpp index 6192267f1f..b5f5163f0d 100644 --- a/GridKit/Model/PhasorDynamics/SynchronousMachine/GenClassical/GenClassical.hpp +++ b/GridKit/Model/PhasorDynamics/SynchronousMachine/GenClassical/GenClassical.hpp @@ -74,13 +74,14 @@ namespace GridKit GenClassical(BusT* bus, const ModelDataT& data); ~GenClassical(); - int setGridKitComponentID(IdxT) override final; - int allocate() override final; + int setGridKitComponentID(IdxT) override final; + int allocate() override final; + int initialize() override final; + int tagDifferentiable() override final; + int setAbsoluteTolerance(RealT rel_tol) override final; + int evaluateResidual() override final; + Model::ConfigurationChecks verify() const override final; - int initialize() override final; - int tagDifferentiable() override final; - int setAbsoluteTolerance(RealT rel_tol) override final; - int evaluateResidual() override final; // Still to be implemented int evaluateJacobian() override final; diff --git a/GridKit/Model/PhasorDynamics/SynchronousMachine/GenClassical/GenClassicalImpl.hpp b/GridKit/Model/PhasorDynamics/SynchronousMachine/GenClassical/GenClassicalImpl.hpp index acede05af0..0d79c2dd46 100644 --- a/GridKit/Model/PhasorDynamics/SynchronousMachine/GenClassical/GenClassicalImpl.hpp +++ b/GridKit/Model/PhasorDynamics/SynchronousMachine/GenClassical/GenClassicalImpl.hpp @@ -10,8 +10,6 @@ #include -#include -#include #include #include #include @@ -49,15 +47,40 @@ 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)); + } - Model::ParameterReader reader(data, "GenClassical"); - reader.loadReal(Parameter::p0, p0_); - reader.loadReal(Parameter::q0, q0_); - reader.loadReal(Parameter::H, H_); - reader.loadReal(Parameter::D, D_); - reader.loadReal(Parameter::Ra, Ra_); - reader.loadReal(Parameter::Xdp, Xdp_); - reader.loadReal(Parameter::mva, mva_base_); + 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)); + } } template @@ -145,13 +168,19 @@ namespace GridKit template Model::ConfigurationChecks GenClassical::verify() const { - Model::ConfigurationChecks checks; + Model::ConfigurationChecks checks("GenClassical"); - 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()) + { + checks.fail("pmech signal attached with no linked governor"); + } - const auto efd_port = ports_.in.template port(); - checks.check(!efd_port.connected() || efd_port.linked(), "efd signal attached with no linked exciter"); + auto efd_port = ports_.in.template port(); + if (efd_port.connected() && !efd_port.linked()) + { + checks.fail("efd signal attached with no linked exciter"); + } return checks; } diff --git a/GridKit/Model/PhasorDynamics/SystemModel.hpp b/GridKit/Model/PhasorDynamics/SystemModel.hpp index 3828763372..0748b9be02 100644 --- a/GridKit/Model/PhasorDynamics/SystemModel.hpp +++ b/GridKit/Model/PhasorDynamics/SystemModel.hpp @@ -78,9 +78,10 @@ namespace GridKit int setGridKitComponentID(IdxT component_id) override; - int allocate() override; + int allocate() override; + int initialize() 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 cc41c11419..e5db0b4b43 100644 --- a/GridKit/Model/PhasorDynamics/SystemModelImpl.hpp +++ b/GridKit/Model/PhasorDynamics/SystemModelImpl.hpp @@ -1,7 +1,6 @@ #include #include #include -#include #include #include @@ -460,12 +459,7 @@ namespace GridKit } // Verify component configuration - const auto checks = this->verify(); - for (const auto& error : checks.errors()) - { - Log::error() << error << '\n'; - } - if (!checks.passed()) + if (!this->verifyAndLog()) { throw std::runtime_error("SystemModel allocation failed"); } @@ -506,6 +500,7 @@ namespace GridKit { Model::ConfigurationChecks checks; + // Verify components for (const auto& component : components_) { const auto component_checks = component->verify(); diff --git a/GridKit/Testing/Testing.hpp b/GridKit/Testing/Testing.hpp index fbf565292e..ba6839f4fd 100644 --- a/GridKit/Testing/Testing.hpp +++ b/GridKit/Testing/Testing.hpp @@ -12,28 +12,12 @@ #include #include #include -#include #include namespace GridKit { namespace Testing { - /// True when constructing a ModelT from the arguments throws - /// std::invalid_argument, the rejection for invalid model data. - template - bool constructionRejected(ArgsT&&... args) - { - try - { - ModelT model(std::forward(args)...); - } - catch (const std::invalid_argument&) - { - return true; - } - return false; - } enum TestOutcome { diff --git a/tests/UnitTests/PhasorDynamics/BusSignalVoltageInTests.hpp b/tests/UnitTests/PhasorDynamics/BusSignalVoltageInTests.hpp index bfd5f830a7..c405d961d8 100644 --- a/tests/UnitTests/PhasorDynamics/BusSignalVoltageInTests.hpp +++ b/tests/UnitTests/PhasorDynamics/BusSignalVoltageInTests.hpp @@ -32,7 +32,7 @@ namespace GridKit BusSignalVoltageInTests() = default; ~BusSignalVoltageInTests() = default; - /// Invalid configurations are reported without throwing. + /// True if verify() reports a problem, as it must for a misconnected bus template static bool verifyFails(const BusLike& bus) { @@ -44,7 +44,7 @@ namespace GridKit { TestStatus success = true; - // Keep expected invalid-configuration diagnostics quiet. + // This test triggers error messages on purpose; silence them. const auto previous_verbosity = Log::verbosity(); Log::setVerbosity(Log::Verbosity::NONE); @@ -83,7 +83,7 @@ namespace GridKit { TestStatus success = true; - // Keep expected invalid-configuration diagnostics quiet. + // This test triggers error messages on purpose; silence them. const auto previous_verbosity = Log::verbosity(); Log::setVerbosity(Log::Verbosity::NONE); @@ -102,7 +102,7 @@ namespace GridKit bus.getPorts().in.template port().connect(&vi_node); bus.allocate(); bus.initialize(); - success *= bus.verify().passed(); + success *= (bus.verify().passed()); // Voltage is read straight from the signals, no evaluation needed success *= isEqual(bus.Vr(), Vr); @@ -169,7 +169,7 @@ namespace GridKit bus.allocate(); bus.initialize(); - success *= bus.verify().passed(); + success *= (bus.verify().passed()); success *= ir_node.linked(); success *= ii_node.linked(); success *= (ir_node.getVariableIndex() == INVALID_INDEX); @@ -200,7 +200,7 @@ namespace GridKit { TestStatus success = true; - // Keep expected invalid-configuration diagnostics quiet. + // This test triggers error messages on purpose; silence them. const auto previous_verbosity = Log::verbosity(); Log::setVerbosity(Log::Verbosity::NONE); @@ -226,7 +226,7 @@ namespace GridKit ScalarT Vi{0.2}; IdxT vi_index{1}; vi_node.link(&Vi, &vi_index); - success *= bus.verify().passed(); + success *= (bus.verify().passed()); Log::setVerbosity(previous_verbosity); diff --git a/tests/UnitTests/PhasorDynamics/BusSignalVoltageOutTests.hpp b/tests/UnitTests/PhasorDynamics/BusSignalVoltageOutTests.hpp index 0b6854be06..6cd8c9121e 100644 --- a/tests/UnitTests/PhasorDynamics/BusSignalVoltageOutTests.hpp +++ b/tests/UnitTests/PhasorDynamics/BusSignalVoltageOutTests.hpp @@ -33,7 +33,7 @@ namespace GridKit BusSignalVoltageOutTests() = default; ~BusSignalVoltageOutTests() = default; - /// Invalid configurations are reported without throwing. + /// True if verify() reports a problem, as it must for a misconnected bus template static bool verifyFails(const BusLike& bus) { @@ -109,7 +109,7 @@ namespace GridKit bus.setVariableIndex(1, vi_index); bus.initialize(); - success *= bus.verify().passed(); + success *= (bus.verify().passed()); success *= vr_node.linked(); success *= vi_node.linked(); success *= isEqual(vr_node.read(), Vr); @@ -159,7 +159,7 @@ namespace GridKit bus.getPorts().in.template port().connect(&ii_node); bus.allocate(); bus.initialize(); - success *= bus.verify().passed(); + success *= (bus.verify().passed()); bus.evaluateResidual(); success *= isEqual(bus.Ir(), Ir); @@ -188,7 +188,7 @@ namespace GridKit { TestStatus success = true; - // Keep expected invalid-configuration diagnostics quiet. + // This test triggers error messages on purpose; silence them. const auto previous_verbosity = Log::verbosity(); Log::setVerbosity(Log::Verbosity::NONE); @@ -211,7 +211,7 @@ namespace GridKit ScalarT Ii{0.2}; IdxT ii_index{1}; ii_node.link(&Ii, &ii_index); - success *= bus.verify().passed(); + success *= (bus.verify().passed()); Log::setVerbosity(previous_verbosity); diff --git a/tests/UnitTests/PhasorDynamics/ControllerReecbTests.hpp b/tests/UnitTests/PhasorDynamics/ControllerReecbTests.hpp index ad1d3b5ad3..3c6e8ad660 100644 --- a/tests/UnitTests/PhasorDynamics/ControllerReecbTests.hpp +++ b/tests/UnitTests/PhasorDynamics/ControllerReecbTests.hpp @@ -116,7 +116,8 @@ namespace GridKit auto missing_mva_data = makeMinimalData(); missing_mva_data.parameters.erase(Params::mva); - success *= constructionRejected>(missing_mva_data); + Fixture missing_mva(missing_mva_data); + success *= (!missing_mva.reecb.verify().passed()); // Integer JSON values are accepted for real parameters; booleans are // not numeric. @@ -2294,15 +2295,8 @@ namespace GridKit { auto data = makeData(); data.parameters[parameter] = value; - try - { - Fixture fixture(data); - return !fixture.reecb.verify().passed(); - } - catch (const std::invalid_argument&) - { - return true; - } + Fixture fixture(data); + return !fixture.reecb.verify().passed(); } template diff --git a/tests/UnitTests/PhasorDynamics/ControllerRepcaTests.hpp b/tests/UnitTests/PhasorDynamics/ControllerRepcaTests.hpp index 521c00e0ee..04b63fadcb 100644 --- a/tests/UnitTests/PhasorDynamics/ControllerRepcaTests.hpp +++ b/tests/UnitTests/PhasorDynamics/ControllerRepcaTests.hpp @@ -606,7 +606,7 @@ namespace GridKit // An invalid configuration is rejected before any state is written. { auto invalid_data = data; - invalid_data.parameters[Params::Ddn] = -0.1; + invalid_data.parameters[Params::Tfv] = -0.1; Fixture invalid_fixture(invalid_data); invalid_fixture.attachAllInputs(); setInitializationInputs(invalid_fixture); @@ -1719,16 +1719,9 @@ namespace GridKit { auto data = makeData(); data.parameters[parameter] = value; - try - { - Fixture fixture(data); - fixture.attachAllInputs(); - return !fixture.repca.verify().passed(); - } - catch (const std::invalid_argument&) - { - return true; - } + Fixture fixture(data); + fixture.attachAllInputs(); + return !fixture.repca.verify().passed(); } template diff --git a/tests/UnitTests/PhasorDynamics/ConverterRegcaTests.hpp b/tests/UnitTests/PhasorDynamics/ConverterRegcaTests.hpp index c39ecc208e..c00c3a3f2f 100644 --- a/tests/UnitTests/PhasorDynamics/ConverterRegcaTests.hpp +++ b/tests/UnitTests/PhasorDynamics/ConverterRegcaTests.hpp @@ -68,12 +68,14 @@ namespace GridKit { auto data = makeData(); data.parameters.erase(parameter); - success *= constructionRejected>(&bus, data); + PhasorDynamics::Converter::Regca missing(&bus, data); + success *= (!missing.verify().passed()); } - auto bad_switch = makeData(); - bad_switch.parameters[Params::sL] = static_cast(2); - success *= constructionRejected>(&bus, bad_switch); + 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().passed()); success *= invalidParameterCase(bus, Params::mva, 0.0); success *= invalidParameterCase(bus, Params::IL1, -0.1); @@ -951,15 +953,8 @@ namespace GridKit { auto data = makeData(); data.parameters[param] = value; - try - { - PhasorDynamics::Converter::Regca model(&bus, data); - return !model.verify().passed(); - } - catch (const std::invalid_argument&) - { - return true; - } + PhasorDynamics::Converter::Regca model(&bus, data); + return !model.verify().passed(); } bool allResidualsZero(PhasorDynamics::Converter::Regca& regca) const diff --git a/tests/UnitTests/PhasorDynamics/ExciterEsdc1aTests.hpp b/tests/UnitTests/PhasorDynamics/ExciterEsdc1aTests.hpp index 39cb771945..67dd1cd333 100644 --- a/tests/UnitTests/PhasorDynamics/ExciterEsdc1aTests.hpp +++ b/tests/UnitTests/PhasorDynamics/ExciterEsdc1aTests.hpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include diff --git a/tests/UnitTests/PhasorDynamics/ExciterSexsPtiTests.hpp b/tests/UnitTests/PhasorDynamics/ExciterSexsPtiTests.hpp index f15873e30e..9898a0f9c0 100644 --- a/tests/UnitTests/PhasorDynamics/ExciterSexsPtiTests.hpp +++ b/tests/UnitTests/PhasorDynamics/ExciterSexsPtiTests.hpp @@ -280,7 +280,8 @@ namespace GridKit auto missing = makeTestData(); missing.parameters.erase(Parameter::K); - success *= constructionRejected>(&bus, missing); + PhasorDynamics::Exciter::SexsPti missing_model(&bus, missing); + success *= (!missing_model.verify().passed()); auto invalid = makeTestData(); invalid.parameters[Parameter::Tb] = 0.0; diff --git a/tests/UnitTests/PhasorDynamics/GovernorGastPtiTests.hpp b/tests/UnitTests/PhasorDynamics/GovernorGastPtiTests.hpp index ba383180a9..f1443c9f2f 100644 --- a/tests/UnitTests/PhasorDynamics/GovernorGastPtiTests.hpp +++ b/tests/UnitTests/PhasorDynamics/GovernorGastPtiTests.hpp @@ -82,12 +82,12 @@ namespace GridKit PhasorDynamics::Governor::GastPti minimal(makeMinimalData()); success *= (!minimal.verify().passed()); // required pmech assignment is absent - success *= (verifyData(makeData())); + success *= (verifyData(makeData()).passed()); success *= defaultsMatchDocumentedValues(); auto missing_trate = makeMinimalData(); missing_trate.parameters.erase(Params::Trate); - success *= (!verifyData(missing_trate)); + success *= (!verifyData(missing_trate).passed()); 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)); + success *= (verifyData(equal).passed()); auto reversed = makeData(); reversed.parameters[Params::Vmin] = 0.6; reversed.parameters[Params::Vmax] = 0.5; - success *= (!verifyData(reversed)); + success *= (!verifyData(reversed).passed()); // Narrow configured limits remain valid. auto narrow = makeData(); narrow.parameters[Params::Vmax] = 0.01; - success *= (verifyData(narrow)); + success *= (verifyData(narrow).passed()); // Integer JSON values are accepted for real parameters; booleans are // not numeric. @@ -1231,23 +1231,15 @@ namespace GridKit { auto data = makeData(); data.parameters[parameter] = value; - return !verifyData(data); + return !verifyData(data).passed(); } - /// False when the data is rejected at construction or by verify(). - bool verifyData(const Data& data) const + Model::ConfigurationChecks verifyData(const Data& data) const { - 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; - } + PhasorDynamics::SignalNode pmech; + PhasorDynamics::Governor::GastPti model(data); + model.getPorts().out.template port().connect(&pmech); + return model.verify(); } template diff --git a/tests/UnitTests/PhasorDynamics/GovernorHygovTests.hpp b/tests/UnitTests/PhasorDynamics/GovernorHygovTests.hpp index 825733547a..221fbfb0c9 100644 --- a/tests/UnitTests/PhasorDynamics/GovernorHygovTests.hpp +++ b/tests/UnitTests/PhasorDynamics/GovernorHygovTests.hpp @@ -70,7 +70,8 @@ namespace GridKit auto missing_trate_data = makeMinimalData(); missing_trate_data.parameters.erase(Params::Trate); - success *= constructionRejected>(missing_trate_data); + Fixture missing_trate(missing_trate_data); + success *= (!missing_trate.hygov.verify().passed()); success *= (!empty.verify().passed()); @@ -115,7 +116,8 @@ namespace GridKit { for (const RealT value : nonfinite_values) { - success *= parameterRejected(parameter, value); + Fixture invalid_fixture(makeData(), {{parameter, value}}); + success *= (!invalid_fixture.hygov.verify().passed()); } } @@ -148,7 +150,8 @@ namespace GridKit for (const auto& [parameter, value] : invalid_parameter_values) { - success *= parameterRejected(parameter, value); + Fixture invalid_fixture(makeData(), {{parameter, value}}); + success *= (!invalid_fixture.hygov.verify().passed()); } // A curve with no rise cannot yield a unique gate. @@ -185,9 +188,10 @@ namespace GridKit Fixture integer_model(integer_real); success *= (integer_model.hygov.verify().passed()); - auto bad_numeric_type = makeData(); - bad_numeric_type.parameters[Params::Trate] = true; - success *= constructionRejected>(bad_numeric_type); + 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().passed()); Fixture overflowing_component_base( makeData(), @@ -1265,20 +1269,6 @@ 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 { From ab56864a0ff7a55f33b8c9931cecadd553287475 Mon Sep 17 00:00:00 2001 From: lukelowry Date: Sun, 11 Oct 2026 09:29:41 -0500 Subject: [PATCH 08/11] polish and improve available Readers and Checkers --- CHANGELOG.md | 2 + GridKit/Model/ParameterReader.hpp | 138 +++++++++++------- .../Exciter/ESDC1A/Esdc1aImpl.hpp | 37 ++--- GridKit/Testing/Testing.hpp | 12 ++ tests/UnitTests/PhasorDynamics/CMakeLists.txt | 5 + .../PhasorDynamics/ExciterEsdc1aTests.hpp | 16 +- .../ModelConfigurationTests.hpp | 99 +++++++++++++ .../runModelConfigurationTests.cpp | 12 ++ 8 files changed, 228 insertions(+), 93 deletions(-) create mode 100644 tests/UnitTests/PhasorDynamics/ModelConfigurationTests.hpp create mode 100644 tests/UnitTests/PhasorDynamics/runModelConfigurationTests.cpp diff --git a/CHANGELOG.md b/CHANGELOG.md index 81ed51aea8..0ffbbeba06 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ - Added `Bus::setFault` to apply or clear a fault to ground directly at a bus. - Removed the `BusFault` class in favor of `Bus::setFault`. - Added `enable` and `disable` solver events that put a branch in or out of service. +- `verify()` returns `Model::ConfigurationChecks` listing every configuration problem instead of an error count. +- Added `Model::ParameterReader` ## v0.2 diff --git a/GridKit/Model/ParameterReader.hpp b/GridKit/Model/ParameterReader.hpp index 47d3f771c0..8c937e548d 100644 --- a/GridKit/Model/ParameterReader.hpp +++ b/GridKit/Model/ParameterReader.hpp @@ -8,14 +8,13 @@ #include #include -#include #include -#include #include #include #include +#include #include namespace GridKit @@ -25,12 +24,12 @@ namespace GridKit /** * @brief Reads typed parameters out of a model data container. * - * A provided value with the wrong type or a non-finite value cannot - * produce a valid model, so every method throws std::invalid_argument - * at the first such value; the message names the model and the - * parameter. An omitted parameter leaves the target untouched, so the - * target's initial value is the default. Real parameters accept integer - * values. + * A load method stores a provided value in its target and returns true. + * An omitted parameter leaves the target untouched, so the target's + * initial value is the default. A require method rejects an omitted + * parameter. A value of the wrong type cannot produce a valid model, so + * it is rejected with std::invalid_argument naming the model and the + * parameter. Real parameters accept integer values and must be finite. * * @tparam ModelDataT A model data container satisfying * PhasorDynamics::ModelData. @@ -53,93 +52,122 @@ namespace GridKit { } - /** - * @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. - */ + /// Load an optional real parameter. bool loadReal(ParametersT key, RealT& target) const { - if (!parameters_.contains(key)) + const auto* value = find(key); + if (value == nullptr) { return false; } - const auto& value = parameters_.at(key); - RealT parsed_value{}; - if (const auto* real_value = std::get_if(&value)) + RealT real{}; + if (const auto* real_value = std::get_if(value)) { - parsed_value = *real_value; + real = *real_value; } - else if (const auto* index_value = std::get_if(&value)) + else if (const auto* integer_value = std::get_if(value)) { - parsed_value = static_cast(*index_value); + real = static_cast(*integer_value); } else { reject(key, "must be numeric"); } - if (!std::isfinite(parsed_value)) + if (!std::isfinite(real)) { reject(key, "must be finite"); } - target = parsed_value; + target = real; return true; } - /** - * @brief Load an optional boolean switch parameter. - * - * @param[in] key Parameter to look up. - * @param[out] target Stores the boolean value when provided. - * @return true when the parameter was provided. - */ - bool loadSwitch(ParametersT key, bool& target) const + /// Load an optional non-negative real parameter. + bool loadNonNegative(ParametersT key, RealT& target) const { - if (!parameters_.contains(key)) + if (!loadReal(key, target)) { return false; } - const auto& value = parameters_.at(key); - if (const auto* bool_value = std::get_if(&value)) + if (target < ZERO) { - target = *bool_value; - return true; + reject(key, "must be non-negative"); } + return true; + } - reject(key, "must be boolean"); + /// Load an optional boolean parameter. + bool loadBool(ParametersT key, bool& target) const + { + return loadExact(key, target, "must be boolean"); } - /** - * @brief Load an optional integer selector parameter. - * - * @param[in] key Parameter to look up. - * @param[out] target Stores the integer value when provided. - * @return true when the parameter was provided. - */ - bool loadSelector(ParametersT key, IdxT& target) const + /// Load an optional integer parameter. + bool loadInteger(ParametersT key, IdxT& target) const + { + return loadExact(key, target, "must be an integer"); + } + + /// Read a real parameter that must be provided. + RealT requireReal(ParametersT key) const { - if (!parameters_.contains(key)) + RealT real{}; + if (!loadReal(key, real)) + { + reject(key, "is required"); + } + return real; + } + + /// Read a boolean parameter that must be provided. + bool requireBool(ParametersT key) const + { + bool flag{}; + if (!loadBool(key, flag)) + { + reject(key, "is required"); + } + return flag; + } + + private: + using ParameterMapT = decltype(ModelDataT::parameters); + using ValueT = typename ParameterMapT::mapped_type; + + /// The provided value, or nullptr when the parameter is omitted. + const ValueT* find(ParametersT key) const + { + const auto entry = parameters_.find(key); + if (entry == parameters_.end()) + { + return nullptr; + } + return &entry->second; + } + + /// Load an optional parameter that must hold exactly a T. + template + bool loadExact(ParametersT key, T& target, std::string_view reason) const + { + const auto* value = find(key); + if (value == nullptr) { return false; } - const auto& value = parameters_.at(key); - if (const auto* index_value = std::get_if(&value)) + const auto* exact_value = std::get_if(value); + if (exact_value == nullptr) { - target = *index_value; - return true; + reject(key, reason); } - reject(key, "must be an integer selector"); + target = *exact_value; + return true; } - private: /// Reject the model data with a message naming the model and parameter. [[noreturn]] void reject(ParametersT key, std::string_view reason) const { @@ -147,8 +175,8 @@ namespace GridKit std::format("{}: parameter '{}' {}", model_, magic_enum::enum_name(key), reason)); } - const std::map>& parameters_; - const char* model_; + const ParameterMapT& parameters_; + const char* model_; }; } // namespace Model } // namespace GridKit diff --git a/GridKit/Model/PhasorDynamics/Exciter/ESDC1A/Esdc1aImpl.hpp b/GridKit/Model/PhasorDynamics/Exciter/ESDC1A/Esdc1aImpl.hpp index f6f5187556..98131c28af 100644 --- a/GridKit/Model/PhasorDynamics/Exciter/ESDC1A/Esdc1aImpl.hpp +++ b/GridKit/Model/PhasorDynamics/Exciter/ESDC1A/Esdc1aImpl.hpp @@ -9,8 +9,6 @@ #include #include #include -#include -#include #include #include @@ -627,7 +625,7 @@ namespace GridKit * * No parameter is required; every parameter keeps the default * documented in the model README when omitted. A non-numeric or - * non-finite real, a non-Boolean switch, a non-integer selector, or a + * non-finite real, a non-Boolean flag, a non-integer selector, or a * negative lag throws std::invalid_argument. Integer JSON values are * accepted for real parameters. * @@ -640,24 +638,24 @@ namespace GridKit Model::ParameterReader reader(data, "Esdc1a"); - reader.loadReal(Params::Tr, Tr_); + reader.loadNonNegative(Params::Tr, Tr_); reader.loadReal(Params::Ka, Ka_); - reader.loadReal(Params::Ta, Ta_); - reader.loadReal(Params::Tb, Tb_); + reader.loadNonNegative(Params::Ta, Ta_); + reader.loadNonNegative(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.loadNonNegative(Params::Te, Te_); reader.loadReal(Params::Kf, Kf_); - reader.loadReal(Params::Tf1, Tf1_); - reader.loadSwitch(Params::Spdmlt, Spdmlt_); + reader.loadNonNegative(Params::Tf1, Tf1_); + reader.loadBool(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_); + reader.loadInteger(Params::UEL, UEL_); + reader.loadBool(Params::exclim, exclim_); setDerivedParameters(); } @@ -713,23 +711,6 @@ namespace GridKit template 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. - auto check_non_negative = [&](RealT value, const char* name) - { - if (value < ZERO) - { - throw std::invalid_argument( - std::format("Esdc1a: parameter '{}' must be non-negative", name)); - } - }; - - check_non_negative(Tr_, "Tr"); - check_non_negative(Ta_, "Ta"); - check_non_negative(Tb_, "Tb"); - check_non_negative(Te_, "Te"); - check_non_negative(Tf1_, "Tf1"); - if (Tr_ < TIME_CONSTANT_MINIMUM || Ta_ < TIME_CONSTANT_MINIMUM || Tb_ < TIME_CONSTANT_MINIMUM || Te_ < TIME_CONSTANT_MINIMUM || Tf1_ < TIME_CONSTANT_MINIMUM) diff --git a/GridKit/Testing/Testing.hpp b/GridKit/Testing/Testing.hpp index ba6839f4fd..428921e9e4 100644 --- a/GridKit/Testing/Testing.hpp +++ b/GridKit/Testing/Testing.hpp @@ -12,6 +12,7 @@ #include #include #include +#include #include namespace GridKit @@ -234,5 +235,16 @@ namespace GridKit { func(std::forward(args)...); }); } + /// True when building a model throws std::invalid_argument or the + /// configuration checks it returns report a problem. + template + inline bool configurationRejected(BuildT build) + { + bool passed = false; + const bool threw = throws([&]() + { passed = build().passed(); }); + return threw || !passed; + } + } // namespace Testing } // namespace GridKit diff --git a/tests/UnitTests/PhasorDynamics/CMakeLists.txt b/tests/UnitTests/PhasorDynamics/CMakeLists.txt index 646b18ec2b..b430e75988 100644 --- a/tests/UnitTests/PhasorDynamics/CMakeLists.txt +++ b/tests/UnitTests/PhasorDynamics/CMakeLists.txt @@ -3,6 +3,10 @@ # - Slaven Peles #]] +add_executable(test_phasor_model_configuration runModelConfigurationTests.cpp) +target_link_libraries( + test_phasor_model_configuration GridKit::phasor_dynamics_core GridKit::testing) + add_executable(test_phasor_bus runBusTests.cpp) target_link_libraries( test_phasor_bus @@ -204,6 +208,7 @@ target_link_libraries( GridKit::phasor_dynamics_systemmodel GridKit::testing) +add_test(NAME PhasorDynamicsModelConfigurationTest COMMAND test_phasor_model_configuration) add_test(NAME PhasorDynamicsBusTest COMMAND test_phasor_bus) add_test(NAME PhasorDynamicsBusSignalVoltageOutTest COMMAND test_phasor_bus_signal_voltage_out) add_test(NAME PhasorDynamicsBusSignalVoltageInTest COMMAND test_phasor_bus_signal_voltage_in) diff --git a/tests/UnitTests/PhasorDynamics/ExciterEsdc1aTests.hpp b/tests/UnitTests/PhasorDynamics/ExciterEsdc1aTests.hpp index 67dd1cd333..d16180f63a 100644 --- a/tests/UnitTests/PhasorDynamics/ExciterEsdc1aTests.hpp +++ b/tests/UnitTests/PhasorDynamics/ExciterEsdc1aTests.hpp @@ -6,7 +6,6 @@ #include #include #include -#include #include #include @@ -1194,15 +1193,12 @@ namespace GridKit { auto data = makeData(); data.parameters[parameter] = value; - try - { - Fixture fixture(data); - return !fixture.esdc1a.verify().passed(); - } - catch (const std::invalid_argument&) - { - return true; - } + return configurationRejected( + [&]() + { + Fixture fixture(data); + return fixture.esdc1a.verify(); + }); } template diff --git a/tests/UnitTests/PhasorDynamics/ModelConfigurationTests.hpp b/tests/UnitTests/PhasorDynamics/ModelConfigurationTests.hpp new file mode 100644 index 0000000000..8ed3425c0e --- /dev/null +++ b/tests/UnitTests/PhasorDynamics/ModelConfigurationTests.hpp @@ -0,0 +1,99 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace GridKit +{ + namespace Testing + { + /// One parameter per ParameterReader case, named for its value. + enum class ReaderTestParameters : std::size_t + { + omitted, + integer, + negative, + infinite, + integer_flag, + }; + + template + class ModelConfigurationTests + { + private: + using Params = ReaderTestParameters; + using DataT = PhasorDynamics::ComponentData; + + public: + /// Omitted parameters keep the default, integers widen to reals, and + /// invalid values are rejected naming the model and parameter. + TestOutcome parameterReader() + { + TestStatus success = true; + + DataT data; + data.parameters = {{Params::integer, static_cast(2)}, + {Params::negative, -ONE}, + {Params::infinite, std::numeric_limits::infinity()}, + {Params::integer_flag, static_cast(1)}}; + + const Model::ParameterReader reader(data, "Test"); + RealT real = ONE; + + success *= !reader.loadReal(Params::omitted, real); + success *= (real == ONE); + success *= (reader.requireReal(Params::integer) == TWO); + success *= throws([&]() + { reader.loadNonNegative(Params::negative, real); }); + success *= throws([&]() + { reader.loadReal(Params::infinite, real); }); + success *= throws([&]() + { reader.requireBool(Params::integer_flag); }); + + try + { + reader.requireReal(Params::omitted); + success = false; + } + catch (const std::invalid_argument& error) + { + success *= (std::string_view(error.what()) == "Test: parameter 'omitted' is required"); + } + + return success.report(__func__); + } + + /// Checks record only failed conditions, prefixed by the model name. + TestOutcome configurationChecks() + { + TestStatus success = true; + + Model::ConfigurationChecks checks("Test"); + checks.check(true, "never recorded"); + success *= checks.passed(); + + checks.check(false, "x must be positive"); + success *= !checks.passed(); + success *= (checks.errors() == std::vector{"Test: x must be positive"}); + + return success.report(__func__); + } + }; + } // namespace Testing +} // namespace GridKit diff --git a/tests/UnitTests/PhasorDynamics/runModelConfigurationTests.cpp b/tests/UnitTests/PhasorDynamics/runModelConfigurationTests.cpp new file mode 100644 index 0000000000..dce0b67b53 --- /dev/null +++ b/tests/UnitTests/PhasorDynamics/runModelConfigurationTests.cpp @@ -0,0 +1,12 @@ +#include "ModelConfigurationTests.hpp" + +int main() +{ + GridKit::Testing::TestingResults result; + GridKit::Testing::ModelConfigurationTests test; + + result += test.parameterReader(); + result += test.configurationChecks(); + + return result.summary(); +} From 7cadc6b7613664de74b0ea317f6a943cf4f101bc Mon Sep 17 00:00:00 2001 From: Luke Lowery Date: Tue, 13 Oct 2026 15:27:24 -0500 Subject: [PATCH 09/11] Update GridKit/Testing/Testing.hpp Co-authored-by: superwhiskers --- GridKit/Testing/Testing.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GridKit/Testing/Testing.hpp b/GridKit/Testing/Testing.hpp index 428921e9e4..2f676099b3 100644 --- a/GridKit/Testing/Testing.hpp +++ b/GridKit/Testing/Testing.hpp @@ -235,7 +235,7 @@ namespace GridKit { func(std::forward(args)...); }); } - /// True when building a model throws std::invalid_argument or the + /// True when building a model throws @ref std::invalid_argument or the /// configuration checks it returns report a problem. template inline bool configurationRejected(BuildT build) From 6baa4956705aaa49821c8bd49ccdf1053700f08c Mon Sep 17 00:00:00 2001 From: lukelowry Date: Tue, 13 Oct 2026 16:03:14 -0500 Subject: [PATCH 10/11] Adjust and cleanup per reviewer comments --- GridKit/Model/ConfigurationChecks.hpp | 2 +- GridKit/Model/ParameterReader.hpp | 78 ++++++------------- GridKit/Model/PhasorDynamics/Component.hpp | 9 ++- .../Exciter/ESDC1A/Esdc1aImpl.hpp | 40 +++++----- .../Model/PhasorDynamics/Load/LoadZ/LoadZ.hpp | 5 -- .../PhasorDynamics/Load/LoadZIP/LoadZIP.hpp | 5 -- .../SignalSource/ConstantSignalSource.hpp | 2 - .../SignalSource/ConstantSignalSourceImpl.hpp | 6 -- .../SignalSource/FunctionSignalSource.hpp | 2 - .../SignalSource/FunctionSignalSourceImpl.hpp | 6 -- .../BusSignalVoltageInTests.hpp | 19 ++--- .../BusSignalVoltageOutTests.hpp | 13 +--- .../ModelConfigurationTests.hpp | 57 +++++++++++--- 13 files changed, 107 insertions(+), 137 deletions(-) diff --git a/GridKit/Model/ConfigurationChecks.hpp b/GridKit/Model/ConfigurationChecks.hpp index f4b874b5fc..9cc55df1e5 100644 --- a/GridKit/Model/ConfigurationChecks.hpp +++ b/GridKit/Model/ConfigurationChecks.hpp @@ -22,7 +22,7 @@ namespace GridKit * 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 + class [[nodiscard]] ConfigurationChecks { public: ConfigurationChecks() = default; diff --git a/GridKit/Model/ParameterReader.hpp b/GridKit/Model/ParameterReader.hpp index 8c937e548d..1d0cac7c3f 100644 --- a/GridKit/Model/ParameterReader.hpp +++ b/GridKit/Model/ParameterReader.hpp @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -24,12 +25,11 @@ namespace GridKit /** * @brief Reads typed parameters out of a model data container. * - * A load method stores a provided value in its target and returns true. - * An omitted parameter leaves the target untouched, so the target's - * initial value is the default. A require method rejects an omitted - * parameter. A value of the wrong type cannot produce a valid model, so - * it is rejected with std::invalid_argument naming the model and the - * parameter. Real parameters accept integer values and must be finite. + * A read method returns the provided value or std::nullopt when the + * parameter is omitted. The model chooses a default or requires a value. + * Invalid values are rejected with std::invalid_argument naming the + * model and parameter. Real parameters accept integer values and must + * be finite. * * @tparam ModelDataT A model data container satisfying * PhasorDynamics::ModelData. @@ -52,13 +52,13 @@ namespace GridKit { } - /// Load an optional real parameter. - bool loadReal(ParametersT key, RealT& target) const + /// Read an optional real parameter. + [[nodiscard]] std::optional readReal(ParametersT key) const { const auto* value = find(key); if (value == nullptr) { - return false; + return std::nullopt; } RealT real{}; @@ -80,57 +80,30 @@ namespace GridKit reject(key, "must be finite"); } - target = real; - return true; + return real; } - /// Load an optional non-negative real parameter. - bool loadNonNegative(ParametersT key, RealT& target) const + /// Read an optional non-negative real parameter. + [[nodiscard]] std::optional readNonNegative(ParametersT key) const { - if (!loadReal(key, target)) - { - return false; - } - - if (target < ZERO) + auto value = readReal(key); + if (value && *value < ZERO) { reject(key, "must be non-negative"); } - return true; - } - - /// Load an optional boolean parameter. - bool loadBool(ParametersT key, bool& target) const - { - return loadExact(key, target, "must be boolean"); - } - - /// Load an optional integer parameter. - bool loadInteger(ParametersT key, IdxT& target) const - { - return loadExact(key, target, "must be an integer"); + return value; } - /// Read a real parameter that must be provided. - RealT requireReal(ParametersT key) const + /// Read an optional boolean parameter. + [[nodiscard]] std::optional readBool(ParametersT key) const { - RealT real{}; - if (!loadReal(key, real)) - { - reject(key, "is required"); - } - return real; + return readExact(key, "must be boolean"); } - /// Read a boolean parameter that must be provided. - bool requireBool(ParametersT key) const + /// Read an optional integer parameter. + [[nodiscard]] std::optional readInteger(ParametersT key) const { - bool flag{}; - if (!loadBool(key, flag)) - { - reject(key, "is required"); - } - return flag; + return readExact(key, "must be an integer"); } private: @@ -148,14 +121,14 @@ namespace GridKit return &entry->second; } - /// Load an optional parameter that must hold exactly a T. + /// Read an optional parameter that must hold exactly a T. template - bool loadExact(ParametersT key, T& target, std::string_view reason) const + [[nodiscard]] std::optional readExact(ParametersT key, std::string_view reason) const { const auto* value = find(key); if (value == nullptr) { - return false; + return std::nullopt; } const auto* exact_value = std::get_if(value); @@ -164,8 +137,7 @@ namespace GridKit reject(key, reason); } - target = *exact_value; - return true; + return *exact_value; } /// Reject the model data with a message naming the model and parameter. diff --git a/GridKit/Model/PhasorDynamics/Component.hpp b/GridKit/Model/PhasorDynamics/Component.hpp index 5c70cf2be6..25239263aa 100644 --- a/GridKit/Model/PhasorDynamics/Component.hpp +++ b/GridKit/Model/PhasorDynamics/Component.hpp @@ -62,8 +62,11 @@ namespace GridKit } } - /// Report every configuration problem; passed() when the model can initialize. - virtual Model::ConfigurationChecks verify() const = 0; + /// Report configuration problems. Override when the component has checks. + virtual Model::ConfigurationChecks verify() const + { + return {}; + } IdxT size() override final { @@ -287,7 +290,7 @@ namespace GridKit * * @return true when the configuration has no problems. */ - bool verifyAndLog() const + [[nodiscard]] bool verifyAndLog() const { const auto checks = verify(); for (const auto& error : checks.errors()) diff --git a/GridKit/Model/PhasorDynamics/Exciter/ESDC1A/Esdc1aImpl.hpp b/GridKit/Model/PhasorDynamics/Exciter/ESDC1A/Esdc1aImpl.hpp index 98131c28af..086f11ae56 100644 --- a/GridKit/Model/PhasorDynamics/Exciter/ESDC1A/Esdc1aImpl.hpp +++ b/GridKit/Model/PhasorDynamics/Exciter/ESDC1A/Esdc1aImpl.hpp @@ -636,26 +636,26 @@ namespace GridKit { using Params = typename ModelDataT::Parameters; - Model::ParameterReader reader(data, "Esdc1a"); - - reader.loadNonNegative(Params::Tr, Tr_); - reader.loadReal(Params::Ka, Ka_); - reader.loadNonNegative(Params::Ta, Ta_); - reader.loadNonNegative(Params::Tb, Tb_); - reader.loadReal(Params::Tc, Tc_); - reader.loadReal(Params::Vrmax, Vrmax_); - reader.loadReal(Params::Vrmin, Vrmin_); - reader.loadReal(Params::Ke, Ke_); - reader.loadNonNegative(Params::Te, Te_); - reader.loadReal(Params::Kf, Kf_); - reader.loadNonNegative(Params::Tf1, Tf1_); - reader.loadBool(Params::Spdmlt, Spdmlt_); - reader.loadReal(Params::E1, E1_); - reader.loadReal(Params::Se1, Se1_); - reader.loadReal(Params::E2, E2_); - reader.loadReal(Params::Se2, Se2_); - reader.loadInteger(Params::UEL, UEL_); - reader.loadBool(Params::exclim, exclim_); + const Model::ParameterReader reader(data, "Esdc1a"); + + Tr_ = reader.readNonNegative(Params::Tr).value_or(Tr_); + Ka_ = reader.readReal(Params::Ka).value_or(Ka_); + Ta_ = reader.readNonNegative(Params::Ta).value_or(Ta_); + Tb_ = reader.readNonNegative(Params::Tb).value_or(Tb_); + Tc_ = reader.readReal(Params::Tc).value_or(Tc_); + Vrmax_ = reader.readReal(Params::Vrmax).value_or(Vrmax_); + Vrmin_ = reader.readReal(Params::Vrmin).value_or(Vrmin_); + Ke_ = reader.readReal(Params::Ke).value_or(Ke_); + Te_ = reader.readNonNegative(Params::Te).value_or(Te_); + Kf_ = reader.readReal(Params::Kf).value_or(Kf_); + Tf1_ = reader.readNonNegative(Params::Tf1).value_or(Tf1_); + Spdmlt_ = reader.readBool(Params::Spdmlt).value_or(Spdmlt_); + E1_ = reader.readReal(Params::E1).value_or(E1_); + Se1_ = reader.readReal(Params::Se1).value_or(Se1_); + E2_ = reader.readReal(Params::E2).value_or(E2_); + Se2_ = reader.readReal(Params::Se2).value_or(Se2_); + UEL_ = reader.readInteger(Params::UEL).value_or(UEL_); + exclim_ = reader.readBool(Params::exclim).value_or(exclim_); setDerivedParameters(); } diff --git a/GridKit/Model/PhasorDynamics/Load/LoadZ/LoadZ.hpp b/GridKit/Model/PhasorDynamics/Load/LoadZ/LoadZ.hpp index 05cf61021d..ebe30a1345 100644 --- a/GridKit/Model/PhasorDynamics/Load/LoadZ/LoadZ.hpp +++ b/GridKit/Model/PhasorDynamics/Load/LoadZ/LoadZ.hpp @@ -56,11 +56,6 @@ namespace GridKit virtual int evaluateResidual() override final; virtual int evaluateJacobian() override final; - virtual Model::ConfigurationChecks verify() const override final - { - return {}; - } - public: void setR(RealT R) { diff --git a/GridKit/Model/PhasorDynamics/Load/LoadZIP/LoadZIP.hpp b/GridKit/Model/PhasorDynamics/Load/LoadZIP/LoadZIP.hpp index 86090bca76..1fdd146e2d 100644 --- a/GridKit/Model/PhasorDynamics/Load/LoadZIP/LoadZIP.hpp +++ b/GridKit/Model/PhasorDynamics/Load/LoadZIP/LoadZIP.hpp @@ -56,11 +56,6 @@ namespace GridKit int evaluateResidual() override final; int evaluateJacobian() override final; - Model::ConfigurationChecks verify() const override final - { - return {}; - } - public: void setPnom(RealT Pnom) { diff --git a/GridKit/Model/PhasorDynamics/SignalSource/ConstantSignalSource.hpp b/GridKit/Model/PhasorDynamics/SignalSource/ConstantSignalSource.hpp index d3f1023f0a..e7de26adba 100644 --- a/GridKit/Model/PhasorDynamics/SignalSource/ConstantSignalSource.hpp +++ b/GridKit/Model/PhasorDynamics/SignalSource/ConstantSignalSource.hpp @@ -51,8 +51,6 @@ namespace GridKit int evaluateResidual() override final; int evaluateJacobian() override final; - Model::ConfigurationChecks verify() const override final; - SignalPortsT& getPorts() { return ports_; diff --git a/GridKit/Model/PhasorDynamics/SignalSource/ConstantSignalSourceImpl.hpp b/GridKit/Model/PhasorDynamics/SignalSource/ConstantSignalSourceImpl.hpp index b87c77bbbb..b7163385c4 100644 --- a/GridKit/Model/PhasorDynamics/SignalSource/ConstantSignalSourceImpl.hpp +++ b/GridKit/Model/PhasorDynamics/SignalSource/ConstantSignalSourceImpl.hpp @@ -83,12 +83,6 @@ namespace GridKit return 0; } - template - Model::ConfigurationChecks ConstantSignalSource::verify() const - { - return {}; - } - template int ConstantSignalSource::initialize() { diff --git a/GridKit/Model/PhasorDynamics/SignalSource/FunctionSignalSource.hpp b/GridKit/Model/PhasorDynamics/SignalSource/FunctionSignalSource.hpp index 085bf25590..16169e0a20 100644 --- a/GridKit/Model/PhasorDynamics/SignalSource/FunctionSignalSource.hpp +++ b/GridKit/Model/PhasorDynamics/SignalSource/FunctionSignalSource.hpp @@ -54,8 +54,6 @@ namespace GridKit int evaluateResidual() override final; int evaluateJacobian() override final; - Model::ConfigurationChecks verify() const override final; - void updateTime(RealT t, RealT a) override; SignalPortsT& getPorts() diff --git a/GridKit/Model/PhasorDynamics/SignalSource/FunctionSignalSourceImpl.hpp b/GridKit/Model/PhasorDynamics/SignalSource/FunctionSignalSourceImpl.hpp index e0efa0e5f9..304810b4fd 100644 --- a/GridKit/Model/PhasorDynamics/SignalSource/FunctionSignalSourceImpl.hpp +++ b/GridKit/Model/PhasorDynamics/SignalSource/FunctionSignalSourceImpl.hpp @@ -109,12 +109,6 @@ namespace GridKit return 0; } - template - Model::ConfigurationChecks FunctionSignalSource::verify() const - { - return {}; - } - template int FunctionSignalSource::initialize() { diff --git a/tests/UnitTests/PhasorDynamics/BusSignalVoltageInTests.hpp b/tests/UnitTests/PhasorDynamics/BusSignalVoltageInTests.hpp index c405d961d8..c1e869a052 100644 --- a/tests/UnitTests/PhasorDynamics/BusSignalVoltageInTests.hpp +++ b/tests/UnitTests/PhasorDynamics/BusSignalVoltageInTests.hpp @@ -32,13 +32,6 @@ namespace GridKit BusSignalVoltageInTests() = default; ~BusSignalVoltageInTests() = default; - /// True if verify() reports a problem, as it must for a misconnected bus - template - static bool verifyFails(const BusLike& bus) - { - return !bus.verify().passed(); - } - /// Constructor, allocation, and initialization checks TestOutcome constructor() { @@ -61,14 +54,14 @@ namespace GridKit success *= isEqual(bus->Ir(), 0.0); success *= isEqual(bus->Ii(), 0.0); // Voltage inlets are mandatory: an unconnected bus fails verification - success *= verifyFails(*bus); + success *= !bus->verify().passed(); delete bus; // Initial voltage arguments are accepted for interface uniformity but not used bus = new BusT(Vr, Vi); bus->allocate(); bus->initialize(); - success *= verifyFails(*bus); + success *= !bus->verify().passed(); delete bus; bus = nullptr; @@ -123,7 +116,7 @@ namespace GridKit BusT plain; plain.allocate(); plain.initialize(); - success *= verifyFails(plain); + success *= !plain.verify().passed(); bool threw = false; try { @@ -213,15 +206,15 @@ namespace GridKit bus.initialize(); // vr connected but unlinked, vi not connected - success *= verifyFails(bus); + success *= !bus.verify().passed(); ScalarT Vr{0.1}; IdxT vr_index{0}; vr_node.link(&Vr, &vr_index); - success *= verifyFails(bus); + success *= !bus.verify().passed(); bus.getPorts().in.template port().connect(&vi_node); - success *= verifyFails(bus); + success *= !bus.verify().passed(); ScalarT Vi{0.2}; IdxT vi_index{1}; diff --git a/tests/UnitTests/PhasorDynamics/BusSignalVoltageOutTests.hpp b/tests/UnitTests/PhasorDynamics/BusSignalVoltageOutTests.hpp index 6cd8c9121e..5a1d52c60e 100644 --- a/tests/UnitTests/PhasorDynamics/BusSignalVoltageOutTests.hpp +++ b/tests/UnitTests/PhasorDynamics/BusSignalVoltageOutTests.hpp @@ -33,13 +33,6 @@ namespace GridKit BusSignalVoltageOutTests() = default; ~BusSignalVoltageOutTests() = default; - /// True if verify() reports a problem, as it must for a misconnected bus - template - static bool verifyFails(const BusLike& bus) - { - return !bus.verify().passed(); - } - /// Constructor, allocation, and initialization checks TestOutcome constructor() { @@ -145,7 +138,7 @@ namespace GridKit bus.initialize(); const auto previous_verbosity = Log::verbosity(); Log::setVerbosity(Log::Verbosity::NONE); // expected errors - success *= verifyFails(bus); + success *= !bus.verify().passed(); Log::setVerbosity(previous_verbosity); } @@ -201,12 +194,12 @@ namespace GridKit bus.allocate(); bus.initialize(); - success *= verifyFails(bus); + success *= !bus.verify().passed(); ScalarT Ir{0.1}; IdxT ir_index{0}; ir_node.link(&Ir, &ir_index); - success *= verifyFails(bus); + success *= !bus.verify().passed(); ScalarT Ii{0.2}; IdxT ii_index{1}; diff --git a/tests/UnitTests/PhasorDynamics/ModelConfigurationTests.hpp b/tests/UnitTests/PhasorDynamics/ModelConfigurationTests.hpp index 8ed3425c0e..91e0f4c698 100644 --- a/tests/UnitTests/PhasorDynamics/ModelConfigurationTests.hpp +++ b/tests/UnitTests/PhasorDynamics/ModelConfigurationTests.hpp @@ -22,9 +22,15 @@ namespace GridKit { omitted, integer, + real, + zero, negative, infinite, + not_a_number, integer_flag, + false_flag, + true_flag, + text, }; template @@ -41,7 +47,7 @@ namespace GridKit Utilities::EmptyEnum>; public: - /// Omitted parameters keep the default, integers widen to reals, and + /// Omitted parameters return no value, integers widen to reals, and /// invalid values are rejected naming the model and parameter. TestOutcome parameterReader() { @@ -49,31 +55,60 @@ namespace GridKit DataT data; data.parameters = {{Params::integer, static_cast(2)}, + {Params::real, static_cast(0.5)}, + {Params::zero, ZERO}, {Params::negative, -ONE}, {Params::infinite, std::numeric_limits::infinity()}, - {Params::integer_flag, static_cast(1)}}; + {Params::not_a_number, std::numeric_limits::quiet_NaN()}, + {Params::integer_flag, static_cast(1)}, + {Params::false_flag, false}, + {Params::true_flag, true}, + {Params::text, std::string("2")}}; const Model::ParameterReader reader(data, "Test"); - RealT real = ONE; - success *= !reader.loadReal(Params::omitted, real); - success *= (real == ONE); - success *= (reader.requireReal(Params::integer) == TWO); + success *= !reader.readReal(Params::omitted).has_value(); + success *= !reader.readNonNegative(Params::omitted).has_value(); + success *= !reader.readBool(Params::omitted).has_value(); + success *= !reader.readInteger(Params::omitted).has_value(); + success *= (reader.readReal(Params::omitted).value_or(ONE) == ONE); + success *= reader.readBool(Params::omitted).value_or(true); + + success *= (reader.readReal(Params::integer) == TWO); + success *= (reader.readReal(Params::real) == static_cast(0.5)); + success *= (reader.readReal(Params::negative) == -ONE); + success *= (reader.readNonNegative(Params::zero) == ZERO); + success *= (reader.readNonNegative(Params::integer) == TWO); + success *= (reader.readInteger(Params::integer) == static_cast(2)); + success *= (reader.readBool(Params::false_flag) == false); + success *= (reader.readBool(Params::true_flag) == true); + success *= !reader.readBool(Params::false_flag).value_or(true); + + success *= throws([&]() + { static_cast(reader.readReal(Params::infinite)); }); + success *= throws([&]() + { static_cast(reader.readReal(Params::not_a_number)); }); + success *= throws([&]() + { static_cast(reader.readReal(Params::text)); }); + success *= throws([&]() + { static_cast(reader.readReal(Params::true_flag)); }); + success *= throws([&]() + { static_cast(reader.readInteger(Params::real)); }); success *= throws([&]() - { reader.loadNonNegative(Params::negative, real); }); + { static_cast(reader.readInteger(Params::zero)); }); success *= throws([&]() - { reader.loadReal(Params::infinite, real); }); + { static_cast(reader.readInteger(Params::true_flag)); }); success *= throws([&]() - { reader.requireBool(Params::integer_flag); }); + { static_cast(reader.readNonNegative(Params::negative)); }); try { - reader.requireReal(Params::omitted); + static_cast(reader.readBool(Params::integer_flag)); success = false; } catch (const std::invalid_argument& error) { - success *= (std::string_view(error.what()) == "Test: parameter 'omitted' is required"); + success *= (std::string_view(error.what()) == "Test: parameter 'integer_flag' must be boolean"); } return success.report(__func__); From fdff03b28fb4698fd2b6146d9bb65ba24756fab0 Mon Sep 17 00:00:00 2001 From: lukelowry Date: Wed, 14 Oct 2026 15:53:24 -0500 Subject: [PATCH 11/11] empty overrides --- GridKit/Model/PhasorDynamics/Component.hpp | 7 ++----- GridKit/Model/PhasorDynamics/Load/LoadZ/LoadZ.hpp | 5 +++++ GridKit/Model/PhasorDynamics/Load/LoadZIP/LoadZIP.hpp | 5 +++++ .../PhasorDynamics/SignalSource/ConstantSignalSource.hpp | 2 ++ .../SignalSource/ConstantSignalSourceImpl.hpp | 6 ++++++ .../PhasorDynamics/SignalSource/FunctionSignalSource.hpp | 2 ++ .../SignalSource/FunctionSignalSourceImpl.hpp | 6 ++++++ 7 files changed, 28 insertions(+), 5 deletions(-) diff --git a/GridKit/Model/PhasorDynamics/Component.hpp b/GridKit/Model/PhasorDynamics/Component.hpp index 25239263aa..a634ac0ce1 100644 --- a/GridKit/Model/PhasorDynamics/Component.hpp +++ b/GridKit/Model/PhasorDynamics/Component.hpp @@ -62,11 +62,8 @@ namespace GridKit } } - /// Report configuration problems. Override when the component has checks. - virtual Model::ConfigurationChecks verify() const - { - return {}; - } + /// Report configuration problems. + virtual Model::ConfigurationChecks verify() const = 0; IdxT size() override final { diff --git a/GridKit/Model/PhasorDynamics/Load/LoadZ/LoadZ.hpp b/GridKit/Model/PhasorDynamics/Load/LoadZ/LoadZ.hpp index ebe30a1345..bdbb5e8a98 100644 --- a/GridKit/Model/PhasorDynamics/Load/LoadZ/LoadZ.hpp +++ b/GridKit/Model/PhasorDynamics/Load/LoadZ/LoadZ.hpp @@ -56,6 +56,11 @@ namespace GridKit virtual int evaluateResidual() override final; virtual int evaluateJacobian() override final; + Model::ConfigurationChecks verify() const override final + { + return {}; + } + public: void setR(RealT R) { diff --git a/GridKit/Model/PhasorDynamics/Load/LoadZIP/LoadZIP.hpp b/GridKit/Model/PhasorDynamics/Load/LoadZIP/LoadZIP.hpp index 1fdd146e2d..86090bca76 100644 --- a/GridKit/Model/PhasorDynamics/Load/LoadZIP/LoadZIP.hpp +++ b/GridKit/Model/PhasorDynamics/Load/LoadZIP/LoadZIP.hpp @@ -56,6 +56,11 @@ namespace GridKit int evaluateResidual() override final; int evaluateJacobian() override final; + Model::ConfigurationChecks verify() const override final + { + return {}; + } + public: void setPnom(RealT Pnom) { diff --git a/GridKit/Model/PhasorDynamics/SignalSource/ConstantSignalSource.hpp b/GridKit/Model/PhasorDynamics/SignalSource/ConstantSignalSource.hpp index e7de26adba..d3f1023f0a 100644 --- a/GridKit/Model/PhasorDynamics/SignalSource/ConstantSignalSource.hpp +++ b/GridKit/Model/PhasorDynamics/SignalSource/ConstantSignalSource.hpp @@ -51,6 +51,8 @@ namespace GridKit int evaluateResidual() override final; int evaluateJacobian() override final; + Model::ConfigurationChecks verify() const override final; + SignalPortsT& getPorts() { return ports_; diff --git a/GridKit/Model/PhasorDynamics/SignalSource/ConstantSignalSourceImpl.hpp b/GridKit/Model/PhasorDynamics/SignalSource/ConstantSignalSourceImpl.hpp index b7163385c4..b87c77bbbb 100644 --- a/GridKit/Model/PhasorDynamics/SignalSource/ConstantSignalSourceImpl.hpp +++ b/GridKit/Model/PhasorDynamics/SignalSource/ConstantSignalSourceImpl.hpp @@ -83,6 +83,12 @@ namespace GridKit return 0; } + template + Model::ConfigurationChecks ConstantSignalSource::verify() const + { + return {}; + } + template int ConstantSignalSource::initialize() { diff --git a/GridKit/Model/PhasorDynamics/SignalSource/FunctionSignalSource.hpp b/GridKit/Model/PhasorDynamics/SignalSource/FunctionSignalSource.hpp index 16169e0a20..085bf25590 100644 --- a/GridKit/Model/PhasorDynamics/SignalSource/FunctionSignalSource.hpp +++ b/GridKit/Model/PhasorDynamics/SignalSource/FunctionSignalSource.hpp @@ -54,6 +54,8 @@ namespace GridKit int evaluateResidual() override final; int evaluateJacobian() override final; + Model::ConfigurationChecks verify() const override final; + void updateTime(RealT t, RealT a) override; SignalPortsT& getPorts() diff --git a/GridKit/Model/PhasorDynamics/SignalSource/FunctionSignalSourceImpl.hpp b/GridKit/Model/PhasorDynamics/SignalSource/FunctionSignalSourceImpl.hpp index 304810b4fd..e0efa0e5f9 100644 --- a/GridKit/Model/PhasorDynamics/SignalSource/FunctionSignalSourceImpl.hpp +++ b/GridKit/Model/PhasorDynamics/SignalSource/FunctionSignalSourceImpl.hpp @@ -109,6 +109,12 @@ namespace GridKit return 0; } + template + Model::ConfigurationChecks FunctionSignalSource::verify() const + { + return {}; + } + template int FunctionSignalSource::initialize() {