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  auto start = std::chrono::steady_clock::now();
247  **mLeftSideVector =
248  mDirectLinearSolvers[mCurrentSwitchStatus][0]->solve(mRightSideVector);
249  auto end = std::chrono::steady_clock::now();
250  std::chrono::duration<Real> diff = end - start;
251  mSolveTimes.push_back(diff.count());
252  }
253 
254  // CHECK: Is this really required? Or can operations actually become part of
255  // correctorStep and mnaPostStep?
256  for (auto syncGen : mSyncGen)
257  syncGen->updateVoltage(**mLeftSideVector);
258 
259  // Reset number of iterations
260  mIter = 0;
261 
262  // Additional solve steps for iterative models
263  if (mSyncGen.size() > 0) {
264  UInt numCompsRequireIter;
265  do {
266  // count synchronous generators that require iteration
267  numCompsRequireIter = 0;
268  for (auto syncGen : mSyncGen)
269  if (syncGen->requiresIteration())
270  numCompsRequireIter++;
271 
272  // recompute solve step if at least one component demands iteration
273  if (numCompsRequireIter > 0) {
274  mIter++;
275 
276  // Reset source vector
277  mRightSideVector.setZero();
278 
279  if (!mIsInInitialization)
281 
282  for (auto syncGen : mSyncGen)
283  syncGen->correctorStep();
284 
285  // Add together the right side vector (computed by the components' pre-step tasks)
286  for (auto stamp : mRightVectorStamps)
287  mRightSideVector += *stamp;
288 
289  if (mSwitchedMatrices.size() > 0) {
290  auto start = std::chrono::steady_clock::now();
291  **mLeftSideVector =
292  mDirectLinearSolvers[mCurrentSwitchStatus][0]->solve(
293  mRightSideVector);
294  auto end = std::chrono::steady_clock::now();
295  std::chrono::duration<Real> diff = end - start;
296  mSolveTimes.push_back(diff.count());
297  }
298 
299  // CHECK: Is this really required? Or can operations actually become part of
300  // correctorStep and mnaPostStep?
301  for (auto syncGen : mSyncGen)
302  syncGen->updateVoltage(**mLeftSideVector);
303  }
304  } while (numCompsRequireIter > 0);
305  }
306 
307  // TODO split into separate task? (dependent on x, updating all v attributes)
308  for (UInt nodeIdx = 0; nodeIdx < mNumNetNodes; ++nodeIdx)
309  mNodes[nodeIdx]->mnaUpdateVoltage(**mLeftSideVector);
310 
311  // Components' states will be updated by the post-step tasks
312 }
313 
314 template <typename VarType>
315 void MnaSolverDirect<VarType>::solveWithHarmonics(Real time, Int timeStepCount,
316  Int freqIdx) {
317  mRightSideVectorHarm[freqIdx].setZero();
318 
319  // Sum of right side vectors (computed by the components' pre-step tasks)
320  for (auto stamp : mRightVectorStamps)
321  mRightSideVectorHarm[freqIdx] += stamp->col(freqIdx);
322 
323  **mLeftSideVectorHarm[freqIdx] =
324  mDirectLinearSolvers[mCurrentSwitchStatus][freqIdx]->solve(
325  mRightSideVectorHarm[freqIdx]);
326 }
327 
328 template <typename VarType> void MnaSolverDirect<VarType>::logSystemMatrices() {
329  if (mFrequencyParallel) {
330  for (UInt i = 0; i < mSwitchedMatrices[std::bitset<SWITCH_NUM>(0)].size();
331  ++i) {
332  SPDLOG_LOGGER_INFO(mSLog, "System matrix for frequency: {:d} \n{:s}", i,
333  Logger::matrixToString(
334  mSwitchedMatrices[std::bitset<SWITCH_NUM>(0)][i]));
335  }
336 
337  for (UInt i = 0; i < mRightSideVectorHarm.size(); ++i) {
338  SPDLOG_LOGGER_INFO(mSLog, "Right side vector for frequency: {:d} \n{:s}",
339  i, Logger::matrixToString(mRightSideVectorHarm[i]));
340  }
341 
342  } else if (mSystemMatrixRecomputation) {
343  SPDLOG_LOGGER_INFO(mSLog, "Summarizing matrices: ");
344  SPDLOG_LOGGER_INFO(mSLog, "Base matrix with only static elements: {}",
345  Logger::matrixToString(mBaseSystemMatrix));
346  SPDLOG_LOGGER_INFO(mSLog, "Initial system matrix with variable elements {}",
347  Logger::matrixToString(mVariableSystemMatrix));
348  SPDLOG_LOGGER_INFO(mSLog, "Right side vector: {}",
349  Logger::matrixToString(mRightSideVector));
350  } else {
351  if (mSwitches.size() < 1) {
352  SPDLOG_LOGGER_INFO(mSLog, "System matrix: \n{}",
353  mSwitchedMatrices[std::bitset<SWITCH_NUM>(0)][0]);
354  } else {
355  SPDLOG_LOGGER_INFO(mSLog, "Initial switch status: {:s}",
356  mCurrentSwitchStatus.to_string());
357 
358  for (auto sys : mSwitchedMatrices) {
359  SPDLOG_LOGGER_INFO(mSLog, "Switching System matrix {:s} \n{:s}",
360  sys.first.to_string(),
361  Logger::matrixToString(sys.second[0]));
362  }
363  }
364  SPDLOG_LOGGER_INFO(mSLog, "Right side vector: \n{}", mRightSideVector);
365  }
366 }
367 
368 template <typename VarType> void MnaSolverDirect<VarType>::logLUTimes() {
369  logFactorizationTime();
370  logRecomputationTime();
371  logSolveTime();
372 }
373 
374 template <typename VarType> void MnaSolverDirect<VarType>::logSolveTime() {
375  Real solveSum = 0.0;
376  Real solveMax = 0.0;
377  for (auto meas : mSolveTimes) {
378  solveSum += meas;
379  if (meas > solveMax)
380  solveMax = meas;
381  }
382  SPDLOG_LOGGER_INFO(mSLog, "Cumulative solve times: {:.12f}", solveSum);
383  SPDLOG_LOGGER_INFO(mSLog, "Average solve time: {:.12f}",
384  solveSum / static_cast<double>(mSolveTimes.size()));
385  SPDLOG_LOGGER_INFO(mSLog, "Maximum solve time: {:.12f}", solveMax);
386  SPDLOG_LOGGER_INFO(mSLog, "Number of solves: {:d}", mSolveTimes.size());
387 }
388 
389 template <typename VarType>
391  for (auto meas : mFactorizeTimes) {
392  SPDLOG_LOGGER_INFO(mSLog, "LU factorization time: {:.12f}", meas);
393  }
394 }
395 
396 template <typename VarType>
398  Real recompSum = 0.0;
399  Real recompMax = 0.0;
400  for (auto meas : mRecomputationTimes) {
401  recompSum += meas;
402  if (meas > recompMax)
403  recompMax = meas;
404  }
405  // Sometimes, refactorization is not used
406  if (mRecomputationTimes.size() != 0) {
407  SPDLOG_LOGGER_INFO(mSLog, "Cumulative refactorization times: {:.12f}",
408  recompSum);
409  SPDLOG_LOGGER_INFO(mSLog, "Average refactorization time: {:.12f}",
410  recompSum / ((double)mRecomputationTimes.size()));
411  SPDLOG_LOGGER_INFO(mSLog, "Maximum refactorization time: {:.12f}",
412  recompMax);
413  SPDLOG_LOGGER_INFO(mSLog, "Number of refactorizations: {:d}",
414  mRecomputationTimes.size());
415  }
416 }
417 
418 template <typename VarType>
419 std::shared_ptr<DirectLinearSolver>
421  CPS::Logger::Log mSLog) {
422  switch (this->mImplementationInUse) {
423  case DirectLinearSolverImpl::DenseLU:
424  return std::make_shared<DenseLUAdapter>(mSLog);
425  case DirectLinearSolverImpl::SparseLU:
426  return std::make_shared<SparseLUAdapter>(mSLog);
427 #ifdef WITH_KLU
428  case DirectLinearSolverImpl::KLU:
429  return std::make_shared<KLUAdapter>(mSLog);
430 #endif
431 #ifdef WITH_CUDA
432  case DirectLinearSolverImpl::CUDADense:
433  return std::make_shared<GpuDenseAdapter>(mSLog);
434 #ifdef WITH_CUDA_SPARSE
435  case DirectLinearSolverImpl::CUDASparse:
436  return std::make_shared<GpuSparseAdapter>(mSLog);
437 #endif
438 #ifdef WITH_MAGMA
439  case DirectLinearSolverImpl::CUDAMagma:
440  return std::make_shared<GpuMagmaAdapter>(mSLog);
441 #endif
442 #endif
443  default:
444  throw CPS::SystemError("unsupported linear solver implementation.");
445  }
446 }
447 
448 template <typename VarType>
450  DirectLinearSolverImpl implementation) {
451  this->mImplementationInUse = implementation;
452 }
453 
454 template <typename VarType>
456  DirectLinearSolverConfiguration &configuration) {
457  this->mConfigurationInUse = configuration;
458 }
459 
460 } // namespace DPsim
461 
462 template class DPsim::MnaSolverDirect<Real>;
463 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:309