DPsim
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 
15 using namespace DPsim;
16 using namespace DPsim::Utils;
17 using namespace CPS;
18 
19 #ifdef HAVE_GETOPT
20 #include <getopt.h>
21 #else
22 #include <dpsim/Compat/getopt.h>
23 #endif
24 
25 CommandLineArgs::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 
67 CommandLineArgs::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 
104 void 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 
286 void 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 
309 std::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 
319 String 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 
348 std::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 
371 fs::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 
415 std::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 void DPsim::Utils::applySimulationParametersFromJson(const json config,
431  Simulation &sim) {
432  if (config.contains("timestep"))
433  sim.setTimeStep(config["timestep"].get<double>());
434  if (config.contains("duration"))
435  sim.setFinalTime(config["duration"].get<double>());
436 }
437 
438 void DPsim::Utils::applySynchronousGeneratorParametersFromJson(
439  const json config,
440  std::shared_ptr<CPS::EMT::Ph3::SynchronGeneratorDQ> syngen) {
441  if (config.contains("options")) {
442  Bool containsSyngenOptions = false;
443  for (String attrName : syngen->attrParamNames) {
444  if (config["options"].contains(attrName)) {
445  syngen->attributeTyped<Real>(attrName)->set(
446  config["options"][attrName].get<double>());
447  containsSyngenOptions = true;
448  }
449  }
450  if (containsSyngenOptions)
451  syngen->applyParametersOperationalPerUnit();
452  }
453 }
The Simulation holds a SystemTopology and a Solver.
Definition: Simulation.h:34