Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
9 changes: 7 additions & 2 deletions GridKit/Model/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
68 changes: 68 additions & 0 deletions GridKit/Model/ConfigurationChecks.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/**
* @file ConfigurationChecks.hpp
* @author Luke Lowery (lukel@tamu.edu)
* @brief Collects the configuration problems found by a model.
*/

#pragma once

#include <format>
#include <string>
#include <string_view>
#include <vector>

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 [[nodiscard]] 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_view message)
{
errors_.push_back(prefix_ + std::string(message));
}

/// Record the message when the condition does not hold.
void check(bool condition, std::string_view message)
{
if (!condition)
{
fail(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<std::string>& errors() const
{
return errors_;
}

Comment thread
lukelowry marked this conversation as resolved.
Resolved
private:
std::string prefix_;
std::vector<std::string> errors_;
};
} // namespace Model
} // namespace GridKit
154 changes: 154 additions & 0 deletions GridKit/Model/ParameterReader.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
/**
* @file ParameterReader.hpp
* @author Luke Lowery (lukel@tamu.edu)
* @brief Typed parameter loading from model data containers.
*/

#pragma once

#include <cmath>
#include <format>
#include <optional>
#include <stdexcept>
#include <string_view>
#include <variant>

#include <magic_enum/magic_enum.hpp>

#include <GridKit/Constants.hpp>
#include <GridKit/Model/PhasorDynamics/ModelData.hpp>

namespace GridKit
{
namespace Model
{
/**
* @brief Reads typed parameters out of a model data container.
*
* 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.
*/
template <PhasorDynamics::ModelData ModelDataT>
class ParameterReader
Comment thread
superwhiskers marked this conversation as resolved.
Resolved
{
public:
using RealT = typename ModelDataT::RealT;
using IdxT = typename ModelDataT::IdxT;
using ParametersT = typename ModelDataT::Parameters;

/**
* @param[in] data Model data container to read from.
* @param[in] model Model name used in rejection messages.
*/
ParameterReader(const ModelDataT& data, const char* model)
: parameters_(data.parameters),
model_(model)
{
}

/// Read an optional real parameter.
[[nodiscard]] std::optional<RealT> readReal(ParametersT key) const
{
const auto* value = find(key);
if (value == nullptr)
{
return std::nullopt;
}

RealT real{};
if (const auto* real_value = std::get_if<RealT>(value))
{
real = *real_value;
}
else if (const auto* integer_value = std::get_if<IdxT>(value))
{
real = static_cast<RealT>(*integer_value);
}
else
{
reject(key, "must be numeric");
}

if (!std::isfinite(real))
{
reject(key, "must be finite");
}

return real;
}

/// Read an optional non-negative real parameter.
[[nodiscard]] std::optional<RealT> readNonNegative(ParametersT key) const
{
auto value = readReal(key);
if (value && *value < ZERO<RealT>)
{
reject(key, "must be non-negative");
}
return value;
}

/// Read an optional boolean parameter.
[[nodiscard]] std::optional<bool> readBool(ParametersT key) const
{
return readExact<bool>(key, "must be boolean");
}

/// Read an optional integer parameter.
[[nodiscard]] std::optional<IdxT> readInteger(ParametersT key) const
{
return readExact<IdxT>(key, "must be an integer");
}

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;
}

/// Read an optional parameter that must hold exactly a T.
template <typename T>
[[nodiscard]] std::optional<T> readExact(ParametersT key, std::string_view reason) const
{
const auto* value = find(key);
if (value == nullptr)
{
return std::nullopt;
}

const auto* exact_value = std::get_if<T>(value);
if (exact_value == nullptr)
{
reject(key, reason);
}

return *exact_value;
}

/// Reject the model data with a message naming the model and parameter.
[[noreturn]] void reject(ParametersT key, std::string_view reason) const
{
throw std::invalid_argument(
std::format("{}: parameter '{}' {}", model_, magic_enum::enum_name(key), reason));
}

const ParameterMapT& parameters_;
const char* model_;
};
} // namespace Model
} // namespace GridKit
3 changes: 2 additions & 1 deletion GridKit/Model/PhasorDynamics/Branch/Branch.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@ namespace GridKit
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 Model::ConfigurationChecks verify() const override final;

int setInService(bool in_service);

Expand Down
45 changes: 19 additions & 26 deletions GridKit/Model/PhasorDynamics/Branch/BranchImpl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -154,34 +154,27 @@ namespace GridKit
}

template <typename scalar_type, typename index_type>
int Branch<scalar_type, index_type>::verify() const
Model::ConfigurationChecks Branch<scalar_type, index_type>::verify() const
{
int ret = parameter_error_count_;
Model::ConfigurationChecks checks("Branch");

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;
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");

checks.check(std::isfinite(R_), "R must be finite");
checks.check(std::isfinite(X_), "X must be finite");
checks.check(std::isfinite(G_), "G must be finite");
checks.check(std::isfinite(B_), "B must be finite");
checks.check(std::isfinite(Gmag_), "Gmag must be finite");
checks.check(std::isfinite(Bmag_), "Bmag must be finite");
checks.check(std::isfinite(tap_), "tap must be finite");
checks.check(std::isfinite(phase_), "phase must be finite");
checks.check(R_ * R_ + X_ * X_ > RealT{0.0}, "R and X cannot both be zero");
checks.check(tap_ > RealT{0.0}, "tap must be positive");

return checks;
}

/// Put the branch in or out of service; out of service, it injects no current.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down Expand Up @@ -70,13 +70,14 @@ namespace GridKit

virtual int setBusID(IdxT) override final;
virtual int allocate() override final;
virtual int 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 Model::ConfigurationChecks verify() const override final;

virtual BusTypeT BusType() const override final
{
return BusTypeT::SIGNAL_VOLTAGE_IN;
Expand Down
Loading
Loading