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/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.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..b7317adda --- /dev/null +++ b/examples/Network/ThreeBusCoSim/ThreeBusCoSimClient.cpp @@ -0,0 +1,176 @@ +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "CoSim.hpp" +#include + +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), + 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"); + } + + /** + * @brief Signal the end of the simulation to the server and destruct object + */ + ~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) + { + } + } + + /** + * @brief Send voltage to and receive current from server-side instance for a + * single time step. + */ + 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: + /// 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_; +}; + +using ScalarT = double; +using RealT = double; +using IdxT = std::size_t; + +int main() +{ + // Instantiate system + 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(); + + // 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. + // Use step_callback for now. + 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..ba01a289b --- /dev/null +++ b/examples/Network/ThreeBusCoSim/ThreeBusCoSimServer.cpp @@ -0,0 +1,122 @@ +#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; + +/** + * @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; + /// 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), + ctx_{}, + socket_(ctx_, zmq::socket_type::rep) + { + 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; + 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: + /// 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_; +}; + +int main() +{ + // Instantiate system + auto filepath = std::filesystem::path("ThreeBusCoSimServer.case.json"); + auto data = parseSystemModelData(filepath); + 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,