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->doSteadyStateInit(**mSteadyStateInit);
157  solver->doFrequencyParallelization(mFreqParallel);
158  solver->setSteadStIniTimeLimit(mSteadStIniTimeLimit);
159  solver->setSteadStIniAccLimit(mSteadStIniAccLimit);
160  solver->setSystem(subnets[net]);
161  solver->setSolverAndComponentBehaviour(mSolverBehaviour);
162  solver->doInitFromNodesAndTerminals(mInitFromNodesAndTerminals);
163  solver->doSystemMatrixRecomputation(mSystemMatrixRecomputation);
164  solver->setDirectLinearSolverConfiguration(
165  mDirectLinearSolverConfiguration);
166  solver->initialize();
167  solver->setMaxNumberOfIterations(mMaxIterations);
168  }
169  mSolvers.push_back(solver);
170  }
171 }
172 
173 void Simulation::sync() const {
174  SPDLOG_LOGGER_INFO(mLog, "Start synchronization with remotes on interfaces");
175 
176  for (auto intf : mInterfaces) {
177  intf->syncExports();
178  intf->syncImports();
179  intf->syncExports();
180  }
181 
182  SPDLOG_LOGGER_INFO(mLog, "Synchronized simulation start with remotes");
183 }
184 
186  mTasks.clear();
187  mTaskOutEdges.clear();
188  mTaskInEdges.clear();
189  for (auto solver : mSolvers) {
190  for (auto t : solver->getTasks()) {
191  mTasks.push_back(t);
192  }
193  }
194 
195  for (auto intf : mInterfaces) {
196  for (auto t : intf->getTasks()) {
197  mTasks.push_back(t);
198  }
199  }
200 
201  for (auto logger : mLoggers) {
202  mTasks.push_back(logger->getTask());
203  }
204  if (!mScheduler) {
205  mScheduler = std::make_shared<SequentialScheduler>();
206  }
207  mScheduler->resolveDeps(mTasks, mTaskInEdges, mTaskOutEdges);
208 }
209 
211  SPDLOG_LOGGER_INFO(mLog, "Scheduling tasks.");
212  prepSchedule();
213  mScheduler->createSchedule(mTasks, mTaskInEdges, mTaskOutEdges);
214  SPDLOG_LOGGER_INFO(mLog, "Scheduling done.");
215 }
216 
217 #ifdef WITH_GRAPHVIZ
218 Graph::Graph Simulation::dependencyGraph() {
219  if (!mInitialized)
220  initialize();
221 
222  std::map<CPS::Task::Ptr, Scheduler::TaskTime> avgTimes;
223  std::map<CPS::Task::Ptr, String> fillColors;
224 
225  /* The main SolveTasks of each Solver usually takes the
226  * largest amount of computing time. We exclude it from
227  * coloring for improving the spread of the color range
228  * for the remaining tasks.
229  */
230  // auto isSolveTask = [](Task::Ptr task) -> Bool {
231  // const std::vector<std::type_index> ignoreTasks = {
232  // #ifdef WITH_SUNDIALS
233  // std::type_index(typeid(ODESolver::SolveTask)),
234  // #endif
235  // std::type_index(typeid(MnaSolver<Real>::SolveTask)),
236  // std::type_index(typeid(MnaSolver<Complex>::SolveTask)),
237  // std::type_index(typeid(DiakopticsSolver<Real>::SubnetSolveTask)),
238  // std::type_index(typeid(DiakopticsSolver<Complex>::SubnetSolveTask)),
239  // std::type_index(typeid(DiakopticsSolver<Real>::SolveTask)),
240  // std::type_index(typeid(DiakopticsSolver<Complex>::SolveTask)),
241  // std::type_index(typeid(NRpolarSolver::SolveTask))
242  // };
243 
244  // return std::find(ignoreTasks.begin(), ignoreTasks.end(), std::type_index(typeid(*task.get()))) != ignoreTasks.end();
245  // };
246 
247  // auto isIgnoredTask = [&isSolveTask](Task::Ptr task) -> Bool {
248  // return typeid(*task.get()) == typeid(Interface::PreStep) || sSolveTask(task);
249  // };
250 
251  // auto isRootTask = [](Task::Ptr task) -> Bool {
252  // return typeid(*task.get()) == typeid(Scheduler::Root);
253  // };
254 
255  auto isScheduled = [this](Task::Ptr task) -> Bool {
256  return !mTaskOutEdges[task].empty();
257  };
258 
259  auto getColor = [](Task::Ptr task) -> String {
260  static std::map<std::type_index, String> colorMap;
261  auto tid = std::type_index(typeid(task.get()));
262 
263  if (colorMap.find(tid) != colorMap.end()) {
264  colorMap[tid] =
265  String("/paired9/") + std::to_string(1 + colorMap.size() % 9);
266  }
267 
268  return colorMap[tid];
269  };
270 
271  auto avgTimeWorst = Scheduler::TaskTime::min();
272  for (auto task : mTasks) {
273  avgTimes[task] = mScheduler->getAveragedMeasurement(task);
274 
275  if (avgTimes[task] > avgTimeWorst)
276  avgTimeWorst = avgTimes[task];
277  }
278 
279  // TODO: For level-based Scheduler's we might want to
280  // maintain the level structure by setting the respective
281  // Graphviz 'rank' attributes and group each level into sub-graph
282 
283  Graph::Graph g("dependencies", Graph::Type::directed);
284  for (auto task : mTasks) {
285  String name = task->toString();
286  String type = CPS::Utils::className(task.get(), "DPsim::");
287 
288  auto *n = g.addNode(name);
289 
290  std::stringstream label;
291 
292  label << "<B>" << Utils::encodeXml(name) << "</B><BR/>";
293  label << "<FONT POINT-SIZE=\"10\" COLOR=\"gray28\">"
294  << Utils::encodeXml(type) << "<BR/>";
295 
296  if (isScheduled(task))
297  label << "Avg. time: " << avgTimes[task].count() << "ns<BR/>";
298  else
299  label << "Unscheduled"
300  << "<BR/>";
301 
302  label << "</FONT>";
303 
304  n->set("color", getColor(task));
305  n->set("label", label.str(), true);
306  n->set("style", "rounded,filled,bold");
307  n->set("shape", "rectangle");
308 
309  // if (isSolveTask(task)) {
310  // n->set("fillcolor", "orange");
311  // }
312  // if (isRootTask(task)) {
313  // n->set("fillcolor", "springgreen1");
314  // }
315  if (isScheduled(task)) {
316  if (avgTimeWorst > Scheduler::TaskTime(0)) {
317  auto grad = (float)avgTimes[task].count() / avgTimeWorst.count();
318  n->set("fillcolor", CPS::Utils::Rgb::gradient(grad).hex());
319  SPDLOG_LOGGER_INFO(mLog, "{} {}", task->toString(),
320  CPS::Utils::Rgb::gradient(grad).hex());
321  }
322  } else {
323  n->set("fillcolor", "white");
324  }
325  }
326  for (auto from : mTasks) {
327  for (auto to : mTaskOutEdges[from]) {
328  g.addEdge("", g.node(from->toString()), g.node(to->toString()));
329  }
330  }
331 
332  g.set("splines", "ortho");
333  return g;
334 }
335 #endif
336 
338  SPDLOG_LOGGER_INFO(mLog, "Initialize simulation: {}", **mName);
339  if (!mInitialized)
340  initialize();
341 
342  SPDLOG_LOGGER_INFO(mLog, "Opening interfaces.");
343 
344  for (auto intf : mInterfaces)
345  intf->open();
346 
347  sync();
348 
349  SPDLOG_LOGGER_INFO(mLog, "Start simulation: {}", **mName);
350  SPDLOG_LOGGER_INFO(mLog, "Time step: {:e}", **mTimeStep);
351  SPDLOG_LOGGER_INFO(mLog, "Final time: {:e}", **mFinalTime);
352 
353  // In PF we dont log the initial conditions of the componentes because they are not calculated
354  // In dynamic simulations log initial values of attributes (t=0)
355  if (mSolverType != Solver::Type::NRP) {
356  if (mLoggers.size() > 0)
357  mLoggers[0]->log(0, 0);
358 
359  // In dynamic simulations increase simulation time to calculate first results at t=timestep
360  mTime += **mTimeStep;
361  }
362 
363  mSimulationStartTimePoint = std::chrono::steady_clock::now();
364 }
365 
367 
368  mSimulationEndTimePoint = std::chrono::steady_clock::now();
371  SPDLOG_LOGGER_INFO(mLog, "Simulation calculation time: {:.6f}",
373 
374  mScheduler->stop();
375 
376  for (auto intf : mInterfaces)
377  intf->close();
378 
379  for (auto lg : mLoggers)
380  lg->close();
381 
382  SPDLOG_LOGGER_INFO(mLog, "Simulation finished.");
383  mLog->flush();
384 }
385 
387  if (mTime < **mFinalTime + DOUBLE_EPSILON)
388  step();
389  else
390  stop();
391 
392  return mTime;
393 }
394 
396  start();
397 
398  while (mTime < **mFinalTime + DOUBLE_EPSILON) {
399  step();
400  }
401 
402  stop();
403 }
404 
406  auto start = std::chrono::steady_clock::now();
407 
408  mEvents.handleEvents(mTime);
410 
411  mTime += **mTimeStep;
412  ++mTimeStepCount;
413 
414  auto end = std::chrono::steady_clock::now();
415  std::chrono::duration<double> diff = end - start;
416  mStepTimes.push_back(diff.count());
417  return mTime;
418 }
419 
420 void Simulation::logStepTimes(String logName) {
421  auto stepTimeLog = Logger::get(logName, Logger::Level::info);
422  Logger::setLogPattern(stepTimeLog, "%v");
423  stepTimeLog->info("step_time");
424 
425  Real stepTimeSum = 0;
426  for (auto meas : mStepTimes) {
427  stepTimeSum += meas;
428  stepTimeLog->info("{:.9f}", meas);
429  }
430  SPDLOG_LOGGER_INFO(mLog, "Average step time: {:.9f}",
431  stepTimeSum / mStepTimes.size());
432 }
433 
435  for (auto solver : mSolvers) {
436  solver->logLUTimes();
437  }
438 }
439 
441  const String &attr) {
442  IdentifiedObject::Ptr idObj = mSystem.component<IdentifiedObject>(comp);
443  if (!idObj) {
444  idObj = mSystem.node<TopologicalNode>(comp);
445  }
446 
447  if (idObj) {
448  try {
449  CPS::AttributeBase::Ptr attrPtr = idObj->attribute(attr);
450  return attrPtr;
451  } catch (InvalidAttributeException &e) {
452  SPDLOG_LOGGER_ERROR(
453  mLog, "Attribute with name {} not found on component {}", attr, comp);
455  }
456  } else {
457  SPDLOG_LOGGER_ERROR(mLog, "Component or node with name {} not found", comp);
458  throw InvalidArgumentException();
459  }
460 }
461 
462 void Simulation::logIdObjAttribute(const String &comp, const String &attr) {
463  CPS::AttributeBase::Ptr attrPtr = getIdObjAttribute(comp, attr);
464  String name = comp + "." + attr;
465  logAttribute(name, attrPtr);
466 }
467 
469  if (mLoggers.size() > 0) {
470  mLoggers[0]->logAttribute(name, attr);
471  } else {
472  throw SystemError("Cannot log attributes when no logger is configured for "
473  "this simulation!");
474  }
475 }
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:434
void sync() const
Synchronize simulation with remotes by exchanging intial state over interfaces.
Definition: Simulation.cpp:173
CPS::IdentifiedObject::List mTearComponents
Definition: Simulation.h:101
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:405
Scheduler::Edges mTaskInEdges
Task dependencies as incoming / outgoing edges.
Definition: Simulation.h:123
const CPS::Attribute< Real >::Ptr mTimeStep
Simulation timestep.
Definition: Simulation.h:45
DataLogger::List mLoggers
The data loggers.
Definition: Simulation.h:136
const CPS::Attribute< Bool >::Ptr mSteadyStateInit
Definition: Simulation.h:56
Real next()
Run until next time step.
Definition: Simulation.cpp:386
void logStepTimes(String logName)
Write step time measurements to log file.
Definition: Simulation.cpp:420
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:185
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:113
CPS::Task::List mTasks
List of all tasks to be scheduled.
Definition: Simulation.h:121
const CPS::Attribute< Bool >::Ptr mSplitSubnets
Definition: Simulation.h:51
std::shared_ptr< Scheduler > mScheduler
Scheduler used for task scheduling.
Definition: Simulation.h:119
std::vector< Interface::Ptr > mInterfaces
Vector of Interfaces.
Definition: Simulation.h:126
void create()
Helper function for constructors.
Definition: Simulation.cpp:53
CPS::SystemTopology mSystem
System list.
Definition: Simulation.h:66
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:440
void start()
Start simulation without advancing in time.
Definition: Simulation.cpp:337
EventQueue mEvents
The simulation event queue.
Definition: Simulation.h:64
void schedule()
Create the schedule for the independent tasks.
Definition: Simulation.cpp:210
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:97
Real mSteadStIniAccLimit
steady state initialization accuracy limit
Definition: Simulation.h:115
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:395
void logAttribute(String name, CPS::AttributeBase::Ptr attr)
CHECK: Can we store the attribute name / UID intrinsically inside the attribute?
Definition: Simulation.cpp:468
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:152
void stop()
Stop simulation including scheduler and interfaces.
Definition: Simulation.cpp:366
std::chrono::time_point< std::chrono::steady_clock > mSimulationEndTimePoint
End time point to measure calculation time.
Definition: Simulation.h:71