DPsim
MNASolverDirect.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/MNASolverDirect.h>
10 #include <dpsim/SequentialScheduler.h>
11 
12 using namespace DPsim;
13 using namespace CPS;
14 
15 namespace DPsim {
16 
17 template <typename VarType>
18 MnaSolverDirect<VarType>::MnaSolverDirect(String name, CPS::Domain domain,
19  CPS::Logger::Level logLevel)
20  : MnaSolver<VarType>(name, domain, logLevel) {
21  mImplementationInUse = DirectLinearSolverImpl::KLU;
22 }
23 
24 template <typename VarType>
26  mSwitchedMatrices[std::bitset<SWITCH_NUM>(index)][0].setZero();
27 }
28 
29 template <typename VarType>
31  Int freqIdx) {
32  mSwitchedMatrices[std::bitset<SWITCH_NUM>(swIdx)][freqIdx].setZero();
33 }
34 
35 template <typename VarType>
37  std::size_t index, std::vector<std::shared_ptr<CPS::MNAInterface>> &comp) {
38  auto bit = std::bitset<SWITCH_NUM>(index);
39  auto &sys = mSwitchedMatrices[bit][0];
40  for (auto component : comp) {
41  component->mnaApplySystemMatrixStamp(sys);
42  }
43  for (UInt i = 0; i < mSwitches.size(); ++i)
44  mSwitches[i]->mnaApplySwitchSystemMatrixStamp(bit[i], sys, 0);
45 
46  // Compute LU-factorization for system matrix
47  mDirectLinearSolvers[bit][0]->preprocessing(sys,
48  mListVariableSystemMatrixEntries);
49  auto start = std::chrono::steady_clock::now();
50  mDirectLinearSolvers[bit][0]->factorize(sys);
51  auto end = std::chrono::steady_clock::now();
52  std::chrono::duration<Real> diff = end - start;
53  mFactorizeTimes.push_back(diff.count());
54 }
55 
56 template <typename VarType>
58 
59  this->mDirectLinearSolverVariableSystemMatrix =
60  createDirectSolverImplementation(mSLog);
61  // TODO: a direct linear solver configuration is only applied if system matrix recomputation is used
62  this->mDirectLinearSolverVariableSystemMatrix->setConfiguration(
63  mConfigurationInUse);
64 
65  SPDLOG_LOGGER_INFO(mSLog,
66  "Number of variable Elements: {}"
67  "\nNumber of MNA components: {}",
68  mVariableComps.size(), mMNAComponents.size());
69 
70  // Build base matrix with only static elements
71  mBaseSystemMatrix.setZero();
72  for (auto statElem : mMNAComponents)
73  statElem->mnaApplySystemMatrixStamp(mBaseSystemMatrix);
74  SPDLOG_LOGGER_INFO(mSLog, "Base matrix with only static elements: {}",
75  Logger::matrixToString(mBaseSystemMatrix));
76  mSLog->flush();
77 
78  // Continue from base matrix
79  mVariableSystemMatrix = mBaseSystemMatrix;
80 
81  // Now stamp switches into matrix
82  SPDLOG_LOGGER_INFO(mSLog, "Stamping switches");
83  for (auto sw : mMNAIntfSwitches)
84  sw->mnaApplySystemMatrixStamp(mVariableSystemMatrix);
85 
86  // Now stamp initial state of variable elements into matrix
87  SPDLOG_LOGGER_INFO(mSLog, "Stamping variable elements");
88  for (auto varElem : mMNAIntfVariableComps)
89  varElem->mnaApplySystemMatrixStamp(mVariableSystemMatrix);
90 
91  SPDLOG_LOGGER_INFO(mSLog, "Initial system matrix with variable elements {}",
92  Logger::matrixToString(mVariableSystemMatrix));
93  /* TODO: find replacement for flush() */
94  mSLog->flush();
95 
96  // Calculate factorization of current matrix
97  mDirectLinearSolverVariableSystemMatrix->preprocessing(
98  mVariableSystemMatrix, mListVariableSystemMatrixEntries);
99 
100  auto start = std::chrono::steady_clock::now();
101  mDirectLinearSolverVariableSystemMatrix->factorize(mVariableSystemMatrix);
102  auto end = std::chrono::steady_clock::now();
103  std::chrono::duration<Real> diff = end - start;
104  mFactorizeTimes.push_back(diff.count());
105 }
106 
107 template <typename VarType>
109  Real time, Int timeStepCount) {
110  // Reset source vector
111  mRightSideVector.setZero();
112 
113  // Add together the right side vector (computed by the components'
114  // pre-step tasks)
115  for (auto stamp : mRightVectorStamps)
116  mRightSideVector += *stamp;
117 
118  // Get switch and variable comp status and update system matrix and lu factorization accordingly
119  if (hasVariableComponentChanged())
120  recomputeSystemMatrix(time);
121 
122  // Calculate new solution vector
123  auto start = std::chrono::steady_clock::now();
124  **mLeftSideVector =
125  mDirectLinearSolverVariableSystemMatrix->solve(mRightSideVector);
126  auto end = std::chrono::steady_clock::now();
127  std::chrono::duration<Real> diff = end - start;
128  mSolveTimes.push_back(diff.count());
129 
130  // TODO split into separate task? (dependent on x, updating all v attributes)
131  for (UInt nodeIdx = 0; nodeIdx < mNumNetNodes; ++nodeIdx)
132  mNodes[nodeIdx]->mnaUpdateVoltage(**mLeftSideVector);
133 
134  // Components' states will be updated by the post-step tasks
135 }
136 
137 template <typename VarType>
139  // Start from base matrix
140  mVariableSystemMatrix = mBaseSystemMatrix;
141 
142  // Now stamp switches into matrix
143  for (auto sw : mMNAIntfSwitches)
144  sw->mnaApplySystemMatrixStamp(mVariableSystemMatrix);
145 
146  // Now stamp variable elements into matrix
147  for (auto comp : mMNAIntfVariableComps)
148  comp->mnaApplySystemMatrixStamp(mVariableSystemMatrix);
149 
150  // Refactorization of matrix assuming that structure remained
151  // constant by omitting analyzePattern
152  auto start = std::chrono::steady_clock::now();
153  mDirectLinearSolverVariableSystemMatrix->partialRefactorize(
154  mVariableSystemMatrix, mListVariableSystemMatrixEntries);
155  auto end = std::chrono::steady_clock::now();
156  std::chrono::duration<Real> diff = end - start;
157  mRecomputationTimes.push_back(diff.count());
158  ++mNumRecomputations;
159 }
160 
162  if (mSwitches.size() > SWITCH_NUM)
163  throw SystemError("Too many Switches.");
164 
165  if (mSystemMatrixRecomputation) {
166  mBaseSystemMatrix =
167  SparseMatrix(mNumMatrixNodeIndices, mNumMatrixNodeIndices);
168  mVariableSystemMatrix =
169  SparseMatrix(mNumMatrixNodeIndices, mNumMatrixNodeIndices);
170  } else {
171  for (std::size_t i = 0; i < (1ULL << mSwitches.size()); i++) {
172  auto bit = std::bitset<SWITCH_NUM>(i);
173  mSwitchedMatrices[bit].push_back(
174  SparseMatrix(mNumMatrixNodeIndices, mNumMatrixNodeIndices));
175  mDirectLinearSolvers[bit].push_back(
176  createDirectSolverImplementation(mSLog));
177  }
178  }
179 }
180 
182  if (mSwitches.size() > SWITCH_NUM)
183  throw SystemError("Too many Switches.");
184 
185  if (mFrequencyParallel) {
186  for (UInt i = 0; i < std::pow(2, mSwitches.size()); ++i) {
187  for (Int freq = 0; freq < mSystem.mFrequencies.size(); ++freq) {
188  auto bit = std::bitset<SWITCH_NUM>(i);
189  mSwitchedMatrices[bit].push_back(SparseMatrix(
190  2 * (mNumMatrixNodeIndices), 2 * (mNumMatrixNodeIndices)));
191  mDirectLinearSolvers[bit].push_back(
192  createDirectSolverImplementation(mSLog));
193  }
194  }
195  } else if (mSystemMatrixRecomputation) {
196  mBaseSystemMatrix =
197  SparseMatrix(2 * (mNumMatrixNodeIndices), 2 * (mNumMatrixNodeIndices));
198  mVariableSystemMatrix =
199  SparseMatrix(2 * (mNumMatrixNodeIndices), 2 * (mNumMatrixNodeIndices));
200  } else {
201  for (std::size_t i = 0; i < (1ULL << mSwitches.size()); i++) {
202  auto bit = std::bitset<SWITCH_NUM>(i);
203  mSwitchedMatrices[bit].push_back(SparseMatrix(
204  2 * (mNumTotalMatrixNodeIndices), 2 * (mNumTotalMatrixNodeIndices)));
205  mDirectLinearSolvers[bit].push_back(
206  createDirectSolverImplementation(mSLog));
207  }
208  }
209 }
210 
211 template <typename VarType>
212 std::shared_ptr<CPS::Task> MnaSolverDirect<VarType>::createSolveTask() {
213  return std::make_shared<MnaSolverDirect<VarType>::SolveTask>(*this);
214 }
215 
216 template <typename VarType>
218  return std::make_shared<MnaSolverDirect<VarType>::SolveTaskRecomp>(*this);
219 }
220 
221 template <typename VarType>
222 std::shared_ptr<CPS::Task>
224  return std::make_shared<MnaSolverDirect<VarType>::SolveTaskHarm>(*this,
225  freqIdx);
226 }
227 
228 template <typename VarType>
229 std::shared_ptr<CPS::Task> MnaSolverDirect<VarType>::createLogTask() {
230  return std::make_shared<MnaSolverDirect<VarType>::LogTask>(*this);
231 }
232 
233 template <typename VarType>
234 void MnaSolverDirect<VarType>::solve(Real time, Int timeStepCount) {
235  // Reset source vector
236  mRightSideVector.setZero();
237 
238  // Add together the right side vector (computed by the components' pre-step tasks)
239  for (auto stamp : mRightVectorStamps)
240  mRightSideVector += *stamp;
241 
242  if (!mIsInInitialization)
244 
245  if (mSwitchedMatrices.size() > 0) {
246  std::chrono::steady_clock::time_point start;
248  start = std::chrono::steady_clock::now();
249 
250  **mLeftSideVector =
251  mDirectLinearSolvers[mCurrentSwitchStatus][0]->solve(mRightSideVector);
252 
254  auto end = std::chrono::steady_clock::now();
255  std::chrono::duration<Real> diff = end - start;
256  mSolveTimes.push_back(diff.count());
257  }
258  }
259 
260  // CHECK: Is this really required? Or can operations actually become part of
261  // correctorStep and mnaPostStep?
262  for (auto syncGen : mSyncGen)
263  syncGen->updateVoltage(**mLeftSideVector);
264 
265  // Reset number of iterations
266  mIter = 0;
267 
268  // Additional solve steps for iterative models
269  if (mSyncGen.size() > 0) {
270  UInt numCompsRequireIter;
271  do {
272  // count synchronous generators that require iteration
273  numCompsRequireIter = 0;
274  for (auto syncGen : mSyncGen)
275  if (syncGen->requiresIteration())
276  numCompsRequireIter++;
277 
278  // recompute solve step if at least one component demands iteration
279  if (numCompsRequireIter > 0) {
280  mIter++;
281 
282  // Reset source vector
283  mRightSideVector.setZero();
284 
285  if (!mIsInInitialization)
287 
288  for (auto syncGen : mSyncGen)
289  syncGen->correctorStep();
290 
291  // Add together the right side vector (computed by the components' pre-step tasks)
292  for (auto stamp : mRightVectorStamps)
293  mRightSideVector += *stamp;
294 
295  if (mSwitchedMatrices.size() > 0) {
296  auto start = std::chrono::steady_clock::now();
297  **mLeftSideVector =
298  mDirectLinearSolvers[mCurrentSwitchStatus][0]->solve(
299  mRightSideVector);
300  auto end = std::chrono::steady_clock::now();
301  std::chrono::duration<Real> diff = end - start;
302  mSolveTimes.push_back(diff.count());
303  }
304 
305  // CHECK: Is this really required? Or can operations actually become part of
306  // correctorStep and mnaPostStep?
307  for (auto syncGen : mSyncGen)
308  syncGen->updateVoltage(**mLeftSideVector);
309  }
310  } while (numCompsRequireIter > 0);
311  }
312 
313  // TODO split into separate task? (dependent on x, updating all v attributes)
314  for (UInt nodeIdx = 0; nodeIdx < mNumNetNodes; ++nodeIdx)
315  mNodes[nodeIdx]->mnaUpdateVoltage(**mLeftSideVector);
316 
317  // Components' states will be updated by the post-step tasks
318 }
319 
320 template <typename VarType>
321 void MnaSolverDirect<VarType>::solveWithHarmonics(Real time, Int timeStepCount,
322  Int freqIdx) {
323  mRightSideVectorHarm[freqIdx].setZero();
324 
325  // Sum of right side vectors (computed by the components' pre-step tasks)
326  for (auto stamp : mRightVectorStamps)
327  mRightSideVectorHarm[freqIdx] += stamp->col(freqIdx);
328 
329  **mLeftSideVectorHarm[freqIdx] =
330  mDirectLinearSolvers[mCurrentSwitchStatus][freqIdx]->solve(
331  mRightSideVectorHarm[freqIdx]);
332 }
333 
334 template <typename VarType> void MnaSolverDirect<VarType>::logSystemMatrices() {
335  if (mFrequencyParallel) {
336  for (UInt i = 0; i < mSwitchedMatrices[std::bitset<SWITCH_NUM>(0)].size();
337  ++i) {
338  SPDLOG_LOGGER_INFO(mSLog, "System matrix for frequency: {:d} \n{:s}", i,
339  Logger::matrixToString(
340  mSwitchedMatrices[std::bitset<SWITCH_NUM>(0)][i]));
341  }
342 
343  for (UInt i = 0; i < mRightSideVectorHarm.size(); ++i) {
344  SPDLOG_LOGGER_INFO(mSLog, "Right side vector for frequency: {:d} \n{:s}",
345  i, Logger::matrixToString(mRightSideVectorHarm[i]));
346  }
347 
348  } else if (mSystemMatrixRecomputation) {
349  SPDLOG_LOGGER_INFO(mSLog, "Summarizing matrices: ");
350  SPDLOG_LOGGER_INFO(mSLog, "Base matrix with only static elements: {}",
351  Logger::matrixToString(mBaseSystemMatrix));
352  SPDLOG_LOGGER_INFO(mSLog, "Initial system matrix with variable elements {}",
353  Logger::matrixToString(mVariableSystemMatrix));
354  SPDLOG_LOGGER_INFO(mSLog, "Right side vector: {}",
355  Logger::matrixToString(mRightSideVector));
356  } else {
357  if (mSwitches.size() < 1) {
358  SPDLOG_LOGGER_INFO(mSLog, "System matrix: \n{}",
359  mSwitchedMatrices[std::bitset<SWITCH_NUM>(0)][0]);
360  } else {
361  SPDLOG_LOGGER_INFO(mSLog, "Initial switch status: {:s}",
362  mCurrentSwitchStatus.to_string());
363 
364  for (auto sys : mSwitchedMatrices) {
365  SPDLOG_LOGGER_INFO(mSLog, "Switching System matrix {:s} \n{:s}",
366  sys.first.to_string(),
367  Logger::matrixToString(sys.second[0]));
368  }
369  }
370  SPDLOG_LOGGER_INFO(mSLog, "Right side vector: \n{}", mRightSideVector);
371  }
372 }
373 
374 template <typename VarType> void MnaSolverDirect<VarType>::logLUTimes() {
375  logFactorizationTime();
376  logRecomputationTime();
377  logSolveTime();
378 }
379 
380 template <typename VarType> void MnaSolverDirect<VarType>::logSolveTime() {
381  Real solveSum = 0.0;
382  Real solveMax = 0.0;
383  for (auto meas : mSolveTimes) {
384  solveSum += meas;
385  if (meas > solveMax)
386  solveMax = meas;
387  }
388  SPDLOG_LOGGER_INFO(mSLog, "Cumulative solve times: {:.12f}", solveSum);
389  SPDLOG_LOGGER_INFO(mSLog, "Average solve time: {:.12f}",
390  solveSum / static_cast<double>(mSolveTimes.size()));
391  SPDLOG_LOGGER_INFO(mSLog, "Maximum solve time: {:.12f}", solveMax);
392  SPDLOG_LOGGER_INFO(mSLog, "Number of solves: {:d}", mSolveTimes.size());
393 }
394 
395 template <typename VarType>
397  for (auto meas : mFactorizeTimes) {
398  SPDLOG_LOGGER_INFO(mSLog, "LU factorization time: {:.12f}", meas);
399  }
400 }
401 
402 template <typename VarType>
404  Real recompSum = 0.0;
405  Real recompMax = 0.0;
406  for (auto meas : mRecomputationTimes) {
407  recompSum += meas;
408  if (meas > recompMax)
409  recompMax = meas;
410  }
411  // Sometimes, refactorization is not used
412  if (mRecomputationTimes.size() != 0) {
413  SPDLOG_LOGGER_INFO(mSLog, "Cumulative refactorization times: {:.12f}",
414  recompSum);
415  SPDLOG_LOGGER_INFO(mSLog, "Average refactorization time: {:.12f}",
416  recompSum / ((double)mRecomputationTimes.size()));
417  SPDLOG_LOGGER_INFO(mSLog, "Maximum refactorization time: {:.12f}",
418  recompMax);
419  SPDLOG_LOGGER_INFO(mSLog, "Number of refactorizations: {:d}",
420  mRecomputationTimes.size());
421  }
422 }
423 
424 template <typename VarType>
425 std::shared_ptr<DirectLinearSolver>
427  CPS::Logger::Log mSLog) {
428  switch (this->mImplementationInUse) {
429  case DirectLinearSolverImpl::DenseLU:
430  return std::make_shared<DenseLUAdapter>(mSLog);
431  case DirectLinearSolverImpl::SparseLU:
432  return std::make_shared<SparseLUAdapter>(mSLog);
433 #ifdef WITH_KLU
434  case DirectLinearSolverImpl::KLU:
435  return std::make_shared<KLUAdapter>(mSLog);
436 #endif
437 #ifdef WITH_CUDA
438  case DirectLinearSolverImpl::CUDADense:
439  return std::make_shared<GpuDenseAdapter>(mSLog);
440 #ifdef WITH_CUDA_SPARSE
441  case DirectLinearSolverImpl::CUDASparse:
442  return std::make_shared<GpuSparseAdapter>(mSLog);
443 #endif
444 #ifdef WITH_MAGMA
445  case DirectLinearSolverImpl::CUDAMagma:
446  return std::make_shared<GpuMagmaAdapter>(mSLog);
447 #endif
448 #endif
449  default:
450  throw CPS::SystemError("unsupported linear solver implementation.");
451  }
452 }
453 
454 template <typename VarType>
456  DirectLinearSolverImpl implementation) {
457  this->mImplementationInUse = implementation;
458 }
459 
460 template <typename VarType>
462  DirectLinearSolverConfiguration &configuration) {
463  this->mConfigurationInUse = configuration;
464 }
465 
466 } // namespace DPsim
467 
468 template class DPsim::MnaSolverDirect<Real>;
469 template class DPsim::MnaSolverDirect<Complex>;
Solver class using Modified Nodal Analysis (MNA).
std::shared_ptr< CPS::Task > createSolveTask() override
Create a solve task for this solver implementation.
void stampVariableSystemMatrix() override
Stamps components into the variable system matrix.
void logSolveTime()
Logging of the right-hand-side solution time.
void logFactorizationTime()
Logging of the LU factorization time.
void solve(Real time, Int timeStepCount) override
Solves system for single frequency.
void logRecomputationTime()
Logging of the LU refactorization time.
virtual void recomputeSystemMatrix(Real time)
Recomputes systems matrix.
void switchedMatrixStamp(std::size_t index, std::vector< std::shared_ptr< CPS::MNAInterface >> &comp) override
Applies a component stamp to the matrix with the given switch index.
std::shared_ptr< DirectLinearSolver > createDirectSolverImplementation(CPS::Logger::Log mSLog)
Returns a pointer to an object of type DirectLinearSolver.
void setDirectLinearSolverConfiguration(DirectLinearSolverConfiguration &configuration) override
Sets the linear solver configuration.
DirectLinearSolverImpl mImplementationInUse
LU factorization indicator.
void setDirectLinearSolverImplementation(DirectLinearSolverImpl implementation)
Sets the linear solver to "implementation" and creates an object.
std::shared_ptr< CPS::Task > createSolveTaskHarm(UInt freqIdx) override
Create a solve task for this solver implementation.
std::shared_ptr< CPS::Task > createSolveTaskRecomp() override
Create a solve task for recomputation solver.
void logSystemMatrices() override
Logging of system matrices and source vector.
void createEmptySystemMatrix() override
Create system matrix.
std::shared_ptr< CPS::Task > createLogTask() override
Create a solve task for this solver implementation.
void logLUTimes() override
log LU decomposition times
void switchedMatrixEmpty(std::size_t index) override
Sets all entries in the matrix with the given switch index to zero.
void solveWithSystemMatrixRecomputation(Real time, Int timeStepCount) override
Solves the system with variable system matrix.
MnaSolverDirect(String name, CPS::Domain domain=CPS::Domain::DP, CPS::Logger::Level logLevel=CPS::Logger::Level::info)
void solveWithHarmonics(Real time, Int timeStepCount, Int freqIdx) override
Solves system for multiple frequencies.
Solver class using Modified Nodal Analysis (MNA).
Definition: MNASolver.h:38
void updateSwitchStatus()
Collects the status of switches to select correct system matrix.
Definition: MNASolver.cpp:311
Bool mLogSolveTimes
Collect step time for logging.
Definition: Solver.h:43