Skip to content

Commit f234f7d

Browse files
committed
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
1 parent 01d6f96 commit f234f7d

54 files changed

Lines changed: 4929 additions & 1889 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎GridKit/Utilities/Logger/Logger.cpp‎

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
#include <GridKit/Definitions.hpp>
1010
#include <GridKit/Utilities/Colors.hpp>
1111

12+
// TODO: allow instantiating a scoped logger that prefixes with some
13+
// class/logical distinction name like `[CliArgs]` or `[GENROU]`.
14+
1215
namespace GridKit
1316
{
1417
namespace Utilities
@@ -129,14 +132,16 @@ namespace GridKit
129132
std::ostream& Logger::warning()
130133
{
131134
using namespace Colors;
132-
*(output_streams_[WARNINGS]) << "[" << YELLOW << "WARNING" << CLEAR << "] ";
135+
*(output_streams_[WARNINGS])
136+
<< "[" << YELLOW << "WARNING" << CLEAR << "] ";
133137
return *(output_streams_[WARNINGS]);
134138
}
135139

136140
/**
137141
* @brief Returns reference to analysis summary messages output stream.
138142
*
139-
* @return Reference to analysis summary messages stream in `output_streams_`.
143+
* @return Reference to analysis summary messages stream in
144+
* `output_streams_`.
140145
*
141146
* @pre `output_streams_` vector is allocated and correctly initialized.
142147
*/
@@ -177,7 +182,8 @@ namespace GridKit
177182
}
178183

179184
/**
180-
* @brief Set outputs of active streams to user provided `std::ostream` object.
185+
* @brief Set outputs of active streams to user provided `std::ostream`
186+
* object.
181187
*
182188
* All active outputs are redirected to `out` stream. All inactive ones are
183189
* directed to null device.

‎GridKit/Utilities/Logger/Logger.hpp‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ namespace GridKit
1616
* @brief Manages and logs outputs from GridKit code.
1717
*
1818
* All methods and data in this class are static.
19-
*
2019
*/
2120
class Logger
2221
{

‎application/PhasorDynamics/AnalysisUtilities.hpp‎

Lines changed: 54 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
#pragma once
22

3+
#include <cmath>
34
#include <cstddef>
45
#include <filesystem>
5-
#include <format>
66
#include <fstream>
77
#include <iostream>
88
#include <stdexcept>
@@ -30,7 +30,8 @@ namespace GridKit
3030
* @brief Describes an event that is used to modify the simulation at the
3131
* given time point
3232
*
33-
* ContingencyAnalysis repeats fault events at each bus with the same R and X.
33+
* ContingencyAnalysis repeats fault events at each bus with the same R and
34+
* X.
3435
*/
3536
struct SystemEvent
3637
{
@@ -72,6 +73,12 @@ namespace GridKit
7273
double rel_tol;
7374
/// absolute tolerance for the solver
7475
double abs_tol;
76+
/// relative residual tolerance for the co-simulation interface
77+
double cosim_rel_tol;
78+
/// scaled step tolerance for the co-simulation interface solver
79+
double cosim_step_tol;
80+
/// estimated relative error of co-simulation residual evaluations
81+
double cosim_function_error;
7582
/// fixed solver time step size, or 0 for adaptive stepping
7683
double dt_fixed;
7784
/// maximum number of solver time steps, or 0 for the IDA default
@@ -101,9 +108,12 @@ namespace GridKit
101108
using json = ::nlohmann::json;
102109
using Log = ::GridKit::Utilities::Logger;
103110

104-
inline constexpr double DEFAULT_SOLVER_REL_TOL = 1.0e-7;
105-
inline constexpr double DEFAULT_SOLVER_ABS_TOL = 1.0e-9;
106-
inline constexpr double DEFAULT_VERIFICATION_TOL = 1.0e-4;
111+
inline constexpr double DEFAULT_SOLVER_REL_TOL = 1.0e-7;
112+
inline constexpr double DEFAULT_SOLVER_ABS_TOL = 1.0e-9;
113+
inline constexpr double DEFAULT_VERIFICATION_TOL = 1.0e-4;
114+
inline constexpr double DEFAULT_COSIM_REL_TOL = 1.0e-8;
115+
inline constexpr double DEFAULT_COSIM_STEP_TOL = 1.0e-12;
116+
inline constexpr double DEFAULT_COSIM_FUNCTION_ERROR = 1.0e-10;
107117

108118
/**
109119
* @brief JSON parser implementation for `StudyData`
@@ -117,20 +127,38 @@ namespace GridKit
117127
j.at("tmax").get_to(c.tmax);
118128
c.rel_tol = j.value("rel_tol", DEFAULT_SOLVER_REL_TOL);
119129
c.abs_tol = j.value("abs_tol", DEFAULT_SOLVER_ABS_TOL);
120-
c.dt_fixed = j.value("dt_fixed", 0.0);
121-
c.max_steps = j.value("max_steps", std::size_t{0});
122-
c.max_order = j.value("max_order", 5);
123-
c.consistent_ic_type = AnalysisManager::Sundials::IdaConsistentICType::YA_YDP;
130+
const auto tolerance = [&](const char* name, double fallback)
131+
{
132+
const double value = j.value(name, fallback);
133+
if (!std::isfinite(value) || value <= 0.0)
134+
{
135+
throw std::invalid_argument(
136+
std::string(name) + " must be positive and finite");
137+
}
138+
return value;
139+
};
140+
c.cosim_rel_tol = tolerance("cosim_rel_tol", DEFAULT_COSIM_REL_TOL);
141+
c.cosim_step_tol = tolerance("cosim_step_tol", DEFAULT_COSIM_STEP_TOL);
142+
c.cosim_function_error =
143+
tolerance("cosim_function_error", DEFAULT_COSIM_FUNCTION_ERROR);
144+
c.dt_fixed = j.value("dt_fixed", 0.0);
145+
c.max_steps = j.value("max_steps", std::size_t{0});
146+
c.max_order = j.value("max_order", 5);
147+
c.consistent_ic_type =
148+
AnalysisManager::Sundials::IdaConsistentICType::YA_YDP;
124149
if (j.contains("consistent_ic_type"))
125150
{
126-
const auto consistent_ic_type_str = j.at("consistent_ic_type").get<std::string>();
151+
const auto consistent_ic_type_str =
152+
j.at("consistent_ic_type").get<std::string>();
127153
if (consistent_ic_type_str == "y")
128154
{
129-
c.consistent_ic_type = AnalysisManager::Sundials::IdaConsistentICType::Y;
155+
c.consistent_ic_type =
156+
AnalysisManager::Sundials::IdaConsistentICType::Y;
130157
}
131158
else if (consistent_ic_type_str == "ya_ydp")
132159
{
133-
c.consistent_ic_type = AnalysisManager::Sundials::IdaConsistentICType::YA_YDP;
160+
c.consistent_ic_type =
161+
AnalysisManager::Sundials::IdaConsistentICType::YA_YDP;
134162
}
135163
else
136164
{
@@ -223,7 +251,8 @@ namespace GridKit
223251
c.error_type = ErrorType::RELATIVE;
224252
}
225253

226-
c.abs_err_threshold = j.value("abs_err_threshold", Testing::DEFAULT_ABS_ERROR_THRESHOLD);
254+
c.abs_err_threshold =
255+
j.value("abs_err_threshold", Testing::DEFAULT_ABS_ERROR_THRESHOLD);
227256
}
228257

229258
/**
@@ -302,7 +331,8 @@ namespace GridKit
302331
// output file must be a symlink to the model output file
303332
if (exists(data.output_file))
304333
{
305-
if ((!is_symlink(data.output_file)) || (read_symlink(data.output_file) != model_output_file))
334+
if ((!is_symlink(data.output_file))
335+
|| (read_symlink(data.output_file) != model_output_file))
306336
{
307337
Log::error() << "Study output file not usable" << std::endl;
308338
}
@@ -317,39 +347,23 @@ namespace GridKit
317347
return data;
318348
}
319349

320-
void checkCommandLine(int argc, const std::string& appName)
321-
{
322-
if (argc < 2)
323-
{
324-
Log::error() << "No input file provided\n"
325-
<< std::format(
326-
"\n"
327-
"Usage:\n"
328-
" {} <json-input-file>\n"
329-
"\n"
330-
"Please provide a json input file for the study to run.\n"
331-
"\n",
332-
appName);
333-
exit(1);
334-
}
335-
}
336-
337350
Testing::TestStatus checkErrors(
338-
const StudyData& study_data,
339-
bool print_results = true)
351+
const StudyData& study_data, bool print_results = true)
340352
{
341-
// Generate aggregate errors comparing variable output to reference solution
353+
// Generate aggregate errors comparing variable output to reference
354+
// solution
342355
auto func = std::string{"monitor file vs reference file"};
343356
auto status = Testing::TestStatus{func.c_str()};
344357

345358
const auto& out_file = study_data.output_file;
346359
const auto& ref_file = study_data.reference_file;
347360
if (!out_file.empty() && !ref_file.empty())
348361
{
349-
auto errorSet = Testing::compareCSV(out_file,
350-
ref_file,
351-
study_data.error_type,
352-
study_data.abs_err_threshold);
362+
auto errorSet = Testing::compareCSV(
363+
out_file,
364+
ref_file,
365+
study_data.error_type,
366+
study_data.abs_err_threshold);
353367

354368
// Print the errors
355369
if (print_results)
@@ -363,7 +377,8 @@ namespace GridKit
363377
status *= study_data.error_tol.size() == errorSet->var_errors.size();
364378
for (std::size_t i = 0; i < study_data.error_tol.size(); ++i)
365379
{
366-
status *= errorSet->var_errors[i].max_value < study_data.error_tol[i];
380+
status *=
381+
errorSet->var_errors[i].max_value < study_data.error_tol[i];
367382
}
368383
}
369384
else

‎application/PhasorDynamics/CMakeLists.txt‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ target_include_directories(
2020
ContingencyAnalysis
2121
PRIVATE ${GRIDKIT_THIRD_PARTY_DIR}/nlohmann-json/include)
2222

23+
if(GRIDKIT_ENABLE_ZMQ)
24+
target_link_libraries(DynamicSimulation PRIVATE GridKit::co_sim GridKit::solvers_steady)
25+
target_link_libraries(ContingencyAnalysis PRIVATE GridKit::co_sim GridKit::solvers_steady)
26+
endif()
27+
2328
if(GRIDKIT_ENABLE_OPENMP)
2429
target_link_libraries(ContingencyAnalysis PUBLIC OpenMP::OpenMP_CXX)
2530
endif()
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
#pragma once
2+
3+
#include "SimulationOptions.hpp"
4+
#include "SimulationRunner.hpp"
5+
6+
#ifdef GRIDKIT_ENABLE_ZMQ
7+
8+
#include <optional>
9+
#include <stdexcept>
10+
#include <utility>
11+
12+
#include <GridKit/CoSim/CoSim.hpp>
13+
14+
#include "CoSimulationTypes.hpp"
15+
#include "CoupledClient.hpp"
16+
#include "CoupledServer.hpp"
17+
18+
namespace GridKit
19+
{
20+
namespace PhasorDynamics
21+
{
22+
inline CoSim::RunResult runCoupledClientStudies(
23+
const SimulationOptions& options,
24+
NextCoupledStudy next,
25+
CompleteCoupledStudy complete,
26+
bool shutdown_peer,
27+
bool print_results = false)
28+
{
29+
CoupledClient client(
30+
options,
31+
std::move(next),
32+
std::move(complete),
33+
shutdown_peer,
34+
print_results);
35+
36+
CoSim::CoSim<double, CoSim::Disposition::CLIENT> adapter(
37+
options.endpoint,
38+
CoSim::MessageStepFunction<double>(
39+
[&](
40+
const CoSimulationMessage& reply,
41+
const CoSim::MessageExchangeFunction<double>& exchange)
42+
{ return client.handleReply(reply, exchange); }),
43+
CoSim::MessageExtractFunction<double>([&](const auto&)
44+
{ return client.extract(); }),
45+
options.timeout_ms);
46+
47+
const auto result = adapter.run();
48+
49+
if (result == CoSim::RunResult::COMPLETED && !client.finished())
50+
{
51+
throw std::runtime_error(
52+
"co-simulation peer ended before the study completed");
53+
}
54+
55+
return result;
56+
}
57+
58+
inline Testing::TestStatus runCoupledServer(
59+
const StudyData& base, const SimulationOptions& options)
60+
{
61+
CoupledServer server(base, options);
62+
63+
CoSim::CoSim<double, CoSim::Disposition::SERVER> adapter(
64+
options.endpoint,
65+
CoSim::MessageStepFunction<double>(
66+
[&](const CoSimulationMessage& message, const auto&)
67+
{ return server.handleRequest(message); }),
68+
CoSim::MessageExtractFunction<double>([&](const auto&)
69+
{ return server.extract(); }),
70+
options.timeout_ms);
71+
72+
if (adapter.run() == CoSim::RunResult::STOPPED)
73+
{
74+
return false;
75+
}
76+
77+
if (!server.finished())
78+
{
79+
throw std::runtime_error(
80+
"co-simulation peer closed before the study completed");
81+
}
82+
83+
return server.status();
84+
}
85+
86+
inline Testing::TestStatus runCoupledStudy(
87+
const StudyData& data, const SimulationOptions& options)
88+
{
89+
if (options.role == "server")
90+
{
91+
return runCoupledServer(data, options);
92+
}
93+
94+
bool first = true;
95+
Testing::TestStatus status = true;
96+
const auto next = [&]() -> std::optional<StudyData>
97+
{
98+
if (!first)
99+
{
100+
return std::nullopt;
101+
}
102+
103+
first = false;
104+
return data;
105+
};
106+
107+
if (runCoupledClientStudies(
108+
options,
109+
next,
110+
[&](Testing::TestStatus result)
111+
{ status *= result; },
112+
false,
113+
true)
114+
== CoSim::RunResult::STOPPED)
115+
{
116+
status = false;
117+
}
118+
119+
return status;
120+
}
121+
} // namespace PhasorDynamics
122+
} // namespace GridKit
123+
#endif
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#pragma once
2+
3+
#include <GridKit/Definitions.hpp>
4+
5+
#ifdef GRIDKIT_ENABLE_ZMQ
6+
7+
#include <functional>
8+
#include <optional>
9+
10+
#include <GridKit/CoSim/Message.hpp>
11+
12+
#include "AnalysisUtilities.hpp"
13+
14+
namespace GridKit
15+
{
16+
namespace PhasorDynamics
17+
{
18+
/// Alias for the co-simulation message type used in the applications.
19+
using CoSimulationMessage = CoSim::MessageData<double>;
20+
21+
using NextCoupledStudy = std::function<std::optional<StudyData>()>;
22+
using CompleteCoupledStudy = std::function<void(Testing::TestStatus)>;
23+
} // namespace PhasorDynamics
24+
} // namespace GridKit
25+
#endif

0 commit comments

Comments
 (0)