DPsim
Simulation.cpp
1 // SPDX-License-Identifier: Apache-2.0
2 
3 #include <algorithm>
4 #include <chrono>
5 #include <iomanip>
6 #include <typeindex>
7 
8 #include <dpsim-models/Utils.h>
9 #include <dpsim/DiakopticsSolver.h>
10 #include <dpsim/MNASolverFactory.h>
11 #include <dpsim/PFSolverPowerPolar.h>
12 #include <dpsim/SequentialScheduler.h>
13 #include <dpsim/Simulation.h>
14 #include <dpsim/Utils.h>
15 
16 #include <spdlog/sinks/stdout_color_sinks.h>
17 
18 #ifdef WITH_CIM
19 #include <dpsim-models/CIM/Reader.h>
20 #endif
21 
22 #ifdef WITH_SUNDIALS
23 #include <dpsim-models/Solver/ODEInterface.h>
24 #include <dpsim/DAESolver.h>
25 #include <dpsim/ODESolver.h>
26 #endif
27 
28 using namespace CPS;
29 using namespace DPsim;
30 
31 Simulation::Simulation(String name, Logger::Level logLevel)
32  : mName(AttributeStatic<String>::make(name)),
33  mFinalTime(AttributeStatic<Real>::make(0.001)),
34  mTimeStep(AttributeStatic<Real>::make(0.001)),
35  mSplitSubnets(AttributeStatic<Bool>::make(true)),
36  mSteadyStateInit(AttributeStatic<Bool>::make(false)),
37  mLogLevel(logLevel) {
38  create();
39 }
40 
42  : mName(AttributeStatic<String>::make(name)),
43  mSolverPluginName(args.solverPluginName),
44  mFinalTime(AttributeStatic<Real>::make(args.duration)),
45  mTimeStep(AttributeStatic<Real>::make(args.timeStep)),
46  mSplitSubnets(AttributeStatic<Bool>::make(true)),
47  mSteadyStateInit(AttributeStatic<Bool>::make(false)),
48  mLogLevel(args.logLevel), mDomain(args.solver.domain),
49  mSolverType(args.solver.type), mDirectImpl(args.directImpl) {
50  create();
51 }
52 
54  // Logging
55  mLog =
56  Logger::get(**mName, mLogLevel, std::max(Logger::Level::info, mLogLevel));
57 
58  Eigen::setNbThreads(1);
59 
60  mInitialized = false;
61 }
62 
64  if (mInitialized)
65  return;
66 
67  mSolvers.clear();
68 
69  switch (mDomain) {
70  case Domain::SP:
71  // Treat SP as DP
72  case Domain::DP:
73  createSolvers<Complex>();
74  break;
75  case Domain::EMT:
76  createSolvers<Real>();
77  break;
78  }
79 
80  mTime = 0;
81  mTimeStepCount = 0;
82 
83  schedule();
84 
85  mInitialized = true;
86 }
87 
88 template <typename VarType> void Simulation::createSolvers() {
89  Solver::Ptr solver;
90  switch (mSolverType) {
91  case Solver::Type::MNA:
92  createMNASolver<VarType>();
93  break;
94 #ifdef WITH_SUNDIALS
95  case Solver::Type::DAE:
96  solver = std::make_shared<DAESolver>(**mName, mSystem, **mTimeStep, 0.0);
97  mSolvers.push_back(solver);
98  break;
99 #endif /* WITH_SUNDIALS */
100  case Solver::Type::NRP:
101  solver = std::make_shared<PFSolverPowerPolar>(**mName, mSystem, **mTimeStep,
102  mLogLevel);
103  solver->doInitFromNodesAndTerminals(mInitFromNodesAndTerminals);
104  solver->setSolverAndComponentBehaviour(mSolverBehaviour);
105  solver->initialize();
106  mSolvers.push_back(solver);
107  break;
108  default:
110  }
111 
112  // Some components require a dedicated ODE solver.
113  // This solver is independent of the system solver.
114 #ifdef WITH_SUNDIALS
115  for (auto comp : mSystem.mComponents) {
116  auto odeComp = std::dynamic_pointer_cast<ODEInterface>(comp);
117  if (odeComp) {
118  // TODO explicit / implicit integration
119  auto odeSolver = std::make_shared<ODESolver>(
120  odeComp->mAttributeList->attributeTyped<String>("name")->get() +
121  "_ODE",
122  odeComp, false, **mTimeStep);
123  mSolvers.push_back(odeSolver);
124  }
125  }
126 #endif /* WITH_SUNDIALS */
127 }
128 
129 template <typename VarType> void Simulation::createMNASolver() {
130  Solver::Ptr solver;
131  std::vector<SystemTopology> subnets;
132  // The Diakoptics solver splits the system at a later point.
133  // That is why the system is not split here if tear components exist.
134  if (**mSplitSubnets && mTearComponents.size() == 0)
135  mSystem.splitSubnets<VarType>(subnets);
136  else
137  subnets.push_back(mSystem);
138 
139  for (UInt net = 0; net < subnets.size(); ++net) {
140  String copySuffix;
141  if (subnets.size() > 1)
142  copySuffix = "_" + std::to_string(net);
143 
144  // TODO: In the future, here we could possibly even use different
145  // solvers for different subnets if deemed useful
146  if (mTearComponents.size() > 0) {
147  // Tear components available, use diakoptics
148  solver = std::make_shared<DiakopticsSolver<VarType>>(
149  **mName, subnets[net], mTearComponents, **mTimeStep, mLogLevel);
150  } else {
151  // Default case with lu decomposition from mna factory
152  solver = MnaSolverFactory::factory<VarType>(**mName + copySuffix, mDomain,
153  mLogLevel, mDirectImpl,
155  solver->setTimeStep(**mTimeStep);
156  solver->setLogSolveTimes(mLogStepTimes);
157  solver->doSteadyStateInit(**mSteadyStateInit);
158  solver->doFrequencyParallelization(mFreqParallel);
159  solver->setSteadStIniTimeLimit(mSteadStIniTimeLimit);
160  solver->setSteadStIniAccLimit(mSteadStIniAccLimit);
161  solver->setSystem(subnets[net]);
162  solver->setSolverAndComponentBehaviour(mSolverBehaviour);
163  solver->doInitFromNodesAndTerminals(mInitFromNodesAndTerminals);
164  solver->doSystemMatrixRecomputation(mSystemMatrixRecomputation);
165  solver->setDirectLinearSolverConfiguration(
166  mDirectLinearSolverConfiguration);
167  solver->initialize();
168  solver->setMaxNumberOfIterations(mMaxIterations);
169  }
170  mSolvers.push_back(solver);
171  }
172 }
173 
174 void Simulation::sync() const {
175  SPDLOG_LOGGER_INFO(mLog, "Start synchronization with remotes on interfaces");
176 
177  for (auto intf : mInterfaces) {
178  intf->syncExports();
179  intf->syncImports();
180  intf->syncExports();
181  }
182 
183  SPDLOG_LOGGER_INFO(mLog, "Synchronized simulation start with remotes");
184 }
185 
187  mTasks.clear();
188  mTaskOutEdges.clear();
189  mTaskInEdges.clear();
190  for (auto solver : mSolvers) {
191  for (auto t : solver->getTasks()) {
192  mTasks.push_back(t);
193  }
194  }
195 
196  for (auto intf : mInterfaces) {
197  for (auto t : intf->getTasks()) {
198  mTasks.push_back(t);
199  }
200  }
201 
202  for (auto logger : mLoggers) {
203  mTasks.push_back(logger->getTask());
204  }
205  if (!mScheduler) {
206  mScheduler = std::make_shared<SequentialScheduler>();
207  }
208  mScheduler->resolveDeps(mTasks, mTaskInEdges, mTaskOutEdges);
209 }
210 
212  SPDLOG_LOGGER_INFO(mLog, "Scheduling tasks.");
213  prepSchedule();
214  mScheduler->createSchedule(mTasks, mTaskInEdges, mTaskOutEdges);
215  SPDLOG_LOGGER_INFO(mLog, "Scheduling done.");
216 }
217 
218 #ifdef WITH_GRAPHVIZ
219 Graph::Graph Simulation::dependencyGraph() {
220  if (!mInitialized)
221  initialize();
222 
223  std::map<CPS::Task::Ptr, Scheduler::TaskTime> avgTimes;
224  std::map<CPS::Task::Ptr, String> fillColors;
225 
226  /* The main SolveTasks of each Solver usually takes the
227  * largest amount of computing time. We exclude it from
228  * coloring for improving the spread of the color range
229  * for the remaining tasks.
230  */
231  // auto isSolveTask = [](Task::Ptr task) -> Bool {
232  // const std::vector<std::type_index> ignoreTasks = {
233  // #ifdef WITH_SUNDIALS
234  // std::type_index(typeid(ODESolver::SolveTask)),
235  // #endif
236  // std::type_index(typeid(MnaSolver<Real>::SolveTask)),
237  // std::type_index(typeid(MnaSolver<Complex>::SolveTask)),
238  // std::type_index(typeid(DiakopticsSolver<Real>::SubnetSolveTask)),
239  // std::type_index(typeid(DiakopticsSolver<Complex>::SubnetSolveTask)),
240  // std::type_index(typeid(DiakopticsSolver<Real>::SolveTask)),
241  // std::type_index(typeid(DiakopticsSolver<Complex>::SolveTask)),
242  // std::type_index(typeid(NRpolarSolver::SolveTask))
243  // };
244 
245  // return std::find(ignoreTasks.begin(), ignoreTasks.end(), std::type_index(typeid(*task.get()))) != ignoreTasks.end();
246  // };
247 
248  // auto isIgnoredTask = [&isSolveTask](Task::Ptr task) -> Bool {
249  // return typeid(*task.get()) == typeid(Interface::PreStep) || sSolveTask(task);
250  // };
251 
252  // auto isRootTask = [](Task::Ptr task) -> Bool {
253  // return typeid(*task.get()) == typeid(Scheduler::Root);
254  // };
255 
256  auto isScheduled = [this](Task::Ptr task) -> Bool {
257  return !mTaskOutEdges[task].empty();
258  };
259 
260  auto getColor = [](Task::Ptr task) -> String {
261  static std::map<std::type_index, String> colorMap;
262  auto tid = std::type_index(typeid(task.get()));
263 
264  if (colorMap.find(tid) != colorMap.end()) {
265  colorMap[tid] =
266  String("/paired9/") + std::to_string(1 + colorMap.size() % 9);
267  }
268 
269  return colorMap[tid];
270  };
271 
272  auto avgTimeWorst = Scheduler::TaskTime::min();
273  for (auto task : mTasks) {
274  avgTimes[task] = mScheduler->getAveragedMeasurement(task);
275 
276  if (avgTimes[task] > avgTimeWorst)
277  avgTimeWorst = avgTimes[task];
278  }
279 
280  // TODO: For level-based Scheduler's we might want to
281  // maintain the level structure by setting the respective
282  // Graphviz 'rank' attributes and group each level into sub-graph
283 
284  Graph::Graph g("dependencies", Graph::Type::directed);
285  for (auto task : mTasks) {
286  String name = task->toString();
287  String type = CPS::Utils::className(task.get(), "DPsim::");
288 
289  auto *n = g.addNode(name);
290 
291  std::stringstream label;
292 
293  label << "<B>" << Utils::encodeXml(name) << "</B><BR/>";
294  label << "<FONT POINT-SIZE=\"10\" COLOR=\"gray28\">"
295  << Utils::encodeXml(type) << "<BR/>";
296 
297  if (isScheduled(task))
298  label << "Avg. time: " << avgTimes[task].count() << "ns<BR/>";
299  else
300  label << "Unscheduled"
301  << "<BR/>";
302 
303  label << "</FONT>";
304 
305  n->set("color", getColor(task));
306  n->set("label", label.str(), true);
307  n->set("style", "rounded,filled,bold");
308  n->set("shape", "rectangle");
309 
310  // if (isSolveTask(task)) {
311  // n->set("fillcolor", "orange");
312  // }
313  // if (isRootTask(task)) {
314  // n->set("fillcolor", "springgreen1");
315  // }
316  if (isScheduled(task)) {
317  if (avgTimeWorst > Scheduler::TaskTime(0)) {
318  auto grad = (float)avgTimes[task].count() / avgTimeWorst.count();
319  n->set("fillcolor", CPS::Utils::Rgb::gradient(grad).hex());
320  SPDLOG_LOGGER_INFO(mLog, "{} {}", task->toString(),
321  CPS::Utils::Rgb::gradient(grad).hex());
322  }
323  } else {
324  n->set("fillcolor", "white");
325  }
326  }
327  for (auto from : mTasks) {
328  for (auto to : mTaskOutEdges[from]) {
329  g.addEdge("", g.node(from->toString()), g.node(to->toString()));
330  }
331  }
332 
333  g.set("splines", "ortho");
334  return g;
335 }
336 #endif
337 
339  SPDLOG_LOGGER_INFO(mLog, "Initialize simulation: {}", **mName);
340  if (!mInitialized)
341  initialize();
342 
343  for (auto lg : mLoggers)
344  lg->start();
345 
346  SPDLOG_LOGGER_INFO(mLog, "Opening interfaces.");
347 
348  for (auto intf : mInterfaces)
349  intf->open();
350 
351  sync();
352 
353  SPDLOG_LOGGER_INFO(mLog, "Start simulation: {}", **mName);
354  SPDLOG_LOGGER_INFO(mLog, "Time step: {:e}", **mTimeStep);
355  SPDLOG_LOGGER_INFO(mLog, "Final time: {:e}", **mFinalTime);
356 
357  // In PF we dont log the initial conditions of the componentes because they are not calculated
358  // In dynamic simulations log initial values of attributes (t=0)
359  if (mSolverType != Solver::Type::NRP) {
360  if (mLoggers.size() > 0)
361  mLoggers[0]->log(0, 0);
362 
363  // In dynamic simulations increase simulation time to calculate first results at t=timestep
364  mTime += **mTimeStep;
365  }
366 
367  mSimulationStartTimePoint = std::chrono::steady_clock::now();
368 }
369 
371 
372  mSimulationEndTimePoint = std::chrono::steady_clock::now();
375  SPDLOG_LOGGER_INFO(mLog, "Simulation calculation time: {:.6f}",
377 
378  mScheduler->stop();
379 
380  for (auto intf : mInterfaces)
381  intf->close();
382 
383  for (auto lg : mLoggers)
384  lg->stop();
385 
386  SPDLOG_LOGGER_INFO(mLog, "Simulation finished.");
387  mLog->flush();
388 }
389 
391  if (mTime < **mFinalTime + DOUBLE_EPSILON)
392  step();
393  else
394  stop();
395 
396  return mTime;
397 }
398 
400  start();
401 
402  while (mTime < **mFinalTime + DOUBLE_EPSILON) {
403  step();
404  }
405 
406  stop();
407 }
408 
410  std::chrono::steady_clock::time_point start;
411  if (mLogStepTimes) {
412  start = std::chrono::steady_clock::now();
413  }
414 
415  mEvents.handleEvents(mTime);
417 
418  mTime += **mTimeStep;
419  ++mTimeStepCount;
420 
421  if (mLogStepTimes) {
422  auto end = std::chrono::steady_clock::now();
423  std::chrono::duration<double> diff = end - start;
424  mStepTimes.push_back(diff.count());
425  }
426  return mTime;
427 }
428 
429 void Simulation::logStepTimes(String logName) {
430  auto stepTimeLog = Logger::get(logName, Logger::Level::info);
431  if (!mLogStepTimes) {
432  SPDLOG_LOGGER_WARN(mLog, "Collection of step times has been disabled.");
433  return;
434  }
435  Logger::setLogPattern(stepTimeLog, "%v");
436  stepTimeLog->info("step_time");
437 
438  Real stepTimeSum = 0;
439  for (auto meas : mStepTimes) {
440  stepTimeSum += meas;
441  stepTimeLog->info("{:.9f}", meas);
442  }
443  SPDLOG_LOGGER_INFO(mLog, "Average step time: {:.9f}",
444  stepTimeSum / mStepTimes.size());
445 }
446 
447 void Simulation::checkForOverruns(String logName) {
448  auto stepTimeLog = Logger::get(logName, Logger::Level::info);
449  Logger::setLogPattern(stepTimeLog, "%v");
450  stepTimeLog->info("overruns");
451 
452  int overruns = 0;
453  for (auto meas : mStepTimes) {
454  if (meas > **mTimeStep) {
455  overruns++;
456  SPDLOG_LOGGER_INFO(mLog, "overrun detected {}: {:.9f}", overruns, meas);
457  }
458  }
459  SPDLOG_LOGGER_INFO(mLog, "Detected {} overruns.", overruns);
460 }
461 
463  for (auto solver : mSolvers) {
464  solver->logLUTimes();
465  }
466 }
467 
469  const String &attr) {
470  IdentifiedObject::Ptr idObj = mSystem.component<IdentifiedObject>(comp);
471  if (!idObj) {
472  idObj = mSystem.node<TopologicalNode>(comp);
473  }
474 
475  if (idObj) {
476  try {
477  CPS::AttributeBase::Ptr attrPtr = idObj->attribute(attr);
478  return attrPtr;
479  } catch (InvalidAttributeException &e) {
480  SPDLOG_LOGGER_ERROR(
481  mLog, "Attribute with name {} not found on component {}", attr, comp);
483  }
484  } else {
485  SPDLOG_LOGGER_ERROR(mLog, "Component or node with name {} not found", comp);
486  throw InvalidArgumentException();
487  }
488 }
489 
490 void Simulation::logIdObjAttribute(const String &comp, const String &attr) {
491  CPS::AttributeBase::Ptr attrPtr = getIdObjAttribute(comp, attr);
492  String name = comp + "." + attr;
493  logAttribute(name, attrPtr);
494 }
495 
497  if (mLoggers.size() > 0) {
498  mLoggers[0]->logAttribute(name, attr);
499  } else {
500  throw SystemError("Cannot log attributes when no logger is configured for "
501  "this simulation!");
502  }
503 }
IdentifiedObject::List mComponents
List of network components.
std::shared_ptr< Type > node(UInt index)
Returns TopologicalNode by index in node list.
std::shared_ptr< Type > component(const String &name)
Returns Component by name.
std::chrono::steady_clock::duration TaskTime
Time measurement for the task execution.
Definition: Scheduler.h:33
std::chrono::duration< double > mSimulationCalculationTime
Measured calculation time for simulation.
Definition: Simulation.h:73
void logLUTimes()
Write LU decomposition times measurements to log file.
Definition: Simulation.cpp:462
void sync() const
Synchronize simulation with remotes by exchanging intial state over interfaces.
Definition: Simulation.cpp:174
CPS::IdentifiedObject::List mTearComponents
Definition: Simulation.h:103
CPS::Logger::Level mLogLevel
Simulation log level.
Definition: Simulation.h:77
virtual Real step()
Solve system A * x = z for x and current time.
Definition: Simulation.cpp:409
Scheduler::Edges mTaskInEdges
Task dependencies as incoming / outgoing edges.
Definition: Simulation.h:125
const CPS::Attribute< Real >::Ptr mTimeStep
Simulation timestep.
Definition: Simulation.h:45
const CPS::Attribute< Bool >::Ptr mSteadyStateInit
Definition: Simulation.h:56
Real next()
Run until next time step.
Definition: Simulation.cpp:390
void logStepTimes(String logName)
Write step time measurements to log file.
Definition: Simulation.cpp:429
String mSolverPluginName
If there we use a solver plugin, this specifies its name (excluding .so)
Definition: Simulation.h:41
void initialize()
Create solver instances etc.
Definition: Simulation.cpp:63
std::vector< Real > mStepTimes
(Real) time needed for the timesteps
Definition: Simulation.h:79
const CPS::Attribute< Real >::Ptr mFinalTime
Final time of the simulation.
Definition: Simulation.h:43
void prepSchedule()
Prepare schedule for simulation.
Definition: Simulation.cpp:186
Int mTimeStepCount
Number of step which have been executed for this simulation.
Definition: Simulation.h:62
Real mSteadStIniTimeLimit
steady state initialization time limit
Definition: Simulation.h:115
CPS::Task::List mTasks
List of all tasks to be scheduled.
Definition: Simulation.h:123
const CPS::Attribute< Bool >::Ptr mSplitSubnets
Definition: Simulation.h:51
std::shared_ptr< Scheduler > mScheduler
Scheduler used for task scheduling.
Definition: Simulation.h:121
std::vector< Interface::Ptr > mInterfaces
Vector of Interfaces.
Definition: Simulation.h:128
Bool mLogStepTimes
activate collection of step times
Definition: Simulation.h:81
void create()
Helper function for constructors.
Definition: Simulation.cpp:53
CPS::SystemTopology mSystem
System list.
Definition: Simulation.h:66
void checkForOverruns(String logName)
Check for overruns.
Definition: Simulation.cpp:447
Simulation(String name, CommandLineArgs &args)
Creates simulation with name and CommandLineArgs.
Definition: Simulation.cpp:41
CPS::AttributeBase::Ptr getIdObjAttribute(const String &comp, const String &attr)
CHECK: Can these be deleted? getIdObjAttribute + "**attr =" should suffice.
Definition: Simulation.cpp:468
void start()
Start simulation without advancing in time.
Definition: Simulation.cpp:338
EventQueue mEvents
The simulation event queue.
Definition: Simulation.h:64
void schedule()
Create the schedule for the independent tasks.
Definition: Simulation.cpp:211
std::chrono::time_point< std::chrono::steady_clock > mSimulationStartTimePoint
Start time point to measure calculation time.
Definition: Simulation.h:69
Real mTime
Time variable that is incremented at every step.
Definition: Simulation.h:60
Bool mSystemMatrixRecomputation
Enable recomputation of system matrix during simulation.
Definition: Simulation.h:99
Real mSteadStIniAccLimit
steady state initialization accuracy limit
Definition: Simulation.h:117
void createMNASolver()
Subroutine for MNA only because there are many MNA options.
Definition: Simulation.cpp:129
void run()
Run simulation until total time is elapsed.
Definition: Simulation.cpp:399
void logAttribute(String name, CPS::AttributeBase::Ptr attr)
CHECK: Can we store the attribute name / UID intrinsically inside the attribute?
Definition: Simulation.cpp:496
DataLoggerInterface::List mLoggers
The data loggers.
Definition: Simulation.h:138
void createSolvers()
Create solvers depending on simulation settings.
Definition: Simulation.cpp:88
const CPS::Attribute< String >::Ptr mName
Simulation name.
Definition: Simulation.h:39
CPS::Logger::Log mLog
Simulation logger.
Definition: Simulation.h:154
void stop()
Stop simulation including scheduler and interfaces.
Definition: Simulation.cpp:370
std::chrono::time_point< std::chrono::steady_clock > mSimulationEndTimePoint
End time point to measure calculation time.
Definition: Simulation.h:71