From 9b4f0dfd323fb7ecf51b688b66c780832c21af40 Mon Sep 17 00:00:00 2001 From: superwhiskers Date: Mon, 2 Nov 2026 14:44:35 -0500 Subject: [PATCH] integrate co-simulation into phasor dynamics applications - add client/server modes to `DynamicSimulation` and `ContingencyAnalysis` - introduce simulation runners to coordinate events, interface solves, and monitor output - remove standalone co-simulation examples - add a partitioned ieee39 example - document co-simulation configuration and execution --- GridKit/Utilities/Logger/Logger.cpp | 12 +- GridKit/Utilities/Logger/Logger.hpp | 1 - .../PhasorDynamics/AnalysisUtilities.hpp | 93 +- application/PhasorDynamics/CMakeLists.txt | 5 + application/PhasorDynamics/CoSimulation.hpp | 123 + .../PhasorDynamics/CoSimulationTypes.hpp | 25 + .../PhasorDynamics/ContingencyAnalysis.cpp | 145 +- application/PhasorDynamics/CoupledClient.hpp | 343 +++ application/PhasorDynamics/CoupledServer.hpp | 167 ++ .../PhasorDynamics/DynamicSimulation.cpp | 115 +- application/PhasorDynamics/README.md | 142 +- .../PhasorDynamics/SimulationOptions.hpp | 215 ++ .../PhasorDynamics/SimulationRunner.hpp | 349 +++ .../IEEE39/IEEE39Generator.case.json | 167 ++ .../IEEE39/IEEE39Grid.case.json | 2459 +++++++++++++++++ .../PhasorDynamics/CoSimulation/README.md | 6 + docs/examples/PhasorDynamics/README.md | 1 + examples/CMakeLists.txt | 1 - examples/Network/CMakeLists.txt | 3 - examples/Network/CoSim/CMakeLists.txt | 32 - examples/Network/CoSim/CoSim.hpp | 51 - examples/Network/CoSim/CoSimClient.cpp | 261 -- examples/Network/CoSim/CoSimServer.cpp | 228 -- examples/Network/CoSim/README.md | 16 - .../CoSim/ThreeBusCoSimClient.case.json | 153 - .../CoSim/ThreeBusCoSimServer.case.json | 153 - .../Network/CoSim/TwoBusCoSimClient.case.json | 101 - .../CoSim/TwoBusCoSimClient.solo.case.json | 107 - .../CoSim/TwoBusCoSimClient.solver.json | 6 - .../Network/CoSim/TwoBusCoSimServer.case.json | 82 - .../CoSim/TwoBusCoSimServer.solo.case.json | 88 - .../CoSim/TwoBusCoSimServer.solver.json | 6 - examples/Network/CoSim/run_cosim.sh | 21 - .../ThreeBusCoSim/ThreeBusCoSimClient.cpp | 186 -- .../ThreeBusCoSim/ThreeBusCoSimServer.cpp | 135 - examples/PhasorDynamics/CMakeLists.txt | 4 + .../CoSimulation/CMakeLists.txt | 3 + .../CoSimulation/IEEE39/CMakeLists.txt | 5 + .../IEEE39Generator.contingency.solver.json | 6 + .../IEEE39/IEEE39Generator.solver.json | 6 + .../IEEE39/IEEE39Grid.contingency.solver.json | 17 + .../IEEE39/IEEE39Grid.solver.json | 19 + .../PhasorDynamics/CoSimulation/README.md | 28 + .../CoSimulation/Toy/CMakeLists.txt | 9 + .../Toy/ThreeBusCoSimClient.case.json | 177 ++ .../Toy/ThreeBusCoSimClient.solver.json | 6 + .../Toy/ThreeBusCoSimServer.case.json | 177 ++ .../Toy/ThreeBusCoSimServer.solver.json | 6 + .../Toy/TwoBusCoSimClient.case.json | 123 + .../Toy/TwoBusCoSimClient.solver.json | 6 + .../Toy/TwoBusCoSimServer.case.json | 105 + .../Toy/TwoBusCoSimServer.solver.json | 6 + .../PhasorDynamics/CoSimulation/run_cosim.sh | 105 + examples/PhasorDynamics/README.md | 12 +- 54 files changed, 4929 insertions(+), 1889 deletions(-) create mode 100644 application/PhasorDynamics/CoSimulation.hpp create mode 100644 application/PhasorDynamics/CoSimulationTypes.hpp create mode 100644 application/PhasorDynamics/CoupledClient.hpp create mode 100644 application/PhasorDynamics/CoupledServer.hpp create mode 100644 application/PhasorDynamics/SimulationOptions.hpp create mode 100644 application/PhasorDynamics/SimulationRunner.hpp create mode 100644 cases/PhasorDynamics/IEEE39/IEEE39Generator.case.json create mode 100644 cases/PhasorDynamics/IEEE39/IEEE39Grid.case.json create mode 100644 docs/examples/PhasorDynamics/CoSimulation/README.md delete mode 100644 examples/Network/CMakeLists.txt delete mode 100644 examples/Network/CoSim/CMakeLists.txt delete mode 100644 examples/Network/CoSim/CoSim.hpp delete mode 100644 examples/Network/CoSim/CoSimClient.cpp delete mode 100644 examples/Network/CoSim/CoSimServer.cpp delete mode 100644 examples/Network/CoSim/README.md delete mode 100644 examples/Network/CoSim/ThreeBusCoSimClient.case.json delete mode 100644 examples/Network/CoSim/ThreeBusCoSimServer.case.json delete mode 100644 examples/Network/CoSim/TwoBusCoSimClient.case.json delete mode 100644 examples/Network/CoSim/TwoBusCoSimClient.solo.case.json delete mode 100644 examples/Network/CoSim/TwoBusCoSimClient.solver.json delete mode 100644 examples/Network/CoSim/TwoBusCoSimServer.case.json delete mode 100644 examples/Network/CoSim/TwoBusCoSimServer.solo.case.json delete mode 100644 examples/Network/CoSim/TwoBusCoSimServer.solver.json delete mode 100644 examples/Network/CoSim/run_cosim.sh delete mode 100644 examples/Network/ThreeBusCoSim/ThreeBusCoSimClient.cpp delete mode 100644 examples/Network/ThreeBusCoSim/ThreeBusCoSimServer.cpp create mode 100644 examples/PhasorDynamics/CoSimulation/CMakeLists.txt create mode 100644 examples/PhasorDynamics/CoSimulation/IEEE39/CMakeLists.txt create mode 100644 examples/PhasorDynamics/CoSimulation/IEEE39/IEEE39Generator.contingency.solver.json create mode 100644 examples/PhasorDynamics/CoSimulation/IEEE39/IEEE39Generator.solver.json create mode 100644 examples/PhasorDynamics/CoSimulation/IEEE39/IEEE39Grid.contingency.solver.json create mode 100644 examples/PhasorDynamics/CoSimulation/IEEE39/IEEE39Grid.solver.json create mode 100644 examples/PhasorDynamics/CoSimulation/README.md create mode 100644 examples/PhasorDynamics/CoSimulation/Toy/CMakeLists.txt create mode 100644 examples/PhasorDynamics/CoSimulation/Toy/ThreeBusCoSimClient.case.json create mode 100644 examples/PhasorDynamics/CoSimulation/Toy/ThreeBusCoSimClient.solver.json create mode 100644 examples/PhasorDynamics/CoSimulation/Toy/ThreeBusCoSimServer.case.json create mode 100644 examples/PhasorDynamics/CoSimulation/Toy/ThreeBusCoSimServer.solver.json create mode 100644 examples/PhasorDynamics/CoSimulation/Toy/TwoBusCoSimClient.case.json create mode 100644 examples/PhasorDynamics/CoSimulation/Toy/TwoBusCoSimClient.solver.json create mode 100644 examples/PhasorDynamics/CoSimulation/Toy/TwoBusCoSimServer.case.json create mode 100644 examples/PhasorDynamics/CoSimulation/Toy/TwoBusCoSimServer.solver.json create mode 100644 examples/PhasorDynamics/CoSimulation/run_cosim.sh diff --git a/GridKit/Utilities/Logger/Logger.cpp b/GridKit/Utilities/Logger/Logger.cpp index 62eba584f2..4502f3578b 100644 --- a/GridKit/Utilities/Logger/Logger.cpp +++ b/GridKit/Utilities/Logger/Logger.cpp @@ -9,6 +9,9 @@ #include #include +// TODO: allow instantiating a scoped logger that prefixes with some +// class/logical distinction name like `[CliArgs]` or `[GENROU]`. + namespace GridKit { namespace Utilities @@ -129,14 +132,16 @@ namespace GridKit std::ostream& Logger::warning() { using namespace Colors; - *(output_streams_[WARNINGS]) << "[" << YELLOW << "WARNING" << CLEAR << "] "; + *(output_streams_[WARNINGS]) + << "[" << YELLOW << "WARNING" << CLEAR << "] "; return *(output_streams_[WARNINGS]); } /** * @brief Returns reference to analysis summary messages output stream. * - * @return Reference to analysis summary messages stream in `output_streams_`. + * @return Reference to analysis summary messages stream in + * `output_streams_`. * * @pre `output_streams_` vector is allocated and correctly initialized. */ @@ -177,7 +182,8 @@ namespace GridKit } /** - * @brief Set outputs of active streams to user provided `std::ostream` object. + * @brief Set outputs of active streams to user provided `std::ostream` + * object. * * All active outputs are redirected to `out` stream. All inactive ones are * directed to null device. diff --git a/GridKit/Utilities/Logger/Logger.hpp b/GridKit/Utilities/Logger/Logger.hpp index db9edb7dcd..10ebc217f1 100644 --- a/GridKit/Utilities/Logger/Logger.hpp +++ b/GridKit/Utilities/Logger/Logger.hpp @@ -16,7 +16,6 @@ namespace GridKit * @brief Manages and logs outputs from GridKit code. * * All methods and data in this class are static. - * */ class Logger { diff --git a/application/PhasorDynamics/AnalysisUtilities.hpp b/application/PhasorDynamics/AnalysisUtilities.hpp index ac04c44aba..e547165266 100644 --- a/application/PhasorDynamics/AnalysisUtilities.hpp +++ b/application/PhasorDynamics/AnalysisUtilities.hpp @@ -1,8 +1,8 @@ #pragma once +#include #include #include -#include #include #include #include @@ -30,7 +30,8 @@ namespace GridKit * @brief Describes an event that is used to modify the simulation at the * given time point * - * ContingencyAnalysis repeats fault events at each bus with the same R and X. + * ContingencyAnalysis repeats fault events at each bus with the same R and + * X. */ struct SystemEvent { @@ -72,6 +73,12 @@ namespace GridKit double rel_tol; /// absolute tolerance for the solver double abs_tol; + /// relative residual tolerance for the co-simulation interface + double cosim_rel_tol; + /// scaled step tolerance for the co-simulation interface solver + double cosim_step_tol; + /// estimated relative error of co-simulation residual evaluations + double cosim_function_error; /// fixed solver time step size, or 0 for adaptive stepping double dt_fixed; /// maximum number of solver time steps, or 0 for the IDA default @@ -101,9 +108,12 @@ namespace GridKit using json = ::nlohmann::json; using Log = ::GridKit::Utilities::Logger; - inline constexpr double DEFAULT_SOLVER_REL_TOL = 1.0e-7; - inline constexpr double DEFAULT_SOLVER_ABS_TOL = 1.0e-9; - inline constexpr double DEFAULT_VERIFICATION_TOL = 1.0e-4; + inline constexpr double DEFAULT_SOLVER_REL_TOL = 1.0e-7; + inline constexpr double DEFAULT_SOLVER_ABS_TOL = 1.0e-9; + inline constexpr double DEFAULT_VERIFICATION_TOL = 1.0e-4; + inline constexpr double DEFAULT_COSIM_REL_TOL = 1.0e-8; + inline constexpr double DEFAULT_COSIM_STEP_TOL = 1.0e-12; + inline constexpr double DEFAULT_COSIM_FUNCTION_ERROR = 1.0e-10; /** * @brief JSON parser implementation for `StudyData` @@ -117,20 +127,38 @@ namespace GridKit j.at("tmax").get_to(c.tmax); c.rel_tol = j.value("rel_tol", DEFAULT_SOLVER_REL_TOL); c.abs_tol = j.value("abs_tol", DEFAULT_SOLVER_ABS_TOL); - c.dt_fixed = j.value("dt_fixed", 0.0); - c.max_steps = j.value("max_steps", std::size_t{0}); - c.max_order = j.value("max_order", 5); - c.consistent_ic_type = AnalysisManager::Sundials::IdaConsistentICType::YA_YDP; + const auto tolerance = [&](const char* name, double fallback) + { + const double value = j.value(name, fallback); + if (!std::isfinite(value) || value <= 0.0) + { + throw std::invalid_argument( + std::string(name) + " must be positive and finite"); + } + return value; + }; + c.cosim_rel_tol = tolerance("cosim_rel_tol", DEFAULT_COSIM_REL_TOL); + c.cosim_step_tol = tolerance("cosim_step_tol", DEFAULT_COSIM_STEP_TOL); + c.cosim_function_error = + tolerance("cosim_function_error", DEFAULT_COSIM_FUNCTION_ERROR); + c.dt_fixed = j.value("dt_fixed", 0.0); + c.max_steps = j.value("max_steps", std::size_t{0}); + c.max_order = j.value("max_order", 5); + c.consistent_ic_type = + AnalysisManager::Sundials::IdaConsistentICType::YA_YDP; if (j.contains("consistent_ic_type")) { - const auto consistent_ic_type_str = j.at("consistent_ic_type").get(); + const auto consistent_ic_type_str = + j.at("consistent_ic_type").get(); if (consistent_ic_type_str == "y") { - c.consistent_ic_type = AnalysisManager::Sundials::IdaConsistentICType::Y; + c.consistent_ic_type = + AnalysisManager::Sundials::IdaConsistentICType::Y; } else if (consistent_ic_type_str == "ya_ydp") { - c.consistent_ic_type = AnalysisManager::Sundials::IdaConsistentICType::YA_YDP; + c.consistent_ic_type = + AnalysisManager::Sundials::IdaConsistentICType::YA_YDP; } else { @@ -223,7 +251,8 @@ namespace GridKit c.error_type = ErrorType::RELATIVE; } - c.abs_err_threshold = j.value("abs_err_threshold", Testing::DEFAULT_ABS_ERROR_THRESHOLD); + c.abs_err_threshold = + j.value("abs_err_threshold", Testing::DEFAULT_ABS_ERROR_THRESHOLD); } /** @@ -302,7 +331,8 @@ namespace GridKit // output file must be a symlink to the model output file if (exists(data.output_file)) { - if ((!is_symlink(data.output_file)) || (read_symlink(data.output_file) != model_output_file)) + if ((!is_symlink(data.output_file)) + || (read_symlink(data.output_file) != model_output_file)) { Log::error() << "Study output file not usable" << std::endl; } @@ -317,28 +347,11 @@ namespace GridKit return data; } - void checkCommandLine(int argc, const std::string& appName) - { - if (argc < 2) - { - Log::error() << "No input file provided\n" - << std::format( - "\n" - "Usage:\n" - " {} \n" - "\n" - "Please provide a json input file for the study to run.\n" - "\n", - appName); - exit(1); - } - } - Testing::TestStatus checkErrors( - const StudyData& study_data, - bool print_results = true) + const StudyData& study_data, bool print_results = true) { - // Generate aggregate errors comparing variable output to reference solution + // Generate aggregate errors comparing variable output to reference + // solution auto func = std::string{"monitor file vs reference file"}; auto status = Testing::TestStatus{func.c_str()}; @@ -346,10 +359,11 @@ namespace GridKit const auto& ref_file = study_data.reference_file; if (!out_file.empty() && !ref_file.empty()) { - auto errorSet = Testing::compareCSV(out_file, - ref_file, - study_data.error_type, - study_data.abs_err_threshold); + auto errorSet = Testing::compareCSV( + out_file, + ref_file, + study_data.error_type, + study_data.abs_err_threshold); // Print the errors if (print_results) @@ -363,7 +377,8 @@ namespace GridKit status *= study_data.error_tol.size() == errorSet->var_errors.size(); for (std::size_t i = 0; i < study_data.error_tol.size(); ++i) { - status *= errorSet->var_errors[i].max_value < study_data.error_tol[i]; + status *= + errorSet->var_errors[i].max_value < study_data.error_tol[i]; } } else diff --git a/application/PhasorDynamics/CMakeLists.txt b/application/PhasorDynamics/CMakeLists.txt index fbe1471b90..c768c850d6 100644 --- a/application/PhasorDynamics/CMakeLists.txt +++ b/application/PhasorDynamics/CMakeLists.txt @@ -20,6 +20,11 @@ target_include_directories( ContingencyAnalysis PRIVATE ${GRIDKIT_THIRD_PARTY_DIR}/nlohmann-json/include) +if(GRIDKIT_ENABLE_ZMQ) + target_link_libraries(DynamicSimulation PRIVATE GridKit::co_sim GridKit::solvers_steady) + target_link_libraries(ContingencyAnalysis PRIVATE GridKit::co_sim GridKit::solvers_steady) +endif() + if(GRIDKIT_ENABLE_OPENMP) target_link_libraries(ContingencyAnalysis PUBLIC OpenMP::OpenMP_CXX) endif() diff --git a/application/PhasorDynamics/CoSimulation.hpp b/application/PhasorDynamics/CoSimulation.hpp new file mode 100644 index 0000000000..edbfa9b776 --- /dev/null +++ b/application/PhasorDynamics/CoSimulation.hpp @@ -0,0 +1,123 @@ +#pragma once + +#include "SimulationOptions.hpp" +#include "SimulationRunner.hpp" + +#ifdef GRIDKIT_ENABLE_ZMQ + +#include +#include +#include + +#include + +#include "CoSimulationTypes.hpp" +#include "CoupledClient.hpp" +#include "CoupledServer.hpp" + +namespace GridKit +{ + namespace PhasorDynamics + { + inline CoSim::RunResult runCoupledClientStudies( + const SimulationOptions& options, + NextCoupledStudy next, + CompleteCoupledStudy complete, + bool shutdown_peer, + bool print_results = false) + { + CoupledClient client( + options, + std::move(next), + std::move(complete), + shutdown_peer, + print_results); + + CoSim::CoSim adapter( + options.endpoint, + CoSim::MessageStepFunction( + [&]( + const CoSimulationMessage& reply, + const CoSim::MessageExchangeFunction& exchange) + { return client.handleReply(reply, exchange); }), + CoSim::MessageExtractFunction([&](const auto&) + { return client.extract(); }), + options.timeout_ms); + + const auto result = adapter.run(); + + if (result == CoSim::RunResult::COMPLETED && !client.finished()) + { + throw std::runtime_error( + "co-simulation peer ended before the study completed"); + } + + return result; + } + + inline Testing::TestStatus runCoupledServer( + const StudyData& base, const SimulationOptions& options) + { + CoupledServer server(base, options); + + CoSim::CoSim adapter( + options.endpoint, + CoSim::MessageStepFunction( + [&](const CoSimulationMessage& message, const auto&) + { return server.handleRequest(message); }), + CoSim::MessageExtractFunction([&](const auto&) + { return server.extract(); }), + options.timeout_ms); + + if (adapter.run() == CoSim::RunResult::STOPPED) + { + return false; + } + + if (!server.finished()) + { + throw std::runtime_error( + "co-simulation peer closed before the study completed"); + } + + return server.status(); + } + + inline Testing::TestStatus runCoupledStudy( + const StudyData& data, const SimulationOptions& options) + { + if (options.role == "server") + { + return runCoupledServer(data, options); + } + + bool first = true; + Testing::TestStatus status = true; + const auto next = [&]() -> std::optional + { + if (!first) + { + return std::nullopt; + } + + first = false; + return data; + }; + + if (runCoupledClientStudies( + options, + next, + [&](Testing::TestStatus result) + { status *= result; }, + false, + true) + == CoSim::RunResult::STOPPED) + { + status = false; + } + + return status; + } + } // namespace PhasorDynamics +} // namespace GridKit +#endif diff --git a/application/PhasorDynamics/CoSimulationTypes.hpp b/application/PhasorDynamics/CoSimulationTypes.hpp new file mode 100644 index 0000000000..02c0c1fbea --- /dev/null +++ b/application/PhasorDynamics/CoSimulationTypes.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include + +#ifdef GRIDKIT_ENABLE_ZMQ + +#include +#include + +#include + +#include "AnalysisUtilities.hpp" + +namespace GridKit +{ + namespace PhasorDynamics + { + /// Alias for the co-simulation message type used in the applications. + using CoSimulationMessage = CoSim::MessageData; + + using NextCoupledStudy = std::function()>; + using CompleteCoupledStudy = std::function; + } // namespace PhasorDynamics +} // namespace GridKit +#endif diff --git a/application/PhasorDynamics/ContingencyAnalysis.cpp b/application/PhasorDynamics/ContingencyAnalysis.cpp index c1a5b61ee2..162583cb6d 100644 --- a/application/PhasorDynamics/ContingencyAnalysis.cpp +++ b/application/PhasorDynamics/ContingencyAnalysis.cpp @@ -14,7 +14,7 @@ #include #include -#include "AnalysisUtilities.hpp" +#include "CoSimulation.hpp" using Clock = std::chrono::high_resolution_clock; using Dur = std::chrono::duration; @@ -31,68 +31,7 @@ using index_type = size_t; TestStatus runStudy(StudyData study_data) { - // Instantiate system - SystemModel sys(study_data.model_data); - sys.allocate(); - - // Set up simulation - Ida ida(&sys); - ida.setTolerance(study_data.rel_tol, study_data.abs_tol); - ida.setFixedStep(study_data.dt_fixed); - ida.setMaxSteps(study_data.max_steps); - ida.setMaxOrder(study_data.max_order); - ida.setConsistentICType(study_data.consistent_ic_type); - ida.configureSimulation(); - - using EventType = SystemEvent::Type; - - // Initialize simulation for first run - real_type dt_monitor = study_data.dt_monitor; - real_type final_time = study_data.tmax; - ida.initializeSimulation(0.0, false); - - for (const auto& event : study_data.events) - { - // Run to event time - ida.runSimulation(event.time, dt_monitor); - - // Set up run for event (to start at event time) - int ret = 0; - - switch (event.type) - { - case EventType::FAULT_ON: - ret = sys.getBus(event.bus)->setFault(true, event.R, event.X); - break; - case EventType::FAULT_OFF: - ret = sys.getBus(event.bus)->clearFault(); - break; - case EventType::IN_SERVICE: - sys.getBranch(event.device)->setInService(true); - break; - case EventType::OUT_OF_SERVICE: - sys.getBranch(event.device)->setInService(false); - break; - } - - if (ret != 0) - { - Log::error() << "Fault event failed for bus " << event.bus - << " at time " << event.time << '\n'; - return {false}; - } - - // Re-initialize simulation at event time - ida.initializeSimulation(event.time, true); - } - - // Run to final time - ida.runSimulation(final_time, dt_monitor); - - // Stop the variable monitor - sys.stopMonitor(); - - return checkErrors(study_data, false); + return runStandaloneStudy(study_data, false, false); } TestStatus singleFaultStudy(std::size_t fault_id, StudyData study_data) @@ -100,20 +39,15 @@ TestStatus singleFaultStudy(std::size_t fault_id, StudyData study_data) // Retarget fault events while preserving branch-switching events for (auto& event : study_data.events) { - if (event.type == SystemEvent::Type::FAULT_ON || event.type == SystemEvent::Type::FAULT_OFF) + if (event.type == SystemEvent::Type::FAULT_ON + || event.type == SystemEvent::Type::FAULT_OFF) { event.bus = study_data.model_data.bus[fault_id].bus_id; } } // Make distinct output files - for (auto& sink : study_data.model_data.monitor_sink) - { - auto path = std::filesystem::path(sink.file_name); - auto ext = path.extension().string(); - auto name = path.stem().string(); - sink.file_name = name + "_" + std::to_string(fault_id) + ext; - } + suffixOutputs(study_data, "_" + std::to_string(fault_id)); try { @@ -126,7 +60,8 @@ TestStatus singleFaultStudy(std::size_t fault_id, StudyData study_data) } } -void runStudySerial(const StudyData& study_data, std::vector& stat_vec) +void runStudySerial( + const StudyData& study_data, std::vector& stat_vec) { for (std::size_t i = 0; i < study_data.model_data.bus.size(); ++i) { @@ -136,7 +71,8 @@ void runStudySerial(const StudyData& study_data, std::vector& stat_v } #ifdef GRIDKIT_ENABLE_THREADS -void runStudyAsync(const StudyData& study_data, std::vector& stat_vec) +void runStudyAsync( + const StudyData& study_data, std::vector& stat_vec) { auto n_faults = study_data.model_data.bus.size(); @@ -157,7 +93,8 @@ void runStudyAsync(const StudyData& study_data, std::vector& stat_ve #endif #ifdef _OPENMP -void runStudyOpenMP(const StudyData& study_data, std::vector& stat_vec) +void runStudyOpenMP( + const StudyData& study_data, std::vector& stat_vec) { auto n_faults = study_data.model_data.bus.size(); #pragma omp parallel for @@ -175,24 +112,65 @@ int runApplication(int argc, const char* argv[]) Log::raiseVerbosity(Log::SUMMARY); // Study file - checkCommandLine(argc, "ContingencyAnalysis"); - auto study_data = parseStudyData(argv[1]); + const auto options = parseSimulationOptions(argc, argv, true); + auto study_data = parseStudyData(options.solver_file); const auto start = Clock::now(); auto faults = study_data.model_data.bus; auto stat_vec = std::vector(faults.size(), true); - // Use std::async if threads are available (so far, std::async has out-performed OpenMP) - // Otherwise, use OpenMP if available - // Fall back to serial if neither threads or OpenMP are available +#ifdef GRIDKIT_ENABLE_ZMQ + if (options.coSimulated()) + { + std::size_t next_fault = 0; + std::size_t completed = 0; + const auto next = [&]() -> std::optional + { + if (next_fault == faults.size()) + { + return std::nullopt; + } + + const auto i = next_fault++; + auto data = study_data; + for (auto& event : data.events) + { + if (event.type == SystemEvent::Type::FAULT_ON + || event.type == SystemEvent::Type::FAULT_OFF) + { + event.bus = faults[i].bus_id; + } + } + + suffixOutputs(data, "_" + std::to_string(i)); + return data; + }; + if (runCoupledClientStudies( + options, + next, + [&](TestStatus result) + { stat_vec[completed++] = result; }, + true) + == GridKit::CoSim::RunResult::STOPPED) + { + return 1; + } + } + else +#endif + { + // Use std::async if threads are available + // Otherwise, use OpenMP if available + // Fall back to serial if neither threads or OpenMP are available #if defined(GRIDKIT_ENABLE_THREADS) - runStudyAsync(study_data, stat_vec); + runStudyAsync(study_data, stat_vec); #elif defined(_OPENMP) - runStudyOpenMP(study_data, stat_vec); + runStudyOpenMP(study_data, stat_vec); #else - runStudySerial(study_data, stat_vec); + runStudySerial(study_data, stat_vec); #endif + } const auto stop = Clock::now(); const auto dur = std::chrono::duration(stop - start); @@ -204,8 +182,7 @@ int runApplication(int argc, const char* argv[]) status *= stat_vec[i]; if (!stat_vec[i]) { - Log::error() << "Study failed for fault: " - << faults[i].name << '\n'; + Log::error() << "Study failed for fault: " << faults[i].name << '\n'; } } diff --git a/application/PhasorDynamics/CoupledClient.hpp b/application/PhasorDynamics/CoupledClient.hpp new file mode 100644 index 0000000000..97abfa9c78 --- /dev/null +++ b/application/PhasorDynamics/CoupledClient.hpp @@ -0,0 +1,343 @@ +#pragma once + +#include + +#ifdef GRIDKIT_ENABLE_ZMQ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "CoSimulationTypes.hpp" +#include "SimulationOptions.hpp" +#include "SimulationRunner.hpp" + +namespace GridKit +{ + namespace PhasorDynamics + { + /// Implementation of a client coupled through co-simulation. + class CoupledClient + { + using InterfaceSolver = + AnalysisManager::Sundials::Kinsol; + + const SimulationOptions& options_; + NextCoupledStudy next_; + CompleteCoupledStudy complete_; + bool shutdown_peer_; + bool print_results_; + std::optional data_; + std::unique_ptr simulation_; + std::unique_ptr interface_solver_; + CoSimulationMessage outgoing_; + std::uint64_t session_{0}; + std::vector boundaries_; + std::size_t boundary_{0}; + std::size_t step_{1}; + + bool beginStudy() + { + data_ = next_(); + if (!data_) + { + return false; + } + + session_++; + simulation_ = std::make_unique( + *data_, options_.inputs, options_.outputs); + + outgoing_ = CoSimulationMessage(CoSim::MessageType::INIT, session_); + + outgoing_.signals = simulation_->outputs(); + outgoing_.final_time = data_->tmax; + outgoing_.interval = options_.interval; + boundary_ = 0; + step_ = 1; + return true; + } + + void startStudy(const CoSimulationMessage& reply) + { + if (!sameTime(reply.final_time, data_->tmax) + || !sameTime(reply.interval, options_.interval)) + { + throw std::runtime_error("incompatible co-simulation study timing"); + } + + boundaries_ = reply.event_times; + for (const auto& event : data_->events) + { + boundaries_.push_back(event.time); + } + + for (auto time : boundaries_) + { + if (time < 0.0 || time > data_->tmax) + { + throw std::runtime_error( + "peer event time is outside the study interval"); + } + } + + boundaries_.push_back(data_->tmax); + std::sort(boundaries_.begin(), boundaries_.end()); + simulation_->setInputs(reply.signals); + simulation_->start(); + } + + bool finishStudy() + { + complete_(simulation_->finish(print_results_)); + simulation_.reset(); + if (beginStudy()) + { + return true; + } + if (!shutdown_peer_) + { + return false; + } + + outgoing_ = CoSimulationMessage(CoSim::MessageType::SHUTDOWN, session_); + return true; + } + + /// Solve $F(inputs) - inputs = 0$ at the current time, holding + /// differential states fixed in both partitions. + void solveInterface( + const Signals& initial, + const CoSim::MessageExchangeFunction& exchange) + { + simulation_->setInputs(initial); + Signals trial = initial; + std::vector values, scales, residual(initial.size()); + bool evaluated = false; + + for (const auto& [name, value] : initial) + { + values.push_back(value); + scales.push_back(std::max(1.0, std::abs(value))); + } + + const auto evaluate = + [&](std::span guess, std::span result) + { + if (evaluated + && std::equal( + guess.begin(), + guess.end(), + trial.begin(), + [](double value, const auto& input) + { return value == input.second; })) + { + if (result.data() != residual.data()) + { + std::copy(residual.begin(), residual.end(), result.begin()); + } + return 0; + } + std::size_t i = 0; + + for (auto& [name, value] : trial) + { + value = guess[i++]; + } + + simulation_->setInputs(trial); + simulation_->refresh(); + + auto message = CoSimulationMessage( + CoSim::MessageType::UPDATE, session_, simulation_->time()); + + message.signals = simulation_->outputs(); + const auto reply = exchange(message); + if (reply.signals.size() != trial.size()) + { + throw std::runtime_error( + "Peer signal names do not match the configured inputs"); + } + + i = 0; + for (const auto& [name, value] : trial) + { + residual[i] = (reply.signals.at(name) - value) / scales[i]; + i++; + } + + if (result.data() != residual.data()) + { + std::copy(residual.begin(), residual.end(), result.begin()); + } + + evaluated = true; + return 0; + }; + const auto converged = [&] + { + for (std::size_t i = 0; i < values.size(); ++i) + { + if (std::abs(residual[i]) * scales[i] + / std::max(1.0, std::abs(values[i])) + >= data_->cosim_rel_tol) + { + return false; + } + } + + return true; + }; + + // check the initial guess and cache its residual for kinsol + evaluate(values, residual); + + if (!converged()) + { + if (!interface_solver_) + { + interface_solver_ = + std::make_unique(values.size()); + interface_solver_->configureSimulation(); + interface_solver_->setJacobianUpdateInterval(1); + } + + interface_solver_->setFunctionError(data_->cosim_function_error); + interface_solver_->setTolerance( + data_->cosim_rel_tol, data_->cosim_step_tol); + interface_solver_->runSimulation(values, evaluate, false); + + evaluate(values, residual); + + // try tightening the tolerance further + if (!converged()) + { + interface_solver_->setTolerance( + std::max( + 0.01 * data_->cosim_rel_tol, + std::numeric_limits::denorm_min()), + data_->cosim_step_tol); + interface_solver_->runSimulation(values, evaluate, false); + + evaluate(values, residual); + if (!converged()) + { + throw std::runtime_error( + std::format( + "co-simulation interface did not converge at t={}", + simulation_->time())); + } + } + } + + outgoing_ = CoSimulationMessage( + CoSim::MessageType::COMMIT, session_, simulation_->time()); + } + + void advance() + { + const auto time = simulation_->time(); + while (boundary_ < boundaries_.size()) + { + const auto boundary = boundaries_[boundary_]; + if (!(boundary < time || sameTime(boundary, time))) + { + break; + } + ++boundary_; + } + + const double exchange_time = + static_cast(step_) * options_.interval; + const double target = + std::min(exchange_time, boundaries_.at(boundary_)); + simulation_->advance(target); + outgoing_ = + CoSimulationMessage(CoSim::MessageType::STEP, session_, target); + + outgoing_.signals = simulation_->outputs(); + if (sameTime(target, exchange_time)) + { + step_++; + } + } + + public: + CoupledClient( + const SimulationOptions& options, + NextCoupledStudy next, + CompleteCoupledStudy complete, + bool shutdown_peer, + bool print_results) + : options_(options), + next_(std::move(next)), + complete_(std::move(complete)), + shutdown_peer_(shutdown_peer), + print_results_(print_results) + { + } + + CoSimulationMessage extract() + { + if (session_ == 0 && !beginStudy()) + { + outgoing_ = CoSimulationMessage(CoSim::MessageType::SHUTDOWN, 0); + } + return outgoing_; + } + + bool finished() const noexcept + { + return simulation_ == nullptr; + } + + bool handleReply( + const CoSimulationMessage& reply, + const CoSim::MessageExchangeFunction& exchange) + { + switch (outgoing_.type) + { + case CoSim::MessageType::INIT: + startStudy(reply); + solveInterface(reply.signals, exchange); + return true; + case CoSim::MessageType::STEP: + solveInterface(reply.signals, exchange); + return true; + case CoSim::MessageType::COMMIT: + simulation_->commit(); + if (sameTime(simulation_->time(), data_->tmax)) + { + outgoing_ = CoSimulationMessage( + CoSim::MessageType::END, session_, data_->tmax); + } + else + { + advance(); + } + + return true; + case CoSim::MessageType::END: + return finishStudy(); + case CoSim::MessageType::SHUTDOWN: + return false; + default: + throw std::logic_error("unexpected co-simulation client state"); + } + } + }; + + } // namespace PhasorDynamics +} // namespace GridKit +#endif diff --git a/application/PhasorDynamics/CoupledServer.hpp b/application/PhasorDynamics/CoupledServer.hpp new file mode 100644 index 0000000000..dfedf6c7dd --- /dev/null +++ b/application/PhasorDynamics/CoupledServer.hpp @@ -0,0 +1,167 @@ +#pragma once + +#include + +#ifdef GRIDKIT_ENABLE_ZMQ + +#include +#include +#include +#include + +#include "CoSimulationTypes.hpp" +#include "SimulationOptions.hpp" +#include "SimulationRunner.hpp" + +namespace GridKit +{ + namespace PhasorDynamics + { + /// Implementation of a server coupled through co-simulation. + class CoupledServer + { + const StudyData& base_; + const SimulationOptions& options_; + StudyData data_; + std::unique_ptr simulation_; + CoSimulationMessage outgoing_; + std::uint64_t session_{0}; + Testing::TestStatus status_ = true; + + void beginStudy(const CoSimulationMessage& message) + { + if (simulation_ || message.session <= session_ || message.time != 0.0 + || !sameTime(message.final_time, base_.tmax) + || !sameTime(message.interval, options_.interval)) + { + throw std::runtime_error( + "invalid co-simulation initialization or incompatible study timing"); + } + + session_ = message.session; + data_ = base_; + + if (options_.serve) + { + suffixOutputs(data_, "_session_" + std::to_string(session_)); + } + + simulation_ = std::make_unique( + data_, options_.inputs, options_.outputs, &message.signals); + simulation_->start(); + outgoing_.signals = simulation_->outputs(); + outgoing_.final_time = data_.tmax; + outgoing_.interval = options_.interval; + for (const auto& event : data_.events) + { + outgoing_.event_times.push_back(event.time); + } + } + + bool finishStudy(const CoSimulationMessage& message) + { + if (!sameTime(message.time, data_.tmax) + || !sameTime(simulation_->time(), data_.tmax)) + { + throw std::runtime_error("co-simulation ended before the final time"); + } + + status_ *= simulation_->finish(false); + simulation_.reset(); + return options_.serve; + } + + public: + CoupledServer(const StudyData& base, const SimulationOptions& options) + : base_(base), options_(options) + { + } + + CoSimulationMessage extract() const + { + return outgoing_; + } + + Testing::TestStatus status() const + { + return status_; + } + + /// Whether no simulations are running. + bool finished() const noexcept + { + return !simulation_; + } + + bool handleRequest(const CoSimulationMessage& message) + { + outgoing_ = + CoSimulationMessage(message.type, message.session, message.time); + if (message.type == CoSim::MessageType::INIT) + { + beginStudy(message); + return true; + } + + if (message.type == CoSim::MessageType::SHUTDOWN) + { + if (simulation_ || message.session != session_) + { + throw std::runtime_error( + "shutdown requires a completed co-simulation session"); + } + return false; + } + + if (!simulation_ || message.session != session_) + { + throw std::runtime_error( + "co-simulation message has no matching active session"); + } + + switch (message.type) + { + case CoSim::MessageType::STEP: + if (message.time - simulation_->time() > options_.interval + && !sameTime( + message.time - simulation_->time(), options_.interval)) + { + throw std::runtime_error( + "co-simulation step exceeds the exchange interval"); + } + + simulation_->setInputs(message.signals); + simulation_->advance(message.time); + outgoing_.signals = simulation_->outputs(); + break; + case CoSim::MessageType::UPDATE: + case CoSim::MessageType::COMMIT: + if (!sameTime(message.time, simulation_->time())) + { + throw std::runtime_error( + "interface update has the wrong simulation time"); + } + + if (message.type == CoSim::MessageType::UPDATE) + { + simulation_->setInputs(message.signals); + simulation_->refresh(); + outgoing_.signals = simulation_->outputs(); + } + else + { + simulation_->commit(); + } + break; + case CoSim::MessageType::END: + return finishStudy(message); + default: + throw std::runtime_error("unexpected co-simulation request"); + } + return true; + } + }; + + } // namespace PhasorDynamics +} // namespace GridKit +#endif diff --git a/application/PhasorDynamics/DynamicSimulation.cpp b/application/PhasorDynamics/DynamicSimulation.cpp index 351a3a1ed1..a473d5f722 100644 --- a/application/PhasorDynamics/DynamicSimulation.cpp +++ b/application/PhasorDynamics/DynamicSimulation.cpp @@ -1,115 +1,31 @@ +#include #include -#include -#include -#include -#include -#include -#include -#include -#include +#include "CoSimulation.hpp" -#include "AnalysisUtilities.hpp" - -using namespace AnalysisManager::Sundials; using namespace GridKit::PhasorDynamics; -using namespace GridKit::Testing; - -using scalar_type = double; -using real_type = double; -using index_type = size_t; int runApplication(int argc, const char* argv[]) { - // Print summaries, such as the run time, without lowering a higher verbosity Log::raiseVerbosity(Log::SUMMARY); - - // Study file - checkCommandLine(argc, "DynamicSimulation"); - auto study = parseStudyData(argv[1]); - - using EventType = SystemEvent::Type; - - for (const auto& event : study.events) + const auto options = parseSimulationOptions(argc, argv); + const auto study = parseStudyData(options.solver_file); + const auto start = std::chrono::steady_clock::now(); + GridKit::Testing::TestStatus status; +#ifdef GRIDKIT_ENABLE_ZMQ + if (options.coSimulated()) { - if ((event.type == EventType::FAULT_ON || event.type == EventType::FAULT_OFF) - && event.bus == GridKit::INVALID_INDEX) - { - Log::error() << "Fault event at time " << event.time - << " requires a bus number\n"; - return 1; - } + status = runCoupledStudy(study, options); } - - // Instantiate system - SystemModel sys(study.model_data); - sys.allocate(); - - // Set up simulation - Ida ida(&sys); - ida.setTolerance(study.rel_tol, study.abs_tol); - ida.setFixedStep(study.dt_fixed); - ida.setMaxSteps(study.max_steps); - ida.setMaxOrder(study.max_order); - ida.setConsistentICType(study.consistent_ic_type); - ida.configureSimulation(); - - // Start timer - real_type start = static_cast(clock()); - - // Initialize simulation for first run - auto dt_monitor = study.dt_monitor; - real_type final_time = study.tmax; - ida.initializeSimulation(0.0); - for (const auto& event : study.events) + else +#endif { - // Run to event time - ida.runSimulation(event.time, dt_monitor); - - // Set up run for event (to start at event time) - int ret = 0; - - switch (event.type) - { - case EventType::FAULT_ON: - ret = sys.getBus(event.bus)->setFault(true, event.R, event.X); - break; - case EventType::FAULT_OFF: - ret = sys.getBus(event.bus)->clearFault(); - break; - case EventType::IN_SERVICE: - sys.getBranch(event.device)->setInService(true); - break; - case EventType::OUT_OF_SERVICE: - sys.getBranch(event.device)->setInService(false); - break; - } - - if (ret != 0) - { - Log::error() << "Fault event failed for bus " << event.bus - << " at time " << event.time << '\n'; - return 1; - } - - // Re-initialize simulation at event time - ida.initializeSimulation(event.time); + status = runStandaloneStudy(study); } - // Run to final time - ida.runSimulation(final_time, dt_monitor); - - real_type stop = static_cast(clock()); - - // Stop the variable monitor - sys.stopMonitor(); - - // Generate aggregate errors comparing variable output to reference solution - TestStatus status = checkErrors(study); - - // Report run time - Log::summary() << "Complete in " << (stop - start) / CLOCKS_PER_SEC << " seconds\n"; - + const auto elapsed = + std::chrono::duration(std::chrono::steady_clock::now() - start); + Log::summary() << "Complete in " << elapsed.count() << " seconds\n"; return status.get(); } @@ -123,6 +39,5 @@ int main(int argc, const char* argv[]) { Log::error() << "DynamicSimulation failed: " << error.what() << '\n'; } - return 1; } diff --git a/application/PhasorDynamics/README.md b/application/PhasorDynamics/README.md index 40442fb78d..b57d2c6fc3 100644 --- a/application/PhasorDynamics/README.md +++ b/application/PhasorDynamics/README.md @@ -2,36 +2,120 @@ ## Root elements - Name | Value - ----------------------|------------------------------------------------------- - `system_model_file` | Path to the system model file[^1] - `dt_monitor` | Monitor output time interval for recorded simulation results (default: 0, no intermediate monitoring) - `tmax` | A floating-point value for max time - `rel_tol` | Relative solver tolerance (default: 1.0e-7) - `abs_tol` | Absolute solver tolerance override (default: 1.0e-9) - `dt_fixed` | Fixed solver time step size, or 0 for adaptive stepping (default: 0) - `max_steps` | Maximum number of solver time steps, 0 for the IDA default, or a negative number for unlimited steps (default: 0) - `max_order` | Maximum IDA integration method order from 1 to 5 (default: 5; fixed stepping is capped at 2) - `consistent_ic_type` | IDA consistent initial condition calculation type; one of { "y", "ya_ydp" } (default: "ya_ydp") - `events` | An array of event groups (see [Events](#events) below) - `output_file` | Path to output (CSV) file (optional) - `output_layout` | Layout of `output_file`; one of { "wide", "long" } (default: "wide"). "long" writes one row per variable and sample. Event times are written twice, before and after the event, so use `sample` rather than `t` to identify a sample. Not supported with `reference_file` - `reference_file` | A string containing the name of the case (optional) - `error_type` | One of { "relative" (default), "absolute" } - `error_tolerance` | A floating-point value for highest allowable total error (default: 1.0e-4) - `abs_err_threshold` | A floating-point value for the smallest value at which to scale relative error (default: machine epsilon for double-precision) - -[^1]: See system model [case format](../../GridKit/Model/PhasorDynamics/INPUT_FORMAT.md) +- `system_model_file`: Path to the system model file.[^1] +- `dt_monitor`: Monitor output time interval for recorded simulation results. + Default: 0, with no intermediate monitoring. +- `tmax`: A floating-point value for max time. +- `rel_tol`: Relative solver tolerance. Default: 1.0e-7. +- `abs_tol`: Absolute solver tolerance override. Default: 1.0e-9. +- `cosim_rel_tol`: Relative co-simulation interface residual tolerance. + Default: 1.0e-8. +- `cosim_step_tol`: KINSOL scaled step tolerance for the co-simulation + interface. Default: 1.0e-12. +- `cosim_function_error`: Estimated relative error of co-simulation residual + evaluations, used to choose finite difference perturbations. Default: 1.0e-10. +- `dt_fixed`: Fixed solver time step size, or 0 for adaptive stepping. + Default: 0. +- `max_steps`: Maximum number of solver time steps, 0 for the IDA default, + or a negative number for unlimited steps. Default: 0. +- `max_order`: Maximum IDA integration method order from 1 to 5. Default: 5; + fixed stepping is capped at 2. +- `consistent_ic_type`: IDA consistent initial condition calculation type; + one of `"y"`, `"ya_ydp"`. Default: `"ya_ydp"`. Co-simulation requires + `"ya_ydp"`. +- `events`: An array of event groups (see [Events](#events) below). +- `output_file`: Path to output file. Optional. +- `output_layout`: Layout of `output_file`. This may be one of `"wide"` + (default) or `"long"`. The long layout writes one row per variable and + sample. Event times are written twice, before and after the event, so + use `sample` rather than `t` to identify a sample. Not supported with + `reference_file`. +- `reference_file`: A string containing the name of the case. Optional. +- `error_type`: One of `"relative"` (default), `"absolute"`. +- `error_tolerance`: A floating-point value for greatest allowable total error. + Default: 1.0e-4. +- `abs_err_threshold`: A floating-point value for the smallest value at which + to scale relative error. Default: machine epsilon for double-precision. + +[^1]: See system model [case format]. + +[case format](../../GridKit/Model/PhasorDynamics/INPUT_FORMAT.md) + +## Co-simulation interface solve + +KINSOL is used to solve the coupling conditions at each simulation step. The +client coordinates this solve at initialization and after both partitions +have advanced to a co-simulation step boundary. + +Let $\mathbf{u}$ contain the client's input signals, supplied by the server. +At time $t_n$, let $\mathcal{C}_n(\mathbf{u})$ denote the client output signals +after applying those inputs and restoring algebraic consistency. Similarly, +$\mathcal{S}_n$ maps the server's input signals to its consistent outputs. +The interface must satisfy + +```math +\begin{aligned} + \mathbf{v}_n &= \mathcal{C}_n(\mathbf{u}_n), \\ + \mathbf{u}_n &= \mathcal{S}_n(\mathbf{v}_n), \\ + F_n(\mathbf{u}) &= \mathcal{S}_n\!\left(\mathcal{C}_n(\mathbf{u})\right). +\end{aligned} +``` + +For each trial $\mathbf{u}$, the client applies the signals and calls +IDA's consistent IC calculation. It sends the resulting outputs to the +server in an `UPDATE` message. The server applies them, restores its own +algebraic consistency, and returns its outputs. Both partitions keep their +differential states fixed during these evaluations. + +The first trial is the server's most recent output vector +$\mathbf{u}^{(0)}$. The client chooses a scale for each signal and supplies +the following residual to KINSOL: + +```math +\begin{aligned} + s_i &= \max\!\left(1, |u_i^{(0)}|\right), \\ + r_i(\mathbf{u}) &= \frac{F_{n,i}(\mathbf{u}) - u_i}{s_i}, \\ + \mathbf{r}(\mathbf{u}_n) &= \mathbf{0}. +\end{aligned} +``` + +If the initial trial already satisfies the interface tolerance, a KINSOL +iteration is not performed. Otherwise, KINSOL performs Newton iterations +without line search, with a finite difference Jacobian: + +```math +\begin{aligned} + J_{i,j}(\mathbf{u}) &= \frac{\partial r_i}{\partial u_j} \approx \frac{r_i(\mathbf{u} + h_j\mathbf{e}_j) - r_i(\mathbf{u})}{h_j}, \\ + \mathbf{J}(\mathbf{u}^{(k)})\,\Delta\mathbf{u}^{(k)} &= -\mathbf{r}(\mathbf{u}^{(k)}), \\ + \mathbf{u}^{(k+1)} &= \mathbf{u}^{(k)} + \Delta\mathbf{u}^{(k)}. +\end{aligned} +``` + +Here, $k$ indexes Newton iterations, $\mathbf{e}_j$ is the $j$-th coordinate +unit vector, and $h_j$ is the finite difference perturbation selected +by KINSOL. After each solve, each signal is compared against `cosim_rel_tol`: + +```math +\max_i \frac{|F_{n,i}(\mathbf{u}) - u_i|}{\max(1, |u_i|)} < \texttt{cosim\_rel\_tol}. +``` + +If this check fails after KINSOL returns, the client retries once with a +function tolerance 100 times smaller, clamped to the smallest possible +subnormal `double`. If the check still fails after retrying, a runtime +error is thrown with a message specifying that the interface did not +converge. Otherwise, the client sends `COMMIT`, then both partitions record +any pending monitor output and the simulation proceeds. ## Events -Each event group describes a system event that occurs at a given time point +Each event group describes a system event that occurs at a given time point. - Name | Value - --------------------|------------------------------------------------------- - `time` | A floating point value for time event occurs - `type` | Event type (one of { "fault_on", "fault_off", "in_service", "out_of_service" }) - `bus` | Number of the faulted bus (omitted for `ContingencyAnalysis`, which faults every bus in turn using the same `R` and `X`) - `R` | Fault resistance in per unit (required for "fault_on") - `X` | Fault reactance in per unit (required for "fault_on") - `device` | `id` of the branch to put in service ("in_service") or take out of service ("out_of_service"), in the top-level system or any subsystem +- `time`: A floating-point value for the time the event occurs. +- `type`: Event type; one of `"fault_on"`, `"fault_off"`, `"in_service"`, + `"out_of_service"`. +- `bus`: Number of the faulted bus. Omitted for `ContingencyAnalysis`, which + faults every bus in turn using the same `R` and `X`. +- `R`: Fault resistance in per unit. Required for `"fault_on"`. +- `X`: Fault reactance in per unit. Required for `"fault_on"`. +- `device`: `id` of the branch to put in service (`"in_service"`) or take out of + service (`"out_of_service"`), in the top-level system or any subsystem. diff --git a/application/PhasorDynamics/SimulationOptions.hpp b/application/PhasorDynamics/SimulationOptions.hpp new file mode 100644 index 0000000000..5e93ce5692 --- /dev/null +++ b/application/PhasorDynamics/SimulationOptions.hpp @@ -0,0 +1,215 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace GridKit +{ + namespace PhasorDynamics + { + /// Options to a PhasorDynamics simulation application. + struct SimulationOptions + { + /// Study file. + std::string solver_file; + + /// Co-simulation role. + std::string role; + + /// Co-simulation endpoint. + std::string endpoint; + + /// Co-simulation exchange interval. + double interval{0.0}; + + /// Co-simulation send/receive timeout. + int timeout_ms{30000}; + + /// Whether to persistently serve when running as a co-simulation server. + bool serve{false}; + + /// Networked signal inputs. + std::map inputs; + + /// Networked signal outputs. + std::map outputs; + + /// Whether the options specified a co-simulation. + bool coSimulated() const + { + return !role.empty(); + } + }; + + inline std::size_t parseSignalId(const std::string& text) + { + if (text.empty() + || text.find_first_not_of("0123456789") != std::string::npos) + { + throw std::invalid_argument( + std::format("expected a nonnegative signal ID: {}", text)); + } + + const auto value = Utilities::parse(text); + + if (value >= std::numeric_limits::max()) + { + throw std::invalid_argument( + std::format("signal ID is out of range: {}", text)); + } + + return value; + } + + /// Parse command line options to a simulation application. + inline SimulationOptions parseSimulationOptions( + int argc, const char* argv[], bool contingency = false) + { + using Utilities::ArgType; + Utilities::CliArgs args{ + {.name = {"solver_file"}, + .help = "Path to the solver JSON file", + .required = true, + .type = ArgType::String, + .positional = true}, + {.name = {"--cosim-role"}, + .help = "Co-simulation role (client, server)", + .type = ArgType::String}, + {.name = {"--cosim-endpoint"}, + .help = "ZeroMQ endpoint, e.g. tcp://127.0.0.1:8800", + .type = ArgType::String}, + {.name = {"--cosim-dt"}, + .help = "Positive exchange interval in seconds", + .type = ArgType::Real}, + {.name = {"--cosim-input"}, + .help = "Receive JSON key NAME into signal ID: " + "NAME=ID (repeatable)", + .type = ArgType::String, + .repeatable = true}, + {.name = {"--cosim-output"}, + .help = "Send signal ID as JSON key NAME: NAME=ID (repeatable)", + .type = ArgType::String, + .repeatable = true}, + {.name = {"--cosim-timeout-ms"}, + .help = "Send/receive timeout in milliseconds (default: 30000)", + .type = ArgType::Integer}, + {.name = {"--cosim-serve"}, + .help = "Server continues to accept sessions until shutdown", + .flag = true}}; + args.parseArgs(argc, argv); + + SimulationOptions options; + options.solver_file = args.get("solver_file"); + options.serve = args.get("cosim-serve"); + if (!args["cosim-role"].empty()) + { + options.role = args.get("cosim-role"); + } + if (!args["cosim-endpoint"].empty()) + { + options.endpoint = args.get("cosim-endpoint"); + } + if (!args["cosim-dt"].empty()) + { + options.interval = args.get("cosim-dt"); + if (!std::isfinite(options.interval) || options.interval <= 0.0) + { + throw std::invalid_argument( + "co-simulation exchange interval must be positive and finite"); + } + } + if (!args["cosim-timeout-ms"].empty()) + { + options.timeout_ms = args.get("cosim-timeout-ms"); + if (options.timeout_ms <= 0) + { + throw std::invalid_argument("co-simulation timeout must be positive"); + } + } + + const auto bindings = [&](const std::string& name, auto& mappings) + { + const auto& values = args[name]; + for (std::size_t i = 0; i < values.size(); ++i) + { + const auto& value = values[i](); + const auto equal = value.find('='); + if (equal == std::string::npos || equal == 0) + { + throw std::invalid_argument( + std::format("signal mapping must be NAME=ID: {}", value)); + } + if (!mappings + .emplace( + value.substr(0, equal), + parseSignalId(value.substr(equal + 1))) + .second) + { + throw std::invalid_argument( + std::format("duplicate signal name: {}", value)); + } + } + }; + bindings("cosim-input", options.inputs); + bindings("cosim-output", options.outputs); + + if (options.solver_file.empty()) + { + throw std::invalid_argument( + "no solver file provided. use --help for usage"); + } + const bool is_cosim = + options.serve || !args["cosim-role"].empty() + || !args["cosim-endpoint"].empty() || !args["cosim-dt"].empty() + || !args["cosim-input"].empty() || !args["cosim-output"].empty() + || !args["cosim-timeout-ms"].empty(); + if (is_cosim) + { +#ifndef GRIDKIT_ENABLE_ZMQ + throw std::invalid_argument( + "co-simulation requires a build with GRIDKIT_ENABLE_ZMQ=ON"); +#endif + if ((options.role != "client" && options.role != "server") + || options.endpoint.empty() || options.interval <= 0.0 + || options.inputs.empty() || options.outputs.empty()) + { + throw std::invalid_argument( + "co-simulation requires role, endpoint, dt, input and output " + "mappings"); + } + if (contingency && options.role != "client") + { + throw std::invalid_argument( + "ContingencyAnalysis requires the co-simulation client role. this " + "is a temporary limitation"); + } + if (options.serve && options.role != "server") + { + throw std::invalid_argument("--cosim-serve requires the server role"); + } + std::set ids; + for (const auto* mappings : {&options.inputs, &options.outputs}) + { + for (const auto& [name, id] : *mappings) + { + if (!ids.insert(id).second) + { + throw std::invalid_argument( + std::format("signal ID mapped more than once: {}", id)); + } + } + } + } + return options; + } + } // namespace PhasorDynamics +} // namespace GridKit diff --git a/application/PhasorDynamics/SimulationRunner.hpp b/application/PhasorDynamics/SimulationRunner.hpp new file mode 100644 index 0000000000..6d499c40a5 --- /dev/null +++ b/application/PhasorDynamics/SimulationRunner.hpp @@ -0,0 +1,349 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "AnalysisUtilities.hpp" + +namespace GridKit +{ + namespace PhasorDynamics + { + + using SimulationSystem = SystemModel; + using SimulationSolver = + AnalysisManager::Sundials::Ida; + using Signals = std::map; + using SignalBindings = std::map; + + inline void configureSolver(SimulationSolver& ida, const StudyData& data) + { + ida.setTolerance(data.rel_tol, data.abs_tol); + ida.setFixedStep(data.dt_fixed); + ida.setMaxSteps(data.max_steps); + ida.setMaxOrder(data.max_order); + ida.setConsistentICType(data.consistent_ic_type); + ida.configureSimulation(); + } + + inline void validateStudy(const StudyData& data) + { + if (!std::isfinite(data.tmax) || data.tmax <= 0.0 + || !std::isfinite(data.dt_monitor) || data.dt_monitor < 0.0) + { + throw std::invalid_argument( + "Study requires a positive tmax and a nonnegative monitor interval"); + } + + double previous = 0.0; + for (const auto& event : data.events) + { + if (!std::isfinite(event.time) || event.time < previous + || event.time > data.tmax) + { + throw std::invalid_argument( + "Events must be ordered and within the simulation interval"); + } + + previous = event.time; + + if ((event.type == SystemEvent::Type::FAULT_ON + || event.type == SystemEvent::Type::FAULT_OFF) + && event.bus == INVALID_INDEX) + { + throw std::invalid_argument("Fault events require a bus number"); + } + } + } + + inline void applyEvent(SimulationSystem& sys, const SystemEvent& event) + { + int ret = 0; + switch (event.type) + { + case SystemEvent::Type::FAULT_ON: + ret = sys.getBus(event.bus)->setFault(true, event.R, event.X); + break; + case SystemEvent::Type::FAULT_OFF: + ret = sys.getBus(event.bus)->clearFault(); + break; + case SystemEvent::Type::IN_SERVICE: + sys.getBranch(event.device)->setInService(true); + break; + case SystemEvent::Type::OUT_OF_SERVICE: + sys.getBranch(event.device)->setInService(false); + break; + } + if (ret != 0) + { + throw std::runtime_error( + "Event failed at time " + std::to_string(event.time)); + } + } + + /// Run a standalone study with event handling and monitoring. + inline Testing::TestStatus runStandaloneStudy( + const StudyData& data, + bool consistent_initial = true, + bool print_results = true) + { + validateStudy(data); + SimulationSystem sys(data.model_data); + sys.allocate(); + SimulationSolver ida(&sys); + configureSolver(ida, data); + ida.initializeSimulation(0.0, consistent_initial); + for (const auto& event : data.events) + { + ida.runSimulation(event.time, data.dt_monitor); + applyEvent(sys, event); + ida.initializeSimulation(event.time); + } + ida.runSimulation(data.tmax, data.dt_monitor); + sys.stopMonitor(); + return checkErrors(data, print_results); + } + + inline bool sameTime(double a, double b) + { + return std::abs(a - b) <= 32.0 * std::numeric_limits::epsilon() + * std::max({1.0, std::abs(a), std::abs(b)}); + } + + inline void suffixOutputs(StudyData& data, const std::string& suffix) + { + const auto rename = [&](const std::filesystem::path& path) + { + return path.parent_path() + / (path.stem().string() + suffix + path.extension().string()); + }; + + if (!data.output_file.empty()) + { + data.output_file = rename(data.output_file); + } + + for (auto& sink : data.model_data.monitor_sink) + { + sink.file_name = rename(sink.file_name).string(); + if (!data.output_file.empty() + && sink.format == Model::VariableMonitorFormat::CSV + && sink.delim == ",") + { + data.output_file = sink.file_name; + } + } + } + + /// Simulation partition with externally supplied signal inputs. + class CoupledSimulation + { + struct Input + { + double value{0.0}; + std::size_t index{INVALID_INDEX}; + }; + + const StudyData& data_; + SignalBindings outputs_; + std::map inputs_; + SimulationSystem sys_; + std::unique_ptr ida_; + double time_{0.0}; + std::size_t event_{0}; + std::size_t monitor_{1}; + bool pending_monitor_{false}; + + bool applyEvents() + { + bool changed = false; + while (event_ < data_.events.size() + && sameTime(data_.events[event_].time, time_)) + { + applyEvent(sys_, data_.events[event_++]); + changed = true; + } + return changed; + } + + public: + CoupledSimulation( + const StudyData& data, + const SignalBindings& inputs, + const SignalBindings& outputs, + const Signals* initial_inputs = nullptr) + : data_(data), outputs_(outputs), sys_(data.model_data) + { + validateStudy(data); + if (data.consistent_ic_type + != AnalysisManager::Sundials::IdaConsistentICType::YA_YDP) + { + throw std::invalid_argument( + "co-simulation requires consistent_ic_type=ya_ydp to preserve " + "differential states during interface solves"); + } + + for (const auto& [name, id] : inputs) + { + auto* signal = sys_.getSignalNode(id); + if (signal->assigned() || signal->linked()) + { + throw std::invalid_argument( + "co-simulation input has a model-owned source: " + name); + } + + auto& input = inputs_[name]; + for (const auto& bus : data.model_data.bus) + { + // TODO: this doesn't seem like the cleanest way to initialize the + // voltages + for (auto component : {BusSignalInputs::vr, BusSignalInputs::vi}) + { + const auto found = bus.signal_inputs.find(component); + if (found != bus.signal_inputs.end() && found->second == id) + { + input.value = + component == BusSignalInputs::vr ? bus.Vr0 : bus.Vi0; + } + } + } + signal->setAssigned(); + signal->link(&input.value, &input.index); + } + if (initial_inputs) + { + setInputs(*initial_inputs); + } + sys_.allocate(); + for (const auto& [name, id] : outputs_) + { + if (!sys_.getSignalNode(id)->linked()) + { + throw std::invalid_argument( + "co-simulation output has no source: " + name); + } + } + ida_ = std::make_unique(&sys_); + configureSolver(*ida_, data); + } + + void setInputs(const Signals& signals) + { + if (signals.size() != inputs_.size()) + { + throw std::invalid_argument( + "peer signal names do not match the configured inputs"); + } + + for (auto& [name, input] : inputs_) + { + const auto found = signals.find(name); + + if (found == signals.end() || !std::isfinite(found->second)) + { + throw std::invalid_argument( + "missing or non-finite input signal: " + name); + } + + input.value = found->second; + } + } + + Signals outputs() + { + // refresh current outputs accumulated during residual evaluation + sys_.evaluateResidual(); + + Signals values; + for (const auto& [name, id] : outputs_) + { + values.emplace(name, sys_.getSignalNode(id)->read()); + } + + return values; + } + + void start() + { + applyEvents(); + ida_->initializeSimulation(0.0, true, false); + pending_monitor_ = true; + } + + void refresh() + { + ida_->initializeSimulation(time_, true, false); + } + + void commit() + { + if (pending_monitor_ && sys_.monitoring()) + { + sys_.evaluateResidual(); + sys_.printMonitoredVariables(); + } + pending_monitor_ = false; + } + + double time() const + { + return time_; + } + + void advance(double target) + { + if (!std::isfinite(target) || target <= time_ || target > data_.tmax) + { + throw std::invalid_argument("Invalid co-simulation step time"); + } + + // restart after an external input change + ida_->initializeSimulation(time_, true, false); + while (time_ < target && !sameTime(time_, target)) + { + double next = target; + if (event_ < data_.events.size()) + { + next = std::min(next, data_.events[event_].time); + } + + const double monitor_time = + data_.dt_monitor > 0.0 + ? static_cast(monitor_) * data_.dt_monitor + : data_.tmax; + next = std::min(next, monitor_time); + const bool print = + sameTime(next, monitor_time) || sameTime(next, data_.tmax); + const bool at_target = sameTime(next, target); + ida_->runSimulationStep(next, 0.0, 1, 1, {}, print && !at_target); + pending_monitor_ = print && at_target; + time_ = next; + if (sameTime(time_, monitor_time)) + { + monitor_++; + } + if (applyEvents()) + { + ida_->initializeSimulation(time_, true, false); + } + } + time_ = target; + } + + Testing::TestStatus finish(bool print_results) + { + sys_.stopMonitor(); + return checkErrors(data_, print_results); + } + }; + } // namespace PhasorDynamics +} // namespace GridKit diff --git a/cases/PhasorDynamics/IEEE39/IEEE39Generator.case.json b/cases/PhasorDynamics/IEEE39/IEEE39Generator.case.json new file mode 100644 index 0000000000..c4a9d3189e --- /dev/null +++ b/cases/PhasorDynamics/IEEE39/IEEE39Generator.case.json @@ -0,0 +1,167 @@ +{ + "header": { + "format_version": 0.2, + "format_revision": 0, + "case_name": "IEEE39Generator", + "case_description": "IEEE39 case generator partition at bus 39", + "case_comments": "" + }, + "params": { + "freq_base": 60.0, + "va_base": 100000000.0 + }, + "buses": [ + { + "number": 39, + "class": "SignalVoltageIn", + "name": "39", + "init": { + "Vr": 1.014553036142942, + "Vi": -0.1777134713972648 + }, + "params": { + "kv": 1.0 + }, + "mon": [ + "Vm" + ], + "ports_in": { + "vr": 30, + "vi": 31 + }, + "ports_out": { + "ir": 32, + "ii": 33 + } + } + ], + "signals": [ + { + "signal_id": 27, + "name": "39_1_efd" + }, + { + "signal_id": 28, + "name": "39_1_speed" + }, + { + "signal_id": 29, + "name": "39_1_vs" + }, + { + "signal_id": 30, + "name": "vr" + }, + { + "signal_id": 31, + "name": "vi" + }, + { + "signal_id": 32, + "name": "ir" + }, + { + "signal_id": 33, + "name": "ii" + } + ], + "devices": [ + { + "class": "Genrou", + "id": "39_1_genrou", + "ports": { + "bus": 39, + "efd": 27, + "speed": 28 + }, + "params": { + "p0": 10.0, + "q0": 0.7360381484031677, + "H": 25.0, + "D": 0.0, + "Ra": 0.0, + "Tdop": 7.0, + "Tdopp": 0.05000000074505806, + "Tqop": 0.699999988079071, + "Tqopp": 0.03500000014901161, + "Xd": 0.4000000059604645, + "Xdp": 0.11999999731779099, + "Xdpp": 0.07999999821186066, + "Xq": 0.3799999952316284, + "Xqp": 0.1599999964237213, + "Xqpp": 0.07999999821186066, + "Xl": 0.05999999865889549, + "S10": 0.0, + "S12": 0.0, + "mva": 2000.0 + }, + "mon": [ + "omega" + ] + }, + { + "class": "Esdc1a", + "id": "39_1_esdc1a", + "ports": { + "bus": 39, + "speed": 28, + "vs": 29, + "efd": 27 + }, + "params": { + "Tr": 0.009999999776482582, + "Ka": 200.0, + "Ta": 0.014999999664723873, + "Tb": 10.0, + "Tc": 1.0, + "Vrmax": 5.0, + "Vrmin": -5.0, + "Ke": 1.0, + "Te": 0.009999999776482582, + "Kf": 0.0, + "Tf1": 0.009999999776482582, + "Spdmlt": false, + "E1": 0.0, + "Se1": 0.0, + "E2": 0.0, + "Se2": 0.0, + "UEL": 2, + "exclim": false + } + }, + { + "class": "Ieeest", + "id": "39_1_ieeest", + "ports": { + "input": 28, + "output": 29 + }, + "params": { + "A1": 0.0, + "A2": 0.0, + "A3": 0.0, + "A4": 0.0, + "A5": 0.0, + "A6": 0.0, + "T1": 5.0, + "T2": 0.6000000238418579, + "T3": 3.0, + "T4": 0.5, + "T5": 10.0, + "T6": 10.0, + "Ks": 1.0, + "Lsmin": -0.20000000298023224, + "Lsmax": 0.20000000298023224, + "Vcl": 0.0, + "Vcu": 0.0, + "Tdelay": 0.0 + } + } + ], + "monitors": [ + { + "file_name": "IEEE39Generator.csv", + "format": "csv" + } + ] +} diff --git a/cases/PhasorDynamics/IEEE39/IEEE39Grid.case.json b/cases/PhasorDynamics/IEEE39/IEEE39Grid.case.json new file mode 100644 index 0000000000..7766af7138 --- /dev/null +++ b/cases/PhasorDynamics/IEEE39/IEEE39Grid.case.json @@ -0,0 +1,2459 @@ +{ + "header": { + "format_version": 0.2, + "format_revision": 0, + "case_name": "IEEE39Grid", + "case_description": "IEEE39 case grid partition at bus 39", + "case_comments": "" + }, + "params": { + "freq_base": 60.0, + "va_base": 100000000.0 + }, + "buses": [ + { + "number": 1, + "class": "Bus", + "name": "1", + "init": { + "Vr": 1.0373518748118353, + "Vi": -0.1526008915954195 + }, + "params": { + "kv": 1.0 + }, + "mon": [] + }, + { + "number": 2, + "class": "Bus", + "name": "2", + "init": { + "Vr": 1.046266237689664, + "Vi": -0.10575826997910436 + }, + "params": { + "kv": 1.0 + }, + "mon": [] + }, + { + "number": 3, + "class": "Bus", + "name": "3", + "init": { + "Vr": 1.0247439597584178, + "Vi": -0.15512676955336763 + }, + "params": { + "kv": 1.0 + }, + "mon": [] + }, + { + "number": 4, + "class": "Bus", + "name": "4", + "init": { + "Vr": 1.0028662301165483, + "Vi": -0.1680419126553545 + }, + "params": { + "kv": 1.0 + }, + "mon": [] + }, + { + "number": 5, + "class": "Bus", + "name": "5", + "init": { + "Vr": 1.003036163899779, + "Vi": -0.1486960634497375 + }, + "params": { + "kv": 1.0 + }, + "mon": [] + }, + { + "number": 6, + "class": "Bus", + "name": "6", + "init": { + "Vr": 1.0017046617048615, + "Vi": -0.13603885168624283 + }, + "params": { + "kv": 1.0 + }, + "mon": [] + }, + { + "number": 7, + "class": "Bus", + "name": "7", + "init": { + "Vr": 0.9870031735745707, + "Vi": -0.17238554234671147 + }, + "params": { + "kv": 1.0 + }, + "mon": [] + }, + { + "number": 8, + "class": "Bus", + "name": "8", + "init": { + "Vr": 0.985329195782532, + "Vi": -0.18089257396374875 + }, + "params": { + "kv": 1.0 + }, + "mon": [] + }, + { + "number": 9, + "class": "Bus", + "name": "9", + "init": { + "Vr": 1.014424647876765, + "Vi": -0.18188917571212723 + }, + "params": { + "kv": 1.0 + }, + "mon": [] + }, + { + "number": 10, + "class": "Bus", + "name": "10", + "init": { + "Vr": 1.0169503313240278, + "Vi": -0.09444731488119429 + }, + "params": { + "kv": 1.0 + }, + "mon": [] + }, + { + "number": 11, + "class": "Bus", + "name": "11", + "init": { + "Vr": 1.0107931653298097, + "Vi": -0.10859388498551786 + }, + "params": { + "kv": 1.0 + }, + "mon": [] + }, + { + "number": 12, + "class": "Bus", + "name": "12", + "init": { + "Vr": 0.9991219888691237, + "Vi": -0.10708428969351273 + }, + "params": { + "kv": 1.0 + }, + "mon": [] + }, + { + "number": 13, + "class": "Bus", + "name": "13", + "init": { + "Vr": 1.0139558778946771, + "Vi": -0.10654819810221192 + }, + "params": { + "kv": 1.0 + }, + "mon": [] + }, + { + "number": 14, + "class": "Bus", + "name": "14", + "init": { + "Vr": 1.0106272656068025, + "Vi": -0.13502056912559224 + }, + "params": { + "kv": 1.0 + }, + "mon": [] + }, + { + "number": 15, + "class": "Bus", + "name": "15", + "init": { + "Vr": 1.010748234799687, + "Vi": -0.13925078636156354 + }, + "params": { + "kv": 1.0 + }, + "mon": [] + }, + { + "number": 16, + "class": "Bus", + "name": "16", + "init": { + "Vr": 1.0288598291857967, + "Vi": -0.11493038641136752 + }, + "params": { + "kv": 1.0 + }, + "mon": [] + }, + { + "number": 17, + "class": "Bus", + "name": "17", + "init": { + "Vr": 1.028909974298365, + "Vi": -0.13402185623322577 + }, + "params": { + "kv": 1.0 + }, + "mon": [] + }, + { + "number": 18, + "class": "Bus", + "name": "18", + "init": { + "Vr": 1.024996962635674, + "Vi": -0.14950402280226757 + }, + "params": { + "kv": 1.0 + }, + "mon": [] + }, + { + "number": 19, + "class": "Bus", + "name": "19", + "init": { + "Vr": 1.0507434915284666, + "Vi": -0.022397963224914344 + }, + "params": { + "kv": 1.0 + }, + "mon": [] + }, + { + "number": 20, + "class": "Bus", + "name": "20", + "init": { + "Vr": 0.9909177534547402, + "Vi": -0.03823577650900858 + }, + "params": { + "kv": 1.0 + }, + "mon": [] + }, + { + "number": 21, + "class": "Bus", + "name": "21", + "init": { + "Vr": 1.0317355401943193, + "Vi": -0.07358478773588971 + }, + "params": { + "kv": 1.0 + }, + "mon": [] + }, + { + "number": 22, + "class": "Bus", + "name": "22", + "init": { + "Vr": 1.051163982956661, + "Vi": 0.00461407155502083 + }, + "params": { + "kv": 1.0 + }, + "mon": [] + }, + { + "number": 23, + "class": "Bus", + "name": "23", + "init": { + "Vr": 1.0462992596105618, + "Vi": -0.00032345730365803267 + }, + "params": { + "kv": 1.0 + }, + "mon": [] + }, + { + "number": 24, + "class": "Bus", + "name": "24", + "init": { + "Vr": 1.034315049307613, + "Vi": -0.11408601273400618 + }, + "params": { + "kv": 1.0 + }, + "mon": [] + }, + { + "number": 25, + "class": "Bus", + "name": "25", + "init": { + "Vr": 1.0565707284439885, + "Vi": -0.08091028958770365 + }, + "params": { + "kv": 1.0 + }, + "mon": [] + }, + { + "number": 26, + "class": "Bus", + "name": "26", + "init": { + "Vr": 1.049553930290708, + "Vi": -0.10279778953572026 + }, + "params": { + "kv": 1.0 + }, + "mon": [] + }, + { + "number": 27, + "class": "Bus", + "name": "27", + "init": { + "Vr": 1.0318742969290995, + "Vi": -0.13732829347477998 + }, + "params": { + "kv": 1.0 + }, + "mon": [] + }, + { + "number": 28, + "class": "Bus", + "name": "28", + "init": { + "Vr": 1.0509867760013054, + "Vi": -0.038380492511356105 + }, + "params": { + "kv": 1.0 + }, + "mon": [] + }, + { + "number": 29, + "class": "Bus", + "name": "29", + "init": { + "Vr": 1.0511115352021057, + "Vi": 0.012144490361103332 + }, + "params": { + "kv": 1.0 + }, + "mon": [] + }, + { + "number": 30, + "class": "Bus", + "name": "30", + "init": { + "Vr": 1.0461985365858009, + "Vi": -0.06142129981527374 + }, + "params": { + "kv": 1.0 + }, + "mon": [] + }, + { + "number": 31, + "class": "Bus", + "name": "31", + "init": { + "Vr": 0.9819999933242798, + "Vi": 0.0 + }, + "params": { + "kv": 1.0 + }, + "mon": [] + }, + { + "number": 32, + "class": "Bus", + "name": "32", + "init": { + "Vr": 0.9819424334379631, + "Vi": 0.04558529343983338 + }, + "params": { + "kv": 1.0 + }, + "mon": [] + }, + { + "number": 33, + "class": "Bus", + "name": "33", + "init": { + "Vr": 0.9945769368084245, + "Vi": 0.06946707805130953 + }, + "params": { + "kv": 1.0 + }, + "mon": [] + }, + { + "number": 34, + "class": "Bus", + "name": "34", + "init": { + "Vr": 1.0106303304710504, + "Vi": 0.052633294721254685 + }, + "params": { + "kv": 1.0 + }, + "mon": [] + }, + { + "number": 35, + "class": "Bus", + "name": "35", + "init": { + "Vr": 1.044669259565993, + "Vi": 0.09522179445309313 + }, + "params": { + "kv": 1.0 + }, + "mon": [] + }, + { + "number": 36, + "class": "Bus", + "name": "36", + "init": { + "Vr": 1.0541024811475754, + "Vi": 0.144789438710612 + }, + "params": { + "kv": 1.0 + }, + "mon": [] + }, + { + "number": 37, + "class": "Bus", + "name": "37", + "init": { + "Vr": 1.0271032888119653, + "Vi": 0.04292817523936375 + }, + "params": { + "kv": 1.0 + }, + "mon": [] + }, + { + "number": 38, + "class": "Bus", + "name": "38", + "init": { + "Vr": 1.0177022391444508, + "Vi": 0.13788056636207754 + }, + "params": { + "kv": 1.0 + }, + "mon": [] + }, + { + "number": 39, + "class": "SignalVoltageOut", + "name": "39", + "init": { + "Vr": 1.014553036142942, + "Vi": -0.1777134713972648 + }, + "params": { + "kv": 1.0 + }, + "mon": [ + "Vm" + ], + "ports_in": { + "ir": 32, + "ii": 33 + }, + "ports_out": { + "vr": 30, + "vi": 31 + } + } + ], + "signals": [ + { + "signal_id": 0, + "name": "30_1_efd" + }, + { + "signal_id": 1, + "name": "30_1_speed" + }, + { + "signal_id": 2, + "name": "30_1_vs" + }, + { + "signal_id": 3, + "name": "31_1_efd" + }, + { + "signal_id": 4, + "name": "31_1_speed" + }, + { + "signal_id": 5, + "name": "31_1_vs" + }, + { + "signal_id": 6, + "name": "32_1_efd" + }, + { + "signal_id": 7, + "name": "32_1_speed" + }, + { + "signal_id": 8, + "name": "32_1_vs" + }, + { + "signal_id": 9, + "name": "33_1_efd" + }, + { + "signal_id": 10, + "name": "33_1_speed" + }, + { + "signal_id": 11, + "name": "33_1_vs" + }, + { + "signal_id": 12, + "name": "34_1_efd" + }, + { + "signal_id": 13, + "name": "34_1_speed" + }, + { + "signal_id": 14, + "name": "34_1_vs" + }, + { + "signal_id": 15, + "name": "35_1_efd" + }, + { + "signal_id": 16, + "name": "35_1_speed" + }, + { + "signal_id": 17, + "name": "35_1_vs" + }, + { + "signal_id": 18, + "name": "36_1_efd" + }, + { + "signal_id": 19, + "name": "36_1_speed" + }, + { + "signal_id": 20, + "name": "36_1_vs" + }, + { + "signal_id": 21, + "name": "37_1_efd" + }, + { + "signal_id": 22, + "name": "37_1_speed" + }, + { + "signal_id": 23, + "name": "37_1_vs" + }, + { + "signal_id": 24, + "name": "38_1_efd" + }, + { + "signal_id": 25, + "name": "38_1_speed" + }, + { + "signal_id": 26, + "name": "38_1_vs" + }, + { + "signal_id": 30, + "name": "vr" + }, + { + "signal_id": 31, + "name": "vi" + }, + { + "signal_id": 32, + "name": "ir" + }, + { + "signal_id": 33, + "name": "ii" + } + ], + "devices": [ + { + "class": "Branch", + "id": "branch_1_2_1", + "ports": { + "bus1": 1, + "bus2": 2 + }, + "params": { + "R": 0.0035000001080334187, + "X": 0.041099999099969864, + "B": 0.6987000107765198, + "tap": 1.0, + "phase": 0.0 + } + }, + { + "class": "Branch", + "id": "branch_1_39_1", + "ports": { + "bus1": 1, + "bus2": 39 + }, + "params": { + "R": 0.0010000000474974513, + "X": 0.02500000037252903, + "B": 0.75, + "tap": 1.0, + "phase": 0.0 + } + }, + { + "class": "Branch", + "id": "branch_2_3_1", + "ports": { + "bus1": 2, + "bus2": 3 + }, + "params": { + "R": 0.0013000000035390258, + "X": 0.01510000042617321, + "B": 0.2572000026702881, + "tap": 1.0, + "phase": 0.0 + } + }, + { + "class": "Branch", + "id": "branch_2_25_1", + "ports": { + "bus1": 2, + "bus2": 25 + }, + "params": { + "R": 0.007000000216066837, + "X": 0.00860000029206276, + "B": 0.1459999978542328, + "tap": 1.0, + "phase": 0.0 + } + }, + { + "class": "Branch", + "id": "branch_2_30_1", + "ports": { + "bus1": 2, + "bus2": 30 + }, + "params": { + "R": 1.0000000116860974e-07, + "X": 0.01810000091791153, + "tap": 1.024999976158142, + "phase": 0.0 + }, + "extension": { + "xfmr": true + } + }, + { + "class": "Branch", + "id": "branch_3_4_1", + "ports": { + "bus1": 3, + "bus2": 4 + }, + "params": { + "R": 0.0013000000035390258, + "X": 0.021299999207258224, + "B": 0.22139999270439148, + "tap": 1.0, + "phase": 0.0 + } + }, + { + "class": "Branch", + "id": "branch_3_18_1", + "ports": { + "bus1": 3, + "bus2": 18 + }, + "params": { + "R": 0.0010999999940395355, + "X": 0.013299999758601189, + "B": 0.21379999816417694, + "tap": 1.0, + "phase": 0.0 + } + }, + { + "class": "Branch", + "id": "branch_4_5_1", + "ports": { + "bus1": 4, + "bus2": 5 + }, + "params": { + "R": 0.0007999999797903001, + "X": 0.012799999676644802, + "B": 0.13420000672340393, + "tap": 1.0, + "phase": 0.0 + } + }, + { + "class": "Branch", + "id": "branch_4_14_1", + "ports": { + "bus1": 4, + "bus2": 14 + }, + "params": { + "R": 0.0007999999797903001, + "X": 0.012900000438094139, + "B": 0.13819999992847443, + "tap": 1.0, + "phase": 0.0 + } + }, + { + "class": "Branch", + "id": "branch_5_6_1", + "ports": { + "bus1": 5, + "bus2": 6 + }, + "params": { + "R": 0.00019999999494757503, + "X": 0.0026000000070780516, + "B": 0.04340000078082085, + "tap": 1.0, + "phase": 0.0 + } + }, + { + "class": "Branch", + "id": "branch_5_8_1", + "ports": { + "bus1": 5, + "bus2": 8 + }, + "params": { + "R": 0.0007999999797903001, + "X": 0.01119999960064888, + "B": 0.147599995136261, + "tap": 1.0, + "phase": 0.0 + } + }, + { + "class": "Branch", + "id": "branch_6_7_1", + "ports": { + "bus1": 6, + "bus2": 7 + }, + "params": { + "R": 0.0006000000284984708, + "X": 0.009200000204145908, + "B": 0.11299999803304672, + "tap": 1.0, + "phase": 0.0 + } + }, + { + "class": "Branch", + "id": "branch_6_11_1", + "ports": { + "bus1": 6, + "bus2": 11 + }, + "params": { + "R": 0.000699999975040555, + "X": 0.008200000040233135, + "B": 0.1388999968767166, + "tap": 1.0, + "phase": 0.0 + } + }, + { + "class": "Branch", + "id": "branch_7_8_1", + "ports": { + "bus1": 7, + "bus2": 8 + }, + "params": { + "R": 0.00039999998989515007, + "X": 0.004600000102072954, + "B": 0.07800000160932541, + "tap": 1.0, + "phase": 0.0 + } + }, + { + "class": "Branch", + "id": "branch_8_9_1", + "ports": { + "bus1": 8, + "bus2": 9 + }, + "params": { + "R": 0.002300000051036477, + "X": 0.03629999980330467, + "B": 0.38040000200271606, + "tap": 1.0, + "phase": 0.0 + } + }, + { + "class": "Branch", + "id": "branch_9_39_1", + "ports": { + "bus1": 9, + "bus2": 39 + }, + "params": { + "R": 0.0010000000474974513, + "X": 0.02500000037252903, + "B": 1.2000000476837158, + "tap": 1.0, + "phase": 0.0 + } + }, + { + "class": "Branch", + "id": "branch_10_11_1", + "ports": { + "bus1": 10, + "bus2": 11 + }, + "params": { + "R": 0.00039999998989515007, + "X": 0.00430000014603138, + "B": 0.07289999723434448, + "tap": 1.0, + "phase": 0.0 + } + }, + { + "class": "Branch", + "id": "branch_10_13_1", + "ports": { + "bus1": 10, + "bus2": 13 + }, + "params": { + "R": 0.00039999998989515007, + "X": 0.00430000014603138, + "B": 0.07289999723434448, + "tap": 1.0, + "phase": 0.0 + } + }, + { + "class": "Branch", + "id": "branch_10_32_1", + "ports": { + "bus1": 10, + "bus2": 32 + }, + "params": { + "R": 1.0000000116860974e-07, + "X": 0.019999999552965164, + "tap": 1.0700000524520874, + "phase": 0.0 + }, + "extension": { + "xfmr": true + } + }, + { + "class": "Branch", + "id": "branch_12_11_1", + "ports": { + "bus1": 12, + "bus2": 11 + }, + "params": { + "R": 0.0015999999595806003, + "X": 0.04349999874830246, + "tap": 1.00600004196167, + "phase": 0.0 + }, + "extension": { + "xfmr": true + } + }, + { + "class": "Branch", + "id": "branch_12_13_1", + "ports": { + "bus1": 12, + "bus2": 13 + }, + "params": { + "R": 0.0015999999595806003, + "X": 0.04349999874830246, + "tap": 1.00600004196167, + "phase": 0.0 + }, + "extension": { + "xfmr": true + } + }, + { + "class": "Branch", + "id": "branch_13_14_1", + "ports": { + "bus1": 13, + "bus2": 14 + }, + "params": { + "R": 0.0008999999845400453, + "X": 0.010099999606609344, + "B": 0.17229999601840973, + "tap": 1.0, + "phase": 0.0 + } + }, + { + "class": "Branch", + "id": "branch_14_15_1", + "ports": { + "bus1": 14, + "bus2": 15 + }, + "params": { + "R": 0.0017999999690800905, + "X": 0.021700000390410423, + "B": 0.3659999966621399, + "tap": 1.0, + "phase": 0.0 + } + }, + { + "class": "Branch", + "id": "branch_15_16_1", + "ports": { + "bus1": 15, + "bus2": 16 + }, + "params": { + "R": 0.0008999999845400453, + "X": 0.009399999864399433, + "B": 0.17100000381469727, + "tap": 1.0, + "phase": 0.0 + } + }, + { + "class": "Branch", + "id": "branch_16_17_1", + "ports": { + "bus1": 16, + "bus2": 17 + }, + "params": { + "R": 0.000699999975040555, + "X": 0.008899999782443047, + "B": 0.13420000672340393, + "tap": 1.0, + "phase": 0.0 + } + }, + { + "class": "Branch", + "id": "branch_16_19_1", + "ports": { + "bus1": 16, + "bus2": 19 + }, + "params": { + "R": 0.0015999999595806003, + "X": 0.019500000402331352, + "B": 0.30399999022483826, + "tap": 1.0, + "phase": 0.0 + } + }, + { + "class": "Branch", + "id": "branch_16_21_1", + "ports": { + "bus1": 16, + "bus2": 21 + }, + "params": { + "R": 0.0007999999797903001, + "X": 0.013500000350177288, + "B": 0.2547999918460846, + "tap": 1.0, + "phase": 0.0 + } + }, + { + "class": "Branch", + "id": "branch_16_24_1", + "ports": { + "bus1": 16, + "bus2": 24 + }, + "params": { + "R": 0.0003000000142492354, + "X": 0.005900000222027302, + "B": 0.06800000369548798, + "tap": 1.0, + "phase": 0.0 + } + }, + { + "class": "Branch", + "id": "branch_17_18_1", + "ports": { + "bus1": 17, + "bus2": 18 + }, + "params": { + "R": 0.000699999975040555, + "X": 0.008200000040233135, + "B": 0.13189999759197235, + "tap": 1.0, + "phase": 0.0 + } + }, + { + "class": "Branch", + "id": "branch_17_27_1", + "ports": { + "bus1": 17, + "bus2": 27 + }, + "params": { + "R": 0.0013000000035390258, + "X": 0.01730000041425228, + "B": 0.3215999901294708, + "tap": 1.0, + "phase": 0.0 + } + }, + { + "class": "Branch", + "id": "branch_19_20_1", + "ports": { + "bus1": 19, + "bus2": 20 + }, + "params": { + "R": 0.000699999975040555, + "X": 0.013799999840557575, + "tap": 1.059999942779541, + "phase": 0.0 + }, + "extension": { + "xfmr": true + } + }, + { + "class": "Branch", + "id": "branch_19_33_1", + "ports": { + "bus1": 19, + "bus2": 33 + }, + "params": { + "R": 0.000699999975040555, + "X": 0.0142000000923872, + "tap": 1.0700000524520874, + "phase": 0.0 + }, + "extension": { + "xfmr": true + } + }, + { + "class": "Branch", + "id": "branch_20_34_1", + "ports": { + "bus1": 20, + "bus2": 34 + }, + "params": { + "R": 0.0008999999845400453, + "X": 0.017999999225139618, + "tap": 1.0089999437332153, + "phase": 0.0 + }, + "extension": { + "xfmr": true + } + }, + { + "class": "Branch", + "id": "branch_21_22_1", + "ports": { + "bus1": 21, + "bus2": 22 + }, + "params": { + "R": 0.0007999999797903001, + "X": 0.014000000432133675, + "B": 0.2565000057220459, + "tap": 1.0, + "phase": 0.0 + } + }, + { + "class": "Branch", + "id": "branch_22_23_1", + "ports": { + "bus1": 22, + "bus2": 23 + }, + "params": { + "R": 0.0006000000284984708, + "X": 0.009600000455975533, + "B": 0.18459999561309814, + "tap": 1.0, + "phase": 0.0 + } + }, + { + "class": "Branch", + "id": "branch_22_35_1", + "ports": { + "bus1": 22, + "bus2": 35 + }, + "params": { + "R": 1.0000000116860974e-07, + "X": 0.014299999922513962, + "tap": 1.024999976158142, + "phase": 0.0 + }, + "extension": { + "xfmr": true + } + }, + { + "class": "Branch", + "id": "branch_23_24_1", + "ports": { + "bus1": 23, + "bus2": 24 + }, + "params": { + "R": 0.002199999988079071, + "X": 0.03500000014901161, + "B": 0.3610000014305115, + "tap": 1.0, + "phase": 0.0 + } + }, + { + "class": "Branch", + "id": "branch_23_36_1", + "ports": { + "bus1": 23, + "bus2": 36 + }, + "params": { + "R": 0.0005000000237487257, + "X": 0.0272000003606081, + "tap": 1.0, + "phase": 0.0 + }, + "extension": { + "xfmr": true + } + }, + { + "class": "Branch", + "id": "branch_25_26_1", + "ports": { + "bus1": 25, + "bus2": 26 + }, + "params": { + "R": 0.0031999999191612005, + "X": 0.03229999914765358, + "B": 0.5130000114440918, + "tap": 1.0, + "phase": 0.0 + } + }, + { + "class": "Branch", + "id": "branch_25_37_1", + "ports": { + "bus1": 25, + "bus2": 37 + }, + "params": { + "R": 0.0006000000284984708, + "X": 0.02319999970495701, + "tap": 1.024999976158142, + "phase": 0.0 + }, + "extension": { + "xfmr": true + } + }, + { + "class": "Branch", + "id": "branch_26_27_1", + "ports": { + "bus1": 26, + "bus2": 27 + }, + "params": { + "R": 0.00139999995008111, + "X": 0.014700000174343586, + "B": 0.23960000276565552, + "tap": 1.0, + "phase": 0.0 + } + }, + { + "class": "Branch", + "id": "branch_26_28_1", + "ports": { + "bus1": 26, + "bus2": 28 + }, + "params": { + "R": 0.00430000014603138, + "X": 0.04740000143647194, + "B": 0.7802000045776367, + "tap": 1.0, + "phase": 0.0 + } + }, + { + "class": "Branch", + "id": "branch_26_29_1", + "ports": { + "bus1": 26, + "bus2": 29 + }, + "params": { + "R": 0.00570000009611249, + "X": 0.0625, + "B": 1.0290000438690186, + "tap": 1.0, + "phase": 0.0 + } + }, + { + "class": "Branch", + "id": "branch_28_29_1", + "ports": { + "bus1": 28, + "bus2": 29 + }, + "params": { + "R": 0.00139999995008111, + "X": 0.01510000042617321, + "B": 0.24899999797344208, + "tap": 1.0, + "phase": 0.0 + } + }, + { + "class": "Branch", + "id": "branch_29_38_1", + "ports": { + "bus1": 29, + "bus2": 38 + }, + "params": { + "R": 0.0007999999797903001, + "X": 0.015599999576807022, + "tap": 1.024999976158142, + "phase": 0.0 + }, + "extension": { + "xfmr": true + } + }, + { + "class": "Branch", + "id": "branch_31_6_1", + "ports": { + "bus1": 31, + "bus2": 6 + }, + "params": { + "R": 1.0000000116860974e-07, + "X": 0.02500000037252903, + "tap": 0.9929999709129333, + "phase": 0.0 + }, + "extension": { + "xfmr": true + } + }, + { + "class": "LoadZIP", + "id": "load_3", + "ports": { + "bus": 3 + }, + "params": { + "Pnom": 3.2200000286102295, + "Qnom": 0.024000000208616257, + "alphaI": 0.0, + "alphaP": 0.0 + } + }, + { + "class": "LoadZIP", + "id": "load_4", + "ports": { + "bus": 4 + }, + "params": { + "Pnom": 5.0, + "Qnom": 0.8060212734615539, + "alphaI": 0.0, + "alphaP": 0.0 + } + }, + { + "class": "LoadZIP", + "id": "load_5", + "ports": { + "bus": 5 + }, + "params": { + "Pnom": 0.0, + "Qnom": -2.056384130752465, + "alphaI": 0.0, + "alphaP": 0.0 + } + }, + { + "class": "LoadZIP", + "id": "load_7", + "ports": { + "bus": 7 + }, + "params": { + "Pnom": 2.3380000591278076, + "Qnom": 0.8399999737739563, + "alphaI": 0.0, + "alphaP": 0.0 + } + }, + { + "class": "LoadZIP", + "id": "load_8", + "ports": { + "bus": 8 + }, + "params": { + "Pnom": 5.21999979019165, + "Qnom": 1.7599999904632568, + "alphaI": 0.0, + "alphaP": 0.0 + } + }, + { + "class": "LoadZIP", + "id": "load_12", + "ports": { + "bus": 12 + }, + "params": { + "Pnom": 0.07500000298023224, + "Qnom": 0.8799999952316284, + "alphaI": 0.0, + "alphaP": 0.0 + } + }, + { + "class": "LoadZIP", + "id": "load_15", + "ports": { + "bus": 15 + }, + "params": { + "Pnom": 3.200000047683716, + "Qnom": 1.5299999713897705, + "alphaI": 0.0, + "alphaP": 0.0 + } + }, + { + "class": "LoadZIP", + "id": "load_16", + "ports": { + "bus": 16 + }, + "params": { + "Pnom": 3.2939999103546143, + "Qnom": 0.3229999840259552, + "alphaI": 0.0, + "alphaP": 0.0 + } + }, + { + "class": "LoadZIP", + "id": "load_18", + "ports": { + "bus": 18 + }, + "params": { + "Pnom": 1.5800000429153442, + "Qnom": 0.30000001192092896, + "alphaI": 0.0, + "alphaP": 0.0 + } + }, + { + "class": "LoadZIP", + "id": "load_20", + "ports": { + "bus": 20 + }, + "params": { + "Pnom": 6.28000020980835, + "Qnom": 1.0299999713897705, + "alphaI": 0.0, + "alphaP": 0.0 + } + }, + { + "class": "LoadZIP", + "id": "load_21", + "ports": { + "bus": 21 + }, + "params": { + "Pnom": 2.740000009536743, + "Qnom": 1.149999976158142, + "alphaI": 0.0, + "alphaP": 0.0 + } + }, + { + "class": "LoadZIP", + "id": "load_23", + "ports": { + "bus": 23 + }, + "params": { + "Pnom": 2.744999885559082, + "Qnom": 0.8459999561309814, + "alphaI": 0.0, + "alphaP": 0.0 + } + }, + { + "class": "LoadZIP", + "id": "load_24", + "ports": { + "bus": 24 + }, + "params": { + "Pnom": 3.0859999656677246, + "Qnom": -0.921999990940094, + "alphaI": 0.0, + "alphaP": 0.0 + } + }, + { + "class": "LoadZIP", + "id": "load_25", + "ports": { + "bus": 25 + }, + "params": { + "Pnom": 2.240000009536743, + "Qnom": 0.47200000286102295, + "alphaI": 0.0, + "alphaP": 0.0 + } + }, + { + "class": "LoadZIP", + "id": "load_26", + "ports": { + "bus": 26 + }, + "params": { + "Pnom": 1.3899999856948853, + "Qnom": 0.17000000178813934, + "alphaI": 0.0, + "alphaP": 0.0 + } + }, + { + "class": "LoadZIP", + "id": "load_27", + "ports": { + "bus": 27 + }, + "params": { + "Pnom": 2.809999942779541, + "Qnom": 0.7549999952316284, + "alphaI": 0.0, + "alphaP": 0.0 + } + }, + { + "class": "LoadZIP", + "id": "load_28", + "ports": { + "bus": 28 + }, + "params": { + "Pnom": 2.059999942779541, + "Qnom": 0.2759999930858612, + "alphaI": 0.0, + "alphaP": 0.0 + } + }, + { + "class": "LoadZIP", + "id": "load_29", + "ports": { + "bus": 29 + }, + "params": { + "Pnom": 2.8350000381469727, + "Qnom": 0.26899999380111694, + "alphaI": 0.0, + "alphaP": 0.0 + } + }, + { + "class": "LoadZIP", + "id": "load_31", + "ports": { + "bus": 31 + }, + "params": { + "Pnom": 0.09200000017881393, + "Qnom": 0.04600000008940697, + "alphaI": 0.0, + "alphaP": 0.0 + } + }, + { + "class": "LoadZIP", + "id": "load_39", + "ports": { + "bus": 39 + }, + "params": { + "Pnom": 11.039999961853027, + "Qnom": 2.5, + "alphaI": 0.0, + "alphaP": 0.0 + } + }, + { + "class": "Genrou", + "id": "30_1_genrou", + "ports": { + "bus": 30, + "efd": 0, + "speed": 1 + }, + "params": { + "p0": 2.5, + "q0": 1.329376220703125, + "H": 21.0, + "D": 0.0, + "Ra": 0.0, + "Tdop": 10.199999809265137, + "Tdopp": 0.05000000074505806, + "Tqop": 0.009999999776482582, + "Tqopp": 0.03500000014901161, + "Xd": 0.20000000298023224, + "Xdp": 0.06199999898672104, + "Xdpp": 0.05000000074505806, + "Xq": 0.1379999965429306, + "Xqp": 0.05999999865889549, + "Xqpp": 0.05000000074505806, + "Xl": 0.02500000037252903, + "S10": 0.0, + "S12": 0.0, + "mva": 200.0 + }, + "mon": [ + "omega" + ] + }, + { + "class": "Esdc1a", + "id": "30_1_esdc1a", + "ports": { + "bus": 30, + "speed": 1, + "vs": 2, + "efd": 0 + }, + "params": { + "Tr": 0.009999999776482582, + "Ka": 200.0, + "Ta": 0.014999999664723873, + "Tb": 10.0, + "Tc": 1.0, + "Vrmax": 5.0, + "Vrmin": -5.0, + "Ke": 1.0, + "Te": 0.009999999776482582, + "Kf": 0.0, + "Tf1": 0.009999999776482582, + "Spdmlt": false, + "E1": 0.0, + "Se1": 0.0, + "E2": 0.0, + "Se2": 0.0, + "UEL": 2, + "exclim": false + } + }, + { + "class": "Ieeest", + "id": "30_1_ieeest", + "ports": { + "input": 1, + "output": 2 + }, + "params": { + "A1": 0.0, + "A2": 0.0, + "A3": 0.0, + "A4": 0.0, + "A5": 0.0, + "A6": 0.0, + "T1": 1.0, + "T2": 0.05000000074505806, + "T3": 3.0, + "T4": 0.5, + "T5": 10.0, + "T6": 10.0, + "Ks": 1.0, + "Lsmin": -0.20000000298023224, + "Lsmax": 0.20000000298023224, + "Vcl": 0.0, + "Vcu": 0.0, + "Tdelay": 0.0 + } + }, + { + "class": "Genrou", + "id": "31_1_genrou", + "ports": { + "bus": 31, + "efd": 3, + "speed": 4 + }, + "params": { + "p0": 5.473272800445557, + "q0": -0.45964500308036804, + "H": 30.299999237060547, + "D": 0.0, + "Ra": 0.0, + "Tdop": 6.559999942779541, + "Tdopp": 0.05000000074505806, + "Tqop": 1.5, + "Tqopp": 0.03500000014901161, + "Xd": 0.29499998688697815, + "Xdp": 0.06970000267028809, + "Xdpp": 0.05000000074505806, + "Xq": 0.28200000524520874, + "Xqp": 0.17000000178813934, + "Xqpp": 0.05000000074505806, + "Xl": 0.03500000014901161, + "S10": 0.0, + "S12": 0.0, + "mva": 100.0 + }, + "mon": [ + "omega" + ] + }, + { + "class": "Esdc1a", + "id": "31_1_esdc1a", + "ports": { + "bus": 31, + "speed": 4, + "vs": 5, + "efd": 3 + }, + "params": { + "Tr": 0.009999999776482582, + "Ka": 200.0, + "Ta": 0.014999999664723873, + "Tb": 10.0, + "Tc": 1.0, + "Vrmax": 5.0, + "Vrmin": -5.0, + "Ke": 1.0, + "Te": 0.009999999776482582, + "Kf": 0.0, + "Tf1": 0.009999999776482582, + "Spdmlt": false, + "E1": 0.0, + "Se1": 0.0, + "E2": 0.0, + "Se2": 0.0, + "UEL": 2, + "exclim": false + } + }, + { + "class": "Ieeest", + "id": "31_1_ieeest", + "ports": { + "input": 4, + "output": 5 + }, + "params": { + "A1": 0.0, + "A2": 0.0, + "A3": 0.0, + "A4": 0.0, + "A5": 0.0, + "A6": 0.0, + "T1": 5.0, + "T2": 0.4000000059604645, + "T3": 1.0, + "T4": 0.10000000149011612, + "T5": 10.0, + "T6": 10.0, + "Ks": 0.5, + "Lsmin": -0.20000000298023224, + "Lsmax": 0.20000000298023224, + "Vcl": 0.0, + "Vcu": 0.0, + "Tdelay": 0.0 + } + }, + { + "class": "Genrou", + "id": "32_1_genrou", + "ports": { + "bus": 32, + "efd": 6, + "speed": 7 + }, + "params": { + "p0": 6.5, + "q0": 1.8526761531829834, + "H": 17.899999618530273, + "D": 0.0, + "Ra": 0.0, + "Tdop": 5.699999809265137, + "Tdopp": 0.05000000074505806, + "Tqop": 1.5, + "Tqopp": 0.03500000014901161, + "Xd": 0.49900001287460327, + "Xdp": 0.10620000213384628, + "Xdpp": 0.09000000357627869, + "Xq": 0.4740000069141388, + "Xqp": 0.17520000040531158, + "Xqpp": 0.09000000357627869, + "Xl": 0.06080000102519989, + "S10": 0.0, + "S12": 0.0, + "mva": 200.0 + }, + "mon": [ + "omega" + ] + }, + { + "class": "Esdc1a", + "id": "32_1_esdc1a", + "ports": { + "bus": 32, + "speed": 7, + "vs": 8, + "efd": 6 + }, + "params": { + "Tr": 0.009999999776482582, + "Ka": 200.0, + "Ta": 0.014999999664723873, + "Tb": 10.0, + "Tc": 1.0, + "Vrmax": 5.0, + "Vrmin": -5.0, + "Ke": 1.0, + "Te": 0.009999999776482582, + "Kf": 0.0, + "Tf1": 0.009999999776482582, + "Spdmlt": false, + "E1": 0.0, + "Se1": 0.0, + "E2": 0.0, + "Se2": 0.0, + "UEL": 2, + "exclim": false + } + }, + { + "class": "Ieeest", + "id": "32_1_ieeest", + "ports": { + "input": 7, + "output": 8 + }, + "params": { + "A1": 0.0, + "A2": 0.0, + "A3": 0.0, + "A4": 0.0, + "A5": 0.0, + "A6": 0.0, + "T1": 3.0, + "T2": 0.20000000298023224, + "T3": 2.0, + "T4": 0.20000000298023224, + "T5": 10.0, + "T6": 10.0, + "Ks": 0.5, + "Lsmin": -0.20000000298023224, + "Lsmax": 0.20000000298023224, + "Vcl": 0.0, + "Vcu": 0.0, + "Tdelay": 0.0 + } + }, + { + "class": "Genrou", + "id": "33_1_genrou", + "ports": { + "bus": 33, + "efd": 9, + "speed": 10 + }, + "params": { + "p0": 6.320000171661377, + "q0": 1.0113619565963745, + "H": 14.300000190734863, + "D": 0.0, + "Ra": 0.0, + "Tdop": 5.690000057220459, + "Tdopp": 0.05000000074505806, + "Tqop": 1.5, + "Tqopp": 0.03500000014901161, + "Xd": 0.5239999890327454, + "Xdp": 0.08720000088214874, + "Xdpp": 0.07000000029802322, + "Xq": 0.515999972820282, + "Xqp": 0.3319999873638153, + "Xqpp": 0.07000000029802322, + "Xl": 0.05900000035762787, + "S10": 0.0, + "S12": 0.0, + "mva": 200.0 + }, + "mon": [ + "omega" + ] + }, + { + "class": "Esdc1a", + "id": "33_1_esdc1a", + "ports": { + "bus": 33, + "speed": 10, + "vs": 11, + "efd": 9 + }, + "params": { + "Tr": 0.009999999776482582, + "Ka": 200.0, + "Ta": 0.014999999664723873, + "Tb": 10.0, + "Tc": 1.0, + "Vrmax": 5.0, + "Vrmin": -5.0, + "Ke": 1.0, + "Te": 0.009999999776482582, + "Kf": 0.0, + "Tf1": 0.009999999776482582, + "Spdmlt": false, + "E1": 0.0, + "Se1": 0.0, + "E2": 0.0, + "Se2": 0.0, + "UEL": 2, + "exclim": false + } + }, + { + "class": "Ieeest", + "id": "33_1_ieeest", + "ports": { + "input": 10, + "output": 11 + }, + "params": { + "A1": 0.0, + "A2": 0.0, + "A3": 0.0, + "A4": 0.0, + "A5": 0.0, + "A6": 0.0, + "T1": 1.0, + "T2": 0.10000000149011612, + "T3": 1.0, + "T4": 0.30000001192092896, + "T5": 10.0, + "T6": 10.0, + "Ks": 2.0, + "Lsmin": -0.20000000298023224, + "Lsmax": 0.20000000298023224, + "Vcl": 0.0, + "Vcu": 0.0, + "Tdelay": 0.0 + } + }, + { + "class": "Genrou", + "id": "34_1_genrou", + "ports": { + "bus": 34, + "efd": 12, + "speed": 13 + }, + "params": { + "p0": 5.079999923706055, + "q0": 1.613751769065857, + "H": 26.0, + "D": 0.0, + "Ra": 0.0, + "Tdop": 5.400000095367432, + "Tdopp": 0.05000000074505806, + "Tqop": 0.4399999976158142, + "Tqopp": 0.03500000014901161, + "Xd": 0.6700000166893005, + "Xdp": 0.13199999928474426, + "Xdpp": 0.08900000154972076, + "Xq": 0.6200000047683716, + "Xqp": 0.16599999368190765, + "Xqpp": 0.08900000154972076, + "Xl": 0.05400000140070915, + "S10": 0.0, + "S12": 0.0, + "mva": 100.0 + }, + "mon": [ + "omega" + ] + }, + { + "class": "Esdc1a", + "id": "34_1_esdc1a", + "ports": { + "bus": 34, + "speed": 13, + "vs": 14, + "efd": 12 + }, + "params": { + "Tr": 0.009999999776482582, + "Ka": 200.0, + "Ta": 0.014999999664723873, + "Tb": 10.0, + "Tc": 1.0, + "Vrmax": 5.0, + "Vrmin": -5.0, + "Ke": 1.0, + "Te": 0.009999999776482582, + "Kf": 0.0, + "Tf1": 0.009999999776482582, + "Spdmlt": false, + "E1": 0.0, + "Se1": 0.0, + "E2": 0.0, + "Se2": 0.0, + "UEL": 2, + "exclim": false + } + }, + { + "class": "Ieeest", + "id": "34_1_ieeest", + "ports": { + "input": 13, + "output": 14 + }, + "params": { + "A1": 0.0, + "A2": 0.0, + "A3": 0.0, + "A4": 0.0, + "A5": 0.0, + "A6": 0.0, + "T1": 1.5, + "T2": 0.20000000298023224, + "T3": 1.0, + "T4": 0.10000000149011612, + "T5": 10.0, + "T6": 10.0, + "Ks": 1.0, + "Lsmin": -0.20000000298023224, + "Lsmax": 0.20000000298023224, + "Vcl": 0.0, + "Vcu": 0.0, + "Tdelay": 0.0 + } + }, + { + "class": "Genrou", + "id": "35_1_genrou", + "ports": { + "bus": 35, + "efd": 15, + "speed": 16 + }, + "params": { + "p0": 6.5, + "q0": 2.002546548843384, + "H": 17.399999618530273, + "D": 0.0, + "Ra": 0.0, + "Tdop": 7.300000190734863, + "Tdopp": 0.05000000074505806, + "Tqop": 0.4000000059604645, + "Tqopp": 0.03500000014901161, + "Xd": 0.5080000162124634, + "Xdp": 0.10000000149011612, + "Xdpp": 0.07999999821186066, + "Xq": 0.4819999933242798, + "Xqp": 0.16279999911785126, + "Xqpp": 0.07999999821186066, + "Xl": 0.04479999840259552, + "S10": 0.0, + "S12": 0.0, + "mva": 200.0 + }, + "mon": [ + "omega" + ] + }, + { + "class": "Esdc1a", + "id": "35_1_esdc1a", + "ports": { + "bus": 35, + "speed": 16, + "vs": 17, + "efd": 15 + }, + "params": { + "Tr": 0.009999999776482582, + "Ka": 200.0, + "Ta": 0.014999999664723873, + "Tb": 10.0, + "Tc": 1.0, + "Vrmax": 5.0, + "Vrmin": -5.0, + "Ke": 1.0, + "Te": 0.009999999776482582, + "Kf": 0.0, + "Tf1": 0.009999999776482582, + "Spdmlt": false, + "E1": 0.0, + "Se1": 0.0, + "E2": 0.0, + "Se2": 0.0, + "UEL": 2, + "exclim": false + } + }, + { + "class": "Ieeest", + "id": "35_1_ieeest", + "ports": { + "input": 16, + "output": 17 + }, + "params": { + "A1": 0.0, + "A2": 0.0, + "A3": 0.0, + "A4": 0.0, + "A5": 0.0, + "A6": 0.0, + "T1": 0.5, + "T2": 0.10000000149011612, + "T3": 0.5, + "T4": 0.10000000149011612, + "T5": 10.0, + "T6": 10.0, + "Ks": 4.0, + "Lsmin": -0.20000000298023224, + "Lsmax": 0.20000000298023224, + "Vcl": 0.0, + "Vcu": 0.0, + "Tdelay": 0.0 + } + }, + { + "class": "Genrou", + "id": "36_1_genrou", + "ports": { + "bus": 36, + "efd": 18, + "speed": 19 + }, + "params": { + "p0": 5.599999904632568, + "q0": 0.9719194769859314, + "H": 13.199999809265137, + "D": 0.0, + "Ra": 0.0, + "Tdop": 5.659999847412109, + "Tdopp": 0.05000000074505806, + "Tqop": 1.5, + "Tqopp": 0.03500000014901161, + "Xd": 0.5899999737739563, + "Xdp": 0.09799999743700027, + "Xdpp": 0.08799999952316284, + "Xq": 0.5839999914169312, + "Xqp": 0.3720000088214874, + "Xqpp": 0.08799999952316284, + "Xl": 0.06440000236034393, + "S10": 0.0, + "S12": 0.0, + "mva": 200.0 + }, + "mon": [ + "omega" + ] + }, + { + "class": "Esdc1a", + "id": "36_1_esdc1a", + "ports": { + "bus": 36, + "speed": 19, + "vs": 20, + "efd": 18 + }, + "params": { + "Tr": 0.009999999776482582, + "Ka": 200.0, + "Ta": 0.014999999664723873, + "Tb": 10.0, + "Tc": 1.0, + "Vrmax": 5.0, + "Vrmin": -5.0, + "Ke": 1.0, + "Te": 0.009999999776482582, + "Kf": 0.0, + "Tf1": 0.009999999776482582, + "Spdmlt": false, + "E1": 0.0, + "Se1": 0.0, + "E2": 0.0, + "Se2": 0.0, + "UEL": 2, + "exclim": false + } + }, + { + "class": "Ieeest", + "id": "36_1_ieeest", + "ports": { + "input": 19, + "output": 20 + }, + "params": { + "A1": 0.0, + "A2": 0.0, + "A3": 0.0, + "A4": 0.0, + "A5": 0.0, + "A6": 0.0, + "T1": 0.20000000298023224, + "T2": 0.019999999552965164, + "T3": 0.5, + "T4": 0.10000000149011612, + "T5": 10.0, + "T6": 10.0, + "Ks": 7.5, + "Lsmin": -0.20000000298023224, + "Lsmax": 0.20000000298023224, + "Vcl": 0.0, + "Vcu": 0.0, + "Tdelay": 0.0 + } + }, + { + "class": "Genrou", + "id": "37_1_genrou", + "ports": { + "bus": 37, + "efd": 21, + "speed": 22 + }, + "params": { + "p0": 5.400000095367432, + "q0": -0.07785409688949585, + "H": 12.149999618530273, + "D": 0.0, + "Ra": 0.0, + "Tdop": 6.699999809265137, + "Tdopp": 0.05000000074505806, + "Tqop": 0.4099999964237213, + "Tqopp": 0.03500000014901161, + "Xd": 0.5799999833106995, + "Xdp": 0.11400000005960464, + "Xdpp": 0.09000000357627869, + "Xq": 0.5600000023841858, + "Xqp": 0.18219999969005585, + "Xqpp": 0.09000000357627869, + "Xl": 0.0560000017285347, + "S10": 0.0, + "S12": 0.0, + "mva": 200.0 + }, + "mon": [ + "omega" + ] + }, + { + "class": "Esdc1a", + "id": "37_1_esdc1a", + "ports": { + "bus": 37, + "speed": 22, + "vs": 23, + "efd": 21 + }, + "params": { + "Tr": 0.009999999776482582, + "Ka": 200.0, + "Ta": 0.014999999664723873, + "Tb": 10.0, + "Tc": 1.0, + "Vrmax": 5.0, + "Vrmin": -5.0, + "Ke": 1.0, + "Te": 0.009999999776482582, + "Kf": 0.0, + "Tf1": 0.009999999776482582, + "Spdmlt": false, + "E1": 0.0, + "Se1": 0.0, + "E2": 0.0, + "Se2": 0.0, + "UEL": 2, + "exclim": false + } + }, + { + "class": "Ieeest", + "id": "37_1_ieeest", + "ports": { + "input": 22, + "output": 23 + }, + "params": { + "A1": 0.0, + "A2": 0.0, + "A3": 0.0, + "A4": 0.0, + "A5": 0.0, + "A6": 0.0, + "T1": 1.0, + "T2": 0.20000000298023224, + "T3": 1.0, + "T4": 0.10000000149011612, + "T5": 10.0, + "T6": 10.0, + "Ks": 2.0, + "Lsmin": -0.20000000298023224, + "Lsmax": 0.20000000298023224, + "Vcl": 0.0, + "Vcu": 0.0, + "Tdelay": 0.0 + } + }, + { + "class": "Genrou", + "id": "38_1_genrou", + "ports": { + "bus": 38, + "efd": 24, + "speed": 25 + }, + "params": { + "p0": 8.300000190734863, + "q0": 0.1812410056591034, + "H": 17.25, + "D": 0.0, + "Ra": 0.0, + "Tdop": 4.789999961853027, + "Tdopp": 0.05000000074505806, + "Tqop": 1.9600000381469727, + "Tqopp": 0.03500000014901161, + "Xd": 0.4212000072002411, + "Xdp": 0.11400000005960464, + "Xdpp": 0.09000000357627869, + "Xq": 0.4099999964237213, + "Xqp": 0.11739999800920486, + "Xqpp": 0.09000000357627869, + "Xl": 0.05959999933838844, + "S10": 0.0, + "S12": 0.0, + "mva": 200.0 + }, + "mon": [ + "omega" + ] + }, + { + "class": "Esdc1a", + "id": "38_1_esdc1a", + "ports": { + "bus": 38, + "speed": 25, + "vs": 26, + "efd": 24 + }, + "params": { + "Tr": 0.009999999776482582, + "Ka": 200.0, + "Ta": 0.014999999664723873, + "Tb": 10.0, + "Tc": 1.0, + "Vrmax": 5.0, + "Vrmin": -5.0, + "Ke": 1.0, + "Te": 0.009999999776482582, + "Kf": 0.0, + "Tf1": 0.009999999776482582, + "Spdmlt": false, + "E1": 0.0, + "Se1": 0.0, + "E2": 0.0, + "Se2": 0.0, + "UEL": 2, + "exclim": false + } + }, + { + "class": "Ieeest", + "id": "38_1_ieeest", + "ports": { + "input": 25, + "output": 26 + }, + "params": { + "A1": 0.0, + "A2": 0.0, + "A3": 0.0, + "A4": 0.0, + "A5": 0.0, + "A6": 0.0, + "T1": 1.0, + "T2": 0.5, + "T3": 2.0, + "T4": 0.10000000149011612, + "T5": 10.0, + "T6": 10.0, + "Ks": 2.0, + "Lsmin": -0.20000000298023224, + "Lsmax": 0.20000000298023224, + "Vcl": 0.0, + "Vcu": 0.0, + "Tdelay": 0.0 + } + } + ], + "monitors": [ + { + "file_name": "IEEE39Grid.csv", + "format": "csv" + } + ] +} diff --git a/docs/examples/PhasorDynamics/CoSimulation/README.md b/docs/examples/PhasorDynamics/CoSimulation/README.md new file mode 100644 index 0000000000..b53bc017e7 --- /dev/null +++ b/docs/examples/PhasorDynamics/CoSimulation/README.md @@ -0,0 +1,6 @@ +# Co-simulation + +```{include} ../../../../examples/PhasorDynamics/CoSimulation/README.md +:start-line: 1 +:relative-images: +``` diff --git a/docs/examples/PhasorDynamics/README.md b/docs/examples/PhasorDynamics/README.md index b3ddf9b179..4db51dcdd4 100644 --- a/docs/examples/PhasorDynamics/README.md +++ b/docs/examples/PhasorDynamics/README.md @@ -7,6 +7,7 @@ DynamicSimulation ContingencyAnalysis +CoSimulation Validation ``` diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 532dc31d9d..55148c2c61 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -57,7 +57,6 @@ add_subdirectory(OptimalPowerFlow) 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 deleted file mode 100644 index e80e6ab3c5..0000000000 --- a/examples/Network/CMakeLists.txt +++ /dev/null @@ -1,3 +0,0 @@ -if(GRIDKIT_ENABLE_ZMQ) - add_subdirectory(CoSim) -endif() diff --git a/examples/Network/CoSim/CMakeLists.txt b/examples/Network/CoSim/CMakeLists.txt deleted file mode 100644 index 714a68b8e2..0000000000 --- a/examples/Network/CoSim/CMakeLists.txt +++ /dev/null @@ -1,32 +0,0 @@ -gridkit_example_add_file(TwoBusCoSimClient.case.json) -gridkit_example_add_file(TwoBusCoSimClient.solo.case.json) -gridkit_example_add_file(TwoBusCoSimClient.solver.json) -gridkit_example_add_file(TwoBusCoSimServer.case.json) -gridkit_example_add_file(TwoBusCoSimServer.solo.case.json) -gridkit_example_add_file(TwoBusCoSimServer.solver.json) -gridkit_example_add_file(ThreeBusCoSimClient.case.json) -gridkit_example_add_file(ThreeBusCoSimServer.case.json) -gridkit_example_add_file(run_cosim.sh) - -add_executable(CoSimClient CoSimClient.cpp) -target_link_libraries( - CoSimClient - PUBLIC GridKit::utilities_cli_args - GridKit::phasor_dynamics_systemmodel - GridKit::solvers_dyn - cppzmq) - -add_executable(CoSimServer CoSimServer.cpp) -target_link_libraries( - CoSimServer - PUBLIC GridKit::utilities_cli_args - GridKit::phasor_dynamics_systemmodel - GridKit::solvers_dyn - cppzmq) - -add_test( - NAME CoSimExample - COMMAND bash run_cosim.sh - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) - -set_tests_properties(CoSimExample PROPERTIES TIMEOUT 30) diff --git a/examples/Network/CoSim/CoSim.hpp b/examples/Network/CoSim/CoSim.hpp deleted file mode 100644 index 4255797d4d..0000000000 --- a/examples/Network/CoSim/CoSim.hpp +++ /dev/null @@ -1,51 +0,0 @@ -#pragma once - -namespace GridKit -{ - namespace CoSim - { - /* - * The following is meant to be used to communicate simulation driver - * information over the network. It is crude, but it at least gives language - * in the code that expresses the intent. - * - * - INIT is used to initialize the co-simulation - * - STEP is used to initiate (and continue) the co-simulation (take a step) - * - END is used to end the co-simulation - * - FAIL is used to signal an early termination due to a failure - */ - enum class Status - { - INIT, - STEP, - END, - FAIL - }; - - /** - * Integer representation of Status - */ - using StatusRep = std::underlying_type_t; - - /** - * @brief Write a Status to a stream as an integer - */ - std::ostream& operator<<(std::ostream& os, Status stat) - { - os << static_cast(stat); - return os; - } - - /** - * @brief Read a Status from a stream, in which it is represented as an - * integer - */ - std::istream& operator>>(std::istream& is, Status& stat) - { - StatusRep rep; - is >> rep; - stat = static_cast(rep); - return is; - } - } // namespace CoSim -} // namespace GridKit diff --git a/examples/Network/CoSim/CoSimClient.cpp b/examples/Network/CoSim/CoSimClient.cpp deleted file mode 100644 index c56c583741..0000000000 --- a/examples/Network/CoSim/CoSimClient.cpp +++ /dev/null @@ -1,261 +0,0 @@ -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include "CoSim.hpp" -#include - -using namespace GridKit; -using namespace GridKit::PhasorDynamics; -using namespace GridKit::Utilities; -using namespace AnalysisManager::Sundials; - -using Log = GridKit::Utilities::Logger; - -/** - * @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 -{ -private: - /// Type representing a scalar value - using ScalarT = scalar_type; - /// Type representing an index - using IdxT = index_type; - /// Type representing a real value - using RealT = ScalarTraits::RealT; - /// Alias for SystemModel - using SystemModelT = SystemModel; - /// Alias for SignalNode - using SignalNodeT = SignalNode; - /// Alias for Ida - using IdaT = Ida; - -public: - 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 sr_out node from which to read real component of signal to send - * @param si_out node from which to read imaginary component of signal to send - * @param sr_in node for communicating received real component of signal - * @param si_in node for communicating received imaginary component of signal - */ - CoSimClient(SystemModelT& sys, SignalNodeT* sr_out, SignalNodeT* si_out, SignalNodeT* sr_in, SignalNodeT* si_in) - : sr_out_(sr_out), - si_out_(si_out), - sr_in_(sr_in), - si_in_(si_in), - ida_(&sys), - ctx_{}, - socket_(ctx_, zmq::socket_type::req) - { - sr_in_->link(&sr_, &sr_idx_); - si_in_->link(&si_, &si_idx_); - - socket_.connect("tcp://0.0.0.0:8800"); - Log::summary() << "CLIENT: Established connection with server\n"; - } - - /** - * @brief Signal the end of the simulation to the server and destruct object - */ - ~CoSimClient() - { - exchange(CoSim::Status::END); - Log::summary() << "CLIENT: Ending simulation\n"; - } - - /** - * @brief Perform initial solver setup - */ - void configure() - { - ida_.setTolerance(1.0e-7, 1.0e-9); - ida_.configureSimulation(); - } - - /** - * @brief Send output signals to and receive input signals from server-side - * instance for a single time step. - */ - void exchange(CoSim::Status stat) - { - // 1. Send data - std::ostringstream oss; - oss << std::scientific << std::setprecision(16); - oss << stat; - if (stat == CoSim::Status::INIT) - { - auto sr = sr_out_->read(); - auto si = si_out_->read(); - oss << " " << ti_ << " " << tf_ << " " << dt_ << " " << nsteps_ - << " " << sr << " " << si; - } - else if (stat != CoSim::Status::FAIL) - { - auto sr = sr_out_->read(); - auto si = si_out_->read(); - oss << " " << step_ << " " << sr << " " << si; - } - - Log::misc() << "CLIENT: Sending \"" << oss.str() << "\"\n"; - zmq::message_t s_msg{oss.str().data(), oss.str().size()}; - socket_.send(s_msg, zmq::send_flags::none); - if (stat == CoSim::Status::FAIL) - { - return; - } - - // 2. Receive data - zmq::message_t r_msg; - auto recv_result = socket_.recv(r_msg, zmq::recv_flags::none); - if (!recv_result) - { - throw std::runtime_error("Co-sim client: failed receive"); - } - auto r_msg_str = r_msg.to_string(); - Log::misc() << "CLIENT: Received \"" << r_msg_str << "\"\n"; - auto iss = std::istringstream(r_msg_str); - - CoSim::Status rstat; - iss >> rstat; - if (rstat == CoSim::Status::FAIL) - { - throw std::runtime_error("Received failure message from server"); - } - iss >> sr_ >> si_; - } - - /** - * @brief Run Ida over specified time period - * @param ti Initial time - * @param tf Final time - * @param dt Time step size - */ - void runSimulation(RealT ti, RealT tf, RealT dt) - { - ti_ = ti; - tf_ = tf; - dt_ = dt; - - ida_.initializeSimulation(ti_, false); - - nsteps_ = ida_.getStepCount(tf_, dt_); - - exchange(CoSim::Status::INIT); - - try - { - for (step_ = 1; step_ <= nsteps_; step_++) - { - const RealT t = std::fma(static_cast(step_ - 1), dt_, ti_); - const RealT target = step_ == nsteps_ ? tf_ : std::fma(static_cast(step_), dt_, ti_); - ida_.initializeSimulation(t); - ida_.runSimulationStep(target, dt_, 1, 1); - exchange(CoSim::Status::STEP); - } - } - catch (const std::exception& e) - { - exchange(CoSim::Status::FAIL); - throw; - } - } - -private: - /// node from which to read real component of signal to send - SignalNodeT* sr_out_; - /// node from which to read imaginary component of signal to send - SignalNodeT* si_out_; - /// node for communicating received real component of input signal - SignalNodeT* sr_in_; - /// node for communicating received imaginary component of input signal - SignalNodeT* si_in_; - /// variable for receiving signal real component - ScalarT sr_{}; - /// variable for receiving signal imaginary component - ScalarT si_{}; - /// dummy index for signal id - IdxT sr_idx_{GridKit::INVALID_INDEX}; - /// dummy index for signal id - IdxT si_idx_{GridKit::INVALID_INDEX}; - - /// Ida solver - IdaT ida_; - - // Solver stepping parameters - RealT ti_; - RealT tf_; - RealT dt_; - int step_; - int nsteps_; - - /// 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(int argc, const char* argv[]) -{ - Log::setVerbosity(Log::Verbosity::EVERYTHING); - - CliArgs args{{.name = {"--case-file", "-c"}, - .required = true}}; - - args.parseArgs(argc, argv); - - auto filepath = std::filesystem::path(args["case-file"]()); - - // Instantiate system - auto data = parseSystemModelData(filepath); - auto sys = SystemModel(data); - - auto client = CoSimClient(sys, - sys.getSignalNode(1), - sys.getSignalNode(2), - sys.getSignalNode(3), - sys.getSignalNode(4)); - sys.allocate(); - client.configure(); - - RealT dt = 1.0 / 16.0 / 60.0; - - client.runSimulation(0.0, 0.1, dt); - - // Run for 1s - // client.runSimulation(0.0, 1.0, dt); - - // // Introduce fault and run for the next 0.1s - // client.runSimulation(1.0, 1.1, dt); - - // // Clear the fault and run until t = 10s. - // client.runSimulation(1.1, 10.0, dt); - - return 0; -} diff --git a/examples/Network/CoSim/CoSimServer.cpp b/examples/Network/CoSim/CoSimServer.cpp deleted file mode 100644 index 234ce93d22..0000000000 --- a/examples/Network/CoSim/CoSimServer.cpp +++ /dev/null @@ -1,228 +0,0 @@ -#include -#include -#include -#include -#include - -#include -#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; -using namespace GridKit::Utilities; -using namespace AnalysisManager::Sundials; - -using Log = GridKit::Utilities::Logger; - -/** - * @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 -{ -private: - /// Type representing a scalar value - using ScalarT = scalar_type; - /// Type representing an index - using IdxT = index_type; - /// Type representing a real value - using RealT = ScalarTraits::RealT; - /// Alias for SystemModel - using SystemModelT = SystemModel; - /// Alias for BusBase - using BusT = BusBase; - /// Alias for SignalNode - using SignalNodeT = SignalNode; - /// Alias for Ida - using IdaT = Ida; - -public: - 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 sr_out node from which to read real component of signal to send - * @param si_out node from which to read imaginary component of signal to send - * @param sr_in node for communicating received real component of signal - * @param si_in node for communicating received imaginary component of signal - */ - CoSimServer(SystemModelT& sys, SignalNodeT* sr_out, SignalNodeT* si_out, SignalNodeT* sr_in, SignalNodeT* si_in) - : sr_out_(sr_out), - si_out_(si_out), - sr_in_(sr_in), - si_in_(si_in), - sys_(sys), - ida_(&sys), - ctx_{}, - socket_(ctx_, zmq::socket_type::rep) - { - sr_in_->link(&sr_, &sr_idx_); - si_in_->link(&si_, &si_idx_); - - socket_.bind("tcp://0.0.0.0:8800"); - } - - /** - * @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. - * - * Messages received from the client begin with a status token. Stepping will - * continue as long as the status received is CoSim::STEP. When CoSim::END is - * received the simulation will wrap up. - */ - void start() - { - Log::summary() << "SERVER: Start simulation loop\n"; - CoSim::Status status; - - do - { - // 1. Receive data - zmq::message_t r_msg; - auto recv_result = socket_.recv(r_msg, zmq::recv_flags::none); - if (!recv_result) - { - throw std::runtime_error("Co-sim server: failed receive"); - } - auto r_msg_str = r_msg.to_string(); - Log::misc() << "SERVER: Received \"" << r_msg_str << "\"\n"; - auto iss = std::istringstream(r_msg_str); - iss >> status; - if (status == CoSim::Status::FAIL) - { - throw std::runtime_error("Received failure message from client"); - } - - // 2. Perform action - try - { - if (status == CoSim::Status::INIT) - { - iss >> ti_ >> tf_ >> dt_ >> nsteps_ >> sr_ >> si_; - sys_.allocate(); - ida_.setTolerance(1.0e-5, 1.0e-7); - ida_.configureSimulation(); - ida_.initializeSimulation(ti_); - } - else if (status == CoSim::Status::STEP) - { - iss >> step_ >> sr_ >> si_; - const RealT t = std::fma(static_cast(step_ - 1), dt_, ti_); - const RealT target = step_ == nsteps_ ? tf_ : std::fma(static_cast(step_), dt_, ti_); - ida_.initializeSimulation(t); - ida_.runSimulationStep(target, dt_, 1, 1); - } - sys_.evaluateResidual(); - } - catch (const std::exception& e) - { - // 3. Send failure - std::ostringstream oss; - oss << CoSim::Status::FAIL; - zmq::message_t fail_msg{oss.str().data(), oss.str().size()}; - socket_.send(fail_msg, zmq::send_flags::none); - throw; - } - - // 3. Respond with new data - std::ostringstream oss; - oss << std::scientific << std::setprecision(16); - auto sr = sr_out_->read(); - auto si = si_out_->read(); - oss << CoSim::Status::STEP << " " << sr << " " << si; - Log::misc() << "SERVER: Sending \"" << oss.str() << "\"\n"; - - zmq::message_t reply{oss.str().data(), oss.str().size()}; - socket_.send(reply, zmq::send_flags::none); - - } while (status != CoSim::Status::END); - - Log::summary() << "SERVER: Simulation stopped\n"; - } - -private: - /// node from which to read real component of signal to send - SignalNodeT* sr_out_; - /// node from which to read imaginary component of signal to send - SignalNodeT* si_out_; - /// node for communicating received real component of signal - SignalNodeT* sr_in_; - /// node for communicating received imaginary component of signal - SignalNodeT* si_in_; - /// variable for receiving signal real component - ScalarT sr_{}; - /// variable for receiving signal imaginary component - ScalarT si_{}; - /// dummy index for signal id - IdxT sr_idx_{GridKit::INVALID_INDEX}; - /// dummy index for signal id - IdxT si_idx_{GridKit::INVALID_INDEX}; - - SystemModelT& sys_; - - /// Ida solver - IdaT ida_; - - // Solver stepping parameters - RealT ti_; - RealT tf_; - RealT dt_; - int step_; - int nsteps_; - - /// ZMQ context - zmq::context_t ctx_; - /// ZMQ socket - zmq::socket_t socket_; -}; - -int main(int argc, const char* argv[]) -{ - Log::setVerbosity(Log::Verbosity::EVERYTHING); - - CliArgs args{{.name = {"--case-file", "-c"}, - .required = true}}; - - args.parseArgs(argc, argv); - - auto filepath = std::filesystem::path(args["case-file"]()); - - // Instantiate system - auto data = parseSystemModelData(filepath); - auto sys = SystemModel(data); - - auto server = CoSimServer(sys, - sys.getSignalNode(1), - sys.getSignalNode(2), - sys.getSignalNode(3), - sys.getSignalNode(4)); - server.start(); - - return 0; -} diff --git a/examples/Network/CoSim/README.md b/examples/Network/CoSim/README.md deleted file mode 100644 index 4fc16d6e71..0000000000 --- a/examples/Network/CoSim/README.md +++ /dev/null @@ -1,16 +0,0 @@ -# Co-simulation example - -The client and server exchange voltage and current over ZeroMQ on TCP port -8800. The interface buses connect directly to signal nodes in the case files: - -- The client uses `BusSignalVoltageOut` (JSON class `SignalVoltageOut`). It - sends voltage on signals 1 and 2 (`vr`, `vi`) and receives current on - signals 3 and 4 (`ir`, `ii`). -- The server uses `BusSignalVoltageIn` (JSON class `SignalVoltageIn`). It - receives voltage on signals 3 and 4 (`vr`, `vi`) and sends the accumulated - current injections on signals 1 and 2 (`ir`, `ii`). - -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/CoSim/ThreeBusCoSimClient.case.json b/examples/Network/CoSim/ThreeBusCoSimClient.case.json deleted file mode 100644 index 854a19318e..0000000000 --- a/examples/Network/CoSim/ThreeBusCoSimClient.case.json +++ /dev/null @@ -1,153 +0,0 @@ -{ - "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": "client_mon.csv", - "format":"csv" - } - ], - "buses": [ - { - "number": 1, - "class": "Bus", - "name": "1", - "init": { - "Vr": 1.06, - "Vi": 0.0 - }, - "params": {"kv": 138.0}, - "mon": [ - "Vm" - ] - }, - { - "number": 2, - "class": "SignalVoltageOut", - "ports_in": {"ir": 3, "ii": 4}, - "ports_out": {"vr": 1, "vi": 2}, - "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": "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" - ] - } - ] -} diff --git a/examples/Network/CoSim/ThreeBusCoSimServer.case.json b/examples/Network/CoSim/ThreeBusCoSimServer.case.json deleted file mode 100644 index f0aca607ed..0000000000 --- a/examples/Network/CoSim/ThreeBusCoSimServer.case.json +++ /dev/null @@ -1,153 +0,0 @@ -{ - "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 - }, - "monitors": [ - { - "file_name": "server_mon.csv", - "format":"csv" - } - ], - "buses": [ - { - "number": 1, - "class": "SignalVoltageIn", - "ports_in": {"vr": 3, "vi": 4}, - "ports_out": {"ir": 1, "ii": 2}, - "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": "ir"}, - { "signal_id": 2, "name": "ii"}, - { "signal_id": 3, "name": "vr"}, - { "signal_id": 4, "name": "vi"} - ], - "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": "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" - ] - } - ] -} diff --git a/examples/Network/CoSim/TwoBusCoSimClient.case.json b/examples/Network/CoSim/TwoBusCoSimClient.case.json deleted file mode 100644 index 0685d3700c..0000000000 --- a/examples/Network/CoSim/TwoBusCoSimClient.case.json +++ /dev/null @@ -1,101 +0,0 @@ -{ - "header": { - "format_version": 0.2, - "format_revision": 0, - "case_name": "CoSimClient", - "case_description": "Simple case sending voltage and receiving current", - "case_comments": "None" - }, - "params": { - "freq_base": 60.0, - "va_base": 100000000.0 - }, - "monitors": [ - { - "file_name": "client_mon.csv", - "format":"csv" - } - ], - "buses": [ - { - "number": 1, - "class": "Bus", - "name": "1", - "init": { - "Vr": 1.06, - "Vi": 0.0 - }, - "params": {"kv": 138.0}, - "mon": [ - "Vm" - ] - }, - { - "number": 2, - "class": "SignalVoltageOut", - "ports_in": {"ir": 3, "ii": 4}, - "ports_out": {"vr": 1, "vi": 2}, - "name": "2", - "init": { - "Vr": 1.0599558398065716, - "Vi": -0.009675621941024773 - }, - "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": "Genrou", - "ports": { - "bus": 1 - }, - "id": "genrou_1_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" - ] - } - ] -} diff --git a/examples/Network/CoSim/TwoBusCoSimClient.solo.case.json b/examples/Network/CoSim/TwoBusCoSimClient.solo.case.json deleted file mode 100644 index 681f00a9bf..0000000000 --- a/examples/Network/CoSim/TwoBusCoSimClient.solo.case.json +++ /dev/null @@ -1,107 +0,0 @@ -{ - "header": { - "format_version": 0.2, - "format_revision": 0, - "case_name": "CoSimClient", - "case_description": "Simple case sending voltage and receiving current", - "case_comments": "None" - }, - "params": { - "freq_base": 60.0, - "va_base": 100000000.0 - }, - "monitors": [ - { - "file_name": "client_mon.csv", - "format":"csv" - } - ], - "buses": [ - { - "number": 1, - "class": "Bus", - "name": "1", - "init": { - "Vr": 1.06, - "Vi": 0.0 - }, - "params": {"kv": 138.0}, - "mon": [ - "Vm" - ] - }, - { - "number": 2, - "class": "SignalVoltageIn", - "ports_in": {"vr": 3, "vi": 4}, - "ports_out": {"ir": 1, "ii": 2}, - "name": "2", - "init": { - "Vr": 1.0599558398065716, - "Vi": -0.009675621941024773 - }, - "params": {"kv": 138.0}, - "mon": [ - "Vm" - ] - } - ], - "signals": [ - { "signal_id": 3, "name": "vr"}, - { "signal_id": 4, "name": "vi"}, - { "signal_id": 1, "name": "ir"}, - { "signal_id": 2, "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": "Genrou", - "ports": { - "bus": 1 - }, - "id": "genrou_1_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": "FunctionSignalSource", - "ports": {"sr":3, "si":4}, - "id": "const_source_1", - "params": { "Fr":"sin", "Fi":"cos" } - } - ] -} diff --git a/examples/Network/CoSim/TwoBusCoSimClient.solver.json b/examples/Network/CoSim/TwoBusCoSimClient.solver.json deleted file mode 100644 index 12a13e9d31..0000000000 --- a/examples/Network/CoSim/TwoBusCoSimClient.solver.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "system_model_file": "TwoBusCoSimClient.case.json", - "dt_monitor": 0.004166666666666667, - "tmax": 0.1, - "events": [] -} diff --git a/examples/Network/CoSim/TwoBusCoSimServer.case.json b/examples/Network/CoSim/TwoBusCoSimServer.case.json deleted file mode 100644 index 748c61652b..0000000000 --- a/examples/Network/CoSim/TwoBusCoSimServer.case.json +++ /dev/null @@ -1,82 +0,0 @@ -{ - "header": { - "format_version": 0.2, - "format_revision": 0, - "case_name": "CoSimServer", - "case_description": "Simple test case receiving voltage and sending current", - "case_comments": "None" - }, - "params": { - "freq_base": 60.0, - "va_base": 100000000.0 - }, - "monitors": [ - { - "file_name": "server_mon.csv", - "format":"csv" - } - ], - "buses": [ - { - "number": 1, - "class": "SignalVoltageIn", - "ports_in": {"vr": 3, "vi": 4}, - "ports_out": {"ir": 1, "ii": 2}, - "name": "1", - "init": { - "Vr": 1.0599558398065716, - "Vi": -0.009675621941024773 - }, - "params": {"kv": 138.0}, - "mon": [ - "Vm" - ] - }, - { - "number": 2, - "class": "Bus", - "name": "2", - "init": { - "Vr": 1.06, - "Vi": 0.0 - }, - "params": {"kv": 138.0}, - "mon": [ - "Vm" - ] - } - ], - "signals": [ - { "signal_id": 1, "name": "ir"}, - { "signal_id": 2, "name": "ii"}, - { "signal_id": 3, "name": "vr"}, - { "signal_id": 4, "name": "vi"} - ], - "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": "LoadZ", - "ports": { - "bus": 2 - }, - "id": "load_2_0", - "params": { - "R": 0.1, - "X": 0.0 - } - } - ] -} diff --git a/examples/Network/CoSim/TwoBusCoSimServer.solo.case.json b/examples/Network/CoSim/TwoBusCoSimServer.solo.case.json deleted file mode 100644 index 78195d8f32..0000000000 --- a/examples/Network/CoSim/TwoBusCoSimServer.solo.case.json +++ /dev/null @@ -1,88 +0,0 @@ -{ - "header": { - "format_version": 0.2, - "format_revision": 0, - "case_name": "CoSimServer", - "case_description": "Simple test case receiving voltage and sending current", - "case_comments": "None" - }, - "params": { - "freq_base": 60.0, - "va_base": 100000000.0 - }, - "monitors": [ - { - "file_name": "server_mon.csv", - "format":"csv" - } - ], - "buses": [ - { - "number": 1, - "class": "SignalVoltageOut", - "ports_in": {"ir": 1, "ii": 2}, - "ports_out": {"vr": 3, "vi": 4}, - "name": "1", - "init": { - "Vr": 1.0599558398065716, - "Vi": -0.009675621941024773 - }, - "params": {"kv": 138.0}, - "mon": [ - "Vm" - ] - }, - { - "number": 2, - "class": "Bus", - "name": "2", - "init": { - "Vr": 1.06, - "Vi": 0.0 - }, - "params": {"kv": 138.0}, - "mon": [ - "Vm" - ] - } - ], - "signals": [ - { "signal_id": 3, "name": "vr"}, - { "signal_id": 4, "name": "vi"}, - { "signal_id": 1, "name": "ir"}, - { "signal_id": 2, "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": "LoadZ", - "ports": { - "bus": 2 - }, - "id": "load_2_0", - "params": { - "R": 0.1, - "X": 0.0 - } - }, - { - "class": "FunctionSignalSource", - "ports": {"sr":1, "si":2}, - "id": "const_source_1", - "params": { "Fr":"sin", "Fi":"cos" } - } - ] -} diff --git a/examples/Network/CoSim/TwoBusCoSimServer.solver.json b/examples/Network/CoSim/TwoBusCoSimServer.solver.json deleted file mode 100644 index de38764067..0000000000 --- a/examples/Network/CoSim/TwoBusCoSimServer.solver.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "system_model_file": "TwoBusCoSimServer.case.json", - "dt_monitor": 0.004166666666666667, - "tmax": 0.1, - "events": [] -} diff --git a/examples/Network/CoSim/run_cosim.sh b/examples/Network/CoSim/run_cosim.sh deleted file mode 100644 index d85773aed5..0000000000 --- a/examples/Network/CoSim/run_cosim.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env sh - -readonly _cosim_dir=$(dirname "$(realpath "$0")") - -cd "${_cosim_dir}" || exit 1 -_exit_code=0 - -./CoSimServer -c "${1:-ThreeBusCoSimServer.case.json}" & -_srv_pid=$! - -./CoSimClient -c "${2:-ThreeBusCoSimClient.case.json}" -if [ $? -ne 0 ]; then - _exit_code=1 -fi - -wait ${_srv_pid} -if [ $? -ne 0 ]; then - _exit_code=1 -fi - -exit ${_exit_code} diff --git a/examples/Network/ThreeBusCoSim/ThreeBusCoSimClient.cpp b/examples/Network/ThreeBusCoSim/ThreeBusCoSimClient.cpp deleted file mode 100644 index 6af6a2b220..0000000000 --- a/examples/Network/ThreeBusCoSim/ThreeBusCoSimClient.cpp +++ /dev/null @@ -1,186 +0,0 @@ -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include "CoSim.hpp" -#include - -using namespace GridKit; -using namespace GridKit::PhasorDynamics; -using namespace AnalysisManager::Sundials; - -using Log = GridKit::Utilities::Logger; - -/** - * @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 -{ -private: - /// 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 = SignalNode; - -public: - 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_->link(&ir_, &ir_idx_); - ii_signal_->link(&ii_, &ii_idx_); - socket_.connect("tcp://0.0.0.0:5556"); - Log::summary() << "CLIENT: Established connection with server\n"; - } - - /** - * @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) - { - } - Log::summary() << "CLIENT: Ending simulation\n"; - } - - /** - * @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(); - Log::misc() << "CLIENT: Sending \"" << oss.str() << "\"\n"; - - 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()); - Log::misc() << "CLIENT: Received \"" << iss.str() << "\"\n"; - 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() -{ - Log::raiseVerbosity(Log::Verbosity::SUMMARY); - - // Instantiate system - auto filepath = std::filesystem::path("ThreeBusCoSimClient.case.json"); - auto data = parseSystemModelData(filepath); - auto sys = SystemModel(data); - auto client = CoSimClient(sys.getSignalNode(1), - sys.getSignalNode(2), - sys.getSignalNode(3), - sys.getSignalNode(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.getBus(3)->setFault(true, 0.0, 1e-5); - ida.initializeSimulation(1.0); - ida.runSimulation(1.1, dt, step_cb); - - // Clear the fault and run until t = 10s. - sys.getBus(3)->setFault(false, 0.0, 1e-5); - ida.initializeSimulation(1.1); - ida.runSimulation(10.0, dt, step_cb); - - return 0; -} diff --git a/examples/Network/ThreeBusCoSim/ThreeBusCoSimServer.cpp b/examples/Network/ThreeBusCoSim/ThreeBusCoSimServer.cpp deleted file mode 100644 index c0762ca3c4..0000000000 --- a/examples/Network/ThreeBusCoSim/ThreeBusCoSimServer.cpp +++ /dev/null @@ -1,135 +0,0 @@ -#include -#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; - -using Log = GridKit::Utilities::Logger; - -/** - * @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 -{ -private: - /// 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 = SignalNode; - -public: - 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. - * - * Messages received from the client begin with a status token. Stepping will - * continue as long as the status received is CoSim::STEP. When CoSim::END is - * received the simulation will wrap up. - * - * @note For this initial implementation, no solver step is taken. Constant - * current values are sent in response. - */ - void start() - { - Log::summary() << "SERVER: Start simulation loop\n"; - 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; - Log::misc() << "SERVER: Received \"" << msg.to_string_view() << "\"\n"; - } - - // 2. Respond with new data - std::ostringstream oss; - oss << std::scientific << std::setprecision(16); - oss << ir_signal_->read() << " " << ii_signal_->read(); - Log::misc() << "SERVER: Sending \"" << oss.str() << "\"\n"; - - zmq::message_t reply{oss.str().data(), oss.str().size()}; - socket_.send(reply, zmq::send_flags::none); - } while (status == CoSim::STEP); - - Log::summary() << "SERVER: Simulation stopped\n"; - } - -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() -{ - Log::raiseVerbosity(Log::Verbosity::SUMMARY); - - // 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.getSignalNode(1), sys.getSignalNode(2)); - server.start(); - - return 0; -} diff --git a/examples/PhasorDynamics/CMakeLists.txt b/examples/PhasorDynamics/CMakeLists.txt index 954fc8bf35..65e86c1430 100644 --- a/examples/PhasorDynamics/CMakeLists.txt +++ b/examples/PhasorDynamics/CMakeLists.txt @@ -1,3 +1,7 @@ add_subdirectory(DynamicSimulation) add_subdirectory(ContingencyAnalysis) add_subdirectory(Validation) + +if(GRIDKIT_ENABLE_ZMQ) + add_subdirectory(CoSimulation) +endif() diff --git a/examples/PhasorDynamics/CoSimulation/CMakeLists.txt b/examples/PhasorDynamics/CoSimulation/CMakeLists.txt new file mode 100644 index 0000000000..aa9dd68dda --- /dev/null +++ b/examples/PhasorDynamics/CoSimulation/CMakeLists.txt @@ -0,0 +1,3 @@ +gridkit_example_add_file(run_cosim.sh) +add_subdirectory(Toy) +add_subdirectory(IEEE39) diff --git a/examples/PhasorDynamics/CoSimulation/IEEE39/CMakeLists.txt b/examples/PhasorDynamics/CoSimulation/IEEE39/CMakeLists.txt new file mode 100644 index 0000000000..7357e6d361 --- /dev/null +++ b/examples/PhasorDynamics/CoSimulation/IEEE39/CMakeLists.txt @@ -0,0 +1,5 @@ +foreach(_side Grid Generator) + gridkit_example_add_file(${CMAKE_SOURCE_DIR}/cases/PhasorDynamics/IEEE39/IEEE39${_side}.case.json) + gridkit_example_add_file(IEEE39${_side}.solver.json) + gridkit_example_add_file(IEEE39${_side}.contingency.solver.json) +endforeach() diff --git a/examples/PhasorDynamics/CoSimulation/IEEE39/IEEE39Generator.contingency.solver.json b/examples/PhasorDynamics/CoSimulation/IEEE39/IEEE39Generator.contingency.solver.json new file mode 100644 index 0000000000..b9c1759b5b --- /dev/null +++ b/examples/PhasorDynamics/CoSimulation/IEEE39/IEEE39Generator.contingency.solver.json @@ -0,0 +1,6 @@ +{ + "system_model_file": "IEEE39Generator.case.json", + "dt_monitor": 0.01, + "tmax": 0.12, + "events": [] +} diff --git a/examples/PhasorDynamics/CoSimulation/IEEE39/IEEE39Generator.solver.json b/examples/PhasorDynamics/CoSimulation/IEEE39/IEEE39Generator.solver.json new file mode 100644 index 0000000000..100ec28628 --- /dev/null +++ b/examples/PhasorDynamics/CoSimulation/IEEE39/IEEE39Generator.solver.json @@ -0,0 +1,6 @@ +{ + "system_model_file": "IEEE39Generator.case.json", + "dt_monitor": 0.004166666666666667, + "tmax": 10.0, + "events": [] +} diff --git a/examples/PhasorDynamics/CoSimulation/IEEE39/IEEE39Grid.contingency.solver.json b/examples/PhasorDynamics/CoSimulation/IEEE39/IEEE39Grid.contingency.solver.json new file mode 100644 index 0000000000..bf9a0d69c2 --- /dev/null +++ b/examples/PhasorDynamics/CoSimulation/IEEE39/IEEE39Grid.contingency.solver.json @@ -0,0 +1,17 @@ +{ + "system_model_file": "IEEE39Grid.case.json", + "dt_monitor": 0.01, + "tmax": 0.12, + "events": [ + { + "time": 0.04, + "type": "fault_on", + "R": 0.0, + "X": 0.1 + }, + { + "time": 0.06, + "type": "fault_off" + } + ] +} diff --git a/examples/PhasorDynamics/CoSimulation/IEEE39/IEEE39Grid.solver.json b/examples/PhasorDynamics/CoSimulation/IEEE39/IEEE39Grid.solver.json new file mode 100644 index 0000000000..13e32ecedf --- /dev/null +++ b/examples/PhasorDynamics/CoSimulation/IEEE39/IEEE39Grid.solver.json @@ -0,0 +1,19 @@ +{ + "system_model_file": "IEEE39Grid.case.json", + "dt_monitor": 0.004166666666666667, + "tmax": 10.0, + "events": [ + { + "time": 1.0, + "type": "fault_on", + "bus": 16, + "R": 0.0, + "X": 0.01 + }, + { + "time": 1.15, + "type": "fault_off", + "bus": 16 + } + ] +} diff --git a/examples/PhasorDynamics/CoSimulation/README.md b/examples/PhasorDynamics/CoSimulation/README.md new file mode 100644 index 0000000000..c434826c54 --- /dev/null +++ b/examples/PhasorDynamics/CoSimulation/README.md @@ -0,0 +1,28 @@ +# Co-simulation + +Both `DynamicSimulation` and `ContingencyAnalysis` are capable of exchanging +scalar signals with other GridKit instances over ZeroMQ. These examples +showcase this functionality in the following ways: + +- Simple three-bus / two-bus network simulation. +- IEEE39 network bifurcation with one GridKit instance running most of + the grid and another simulating a large generator and its dependents. + +## Running the examples + +These examples may be in the following way: + +```sh +sh /path/to/build/examples/PhasorDynamics/CoSimulation/run_cosim.sh \ + --case ieee39 +``` + +The available cases are `two-bus`, `three-bus`, `ieee39`, and +`ieee39-contingencies`. Ensure either the executables are on your `PATH` +or that you specify them with `--dynamic` and `--analysis`. By default, +the script uses a unique IPC endpoint. Use the `--endpoint` flag to +override this. + +## Building + +Enable SUNDIALS, `GridKit_ENABLE_ZMQ=ON`. Build GridKit normally otherwise. diff --git a/examples/PhasorDynamics/CoSimulation/Toy/CMakeLists.txt b/examples/PhasorDynamics/CoSimulation/Toy/CMakeLists.txt new file mode 100644 index 0000000000..d0d8f3049f --- /dev/null +++ b/examples/PhasorDynamics/CoSimulation/Toy/CMakeLists.txt @@ -0,0 +1,9 @@ +foreach( + _case + TwoBusCoSimClient + TwoBusCoSimServer + ThreeBusCoSimClient + ThreeBusCoSimServer) + gridkit_example_add_file(${_case}.case.json) + gridkit_example_add_file(${_case}.solver.json) +endforeach() diff --git a/examples/PhasorDynamics/CoSimulation/Toy/ThreeBusCoSimClient.case.json b/examples/PhasorDynamics/CoSimulation/Toy/ThreeBusCoSimClient.case.json new file mode 100644 index 0000000000..4b7ec83c8c --- /dev/null +++ b/examples/PhasorDynamics/CoSimulation/Toy/ThreeBusCoSimClient.case.json @@ -0,0 +1,177 @@ +{ + "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": "client_mon.csv", + "format": "csv" + } + ], + "buses": [ + { + "number": 1, + "class": "Bus", + "name": "1", + "init": { + "Vr": 1.06, + "Vi": 0.0 + }, + "params": { + "kv": 138.0 + }, + "mon": [ + "Vm" + ] + }, + { + "number": 2, + "class": "SignalVoltageOut", + "ports_in": { + "ir": 3, + "ii": 4 + }, + "ports_out": { + "vr": 1, + "vi": 2 + }, + "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": "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" + ] + } + ] +} diff --git a/examples/PhasorDynamics/CoSimulation/Toy/ThreeBusCoSimClient.solver.json b/examples/PhasorDynamics/CoSimulation/Toy/ThreeBusCoSimClient.solver.json new file mode 100644 index 0000000000..fa923574f2 --- /dev/null +++ b/examples/PhasorDynamics/CoSimulation/Toy/ThreeBusCoSimClient.solver.json @@ -0,0 +1,6 @@ +{ + "system_model_file": "ThreeBusCoSimClient.case.json", + "dt_monitor": 0.004166666666666667, + "tmax": 0.1, + "events": [] +} diff --git a/examples/PhasorDynamics/CoSimulation/Toy/ThreeBusCoSimServer.case.json b/examples/PhasorDynamics/CoSimulation/Toy/ThreeBusCoSimServer.case.json new file mode 100644 index 0000000000..562a46639d --- /dev/null +++ b/examples/PhasorDynamics/CoSimulation/Toy/ThreeBusCoSimServer.case.json @@ -0,0 +1,177 @@ +{ + "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 + }, + "monitors": [ + { + "file_name": "server_mon.csv", + "format": "csv" + } + ], + "buses": [ + { + "number": 1, + "class": "SignalVoltageIn", + "ports_in": { + "vr": 3, + "vi": 4 + }, + "ports_out": { + "ir": 1, + "ii": 2 + }, + "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": "ir" + }, + { + "signal_id": 2, + "name": "ii" + }, + { + "signal_id": 3, + "name": "vr" + }, + { + "signal_id": 4, + "name": "vi" + } + ], + "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": "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" + ] + } + ] +} diff --git a/examples/PhasorDynamics/CoSimulation/Toy/ThreeBusCoSimServer.solver.json b/examples/PhasorDynamics/CoSimulation/Toy/ThreeBusCoSimServer.solver.json new file mode 100644 index 0000000000..7d3ca7f05f --- /dev/null +++ b/examples/PhasorDynamics/CoSimulation/Toy/ThreeBusCoSimServer.solver.json @@ -0,0 +1,6 @@ +{ + "system_model_file": "ThreeBusCoSimServer.case.json", + "dt_monitor": 0.004166666666666667, + "tmax": 0.1, + "events": [] +} diff --git a/examples/PhasorDynamics/CoSimulation/Toy/TwoBusCoSimClient.case.json b/examples/PhasorDynamics/CoSimulation/Toy/TwoBusCoSimClient.case.json new file mode 100644 index 0000000000..03d7b0f74d --- /dev/null +++ b/examples/PhasorDynamics/CoSimulation/Toy/TwoBusCoSimClient.case.json @@ -0,0 +1,123 @@ +{ + "header": { + "format_version": 0.2, + "format_revision": 0, + "case_name": "CoSimClient", + "case_description": "Simple case sending voltage and receiving current", + "case_comments": "None" + }, + "params": { + "freq_base": 60.0, + "va_base": 100000000.0 + }, + "monitors": [ + { + "file_name": "client_mon.csv", + "format": "csv" + } + ], + "buses": [ + { + "number": 1, + "class": "Bus", + "name": "1", + "init": { + "Vr": 1.06, + "Vi": 0.0 + }, + "params": { + "kv": 138.0 + }, + "mon": [ + "Vm" + ] + }, + { + "number": 2, + "class": "SignalVoltageOut", + "ports_in": { + "ir": 3, + "ii": 4 + }, + "ports_out": { + "vr": 1, + "vi": 2 + }, + "name": "2", + "init": { + "Vr": 1.0599558398065716, + "Vi": -0.009675621941024773 + }, + "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": "Genrou", + "ports": { + "bus": 1 + }, + "id": "genrou_1_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" + ] + } + ] +} diff --git a/examples/PhasorDynamics/CoSimulation/Toy/TwoBusCoSimClient.solver.json b/examples/PhasorDynamics/CoSimulation/Toy/TwoBusCoSimClient.solver.json new file mode 100644 index 0000000000..f90991353e --- /dev/null +++ b/examples/PhasorDynamics/CoSimulation/Toy/TwoBusCoSimClient.solver.json @@ -0,0 +1,6 @@ +{ + "system_model_file": "TwoBusCoSimClient.case.json", + "dt_monitor": 0.004166666666666667, + "tmax": 0.1, + "events": [] +} diff --git a/examples/PhasorDynamics/CoSimulation/Toy/TwoBusCoSimServer.case.json b/examples/PhasorDynamics/CoSimulation/Toy/TwoBusCoSimServer.case.json new file mode 100644 index 0000000000..d1aa7f1d0d --- /dev/null +++ b/examples/PhasorDynamics/CoSimulation/Toy/TwoBusCoSimServer.case.json @@ -0,0 +1,105 @@ +{ + "header": { + "format_version": 0.2, + "format_revision": 0, + "case_name": "CoSimServer", + "case_description": + "Simple test case receiving voltage and sending current", + "case_comments": "None" + }, + "params": { + "freq_base": 60.0, + "va_base": 100000000.0 + }, + "monitors": [ + { + "file_name": "server_mon.csv", + "format": "csv" + } + ], + "buses": [ + { + "number": 1, + "class": "SignalVoltageIn", + "ports_in": { + "vr": 3, + "vi": 4 + }, + "ports_out": { + "ir": 1, + "ii": 2 + }, + "name": "1", + "init": { + "Vr": 1.0599558398065716, + "Vi": -0.009675621941024773 + }, + "params": { + "kv": 138.0 + }, + "mon": [ + "Vm" + ] + }, + { + "number": 2, + "class": "Bus", + "name": "2", + "init": { + "Vr": 1.06, + "Vi": 0.0 + }, + "params": { + "kv": 138.0 + }, + "mon": [ + "Vm" + ] + } + ], + "signals": [ + { + "signal_id": 1, + "name": "ir" + }, + { + "signal_id": 2, + "name": "ii" + }, + { + "signal_id": 3, + "name": "vr" + }, + { + "signal_id": 4, + "name": "vi" + } + ], + "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": "LoadZ", + "ports": { + "bus": 2 + }, + "id": "load_2_0", + "params": { + "R": 0.1, + "X": 0.0 + } + } + ] +} diff --git a/examples/PhasorDynamics/CoSimulation/Toy/TwoBusCoSimServer.solver.json b/examples/PhasorDynamics/CoSimulation/Toy/TwoBusCoSimServer.solver.json new file mode 100644 index 0000000000..eed744c826 --- /dev/null +++ b/examples/PhasorDynamics/CoSimulation/Toy/TwoBusCoSimServer.solver.json @@ -0,0 +1,6 @@ +{ + "system_model_file": "TwoBusCoSimServer.case.json", + "dt_monitor": 0.004166666666666667, + "tmax": 0.1, + "events": [] +} diff --git a/examples/PhasorDynamics/CoSimulation/run_cosim.sh b/examples/PhasorDynamics/CoSimulation/run_cosim.sh new file mode 100644 index 0000000000..b059889394 --- /dev/null +++ b/examples/PhasorDynamics/CoSimulation/run_cosim.sh @@ -0,0 +1,105 @@ +#!/bin/sh + +set -eu + +example_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) +dynamic=DynamicSimulation +analysis= +case_name=three-bus +interval= +endpoint= +client_solver_file= +server_solver_file= + +usage() { + cat <&2; exit 1 ;; + esac + if [ "$#" -lt 2 ]; then + echo "Missing value for $1" >&2 + exit 1 + fi + shift 2 +done + +case "$case_name" in + two-bus|three-bus) + case "$case_name" in + two-bus) prefix=TwoBus ;; + three-bus) prefix=ThreeBus ;; + esac + + toy_dir=$example_dir/Toy + client_solver_file=${client_solver_file:-$toy_dir/${prefix}CoSimClient.solver.json} + server_solver_file=${server_solver_file:-$toy_dir/${prefix}CoSimServer.solver.json} + interval=${interval:-0.0010416666666666667} + client_vr=1; client_vi=2; client_ir=3; client_ii=4 + server_vr=3; server_vi=4; server_ir=1; server_ii=2 ;; + ieee39|ieee39-contingencies) + suffix=solver.json + if [ "$case_name" = ieee39-contingencies ]; then + suffix=contingency.solver.json + analysis=${analysis:-ContingencyAnalysis} + fi + + ieee39_dir=$example_dir/IEEE39 + client_solver_file=${client_solver_file:-$ieee39_dir/IEEE39Grid.$suffix} + server_solver_file=${server_solver_file:-$ieee39_dir/IEEE39Generator.$suffix} + interval=${interval:-0.0005208333333333333} + client_vr=30; client_vi=31; client_ir=32; client_ii=33 + server_vr=30; server_vi=31; server_ir=32; server_ii=33 ;; + *) echo "Unknown case: $case_name" >&2; exit 1 ;; +esac + +work_dir=$(mktemp -d "${TMPDIR:-/tmp}/gridkit-cosim.XXXXXXXX") +server_pid= +cleanup() { + if [ -n "$server_pid" ]; then + kill "$server_pid" 2>/dev/null || : + wait "$server_pid" 2>/dev/null || : + fi + rm -rf -- "$work_dir" +} +trap cleanup 0 +trap 'exit 1' HUP INT TERM +endpoint=${endpoint:-ipc://$work_dir/peer} + +set -- "$server_solver_file" --cosim-role server \ + --cosim-endpoint "$endpoint" --cosim-dt "$interval" \ + --cosim-input "vr=$server_vr" --cosim-input "vi=$server_vi" \ + --cosim-output "ir=$server_ir" --cosim-output "ii=$server_ii" +if [ -n "$analysis" ]; then + set -- "$@" --cosim-serve +fi +"$dynamic" "$@" & +server_pid=$! + +"${analysis:-$dynamic}" "$client_solver_file" --cosim-role client \ + --cosim-endpoint "$endpoint" --cosim-dt "$interval" \ + --cosim-input "ir=$client_ir" --cosim-input "ii=$client_ii" \ + --cosim-output "vr=$client_vr" --cosim-output "vi=$client_vi" + +wait "$server_pid" +server_pid= diff --git a/examples/PhasorDynamics/README.md b/examples/PhasorDynamics/README.md index 195283135d..7472913fb7 100644 --- a/examples/PhasorDynamics/README.md +++ b/examples/PhasorDynamics/README.md @@ -5,9 +5,15 @@ use the reusable models in `cases/PhasorDynamics/`. | Examples | Purpose | | --- | --- | -| [DynamicSimulation](DynamicSimulation/README.md) | Individual simulation studies. | -| [ContingencyAnalysis](ContingencyAnalysis/README.md) | Bus-fault contingency studies. | -| [Validation](Validation/README.md) | System studies compared against PowerWorld reference results. | +| [DynamicSimulation][dynamic] | Individual simulation studies. | +| [ContingencyAnalysis][contingency] | Bus-fault contingency studies. | +| [CoSimulation][cosim] | Co-simulation examples. | +| [Validation][validation] | Studies compared with PowerWorld results. | + +[dynamic]: DynamicSimulation/README.md +[contingency]: ContingencyAnalysis/README.md +[cosim]: CoSimulation/README.md +[validation]: Validation/README.md ## Running a Study