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