DPsim
Loading...
Searching...
No Matches
PFSolver.cpp
Go to the documentation of this file.
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
10#include <dpsim/PFSolver.h>
12#include <iostream>
13
14using namespace DPsim;
15using namespace CPS;
16
18 CPS::Real timeStep, CPS::Logger::Level logLevel)
19 : Solver(name + "_PF", logLevel) {
20 mSystem = system;
21 mTimeStep = timeStep;
22}
23
25 SPDLOG_LOGGER_INFO(mSLog, "#### INITIALIZATION OF POWERFLOW SOLVER ");
26 // mComponentsAtNode is a view derived from mComponents; rebuild it here so
27 // the solver never sees entries for removed or re-added components (#635)
28 mSystem.componentsAtNodeList();
29 for (auto comp : mSystem.mComponents) {
30 if (std::shared_ptr<CPS::SP::Ph1::SynchronGenerator> gen =
31 std::dynamic_pointer_cast<CPS::SP::Ph1::SynchronGenerator>(comp))
32 mSynchronGenerators.push_back(gen);
33 else if (std::shared_ptr<CPS::SP::Ph1::Load> load =
34 std::dynamic_pointer_cast<CPS::SP::Ph1::Load>(comp))
35 mLoads.push_back(load);
36 else if (std::shared_ptr<CPS::SP::Ph1::Transformer> trafo =
37 std::dynamic_pointer_cast<CPS::SP::Ph1::Transformer>(comp))
38 mTransformers.push_back(trafo);
39 else if (std::shared_ptr<CPS::SP::Ph1::PiLine> line =
40 std::dynamic_pointer_cast<CPS::SP::Ph1::PiLine>(comp))
41 mLines.push_back(line);
42 else if (std::shared_ptr<CPS::SP::Ph1::NetworkInjection> extnet =
43 std::dynamic_pointer_cast<CPS::SP::Ph1::NetworkInjection>(
44 comp))
45 mExternalGrids.push_back(extnet);
46 else if (std::shared_ptr<CPS::SP::Ph1::Shunt> shunt =
47 std::dynamic_pointer_cast<CPS::SP::Ph1::Shunt>(comp))
48 mShunts.push_back(shunt);
49 else if (std::shared_ptr<CPS::SP::Ph1::SolidStateTransformer> sst =
50 std::dynamic_pointer_cast<CPS::SP::Ph1::SolidStateTransformer>(
51 comp))
52 mSolidStateTransformers.push_back(sst);
53 else if (std::shared_ptr<CPS::SP::Ph1::AvVoltageSourceInverterDQ> vsi =
54 std::dynamic_pointer_cast<
56 mAverageVoltageSourceInverters.push_back(vsi);
57 }
58 }
59
66
68 mX.setZero(mNumUnknowns);
69 mF.setZero(mNumUnknowns);
70}
71
75
77 auto sparseJ = mJ.sparseView();
78 Eigen::SparseLU<SparseMatrix> lu(sparseJ);
79 mX = lu.solve(mF);
80}
81
83 SPDLOG_LOGGER_INFO(mSLog, "Assigning simulation nodes to topology nodes:");
84 UInt matrixNodeIndexIdx = 0;
85 for (UInt idx = 0; idx < mSystem.mNodes.size(); ++idx) {
86 mSystem.mNodes[idx]->setMatrixNodeIndex(0, matrixNodeIndexIdx);
87 SPDLOG_LOGGER_INFO(mSLog, "Node {}: MatrixNodeIndex {}",
88 mSystem.mNodes[idx]->uid(),
89 mSystem.mNodes[idx]->matrixNodeIndex());
90 ++matrixNodeIndexIdx;
91 }
92 SPDLOG_LOGGER_INFO(mSLog, "Number of simulation nodes: {:d}",
93 matrixNodeIndexIdx);
94}
95
97 for (auto comp : mSystem.mComponents) {
98 std::dynamic_pointer_cast<SimPowerComp<Complex>>(comp)
99 ->updateMatrixNodeIndices();
100 }
101
102 SPDLOG_LOGGER_INFO(
103 mSLog, "-- Initialize components from terminals or nodes of topology");
104 for (auto comp : mSystem.mComponents) {
105 auto pComp = std::dynamic_pointer_cast<SimPowerComp<Complex>>(comp);
106 if (!pComp)
107 continue;
109 pComp->initializeFromNodesAndTerminals(mSystem.mSystemFrequency);
110 }
111
112 SPDLOG_LOGGER_INFO(mSLog,
113 "-- Calculate per unit parameters for all components");
114 for (auto extnet : mExternalGrids) {
115 extnet->calculatePerUnitParameters(mBaseApparentPower,
116 mSystem.mSystemOmega);
117 }
118 for (auto line : mLines) {
119 line->calculatePerUnitParameters(mBaseApparentPower, mSystem.mSystemOmega);
120 }
121 for (auto trans : mTransformers) {
122 trans->calculatePerUnitParameters(mBaseApparentPower, mSystem.mSystemOmega);
123 }
124 for (auto shunt : mShunts) {
125 shunt->calculatePerUnitParameters(mBaseApparentPower, mSystem.mSystemOmega);
126 }
127 for (auto load : mLoads) {
128 load->calculatePerUnitParameters(mBaseApparentPower, mSystem.mSystemOmega);
129 }
130 for (auto gen : mSynchronGenerators) {
131 gen->calculatePerUnitParameters(mBaseApparentPower, mSystem.mSystemOmega);
132 }
133 for (auto sst : mSolidStateTransformers) {
134 sst->calculatePerUnitParameters(mBaseApparentPower, mSystem.mSystemOmega);
135 }
136}
137
139 Real maxPower = 0.;
140 if (!mSynchronGenerators.empty()) {
141 for (auto gen : mSynchronGenerators)
142 if (std::abs(gen->attributeTyped<Real>("P_set")->get()) > maxPower)
143 maxPower = std::abs(gen->attributeTyped<Real>("P_set")->get());
144 } else if (!mTransformers.empty()) {
145 for (auto trafo : mTransformers)
146 if (trafo->attributeTyped<Real>("S")->get() > maxPower)
147 maxPower = trafo->attributeTyped<Real>("S")->get();
148 }
149 if (maxPower != 0.)
150 mBaseApparentPower = pow(10, 1 + floor(log10(maxPower)));
151 else {
153 SPDLOG_LOGGER_WARN(mSLog,
154 "No suitable quantity found for setting "
155 "mBaseApparentPower. Using {} VA.",
157 }
158 SPDLOG_LOGGER_INFO(mSLog, "Base power = {} VA", mBaseApparentPower);
159}
160
162 mPQBuses.clear();
163 mPVBuses.clear();
164 mVDBuses.clear();
165
166 SPDLOG_LOGGER_INFO(mSLog, "-- Determine powerflow bus type for each node");
167
168 // Determine powerflow bus type of each node through analysis of system topology
169 for (auto node : mSystem.mNodes) {
170 bool connectedPV = false;
171 bool connectedPQ = false;
172 bool connectedVD = false;
173
174 for (auto comp : mSystem.mComponentsAtNode[node]) {
175 if (std::shared_ptr<CPS::SP::Ph1::Load> load =
176 std::dynamic_pointer_cast<CPS::SP::Ph1::Load>(comp)) {
177 if (load->mPowerflowBusType == CPS::PowerflowBusType::PQ) {
178 connectedPQ = true;
179 }
180 } else if (std::shared_ptr<CPS::SP::Ph1::SynchronGenerator> gen =
181 std::dynamic_pointer_cast<CPS::SP::Ph1::SynchronGenerator>(
182 comp)) {
183 if (gen->mPowerflowBusType == CPS::PowerflowBusType::PV) {
184 connectedPV = true;
185 } else if (gen->mPowerflowBusType == CPS::PowerflowBusType::VD) {
186 connectedVD = true;
187 } else if (gen->mPowerflowBusType == CPS::PowerflowBusType::PQ) {
188 connectedPQ = true;
189 }
190 } else if (std::shared_ptr<CPS::SP::Ph1::NetworkInjection> extnet =
191 std::dynamic_pointer_cast<CPS::SP::Ph1::NetworkInjection>(
192 comp)) {
193 if (extnet->mPowerflowBusType == CPS::PowerflowBusType::VD) {
194 connectedVD = true;
195 } else if (extnet->mPowerflowBusType == CPS::PowerflowBusType::PV) {
196 connectedPV = true;
197 }
198 }
199 }
200
201 // determine powerflow bus types according connected type of connected components
202 // only PQ type component connected -> set as PQ bus
203 if (!connectedPV && connectedPQ && !connectedVD) {
204 SPDLOG_LOGGER_INFO(
205 mSLog, "{}: only PQ type component connected -> set as PQ bus",
206 node->name());
207 mPQBuses.push_back(node);
208 } // no component connected -> set as PQ bus (P & Q will be zero)
209 else if (!connectedPV && !connectedPQ && !connectedVD) {
210 SPDLOG_LOGGER_INFO(mSLog, "{}: no component connected -> set as PQ bus",
211 node->name());
212 mPQBuses.push_back(node);
213 } // only PV type component connected -> set as PV bus
214 else if (connectedPV && !connectedPQ && !connectedVD) {
215 SPDLOG_LOGGER_INFO(
216 mSLog, "{}: only PV type component connected -> set as PV bus",
217 node->name());
218 mPVBuses.push_back(node);
219 } // PV and PQ type component connected -> set as PV bus (TODO: bus type should be modifiable by user afterwards)
220 else if (connectedPV && connectedPQ && !connectedVD) {
221 SPDLOG_LOGGER_INFO(
222 mSLog, "{}: PV and PQ type component connected -> set as PV bus",
223 node->name());
224 mPVBuses.push_back(node);
225 } // only VD type component connected -> set as VD bus
226 else if (!connectedPV && !connectedPQ && connectedVD) {
227 SPDLOG_LOGGER_INFO(
228 mSLog, "{}: only VD type component connected -> set as VD bus",
229 node->name());
230 mVDBuses.push_back(node);
231 } // VD and PV type component connect -> set as VD bus
232 else if (connectedPV && !connectedPQ && connectedVD) {
233 SPDLOG_LOGGER_INFO(
234 mSLog, "{}: VD and PV type component connect -> set as VD bus",
235 node->name());
236 mVDBuses.push_back(node);
237 } // VD and PQ type component connected -> set as VD bus
238 else if (!connectedPV && connectedPQ && connectedVD) {
239 SPDLOG_LOGGER_INFO(
240 mSLog, "{}: VD and PQ type component connected -> set as VD bus",
241 node->name());
242 mVDBuses.push_back(node);
243 } // VD, PV and PQ type component connect -> set as VD bus
244 else if (connectedPV && connectedPQ && connectedVD) {
245 SPDLOG_LOGGER_INFO(
246 mSLog, "{}: VD, PV and PQ type component connect -> set as VD bus",
247 node->name());
248 mVDBuses.push_back(node);
249 } else {
250 std::stringstream ss;
251 ss << "Node>>" << node->name()
252 << ": combination of connected components is invalid";
253 throw std::invalid_argument(ss.str());
254 }
255 }
256
258
259 // Snapshot so each solve can reset before Q-limit switching (solver is reused).
262
263 SPDLOG_LOGGER_INFO(mSLog, "#### Create index vectors for power flow solver:");
264 SPDLOG_LOGGER_INFO(mSLog, "PQ Buses: {}", logVector(mPQBusIndices));
265 SPDLOG_LOGGER_INFO(mSLog, "PV Buses: {}", logVector(mPVBusIndices));
266 SPDLOG_LOGGER_INFO(mSLog, "VD Buses: {}", logVector(mVDBusIndices));
267}
268
270 // Rebuild index vectors from the PQ/PV/VD lists (initial + after each Q-limit switch).
271 mPQBusIndices.clear();
272 mPVBusIndices.clear();
273 mVDBusIndices.clear();
274 for (auto node : mPQBuses)
275 mPQBusIndices.push_back(node->matrixNodeIndex());
276 for (auto node : mPVBuses)
277 mPVBusIndices.push_back(node->matrixNodeIndex());
278 for (auto node : mVDBuses)
279 mVDBusIndices.push_back(node->matrixNodeIndex());
280
281 mNumPQBuses = mPQBusIndices.size();
282 mNumPVBuses = mPVBusIndices.size();
283 mNumVDBuses = mVDBusIndices.size();
285
286 // Aggregate PQ bus and PV bus index vectors for easy handling in solver
287 mPQPVBusIndices.clear();
289 mPQPVBusIndices.insert(mPQPVBusIndices.end(), mPQBusIndices.begin(),
290 mPQBusIndices.end());
291 mPQPVBusIndices.insert(mPQPVBusIndices.end(), mPVBusIndices.begin(),
292 mPVBusIndices.end());
293}
294
296 // Reset to the pre-switching classification so a fresh solve starts clean.
301}
302
304 // Re-derive index vectors and resize storage; sol_V/sol_D carry over as a warm start.
307 mX.setZero(mNumUnknowns);
308 mF.setZero(mNumUnknowns);
309}
310
313 if (auto vsi =
314 std::dynamic_pointer_cast<CPS::SP::Ph1::AvVoltageSourceInverterDQ>(
315 comp))
316 return vsi->getBaseVoltage();
317 if (auto rxline = std::dynamic_pointer_cast<CPS::SP::Ph1::RXLine>(comp))
318 return rxline->getBaseVoltage();
319 if (auto line = std::dynamic_pointer_cast<CPS::SP::Ph1::PiLine>(comp))
320 return line->getBaseVoltage();
321 if (auto trans = std::dynamic_pointer_cast<CPS::SP::Ph1::Transformer>(comp)) {
322 if (trans->terminal(0)->node()->name() == node->name())
323 return trans->getNominalVoltagePrimary();
324 if (trans->terminal(1)->node()->name() == node->name())
325 return trans->getNominalVoltageSecondary();
326 return 0;
327 }
328 if (auto gen =
329 std::dynamic_pointer_cast<CPS::SP::Ph1::SynchronGenerator>(comp))
330 return gen->getBaseVoltage();
331 if (auto load = std::dynamic_pointer_cast<CPS::SP::Ph1::Load>(comp))
332 return load->getNomVoltage();
333 if (auto extnet =
334 std::dynamic_pointer_cast<CPS::SP::Ph1::NetworkInjection>(comp))
335 return extnet->getBaseVoltage();
336 if (auto shunt = std::dynamic_pointer_cast<CPS::SP::Ph1::Shunt>(comp))
337 return shunt->getBaseVoltage();
338 SPDLOG_LOGGER_WARN(mSLog, "Unable to get base voltage at {}", node->name());
339 return 0;
340}
341
343
344 SPDLOG_LOGGER_INFO(mSLog, "-- Determine base voltages for each node "
345 "according to connected components");
346 mSLog->flush();
347
348 // Zones: nodes joined by a line share one voltage level; transformers are boundaries.
349 std::vector<UInt> zoneParent(mSystem.mNodes.size());
350 for (UInt i = 0; i < zoneParent.size(); ++i)
351 zoneParent[i] = i;
352 auto findZone = [&](UInt node) -> UInt {
353 while (zoneParent[node] != node) {
354 zoneParent[node] = zoneParent[zoneParent[node]];
355 node = zoneParent[node];
356 }
357 return node;
358 };
359 auto uniteZones = [&](UInt a, UInt b) {
360 zoneParent[findZone(a)] = findZone(b);
361 };
362
363 for (auto comp : mSystem.mComponents) {
364 if (auto line = std::dynamic_pointer_cast<CPS::SP::Ph1::PiLine>(comp))
365 uniteZones(line->node(0)->matrixNodeIndex(),
366 line->node(1)->matrixNodeIndex());
367 else if (auto rxline =
368 std::dynamic_pointer_cast<CPS::SP::Ph1::RXLine>(comp))
369 uniteZones(rxline->node(0)->matrixNodeIndex(),
370 rxline->node(1)->matrixNodeIndex());
371 }
372
373 // Generator/Transformer/NetworkInjection/VSI ratings are authoritative;
374 // everything else (incl. Load's solved-voltage proxy) is a looser fallback.
375 std::map<UInt, std::vector<std::pair<CPS::Real, CPS::String>>> authoritative;
376 std::map<UInt, std::vector<std::pair<CPS::Real, CPS::String>>> fallback;
377 std::map<UInt, std::vector<std::shared_ptr<CPS::SP::Ph1::Load>>> zoneLoads;
378 for (auto node : mSystem.mNodes) {
379 UInt zone = findZone(node->matrixNodeIndex());
380 for (auto comp : mSystem.mComponentsAtNode[node]) {
381 if (auto load = std::dynamic_pointer_cast<CPS::SP::Ph1::Load>(comp))
382 zoneLoads[zone].push_back(load);
383
384 CPS::Real voltage = componentBaseVoltage(comp, node);
385 if (std::abs(voltage) <= 1e-6)
386 continue;
387 bool isAuthoritative =
388 std::dynamic_pointer_cast<CPS::SP::Ph1::SynchronGenerator>(comp) ||
389 std::dynamic_pointer_cast<CPS::SP::Ph1::Transformer>(comp) ||
390 std::dynamic_pointer_cast<CPS::SP::Ph1::NetworkInjection>(comp) ||
391 std::dynamic_pointer_cast<CPS::SP::Ph1::AvVoltageSourceInverterDQ>(
392 comp);
393 auto &bucket = isAuthoritative ? authoritative : fallback;
394 bucket[zone].emplace_back(voltage, comp->name());
395 }
396 }
397
398 // Disagreement beyond tolerance means two voltage levels are wired together without a transformer.
399 auto verify =
400 [&](const std::vector<std::pair<CPS::Real, CPS::String>> &candidates,
401 CPS::Real reference, const CPS::String &refSource,
402 CPS::Real tolerance) {
403 for (auto &candidate : candidates) {
404 CPS::Real relDiff =
405 std::abs(candidate.first - reference) /
406 std::max(std::abs(candidate.first), std::abs(reference));
407 if (relDiff > tolerance) {
408 std::stringstream ss;
409 ss << "Base voltage mismatch within one electrical zone (nodes "
410 "connected without an intervening transformer): "
411 << refSource << " implies " << reference << "V but "
412 << candidate.second << " implies " << candidate.first << "V";
413 throw std::invalid_argument(ss.str());
414 }
415 }
416 };
417
418 std::map<UInt, CPS::Real> zoneVoltage;
419 for (auto &entry : authoritative) {
420 CPS::Real refVoltage = entry.second.front().first;
421 verify(entry.second, refVoltage, entry.second.front().second,
423 zoneVoltage[entry.first] = refVoltage;
424 }
425 // Fallback checked against the zone's rating, or each other if there is none.
426 for (auto &entry : fallback) {
427 auto it = zoneVoltage.find(entry.first);
428 bool hasAuthoritative = it != zoneVoltage.end();
429 CPS::Real reference =
430 hasAuthoritative ? it->second : entry.second.front().first;
431 const CPS::String &refSource = hasAuthoritative
432 ? "the zone's authoritative rating"
433 : entry.second.front().second;
434 verify(entry.second, reference, refSource, mBaseVoltageLooseTolerance);
435 if (!hasAuthoritative)
436 zoneVoltage[entry.first] = reference;
437 }
438
439 // Assign the resolved zone voltage to every node in it.
440 for (auto node : mSystem.mNodes) {
441 auto it = zoneVoltage.find(findZone(node->matrixNodeIndex()));
442 mBaseVoltageAtNode[node] = it != zoneVoltage.end() ? it->second : 0;
443 }
444
445 // Sync each Load's nominal voltage to its zone's resolved value.
446 for (auto &entry : zoneVoltage) {
447 auto it = zoneLoads.find(entry.first);
448 if (it == zoneLoads.end())
449 continue;
450 for (auto &load : it->second) {
451 if (std::abs(load->getNomVoltage() - entry.second) > 1e-6)
452 load->setParameters(load->attributeTyped<CPS::Real>("P")->get(),
453 load->attributeTyped<CPS::Real>("Q")->get(),
454 entry.second);
455 }
456 }
457
458 UInt numMissing = 0;
459 UInt numZero = 0;
460
461 for (auto node : mSystem.mNodes) {
462
463 auto it = mBaseVoltageAtNode.find(node);
464
465 if (it == mBaseVoltageAtNode.end()) {
466 SPDLOG_LOGGER_WARN(mSLog, "No base voltage entry for {}", node->name());
467
468 numMissing++;
469 continue;
470 }
471
472 if (std::abs(it->second) < 1e-6) {
473 SPDLOG_LOGGER_WARN(mSLog, "Zero base voltage for {}", node->name());
474
475 numZero++;
476 }
477 }
478
479 SPDLOG_LOGGER_INFO(mSLog, "Base voltage summary: missing={}, zero={}",
480 numMissing, numZero);
481}
482
484 if (!mExternalGrids.empty()) {
485 if (mExternalGrids[0]->node(0)->name() == name) {
486 mExternalGrids[0]->modifyPowerFlowBusType(CPS::PowerflowBusType::VD);
487 }
488 } else {
489 for (auto gen : mSynchronGenerators) {
490 if (gen->node(0)->name() == name) {
491 gen->modifyPowerFlowBusType(CPS::PowerflowBusType::VD);
492 return;
493 }
494 }
495 throw std::invalid_argument("Invalid slack bus, no external grid or "
496 "synchronous generator attached");
497 }
498}
499
501 CPS::String name, CPS::PowerflowBusType powerFlowBusType) {
502 for (auto comp : mSystem.mComponents) {
503 if (comp->name() == name) {
504 if (std::shared_ptr<CPS::SP::Ph1::NetworkInjection> extnet =
505 std::dynamic_pointer_cast<CPS::SP::Ph1::NetworkInjection>(comp))
506 extnet->modifyPowerFlowBusType(powerFlowBusType);
507 else if (std::shared_ptr<CPS::SP::Ph1::SynchronGenerator> gen =
508 std::dynamic_pointer_cast<CPS::SP::Ph1::SynchronGenerator>(
509 comp))
510 gen->modifyPowerFlowBusType(powerFlowBusType);
511 }
512 }
513}
514
516 mBehaviour = behaviour;
518 SPDLOG_LOGGER_INFO(mSLog, "-- Set solver behaviour to Initialization");
519 // TODO: solver setting specific to initialization (e.g. one single PF run)
520
521 SPDLOG_LOGGER_INFO(mSLog, "-- Set component behaviour to Initialization");
522 for (auto comp : mSystem.mComponents) {
523 auto powerComp =
524 std::dynamic_pointer_cast<CPS::TopologicalPowerComp>(comp);
525 if (powerComp)
526 powerComp->setBehaviour(
528 }
529 } else {
530 SPDLOG_LOGGER_INFO(mSLog, "-- Set solver behaviour to Simulation");
531 // TODO: solver setting specific to simulation
532
533 SPDLOG_LOGGER_INFO(mSLog, "-- Set component behaviour to PFSimulation");
534 for (auto comp : mSystem.mComponents) {
535 auto powerComp =
536 std::dynamic_pointer_cast<CPS::TopologicalPowerComp>(comp);
537 if (powerComp)
538 powerComp->setBehaviour(TopologicalPowerComp::Behaviour::PFSimulation);
539 }
540 }
541}
542
544 int n = mSystem.mNodes.size();
545 if (n > 0) {
547 for (auto line : mLines) {
548 line->pfApplyAdmittanceMatrixStamp(mY);
549 }
550 for (auto trans : mTransformers) {
551 //to check if this transformer could be ignored
552 if (**trans->mResistance == 0 && **trans->mInductance == 0) {
553 SPDLOG_LOGGER_INFO(mSLog, "{} {} ignored for R = 0 and L = 0",
554 trans->type(), trans->name());
555 continue;
556 }
557 trans->pfApplyAdmittanceMatrixStamp(mY);
558 }
559 for (auto shunt : mShunts) {
560 shunt->pfApplyAdmittanceMatrixStamp(mY);
561 }
562 }
563 if (mLines.empty() && mTransformers.empty()) {
564 throw std::invalid_argument("There are no bus");
565 }
566}
567
568CPS::Real PFSolver::G(int i, int j) { return mY.coeff(i, j).real(); }
569
570CPS::Real PFSolver::B(int i, int j) { return mY.coeff(i, j).imag(); }
571
573 // Converged if all mismatches are below the tolerance
574 for (CPS::UInt i = 0; i < mNumUnknowns; i++) {
575 if (!Math::isFinite(mF(i))) {
576 SPDLOG_LOGGER_WARN(mSLog, "mF[{}] not finite (NaN/Inf)", i);
577 return false;
578 }
579 if (abs(mF(i)) > mTolerance)
580 return false;
581 }
582 return true;
583}
584
586
587 // Reset values for new power flow run
588 isConverged = false;
589 mIterations = 0;
590 mX.setZero();
591 mF.setZero();
592
593 // Calculate the mismatch according to the initial solution
595
596 // Check whether model already converged
598
599 for (unsigned i = 1; i < mMaxIterations && !isConverged; ++i) {
600
602
603 // Solve system mJ*mX = mF
605
606 // Calculate new solution based on mX increments obtained from equation system
608
609 // Calculate the mismatch according to the current solution
611
612 SPDLOG_LOGGER_DEBUG(mSLog, "Mismatch vector at iteration {}: \n {}", i, mF);
613 mSLog->flush();
614
615 // Check convergence
617 mIterations = i;
618 }
619 return isConverged;
620}
621
623 Bool converged = runNewtonRaphson();
624
626 return converged;
627
628 // Outer loop: switch PV<->PQ on Q-limit violations, re-solve until no bus switches.
629 Bool settled = false;
630 for (CPS::UInt outer = 0; converged && outer < mMaxOuterIterations; ++outer) {
631 if (!enforceReactiveLimits()) {
632 settled = true;
633 break; // all generators within their reactive limits
634 }
636 converged = runNewtonRaphson();
637 }
638
639 if (converged && !settled) {
640 // Unsettled PV/PQ classification must not look converged to setSolution().
641 SPDLOG_LOGGER_WARN(
642 mSLog,
643 "Q-limit outer loop did not settle within {} iterations; "
644 "PV/PQ classification may still be oscillating",
646 isConverged = false;
647 converged = false;
648 }
649 return converged;
650}
651
652void PFSolver::SolveTask::execute(Real time, Int timeStepCount) {
653 // apply keepLastSolution to save computation time
654 mSolver.generateInitialSolution(time, mSolver.mKeepLastSolution);
655 mSolver.solvePowerflow();
656 mSolver.setSolution();
657}
658
660 return Task::List{std::make_shared<SolveTask>(*this)};
661}
spdlog::level::level_enum Level
Definition Logger.h:33
static bool isFinite(Real value)
Definition MathUtils.cpp:63
std::vector< Ptr > List
Definition Task.h:28
std::shared_ptr< TopologicalNode > Ptr
std::shared_ptr< TopologicalPowerComp > Ptr
void execute(Real time, Int timeStepCount)
Definition PFSolver.cpp:652
void determinePFBusType()
Determine bus type for all buses.
Definition PFSolver.cpp:161
void reclassifyBuses()
Re-derive index vectors and resize the system after PV<->PQ switching.
Definition PFSolver.cpp:303
CPS::Real mBaseApparentPowerFallback
Fallback base apparent power if no generator or transformer rating is found.
Definition PFSolver.h:102
Bool runNewtonRaphson()
Run a single Newton-Raphson solve with the current bus classification.
Definition PFSolver.cpp:585
void resetToOriginalClassification()
Restore the original PV/PQ classification before a fresh solve.
Definition PFSolver.cpp:295
std::vector< std::shared_ptr< CPS::SP::Ph1::Load > > mLoads
Vector of load components.
Definition PFSolver.h:70
CPS::TopologicalNode::List mPQBuses
Vector of nodes characterized as PQ buses.
Definition PFSolver.h:32
UInt mNumPQBuses
Number of PQ nodes.
Definition PFSolver.h:24
virtual void solveJacobianSystem()
Solve the linearized system mJ*mX = mF into mX; sparse subclass overrides.
Definition PFSolver.cpp:76
UInt mNumVDBuses
Number of PV nodes.
Definition PFSolver.h:28
Real mTolerance
Solver tolerance.
Definition PFSolver.h:84
CPS::String logVector(std::vector< CPS::UInt > indexVector)
Logging for integer vectors.
Definition PFSolver.h:168
std::vector< std::shared_ptr< CPS::SP::Ph1::PiLine > > mLines
Vector of line components.
Definition PFSolver.h:72
CPS::Task::List getTasks() override
Get tasks for scheduler.
Definition PFSolver.cpp:659
void assignMatrixNodeIndices()
Assignment of matrix indices for nodes.
Definition PFSolver.cpp:82
std::vector< CPS::UInt > mVDBusIndices
Vector with indices of VD buses.
Definition PFSolver.h:45
virtual void setUpJacobianStorage()
Allocate Jacobian storage; dense by default, sparse subclass overrides.
Definition PFSolver.cpp:72
void initializeComponents()
Initialization of individual components.
Definition PFSolver.cpp:96
std::vector< std::shared_ptr< CPS::SP::Ph1::SynchronGenerator > > mSynchronGenerators
Vector of synchronous generator components.
Definition PFSolver.h:68
CPS::TopologicalNode::List mPVBusesOrig
Definition PFSolver.h:39
void propagateAndVerifyBaseVoltage()
Determine, verify and propagate each node's base voltage per electrical zone.
Definition PFSolver.cpp:342
std::vector< CPS::UInt > mPQBusIndices
Vector with indices of PQ buses.
Definition PFSolver.h:41
void modifyPowerFlowBusComponent(CPS::String name, CPS::PowerflowBusType powerFlowBusType)
Allows to modify the powerflow bus type of a specific component.
Definition PFSolver.cpp:500
std::vector< std::shared_ptr< CPS::SP::Ph1::NetworkInjection > > mExternalGrids
Vector of external grid components.
Definition PFSolver.h:76
std::vector< std::shared_ptr< CPS::SP::Ph1::SolidStateTransformer > > mSolidStateTransformers
Vector of solid state transformer components.
Definition PFSolver.h:65
std::vector< CPS::UInt > mPVBusIndices
Vector with indices of PV buses.
Definition PFSolver.h:43
void setVDNode(CPS::String name)
Set a node to VD using its name.
Definition PFSolver.cpp:483
std::vector< std::shared_ptr< CPS::SP::Ph1::Transformer > > mTransformers
Vector of transformer components.
Definition PFSolver.h:62
std::vector< std::shared_ptr< CPS::SP::Ph1::Shunt > > mShunts
Vector of shunt components.
Definition PFSolver.h:74
CPS::Bool checkConvergence()
Check whether below tolerance.
Definition PFSolver.cpp:572
CPS::Matrix mJ
Jacobian matrix.
Definition PFSolver.h:53
CPS::UInt mMaxIterations
Maximum number of iterations.
Definition PFSolver.h:86
CPS::Real componentBaseVoltage(CPS::TopologicalPowerComp::Ptr comp, CPS::TopologicalNode::Ptr node)
Base voltage a single component reports for node, or 0 if unknown.
Definition PFSolver.cpp:311
CPS::Vector mX
Solution vector.
Definition PFSolver.h:55
std::vector< std::shared_ptr< CPS::SP::Ph1::AvVoltageSourceInverterDQ > > mAverageVoltageSourceInverters
Vector of average voltage source inverters.
Definition PFSolver.h:79
void rebuildBusIndexAggregates()
Rebuild index vectors + counts from the PQ/PV/VD node lists.
Definition PFSolver.cpp:269
CPS::Real mBaseApparentPower
Base power of per-unit system.
Definition PFSolver.h:100
void setBaseApparentPower()
Set apparent base power of per-unit system.
Definition PFSolver.cpp:138
virtual void calculateMismatch()=0
Calculate mismatch.
CPS::SparseMatrixCompRow mY
Admittance matrix.
Definition PFSolver.h:50
UInt mNumPVBuses
Number of PV nodes.
Definition PFSolver.h:26
CPS::Real B(int i, int j)
Gets the imaginary part of admittance matrix element.
Definition PFSolver.cpp:570
void composeAdmittanceMatrix()
Compose admittance matrix.
Definition PFSolver.cpp:543
CPS::TopologicalNode::List mPQBusesOrig
Original PQ/PV classification (snapshot before Q-limit switching)
Definition PFSolver.h:38
CPS::Bool isConverged
Convergence flag.
Definition PFSolver.h:104
CPS::Vector mF
Vector of mismatch values.
Definition PFSolver.h:57
CPS::UInt mIterations
Actual number of iterations.
Definition PFSolver.h:88
CPS::UInt mMaxOuterIterations
Maximum number of Q-limit outer iterations.
Definition PFSolver.h:92
virtual CPS::Bool enforceReactiveLimits()
Switch generators violating their Q limits between PV/PQ; base impl is a no-op.
Definition PFSolver.h:160
PFSolver(CPS::String name, CPS::SystemTopology system, Real timeStep, CPS::Logger::Level logLevel)
Constructor to be used in simulation examples.
Definition PFSolver.cpp:17
CPS::SystemTopology mSystem
System list.
Definition PFSolver.h:60
virtual void calculateJacobian()=0
Calculate the Jacobian.
void setSolverAndComponentBehaviour(Solver::Behaviour behaviour) override
set solver and component to initialization or simulation behaviour
Definition PFSolver.cpp:515
std::map< CPS::TopologicalNode::Ptr, CPS::Real > mBaseVoltageAtNode
Map providing determined base voltages for each node.
Definition PFSolver.h:81
void initialize() override
Initialization of the solver.
Definition PFSolver.cpp:24
CPS::Real mBaseVoltageLooseTolerance
Relative tolerance for non-authoritative (e.g. Load) base-voltage candidates vs. the zone's rating.
Definition PFSolver.h:96
CPS::Real mBaseVoltageStrictTolerance
Relative tolerance between authoritative base-voltage sources (generator/transformer/network-injectio...
Definition PFSolver.h:98
virtual void updateSolution()=0
Update solution in each iteration.
Bool solvePowerflow()
Solves the powerflow problem.
Definition PFSolver.cpp:622
CPS::Bool mEnforceReactiveLimits
Enforce generator reactive-power limits via PV<->PQ outer-loop switching.
Definition PFSolver.h:90
CPS::TopologicalNode::List mVDBuses
Vector of nodes characterized as VD buses.
Definition PFSolver.h:36
CPS::TopologicalNode::List mPVBuses
Vector of nodes characterized as PV buses.
Definition PFSolver.h:34
UInt mNumUnknowns
Number of unknowns, defining system dimension.
Definition PFSolver.h:30
virtual void clearReactiveLimitState()
Clear Q-limit bookkeeping; overridden by PFSolverPowerPolar.
Definition PFSolver.h:142
CPS::Real G(int i, int j)
Gets the real part of admittance matrix element.
Definition PFSolver.cpp:568
std::vector< CPS::UInt > mPQPVBusIndices
Vector with indices of both PQ and PV buses.
Definition PFSolver.h:47
Real mTimeStep
Time step for fixed step solvers.
Definition Solver.h:62
Behaviour mBehaviour
Solver behaviour initialization or simulation.
Definition Solver.h:85
CPS::Logger::Log mSLog
Logger.
Definition Solver.h:60
Solver(String name, CPS::Logger::Level logLevel)
Definition Solver.h:88
Bool mInitFromNodesAndTerminals
Definition Solver.h:77
@ Initialization
Definition Solver.h:40
PowerflowBusType
std::string String
Definition Definitions.h:63
Eigen::SparseMatrix< Complex, Eigen::ColMajor > SparseMatrixComp
Sparse matrix for complex numbers.
Definition Definitions.h:74
double Real
Definition Definitions.h:60
bool Bool
Definition Definitions.h:62
unsigned int UInt
Definition Definitions.h:58
CPS::Real Real
Definition Definitions.h:18
CPS::Int Int
Definition Definitions.h:22
CPS::Bool Bool
Definition Definitions.h:21
CPS::UInt UInt
Definition Definitions.h:23