diff --git a/CHANGELOG.md b/CHANGELOG.md index aec17250..eb388b87 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,7 +43,7 @@ - Added IDA statistics object which can be accumulated over multiple simulations. - Minor performance improvements to residual evaluation in PowerElectronics module. - Added full support for sparse Jacobians obtained with Enzyme in PhasorDynamics. - +- Added `Node` class to the PowerElectronics module to separate nodes from circuit components. ## v0.1 - Refactored code to support adding different model families. diff --git a/GridKit/Model/PowerElectronics/CMakeLists.txt b/GridKit/Model/PowerElectronics/CMakeLists.txt index aa9ba7fa..395917b4 100644 --- a/GridKit/Model/PowerElectronics/CMakeLists.txt +++ b/GridKit/Model/PowerElectronics/CMakeLists.txt @@ -1,4 +1,12 @@ +add_library(power_electronics_circuit_node INTERFACE) +target_include_directories(power_electronics_circuit_node + INTERFACE + $ + $) + +add_library(GridKit::power_electronics_circuit_node + ALIAS power_electronics_circuit_node) add_subdirectory(Capacitor) add_subdirectory(Resistor) @@ -16,6 +24,7 @@ add_subdirectory(MicrogridBusDQ) install( FILES CircuitComponent.hpp + CircuitNode.hpp CircuitGraph.hpp SystemModelPowerElectronics.hpp DESTINATION diff --git a/GridKit/Model/PowerElectronics/CircuitNode.hpp b/GridKit/Model/PowerElectronics/CircuitNode.hpp new file mode 100644 index 00000000..5fd096f6 --- /dev/null +++ b/GridKit/Model/PowerElectronics/CircuitNode.hpp @@ -0,0 +1,355 @@ +#pragma once + +#include +#include +#include +#include + +#include + +namespace GridKit +{ + /** + * @brief Circuit node representing a connection point. + */ + template + class CircuitNode : public Model::Evaluator + { + using RealT = typename Model::Evaluator::RealT; + using MatrixT = typename Model::Evaluator::MatrixT; + + public: + CircuitNode() + { + size_ = 1; + } + + CircuitNode(ScalarT v0) + : V0_(v0) + { + size_ = 1; + } + + ~CircuitNode() = default; + + int setNodeID(IdxT id) + { + id_ = id; + return 0; + } + + IdxT nodeID() const + { + return id_; + } + + // Voltage accessor + ScalarT& V() + { + return y_[0]; + } + + const ScalarT& V() const + { + return y_[0]; + } + + // KCL residual accessor + ScalarT& I() + { + return f_[0]; + } + + const ScalarT& I() const + { + return f_[0]; + } + + // Allocate storage for a single-node voltage and KCL residual + int allocate() + { + size_t size = static_cast(size_); + + y_.resize(size); + yp_.resize(size); + f_.resize(size); + tag_.resize(size); + + variable_indices_[0] = 0; + residual_indices_[0] = 0; + + return 0; + } + + /** + * @brief Initialize node variables + */ + int initialize() + { + y_[0] = V0_; + yp_[0] = 0.0; + + return 0; + } + + /** + * @brief Node variables are algebraic. + */ + int tagDifferentiable() + { + tag_[0] = false; + + return 0; + } + + /** + * @brief Node does not compute residuals, so here we just reset residual values. + * + * @warning This implementation assumes node residuals are always evaluated + * _before_ component model residuals. + * + */ + int evaluateResidual() + { + f_[0] = 0.0; + + return 0; + } + + bool hasJacobian() final + { + return false; + } + + /** + * @brief There is no Jacobian for node variables + */ + int evaluateJacobian() + { + return 0; + } + + int evaluateIntegrand() + { + return 0; + } + + int initializeAdjoint() + { + return 0; + } + + int evaluateAdjointResidual() + { + return 0; + } + + int evaluateAdjointIntegrand() + { + return 0; + } + + private: + IdxT id_{static_cast(-1)}; + IdxT size_{0}; + IdxT nnz_{0}; + IdxT size_quad_{0}; + IdxT size_opt_{0}; + ScalarT V0_{0.0}; + + std::map variable_indices_; + std::map residual_indices_; + + std::vector y_; + std::vector yp_; + std::vector tag_; + std::vector f_; + + MatrixT J_; + + std::vector g_{}; + std::vector param_{}; + std::vector param_up_{}; + std::vector param_lo_{}; + + std::vector yB_{}; + std::vector ypB_{}; + std::vector fB_{}; + std::vector gB_{}; + + RealT time_{0}; + RealT alpha_{0}; + + RealT rel_tol_{0}; + RealT abs_tol_{0}; + + IdxT max_steps_{0}; + + public: + IdxT size() final + { + return size_; + } + + IdxT nnz() final + { + return nnz_; + } + + IdxT sizeQuadrature() final + { + return size_quad_; + } + + IdxT sizeParams() final + { + return size_opt_; + } + + void updateTime(RealT /* t */, RealT /* a */) final + { + // No time to update in node models + } + + void setTolerances(RealT& rel_tol, RealT& abs_tol) const final + { + rel_tol = rel_tol_; + abs_tol = abs_tol_; + } + + void setMaxSteps(IdxT& msa) const final + { + msa = max_steps_; + } + + std::vector& y() final + { + return y_; + } + + const std::vector& y() const final + { + return y_; + } + + std::vector& yp() final + { + return yp_; + } + + const std::vector& yp() const final + { + return yp_; + } + + std::vector& tag() final + { + return tag_; + } + + const std::vector& tag() const final + { + return tag_; + } + + std::vector& yB() final + { + return yB_; + } + + const std::vector& yB() const final + { + return yB_; + } + + std::vector& ypB() final + { + return ypB_; + } + + const std::vector& ypB() const final + { + return ypB_; + } + + std::vector& param() final + { + return param_; + } + + const std::vector& param() const final + { + return param_; + } + + std::vector& param_up() final + { + return param_up_; + } + + const std::vector& param_up() const final + { + return param_up_; + } + + std::vector& param_lo() final + { + return param_lo_; + } + + const std::vector& param_lo() const final + { + return param_lo_; + } + + std::vector& getResidual() final + { + return f_; + } + + const std::vector& getResidual() const final + { + return f_; + } + + MatrixT& getJacobian() final + { + return J_; + } + + const MatrixT& getJacobian() const final + { + return J_; + } + + std::vector& getIntegrand() final + { + return g_; + } + + const std::vector& getIntegrand() const final + { + return g_; + } + + std::vector& getAdjointResidual() final + { + return fB_; + } + + const std::vector& getAdjointResidual() const final + { + return fB_; + } + + std::vector& getAdjointIntegrand() final + { + return gB_; + } + + const std::vector& getAdjointIntegrand() const final + { + return gB_; + } + }; +} // namespace GridKit diff --git a/tests/UnitTests/CMakeLists.txt b/tests/UnitTests/CMakeLists.txt index c002660b..3c7e0892 100644 --- a/tests/UnitTests/CMakeLists.txt +++ b/tests/UnitTests/CMakeLists.txt @@ -2,5 +2,6 @@ add_subdirectory(AutomaticDifferentiation) add_subdirectory(LinearAlgebra) add_subdirectory(PhasorDynamics) +add_subdirectory(PowerElectronics) add_subdirectory(Solver) add_subdirectory(Utilities) diff --git a/tests/UnitTests/PowerElectronics/CMakeLists.txt b/tests/UnitTests/PowerElectronics/CMakeLists.txt new file mode 100644 index 00000000..a9bcbcc9 --- /dev/null +++ b/tests/UnitTests/PowerElectronics/CMakeLists.txt @@ -0,0 +1,7 @@ +add_executable(test_power_electronics_node runCircuitNodeTests.cpp) +target_link_libraries(test_power_electronics_node PRIVATE GridKit::power_electronics_circuit_node) + +add_test(NAME PowerElectronicsNodeTest COMMAND $) + +install(TARGETS test_power_electronics_node + RUNTIME DESTINATION bin) diff --git a/tests/UnitTests/PowerElectronics/CircuitNodeTests.hpp b/tests/UnitTests/PowerElectronics/CircuitNodeTests.hpp new file mode 100644 index 00000000..2d90f2ab --- /dev/null +++ b/tests/UnitTests/PowerElectronics/CircuitNodeTests.hpp @@ -0,0 +1,74 @@ + +#include +#include + +#include +#include +#include + +namespace GridKit +{ + namespace Testing + { + template + class NodeTests + { + public: + NodeTests() = default; + ~NodeTests() = default; + + /// Constructor, allocation, and initialization checks + TestOutcome constructor() + { + TestStatus success = true; + + ScalarT V{1.0}; + + CircuitNode* node = nullptr; + + // Default construct + node = new CircuitNode(); + node->allocate(); + node->initialize(); + success *= isEqual(node->V(), static_cast(0)); + success *= isEqual(node->I(), static_cast(0)); + delete node; + + // Construct with initial voltage + node = new CircuitNode(V); + node->allocate(); + node->initialize(); + success *= isEqual(node->V(), V); + success *= isEqual(node->I(), static_cast(0)); + delete node; + + node = nullptr; + + return success.report(__func__); + } + + /// Accessor method tests + TestOutcome residual() + { + TestStatus success = true; + + ScalarT V{1.0}; + ScalarT I{1.0}; + + CircuitNode node(V); + node.allocate(); + node.initialize(); + success *= isEqual(node.V(), V); + + node.I() = I; + success *= isEqual(node.I(), I); + + node.evaluateResidual(); + success *= isEqual(node.I(), 0.0); + + return success.report(__func__); + } + }; + + } // namespace Testing +} // namespace GridKit diff --git a/tests/UnitTests/PowerElectronics/runCircuitNodeTests.cpp b/tests/UnitTests/PowerElectronics/runCircuitNodeTests.cpp new file mode 100644 index 00000000..600babd5 --- /dev/null +++ b/tests/UnitTests/PowerElectronics/runCircuitNodeTests.cpp @@ -0,0 +1,12 @@ +#include "CircuitNodeTests.hpp" + +int main() +{ + GridKit::Testing::TestingResults result; + GridKit::Testing::NodeTests test; + + result += test.constructor(); + result += test.residual(); + + return result.summary(); +}