DPsim
Loading...
Searching...
No Matches
Utils.cpp
1/* Copyright 2017-2021 Institute for Automation of Complex Power Systems,
2 * EONERC, RWTH Aachen University
3 *
4 * This Source Code Form is subject to the terms of the Mozilla Public
5 * License, v. 2.0. If a copy of the MPL was not distributed with this
6 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
7 *********************************************************************************/
8
9#include "dpsim/MNASolverFactory.h"
10#include <string>
11
12#include <dpsim/Config.h>
13#include <dpsim/Utils.h>
14
15using namespace DPsim;
16using namespace DPsim::Utils;
17using namespace CPS;
18
19#ifdef HAVE_GETOPT
20#include <getopt.h>
21#else
22#include <dpsim/Compat/getopt.h>
23#endif
24
25CommandLineArgs::CommandLineArgs(int argc, char *argv[], String nm, Real dt,
26 Real d, Real sf, Int s, CPS::Logger::Level ll,
27 CPS::Logger::Level clill, Bool ss, Bool b,
28 Bool si, CPS::Domain sd, Solver::Type st,
29 DirectLinearSolverImpl mi, String spn,
30 String ps)
31 : mProgramName(argv[0]),
32 mArguments{
33 {"start-synch", no_argument, 0, 'S', NULL, ""},
34 {"steady-init", no_argument, 0, 'I', NULL, ""},
35 {"blocking", no_argument, 0, 'b', NULL, ""},
36 {"help", no_argument, 0, 'h', NULL, ""},
37 {"timestep", required_argument, 0, 't', "SECS",
38 "Simulation time-step"},
39 {"duration", required_argument, 0, 'd', "SECS",
40 "Simulation duration"},
41 {"system-freq", required_argument, 0, 'f', "HZ", "System Frequency"},
42 {"scenario", required_argument, 0, 's', "NUM", "Scenario selection"},
43 {"log-level", required_argument, 0, 'l', "(NONE|INFO|DEBUG|WARN|ERR)",
44 "Logging level"},
45 {"start-at", required_argument, 0, 'a', "ISO8601",
46 "Start time of real-time simulation"},
47 {"start-in", required_argument, 0, 'i', "SECS", ""},
48 {"solver-domain", required_argument, 0, 'D', "(SP|DP|EMT)",
49 "Domain of solver"},
50 {"solver-type", required_argument, 0, 'T', "(NRP|MNA)",
51 "Type of solver"},
52 {"linear-solver-impl", required_argument, 0, 'U',
53 "(DenseLU|SparseLU|KLU|CUDADense|CUDASparse)",
54 "Type of direct linear solver implementation"},
55 {"option", required_argument, 0, 'o', "KEY=VALUE",
56 "User-definable options"},
57 {"name", required_argument, 0, 'n', "NAME", "Name of log files"},
58 {"params", required_argument, 0, 'p', "PATH",
59 "Json file containing parametrization"},
60 {0}},
61 timeStep(dt), duration(d), sysFreq(sf), scenario(s), logLevel(ll),
62 cliLogLevel(clill), name(nm), params(ps), startSynch(ss), blocking(b),
63 steadyInit(si), solver{sd, st}, directImpl(mi), solverPluginName(spn) {
64 parseArguments(argc, argv);
65}
66
67CommandLineArgs::CommandLineArgs(String nm, Real dt, Real d, Real sf, Int s,
68 CPS::Logger::Level ll,
69 CPS::Logger::Level clill, Bool ss, Bool b,
70 Bool si, CPS::Domain sd, Solver::Type st,
71 DirectLinearSolverImpl mi, String spn)
72 : mProgramName("dpsim"),
73 mArguments{
74 {"start-synch", no_argument, 0, 'S', NULL, ""},
75 {"steady-init", no_argument, 0, 'I', NULL, ""},
76 {"blocking", no_argument, 0, 'b', NULL, ""},
77 {"help", no_argument, 0, 'h', NULL, ""},
78 {"timestep", required_argument, 0, 't', "SECS",
79 "Simulation time-step"},
80 {"duration", required_argument, 0, 'd', "SECS",
81 "Simulation duration"},
82 {"system-freq", required_argument, 0, 'f', "HZ", "System Frequency"},
83 {"scenario", required_argument, 0, 's', "NUM", "Scenario selection"},
84 {"log-level", required_argument, 0, 'l', "(NONE|INFO|DEBUG|WARN|ERR)",
85 "Logging level"},
86 {"start-at", required_argument, 0, 'a', "ISO8601",
87 "Start time of real-time simulation"},
88 {"start-in", required_argument, 0, 'i', "SECS", ""},
89 {"solver-domain", required_argument, 0, 'D', "(SP|DP|EMT)",
90 "Domain of solver"},
91 {"solver-type", required_argument, 0, 'T', "(NRP|MNA)",
92 "Type of solver"},
93 {"linear-solver-impl", required_argument, 0, 'U',
94 "(DenseLU|SparseLU|KLU|CUDADense|CUDASparse)",
95 "Type of direct linear solver implementation"},
96 {"option", required_argument, 0, 'o', "KEY=VALUE",
97 "User-definable options"},
98 {"name", required_argument, 0, 'n', "NAME", "Name of log files"},
99 {0}},
100 timeStep(dt), duration(d), sysFreq(sf), scenario(s), logLevel(ll),
101 cliLogLevel(clill), name(nm), startSynch(ss), blocking(b), steadyInit(si),
102 solver{sd, st}, directImpl(mi), solverPluginName(spn) {}
103
104void CommandLineArgs::parseArguments(int argc, char *argv[]) {
105 mProgramName = argv[0];
106 std::vector<option> long_options;
107 for (auto a : mArguments)
108 long_options.push_back({a.name, a.has_arg, a.flag, a.val});
109
110 int c;
111 while (1) {
112 /* getopt_long stores the option index here. */
113 int option_index = 0;
114
115 c = getopt_long(argc, argv,
116 "ht:d:s:l:a:i:f:D:P:T:U:o:Sbn:", long_options.data(),
117 &option_index);
118
119 /* Detect the end of the options. */
120 if (c == -1)
121 break;
122
123 switch (c) {
124 case 'S':
125 startSynch = true;
126 break;
127
128 case 'I':
129 steadyInit = true;
130 break;
131
132 case 'b':
133 blocking = true;
134 break;
135
136 case 't':
137 timeStep = std::stod(optarg);
138 break;
139
140 case 'd':
141 duration = std::stod(optarg);
142 break;
143
144 case 'f':
145 sysFreq = std::stod(optarg);
146 break;
147
148 case 's':
149 scenario = std::stoi(optarg);
150 break;
151
152 case 'o': {
153 String arg = optarg;
154
155 auto p = arg.find("=");
156 auto key = arg.substr(0, p);
157 auto value = arg.substr(p + 1);
158 if (p != String::npos)
159 options[key] = value;
160
161 break;
162 }
163
164 case 'l': {
165 String arg = optarg;
166
167 if (arg == "DEBUG")
168 logLevel = Logger::Level::debug;
169 else if (arg == "INFO")
170 logLevel = Logger::Level::info;
171 else if (arg == "ERR")
172 logLevel = Logger::Level::err;
173 else if (arg == "WARN")
174 logLevel = Logger::Level::warn;
175 else if (arg == "NONE")
176 logLevel = Logger::Level::off;
177 else
178 throw std::invalid_argument("Invalid value for --log-level: must be a "
179 "string of DEBUG, INFO, ERR, WARN or NONE");
180 break;
181 }
182
183 case 'D': {
184 String arg = optarg;
185
186 if (arg == "DP")
187 solver.domain = Domain::DP;
188 else if (arg == "EMT")
189 solver.domain = Domain::EMT;
190 else if (arg == "SP")
191 solver.domain = Domain::SP;
192 else
193 throw std::invalid_argument("Invalid value for --solver-domain: must "
194 "be a string of SP, DP, EMT");
195 break;
196 }
197
198 case 'T': {
199 String arg = optarg;
200
201 if (arg == "MNA")
202 solver.type = Solver::Type::MNA;
203 else if (arg == "NRP")
204 solver.type = Solver::Type::NRP;
205 else
206 throw std::invalid_argument(
207 "Invalid value for --solver-type: must be a string of NRP or MNA");
208 break;
209 }
210 case 'U': {
211 String arg = optarg;
212 if (arg == "DenseLU") {
213 directImpl = DirectLinearSolverImpl::DenseLU;
214 } else if (arg == "SparseLU") {
215 directImpl = DirectLinearSolverImpl::SparseLU;
216 } else if (arg == "KLU") {
217 directImpl = DirectLinearSolverImpl::KLU;
218 } else if (arg == "CUDADense") {
219 directImpl = DirectLinearSolverImpl::CUDADense;
220 } else if (arg == "CUDASparse") {
221 directImpl = DirectLinearSolverImpl::CUDASparse;
222 } else if (arg == "CUDAMagma") {
223 directImpl = DirectLinearSolverImpl::CUDAMagma;
224 } else if (arg == "Plugin") {
225 directImpl = DirectLinearSolverImpl::Plugin;
226 } else {
227 throw std::invalid_argument("Invalid value for --solver-mna-impl");
228 }
229 break;
230 }
231 case 'P': {
232 solverPluginName = optarg;
233 break;
234 }
235
236 case 'i': {
237 double deltaT = std::stod(optarg);
238
239 startTime = Timer::StartClock::now() +
240 std::chrono::milliseconds(static_cast<int>(deltaT * 1e3));
241
242 break;
243 }
244
245 case 'a': {
246 std::tm t;
247 std::istringstream ss(optarg);
248
249 ss >> std::get_time(&t, "%Y%m%dT%H%M%S");
250
251 if (ss.fail())
252 throw std::invalid_argument(
253 "Invalid value for --start-at: must be a ISO8601 date");
254
255 std::time_t tt = std::mktime(&t);
256
257 startTime = Timer::StartClock::from_time_t(tt);
258
259 break;
260 }
261
262 case 'n':
263 name = optarg;
264 break;
265
266 case 'p':
267 params = optarg;
268 break;
269
270 case 'h':
271 showUsage();
272 exit(0);
273
274 case '?':
275 default:
276 showUsage();
277 exit(-1);
278 }
279 }
280
281 /* Positional arguments like files */
282 while (optind < argc)
283 positional.push_back(argv[optind++]);
284}
285
286void CommandLineArgs::showUsage() {
287 std::cout << "Usage: " << mProgramName << " [OPTIONS] [FILES]" << std::endl;
288 std::cout << std::endl;
289 std::cout << " Available options:" << std::endl;
290
291 for (auto a : mArguments) {
292 if (!a.val)
293 continue;
294
295 std::cout << " -" << static_cast<char>(a.val) << ", --" << a.name;
296
297 if (a.valdesc)
298 std::cout << " " << a.valdesc;
299
300 if (a.desc)
301 std::cout << " " << a.desc;
302
303 std::cout << std::endl;
304 }
305
306 std::cout << std::endl;
307}
308
309std::list<fs::path> CommandLineArgs::positionalPaths() const {
310 std::list<fs::path> paths;
311
312 for (auto p : positional) {
313 paths.emplace_back(p);
314 }
315
316 return paths;
317}
318
319String DPsim::Utils::encodeXml(String &data) {
320 String buffer;
321 buffer.reserve(data.size());
322 for (size_t pos = 0; pos != data.size(); ++pos) {
323 switch (data[pos]) {
324 case '&':
325 buffer.append("&amp;");
326 break;
327 case '\"':
328 buffer.append("&quot;");
329 break;
330 case '\'':
331 buffer.append("&apos;");
332 break;
333 case '<':
334 buffer.append("&lt;");
335 break;
336 case '>':
337 buffer.append("&gt;");
338 break;
339 default:
340 buffer.append(&data[pos], 1);
341 break;
342 }
343 }
344
345 return buffer;
346}
347
348std::vector<std::string> DPsim::Utils::tokenize(std::string s, char delimiter) {
349 std::vector<std::string> tokens;
350
351 size_t lastPos = 0;
352 size_t curentPos;
353
354 while ((curentPos = s.find(delimiter, lastPos)) != std::string::npos) {
355 const size_t tokenLength = curentPos - lastPos;
356 tokens.push_back(s.substr(lastPos, tokenLength));
357
358 /* Advance in string */
359 lastPos = curentPos + 1;
360 }
361
362 /* Check if there's a last token behind the last delimiter. */
363 if (lastPos != s.length()) {
364 const size_t lastTokenLength = s.length() - lastPos;
365 tokens.push_back(s.substr(lastPos, lastTokenLength));
366 }
367
368 return tokens;
369}
370
371fs::path DPsim::Utils::findFile(const fs::path &name, const fs::path &hint,
372 const std::string &useEnv) {
373#ifdef _WIN32
374 char sep = ';';
375#else
376 char sep = ':';
377#endif
378
379 std::vector<fs::path> searchPaths = {fs::current_path()};
380
381 if (!hint.empty()) {
382 searchPaths.push_back(hint);
383 }
384
385 if (!useEnv.empty() && getenv(useEnv.c_str())) {
386 std::vector<std::string> envPaths = tokenize(getenv(useEnv.c_str()), sep);
387
388 for (std::string envPath : envPaths) {
389 searchPaths.emplace_back(envPath);
390 }
391 }
392
393 for (auto searchPath : searchPaths) {
394 fs::path fullPath;
395
396 if (searchPath.is_relative())
397 fullPath /= fs::current_path();
398
399 fullPath /= searchPath;
400 fullPath /= name;
401
402 if (fs::exists(fullPath)) {
403 return fs::absolute(fullPath);
404 }
405 }
406
407 String searchPathsString;
408 for (auto searchPath : searchPaths)
409 searchPathsString.append(searchPath.string().append("\n"));
410
411 throw std::runtime_error(fmt::format("File not found: {}\nSearch paths:\n{}",
412 name.string(), searchPathsString));
413}
414
415std::list<fs::path> DPsim::Utils::findFiles(std::list<fs::path> filennames,
416 const fs::path &hint,
417 const std::string &useEnv) {
418
419 std::list<fs::path> foundnames;
420
421 for (auto filename : filennames) {
422 auto foundname = findFile(filename, hint, useEnv);
423
424 foundnames.emplace_back(foundname);
425 }
426
427 return foundnames;
428}
429
430#ifdef WITH_JSON
431void DPsim::Utils::applySimulationParametersFromJson(const json config,
432 Simulation &sim) {
433 if (config.contains("timestep"))
434 sim.setTimeStep(config["timestep"].get<double>());
435 if (config.contains("duration"))
436 sim.setFinalTime(config["duration"].get<double>());
437}
438
439void DPsim::Utils::applySynchronousGeneratorParametersFromJson(
440 const json config,
441 std::shared_ptr<CPS::EMT::Ph3::SynchronGeneratorDQ> syngen) {
442 if (config.contains("options")) {
443 Bool containsSyngenOptions = false;
444 for (String attrName : syngen->attrParamNames) {
445 if (config["options"].contains(attrName)) {
446 syngen->attributeTyped<Real>(attrName)->set(
447 config["options"][attrName].get<double>());
448 containsSyngenOptions = true;
449 }
450 }
451 if (containsSyngenOptions)
452 syngen->applyParametersOperationalPerUnit();
453 }
454}
455#endif
The Simulation holds a SystemTopology and a Solver.
Definition Simulation.h:31