From b9408d4c4cc675dfccb32c9c6d7a55cec7125682 Mon Sep 17 00:00:00 2001 From: Philip Fackler Date: Thu, 13 Aug 2026 10:47:17 -0500 Subject: [PATCH 1/3] Add simple one-sided co-sim example taking constant current over zmq --- .../Model/PhasorDynamics/SystemModelData.cpp | 13 -- .../Model/PhasorDynamics/SystemModelData.hpp | 1 - examples/CMakeLists.txt | 1 + examples/Network/CMakeLists.txt | 3 + examples/Network/ThreeBusCoSim/CMakeLists.txt | 18 ++ examples/Network/ThreeBusCoSim/CoSim.hpp | 12 ++ .../ThreeBusCoSimClient.case.json | 169 ++++++++++++++++++ .../ThreeBusCoSim/ThreeBusCoSimClient.cpp | 134 ++++++++++++++ .../ThreeBusCoSimServer.case.json | 167 +++++++++++++++++ .../ThreeBusCoSim/ThreeBusCoSimServer.cpp | 83 +++++++++ examples/Network/ThreeBusCoSim/run_cosim.sh | 7 + .../ThreeBusConstantSource.case.json | 6 + 12 files changed, 600 insertions(+), 14 deletions(-) create mode 100644 examples/Network/CMakeLists.txt create mode 100644 examples/Network/ThreeBusCoSim/CMakeLists.txt create mode 100644 examples/Network/ThreeBusCoSim/CoSim.hpp create mode 100644 examples/Network/ThreeBusCoSim/ThreeBusCoSimClient.case.json create mode 100644 examples/Network/ThreeBusCoSim/ThreeBusCoSimClient.cpp create mode 100644 examples/Network/ThreeBusCoSim/ThreeBusCoSimServer.case.json create mode 100644 examples/Network/ThreeBusCoSim/ThreeBusCoSimServer.cpp create mode 100644 examples/Network/ThreeBusCoSim/run_cosim.sh diff --git a/GridKit/Model/PhasorDynamics/SystemModelData.cpp b/GridKit/Model/PhasorDynamics/SystemModelData.cpp index f458583be..0dcada8a8 100644 --- a/GridKit/Model/PhasorDynamics/SystemModelData.cpp +++ b/GridKit/Model/PhasorDynamics/SystemModelData.cpp @@ -32,18 +32,5 @@ namespace GridKit } return parseSystemModelData(stream); } - - SystemModelData parseSystemModelData(const std::string& fileName) - { - auto stream = std::ifstream(fileName); - if (!stream) - { - std::stringstream ss; - ss << "Could not open file: " << fileName; - Log::error() << ss.str() << std::endl; - throw std::runtime_error(ss.str()); - } - return parseSystemModelData(stream); - } } // namespace PhasorDynamics } // namespace GridKit diff --git a/GridKit/Model/PhasorDynamics/SystemModelData.hpp b/GridKit/Model/PhasorDynamics/SystemModelData.hpp index 61014df63..95f3137b6 100644 --- a/GridKit/Model/PhasorDynamics/SystemModelData.hpp +++ b/GridKit/Model/PhasorDynamics/SystemModelData.hpp @@ -135,6 +135,5 @@ namespace GridKit SystemModelData parseSystemModelData(std::istream& stream); SystemModelData parseSystemModelData(std::istream&& stream); SystemModelData parseSystemModelData(const std::filesystem::path& filePath); - SystemModelData parseSystemModelData(const std::string& fileName); } // namespace PhasorDynamics } // namespace GridKit diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 38dae920d..e77f7d6ca 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -51,6 +51,7 @@ add_subdirectory(Experimental) if(TARGET SUNDIALS::idas) add_subdirectory(PhasorDynamics) + add_subdirectory(Network) endif() add_subdirectory(Consumer) diff --git a/examples/Network/CMakeLists.txt b/examples/Network/CMakeLists.txt new file mode 100644 index 000000000..1f1c0e526 --- /dev/null +++ b/examples/Network/CMakeLists.txt @@ -0,0 +1,3 @@ +if(GRIDKIT_ENABLE_ZMQ) + add_subdirectory(ThreeBusCoSim) +endif() diff --git a/examples/Network/ThreeBusCoSim/CMakeLists.txt b/examples/Network/ThreeBusCoSim/CMakeLists.txt new file mode 100644 index 000000000..c20ed52a4 --- /dev/null +++ b/examples/Network/ThreeBusCoSim/CMakeLists.txt @@ -0,0 +1,18 @@ +gridkit_example_add_file(ThreeBusCoSimClient.case.json) +gridkit_example_add_file(ThreeBusCoSimServer.case.json) +gridkit_example_add_file(run_cosim.sh) + +add_executable(CoSimClient ThreeBusCoSimClient.cpp) +target_link_libraries( + CoSimClient + PUBLIC GridKit::phasor_dynamics_systemmodel GridKit::solvers_dyn ZMQ) + +add_executable(CoSimServer ThreeBusCoSimServer.cpp) +target_link_libraries( + CoSimServer + PUBLIC GridKit::phasor_dynamics_systemmodel GridKit::solvers_dyn ZMQ) + +add_test( + NAME CoSimExample + COMMAND bash run_cosim.sh + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) diff --git a/examples/Network/ThreeBusCoSim/CoSim.hpp b/examples/Network/ThreeBusCoSim/CoSim.hpp new file mode 100644 index 000000000..c519b0e6c --- /dev/null +++ b/examples/Network/ThreeBusCoSim/CoSim.hpp @@ -0,0 +1,12 @@ +#pragma once + +namespace GridKit +{ + namespace CoSim + { + using StatusT = int; + + inline constexpr StatusT STEP = 1; + inline constexpr StatusT END = 0; + } // namespace CoSim +} // namespace GridKit diff --git a/examples/Network/ThreeBusCoSim/ThreeBusCoSimClient.case.json b/examples/Network/ThreeBusCoSim/ThreeBusCoSimClient.case.json new file mode 100644 index 000000000..4e07aaa7e --- /dev/null +++ b/examples/Network/ThreeBusCoSim/ThreeBusCoSimClient.case.json @@ -0,0 +1,169 @@ +{ + "header": { + "format_version": 0.2, + "format_revision": 0, + "case_name": "ThreeBusCoSimClient", + "case_description": "3-bus test case sending voltage and receiving current", + "case_comments": "None" + }, + "params": { + "freq_base": 60.0, + "va_base": 100000000.0 + }, + "monitors": [ + { + "file_name": "mon.csv", + "format":"csv" + } + ], + "buses": [ + { + "number": 1, + "class": "BusInfinite", + "name": "1", + "init": { + "Vr": 1.06, + "Vi": 0.0 + }, + "params": {"kv": 138.0}, + "mon": [ + "Vm" + ] + }, + { + "number": 2, + "class": "Bus", + "name": "2", + "init": { + "Vr": 1.0599558398065716, + "Vi": -0.009675621941024773 + }, + "params": {"kv": 138.0}, + "mon": [ + "Vm" + ] + }, + { + "number": 3, + "class": "Bus", + "name": "3", + "init": { + "Vr": 0.9610827543495831, + "Vi": -0.13122476630506485 + }, + "params": {"kv": 138.0}, + "mon": [ + "Vm" + ] + } + ], + "signals": [ + { "signal_id": 1, "name": "vr"}, + { "signal_id": 2, "name": "vi"}, + { "signal_id": 3, "name": "ir"}, + { "signal_id": 4, "name": "ii"} + ], + "devices": [ + { + "class": "Branch", + "ports": { + "bus1": 1, + "bus2": 2 + }, + "id": "BR_0_1", + "params": { + "R": 0.05, + "X": 0.21, + "G": 0.0, + "B": 0.1 + } + }, + { + "class": "Branch", + "ports": { + "bus1": 1, + "bus2": 3 + }, + "id": "BR_0_2", + "params": { + "R": 0.06, + "X": 0.15, + "G": 0.0, + "B": 0.12 + } + }, + { + "class": "Branch", + "ports": { + "bus1": 2, + "bus2": 3 + }, + "id": "BR_1_2", + "params": { + "R": 0.08, + "X": 0.27, + "G": 0.0, + "B": 0.45 + } + }, + { + "class": "LoadZ", + "ports": { + "bus": 3 + }, + "id": "load_2_0", + "params": { + "R": 0.4447197839297772, + "X": 0.20330047265361242 + } + }, + { + "class": "BusFault", + "ports": { + "bus": 3 + }, + "id": "bus_fault_2", + "params": { + "R": 0.0, + "X": 1e-5, + "state0": false + } + }, + { + "class": "Genrou", + "ports": { + "bus": 2 + }, + "id": "genrou_2_1", + "params": { + "p0": 0.5, + "q0": -0.07588, + "H": 2.7, + "D": 0.0, + "Ra": 0.0, + "Tdop": 7.0, + "Tdopp": 0.04, + "Tqopp": 0.05, + "Tqop": 0.75, + "Xd": 1.9, + "Xdp": 0.17, + "Xdpp": 0.15, + "Xq": 0.4, + "Xqp": 0.35, + "Xqpp": 0.15, + "Xl": 0.14999, + "S10": 0.0, + "S12": 0.0 + }, + "mon": [ + "speed" + ] + }, + { + "class": "BusToSignalAdapter", + "ports": {"bus":1, "vr":1, "vi":2, "ir":3, "ii":4}, + "id": "adapt3", + "params": {} + } + ] +} diff --git a/examples/Network/ThreeBusCoSim/ThreeBusCoSimClient.cpp b/examples/Network/ThreeBusCoSim/ThreeBusCoSimClient.cpp new file mode 100644 index 000000000..cdf47da7c --- /dev/null +++ b/examples/Network/ThreeBusCoSim/ThreeBusCoSimClient.cpp @@ -0,0 +1,134 @@ +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "CoSim.hpp" +#include + +using namespace GridKit; +using namespace GridKit::PhasorDynamics; +using namespace AnalysisManager::Sundials; + +template +class CoSimClient +{ +public: + using ScalarT = scalar_type; + using IdxT = index_type; + using SystemModelT = SystemModel; + using SignalT = typename SystemModelT::SignalT; + + CoSimClient() = delete; + + CoSimClient(SignalT* vr, SignalT* vi, SignalT* ir, SignalT* ii) + : vr_signal_(vr), + vi_signal_(vi), + ir_signal_(ir), + ii_signal_(ii), + ctx_{}, + socket_(ctx_, zmq::socket_type::req) + { + ir_signal_->set(&ir_, &ir_idx_); + ii_signal_->set(&ii_, &ii_idx_); + socket_.connect("tcp://0.0.0.0:5556"); + } + + ~CoSimClient() + { + std::ostringstream oss; + oss << CoSim::END << " " << vr_signal_->read() << " " << vi_signal_->read(); + zmq::message_t s_msg{oss.str().data(), oss.str().size()}; + socket_.send(s_msg, zmq::send_flags::none); + + zmq::message_t r_msg; + auto recv_result = socket_.recv(r_msg, zmq::recv_flags::none); + if (recv_result) + { + } + } + + void exchange() + { + // 1. Send data + std::ostringstream oss; + oss << std::scientific << std::setprecision(16); + oss << CoSim::STEP << " " << vr_signal_->read() << " " << vi_signal_->read(); + // std::cout << "[CLIENT] Sending: " << oss.str() << std::endl; + + zmq::message_t s_msg{oss.str().data(), oss.str().size()}; + socket_.send(s_msg, zmq::send_flags::none); + + // 2. Receive data from DataBroker + zmq::message_t r_msg; + auto recv_result = socket_.recv(r_msg, zmq::recv_flags::none); + if (recv_result) + { + std::istringstream iss(r_msg.to_string()); + // std::cout << "[CLIENT] Received: " << iss.str() << std::endl; + iss >> ir_ >> ii_; + } + } + +private: + SignalT* vr_signal_; + SignalT* vi_signal_; + SignalT* ir_signal_; + SignalT* ii_signal_; + ScalarT ir_{}; + ScalarT ii_{}; + IdxT ir_idx_{GridKit::INVALID_INDEX}; + IdxT ii_idx_{GridKit::INVALID_INDEX}; + zmq::context_t ctx_; + zmq::socket_t socket_; +}; + +using ScalarT = double; +using RealT = double; +using IdxT = std::size_t; + +int main() +{ + // Instantiate system + auto data = parseSystemModelData("ThreeBusCoSimClient.case.json"); + auto sys = SystemModel(data); + CoSimClient client( + sys.getSignal(1), sys.getSignal(2), sys.getSignal(3), sys.getSignal(4)); + sys.allocate(); + client.exchange(); + + // Set up simulation + Ida ida(&sys); + ida.setTolerance(1.0e-7, 1.0e-9); + ida.configureSimulation(); + + // TODO: Take one step at a time and exchange data between. + // Just use step_callback + auto step_cb = [&client](auto) + { + client.exchange(); + }; + + RealT dt = 1.0 / 4.0 / 60.0; + + // Run for 1s + ida.initializeSimulation(0.0); + ida.runSimulation(1.0, dt, step_cb); + + // Introduce fault and run for the next 0.1s + sys.getBusFault(0)->setStatus(true); + ida.initializeSimulation(1.0); + ida.runSimulation(1.1, dt, step_cb); + + // Clear the fault and run until t = 10s. + sys.getBusFault(0)->setStatus(false); + ida.initializeSimulation(1.1); + ida.runSimulation(10.0, dt, step_cb); + + return 0; +} diff --git a/examples/Network/ThreeBusCoSim/ThreeBusCoSimServer.case.json b/examples/Network/ThreeBusCoSim/ThreeBusCoSimServer.case.json new file mode 100644 index 000000000..970ebd1ae --- /dev/null +++ b/examples/Network/ThreeBusCoSim/ThreeBusCoSimServer.case.json @@ -0,0 +1,167 @@ +{ + "header": { + "format_version": 0.2, + "format_revision": 0, + "case_name": "ThreeBusCoSimServer", + "case_description": "3-bus test case receiving voltage and sending current", + "case_comments": "None" + }, + "params": { + "freq_base": 60.0, + "va_base": 100000000.0 + }, + "buses": [ + { + "number": 1, + "class": "BusInfinite", + "name": "1", + "init": { + "Vr": 1.06, + "Vi": 0.0 + }, + "params": {"kv": 138.0}, + "mon": [ + "Vm" + ] + }, + { + "number": 2, + "class": "Bus", + "name": "2", + "init": { + "Vr": 1.0599558398065716, + "Vi": -0.009675621941024773 + }, + "params": {"kv": 138.0}, + "mon": [ + "Vm" + ] + }, + { + "number": 3, + "class": "Bus", + "name": "3", + "init": { + "Vr": 0.9610827543495831, + "Vi": -0.13122476630506485 + }, + "params": {"kv": 138.0}, + "mon": [ + "Vm" + ] + } + ], + "signals": [ + { "signal_id": 1, "name": "ir1"}, + { "signal_id": 2, "name": "ir2"} + ], + "devices": [ + { + "class": "Branch", + "ports": { + "bus1": 1, + "bus2": 2 + }, + "id": "BR_0_1", + "params": { + "R": 0.05, + "X": 0.21, + "G": 0.0, + "B": 0.1 + } + }, + { + "class": "Branch", + "ports": { + "bus1": 1, + "bus2": 3 + }, + "id": "BR_0_2", + "params": { + "R": 0.06, + "X": 0.15, + "G": 0.0, + "B": 0.12 + } + }, + { + "class": "Branch", + "ports": { + "bus1": 2, + "bus2": 3 + }, + "id": "BR_1_2", + "params": { + "R": 0.08, + "X": 0.27, + "G": 0.0, + "B": 0.45 + } + }, + { + "class": "LoadZ", + "ports": { + "bus": 3 + }, + "id": "load_2_0", + "params": { + "R": 0.4447197839297772, + "X": 0.20330047265361242 + } + }, + { + "class": "BusFault", + "ports": { + "bus": 3 + }, + "id": "bus_fault_2", + "params": { + "R": 0.0, + "X": 1e-5, + "state0": false + } + }, + { + "class": "Genrou", + "ports": { + "bus": 2 + }, + "id": "genrou_2_1", + "params": { + "p0": 0.5, + "q0": -0.07588, + "H": 2.7, + "D": 0.0, + "Ra": 0.0, + "Tdop": 7.0, + "Tdopp": 0.04, + "Tqopp": 0.05, + "Tqop": 0.75, + "Xd": 1.9, + "Xdp": 0.17, + "Xdpp": 0.15, + "Xq": 0.4, + "Xqp": 0.35, + "Xqpp": 0.15, + "Xl": 0.14999, + "S10": 0.0, + "S12": 0.0 + }, + "mon": [ + "speed" + ] + }, + { + "class": "BusToSignalAdapter", + "ports": {"bus":1, "ir":1, "ii":2}, + "id": "adapt3", + "params": {} + }, + { + "class": "ConstantSignalSource", + "ports": {"sr":1, "si":2}, + "id": "const_source_1", + "params": {"Sr":0.0, "Si":0.0} + } + ] +} diff --git a/examples/Network/ThreeBusCoSim/ThreeBusCoSimServer.cpp b/examples/Network/ThreeBusCoSim/ThreeBusCoSimServer.cpp new file mode 100644 index 000000000..d3a45db4a --- /dev/null +++ b/examples/Network/ThreeBusCoSim/ThreeBusCoSimServer.cpp @@ -0,0 +1,83 @@ +#include +#include +#include + +#include +#include +#include + +#include "CoSim.hpp" +#include + +using ScalarT = double; +using IdxT = std::size_t; + +using namespace GridKit; +using namespace GridKit::PhasorDynamics; + +template +class CoSimServer +{ +public: + using ScalarT = scalar_type; + using IdxT = index_type; + using SystemModelT = GridKit::PhasorDynamics::SystemModel; + using SignalT = typename SystemModelT::SignalT; + + CoSimServer() = delete; + + CoSimServer(SignalT* ir, SignalT* ii) + : ir_signal_(ir), + ii_signal_(ii), + ctx_{}, + socket_(ctx_, zmq::socket_type::rep) + { + socket_.bind("tcp://0.0.0.0:5556"); + } + + void start() + { + CoSim::StatusT status; + ScalarT d1, d2; + do + { + // 1. Receive data + zmq::message_t msg; + auto recv_result = socket_.recv(msg, zmq::recv_flags::none); + if (recv_result) + { + std::istringstream(msg.to_string()) >> status >> d1 >> d2; + // std::cout << "[SERVER] Received: " << msg.to_string_view() << std::endl; + } + + // 2. Respond with new data + std::ostringstream oss; + oss << std::scientific << std::setprecision(16); + oss << ir_signal_->read() << " " << ii_signal_->read(); + // std::cout << "[SERVER] Sending: " << oss.str() << std::endl; + + zmq::message_t reply{oss.str().data(), oss.str().size()}; + socket_.send(reply, zmq::send_flags::none); + } while (status == CoSim::STEP); + } + +private: + SignalT* ir_signal_; + SignalT* ii_signal_; + zmq::context_t ctx_; + zmq::socket_t socket_; +}; + +int main() +{ + // Instantiate system + auto data = parseSystemModelData("ThreeBusCoSimServer.case.json"); + auto sys = SystemModel(data); + sys.allocate(); + + // Set up cosim + CoSimServer server(sys.getSignal(1), sys.getSignal(2)); + server.start(); + + return 0; +} diff --git a/examples/Network/ThreeBusCoSim/run_cosim.sh b/examples/Network/ThreeBusCoSim/run_cosim.sh new file mode 100644 index 000000000..4dabf3bfe --- /dev/null +++ b/examples/Network/ThreeBusCoSim/run_cosim.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +./CoSimServer & + +./CoSimClient + +wait diff --git a/examples/PhasorDynamics/Tiny/ThreeBus/ConstantSource/ThreeBusConstantSource.case.json b/examples/PhasorDynamics/Tiny/ThreeBus/ConstantSource/ThreeBusConstantSource.case.json index 8e274e209..49b2c3a5c 100644 --- a/examples/PhasorDynamics/Tiny/ThreeBus/ConstantSource/ThreeBusConstantSource.case.json +++ b/examples/PhasorDynamics/Tiny/ThreeBus/ConstantSource/ThreeBusConstantSource.case.json @@ -10,6 +10,12 @@ "freq_base": 60.0, "va_base": 100000000.0 }, + "monitors": [ + { + "file_name": "mon.csv", + "format":"csv" + } + ], "buses": [ { "number": 1, From 9a9df2d046faf057d0a5bbe3b3897b61e03f8d63 Mon Sep 17 00:00:00 2001 From: Philip Fackler Date: Thu, 20 Aug 2026 10:37:55 -0500 Subject: [PATCH 2/3] Undo changes to SystemModelData --- GridKit/Model/PhasorDynamics/SystemModelData.cpp | 13 +++++++++++++ GridKit/Model/PhasorDynamics/SystemModelData.hpp | 1 + .../Network/ThreeBusCoSim/ThreeBusCoSimClient.cpp | 7 ++++--- .../Network/ThreeBusCoSim/ThreeBusCoSimServer.cpp | 5 +++-- 4 files changed, 21 insertions(+), 5 deletions(-) diff --git a/GridKit/Model/PhasorDynamics/SystemModelData.cpp b/GridKit/Model/PhasorDynamics/SystemModelData.cpp index 0dcada8a8..f458583be 100644 --- a/GridKit/Model/PhasorDynamics/SystemModelData.cpp +++ b/GridKit/Model/PhasorDynamics/SystemModelData.cpp @@ -32,5 +32,18 @@ namespace GridKit } return parseSystemModelData(stream); } + + SystemModelData parseSystemModelData(const std::string& fileName) + { + auto stream = std::ifstream(fileName); + if (!stream) + { + std::stringstream ss; + ss << "Could not open file: " << fileName; + Log::error() << ss.str() << std::endl; + throw std::runtime_error(ss.str()); + } + return parseSystemModelData(stream); + } } // namespace PhasorDynamics } // namespace GridKit diff --git a/GridKit/Model/PhasorDynamics/SystemModelData.hpp b/GridKit/Model/PhasorDynamics/SystemModelData.hpp index 95f3137b6..61014df63 100644 --- a/GridKit/Model/PhasorDynamics/SystemModelData.hpp +++ b/GridKit/Model/PhasorDynamics/SystemModelData.hpp @@ -135,5 +135,6 @@ namespace GridKit SystemModelData parseSystemModelData(std::istream& stream); SystemModelData parseSystemModelData(std::istream&& stream); SystemModelData parseSystemModelData(const std::filesystem::path& filePath); + SystemModelData parseSystemModelData(const std::string& fileName); } // namespace PhasorDynamics } // namespace GridKit diff --git a/examples/Network/ThreeBusCoSim/ThreeBusCoSimClient.cpp b/examples/Network/ThreeBusCoSim/ThreeBusCoSimClient.cpp index cdf47da7c..74d699dfa 100644 --- a/examples/Network/ThreeBusCoSim/ThreeBusCoSimClient.cpp +++ b/examples/Network/ThreeBusCoSim/ThreeBusCoSimClient.cpp @@ -95,9 +95,10 @@ using IdxT = std::size_t; int main() { // Instantiate system - auto data = parseSystemModelData("ThreeBusCoSimClient.case.json"); - auto sys = SystemModel(data); - CoSimClient client( + auto filepath = std::filesystem::path("ThreeBusCoSimClient.case.json"); + auto data = parseSystemModelData(filepath); + auto sys = SystemModel(data); + auto client = CoSimClient( sys.getSignal(1), sys.getSignal(2), sys.getSignal(3), sys.getSignal(4)); sys.allocate(); client.exchange(); diff --git a/examples/Network/ThreeBusCoSim/ThreeBusCoSimServer.cpp b/examples/Network/ThreeBusCoSim/ThreeBusCoSimServer.cpp index d3a45db4a..581561334 100644 --- a/examples/Network/ThreeBusCoSim/ThreeBusCoSimServer.cpp +++ b/examples/Network/ThreeBusCoSim/ThreeBusCoSimServer.cpp @@ -71,8 +71,9 @@ class CoSimServer int main() { // Instantiate system - auto data = parseSystemModelData("ThreeBusCoSimServer.case.json"); - auto sys = SystemModel(data); + auto filepath = std::filesystem::path("ThreeBusCoSimServer.case.json"); + auto data = parseSystemModelData(filepath); + auto sys = SystemModel(data); sys.allocate(); // Set up cosim From 139dc625e7bcce9e3243845f56831c602fb4c8db Mon Sep 17 00:00:00 2001 From: Philip Fackler Date: Thu, 20 Aug 2026 11:54:07 -0500 Subject: [PATCH 3/3] Add documentation --- examples/Network/ThreeBusCoSim/README.md | 10 +++++ .../ThreeBusCoSim/ThreeBusCoSimClient.cpp | 43 ++++++++++++++++++- .../ThreeBusCoSim/ThreeBusCoSimServer.cpp | 40 ++++++++++++++++- 3 files changed, 91 insertions(+), 2 deletions(-) create mode 100644 examples/Network/ThreeBusCoSim/README.md diff --git a/examples/Network/ThreeBusCoSim/README.md b/examples/Network/ThreeBusCoSim/README.md new file mode 100644 index 000000000..50bb0347d --- /dev/null +++ b/examples/Network/ThreeBusCoSim/README.md @@ -0,0 +1,10 @@ +# Three-bus co-simulation example case with constant signal source + +This example duplicates the behavior of the ThreeBusConstantSource example, but +with the `ConstantSignalSource` component removed and its behavior reproduced in +the app (external to the system model) with currents being received over zmq +from another app. + +Note that this example code is temporary and meant simply as an initial step +towards implementing multi-instance co-simulation with GridKit. It will be +generalized into an application that can be used for other co-simulation cases. diff --git a/examples/Network/ThreeBusCoSim/ThreeBusCoSimClient.cpp b/examples/Network/ThreeBusCoSim/ThreeBusCoSimClient.cpp index 74d699dfa..b7317adda 100644 --- a/examples/Network/ThreeBusCoSim/ThreeBusCoSimClient.cpp +++ b/examples/Network/ThreeBusCoSim/ThreeBusCoSimClient.cpp @@ -15,17 +15,41 @@ using namespace GridKit; using namespace GridKit::PhasorDynamics; using namespace AnalysisManager::Sundials; +/** + * @brief A simple implementation of the "client" side of a co-simulation pair + * + * This is a "client" in the sense that it initiates the simulation and triggers + * its end. + * + * @tparam scalar_type scalar parameter type + * @tparam index_type integer parameter type + */ template class CoSimClient { public: + /// Type representing a scalar value using ScalarT = scalar_type; + /// Type representing an index using IdxT = index_type; + /// Alias for SystemModel using SystemModelT = SystemModel; + /// Alias for SignalNode using SignalT = typename SystemModelT::SignalT; CoSimClient() = delete; + /** + * @brief Construct with set of signal nodes to connect + * + * This links the current signal nodes with variables received from server and + * connects to the expected tcp port from which to receive. + * + * @param vr node from which to read real component of voltage to send + * @param vi node from which to read imaginary component of voltage to send + * @param ir node for communicating received real component of current + * @param ii node for communicating received imaginary component of current + */ CoSimClient(SignalT* vr, SignalT* vi, SignalT* ir, SignalT* ii) : vr_signal_(vr), vi_signal_(vi), @@ -39,6 +63,9 @@ class CoSimClient socket_.connect("tcp://0.0.0.0:5556"); } + /** + * @brief Signal the end of the simulation to the server and destruct object + */ ~CoSimClient() { std::ostringstream oss; @@ -53,6 +80,10 @@ class CoSimClient } } + /** + * @brief Send voltage to and receive current from server-side instance for a + * single time step. + */ void exchange() { // 1. Send data @@ -76,15 +107,25 @@ class CoSimClient } private: + /// node from which to read real component of voltage to send SignalT* vr_signal_; + /// node from which to read imaginary component of voltage to send SignalT* vi_signal_; + /// node for communicating received real component of current SignalT* ir_signal_; + /// node for communicating received imaginary component of current SignalT* ii_signal_; + /// variable for receiving current ScalarT ir_{}; + /// variable for receiving current ScalarT ii_{}; + /// dummy index for current signal IdxT ir_idx_{GridKit::INVALID_INDEX}; + /// dummy index for current signal IdxT ii_idx_{GridKit::INVALID_INDEX}; + /// ZMQ context zmq::context_t ctx_; + /// ZMQ socket zmq::socket_t socket_; }; @@ -109,7 +150,7 @@ int main() ida.configureSimulation(); // TODO: Take one step at a time and exchange data between. - // Just use step_callback + // Use step_callback for now. auto step_cb = [&client](auto) { client.exchange(); diff --git a/examples/Network/ThreeBusCoSim/ThreeBusCoSimServer.cpp b/examples/Network/ThreeBusCoSim/ThreeBusCoSimServer.cpp index 581561334..ba01a289b 100644 --- a/examples/Network/ThreeBusCoSim/ThreeBusCoSimServer.cpp +++ b/examples/Network/ThreeBusCoSim/ThreeBusCoSimServer.cpp @@ -15,17 +15,39 @@ using IdxT = std::size_t; using namespace GridKit; using namespace GridKit::PhasorDynamics; +/** + * @brief A simple implementation of the "server" side of a co-simulation pair + * + * This is a "server" in the sense that it waits for a request from the "client" + * to initiate each step in the simulation and to trigger when to stop the + * simulation. + * + * @tparam scalar_type scalar parameter type + * @tparam index_type integer parameter type + */ template class CoSimServer { public: + /// Type representing a scalar value using ScalarT = scalar_type; + /// Type representing an index using IdxT = index_type; - using SystemModelT = GridKit::PhasorDynamics::SystemModel; + /// Alias for SystemModel + using SystemModelT = SystemModel; + /// Alias for SignalNode using SignalT = typename SystemModelT::SignalT; CoSimServer() = delete; + /** + * @brief Construct with set of signal nodes to connect + * + * This also binds the tcp port to which the client is expected to connect + * + * @param ir node from which to read real component of current to send + * @param ii node from which to read imaginary component of current to send + */ CoSimServer(SignalT* ir, SignalT* ii) : ir_signal_(ir), ii_signal_(ii), @@ -35,6 +57,18 @@ class CoSimServer socket_.bind("tcp://0.0.0.0:5556"); } + /** + * @brief Start the server + * + * The server will stay in this function until the end of the simulation is + * triggered by the client. + * + * Each time a voltage message is received, a + * step is taken and the resulting currents are sent as a response. + * + * @note For this initial implementation, no solver step is taken. Constant + * current values are sent in response. + */ void start() { CoSim::StatusT status; @@ -62,9 +96,13 @@ class CoSimServer } private: + /// node from which to read real component of current to send SignalT* ir_signal_; + /// node from which to read imaginary component of current to send SignalT* ii_signal_; + /// ZMQ context zmq::context_t ctx_; + /// ZMQ socket zmq::socket_t socket_; };