9#include <dpsim-models/MathUtils.h>
10#include <dpsim/PFSolver.h>
11#include <dpsim/SequentialScheduler.h>
18 CPS::Real timeStep, CPS::Logger::Level logLevel)
19 : Solver(name +
"_PF", logLevel) {
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))
30 else if (std::shared_ptr<CPS::SP::Ph1::Load> load =
31 std::dynamic_pointer_cast<CPS::SP::Ph1::Load>(comp))
33 else if (std::shared_ptr<CPS::SP::Ph1::Transformer> trafo =
34 std::dynamic_pointer_cast<CPS::SP::Ph1::Transformer>(comp))
36 else if (std::shared_ptr<CPS::SP::Ph1::PiLine> line =
37 std::dynamic_pointer_cast<CPS::SP::Ph1::PiLine>(comp))
39 else if (std::shared_ptr<CPS::SP::Ph1::NetworkInjection> extnet =
40 std::dynamic_pointer_cast<CPS::SP::Ph1::NetworkInjection>(
43 else if (std::shared_ptr<CPS::SP::Ph1::Shunt> shunt =
44 std::dynamic_pointer_cast<CPS::SP::Ph1::Shunt>(comp))
46 else if (std::shared_ptr<CPS::SP::Ph1::SolidStateTransformer> sst =
47 std::dynamic_pointer_cast<CPS::SP::Ph1::SolidStateTransformer>(
50 else if (std::shared_ptr<CPS::SP::Ph1::AvVoltageSourceInverterDQ> vsi =
51 std::dynamic_pointer_cast<
74 auto sparseJ =
mJ.sparseView();
75 Eigen::SparseLU<SparseMatrix> lu(sparseJ);
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 {}",
86 mSystem.mNodes[idx]->matrixNodeIndex());
89 SPDLOG_LOGGER_INFO(
mSLog,
"Number of simulation nodes: {:d}",
94 for (
auto comp :
mSystem.mComponents) {
95 std::dynamic_pointer_cast<SimPowerComp<Complex>>(comp)
96 ->updateMatrixNodeIndices();
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);
106 pComp->initializeFromNodesAndTerminals(
mSystem.mSystemFrequency);
109 SPDLOG_LOGGER_INFO(
mSLog,
110 "-- Calculate per unit parameters for all components");
115 for (
auto line :
mLines) {
124 for (
auto load :
mLoads) {
139 if (std::abs(gen->attributeTyped<Real>(
"P_set")->get()) > maxPower)
140 maxPower = std::abs(gen->attributeTyped<Real>(
"P_set")->get());
143 if (trafo->attributeTyped<Real>(
"S")->get() > maxPower)
144 maxPower = trafo->attributeTyped<Real>(
"S")->get();
150 SPDLOG_LOGGER_WARN(
mSLog,
151 "No suitable quantity found for setting "
152 "mBaseApparentPower. Using {} VA.",
163 SPDLOG_LOGGER_INFO(
mSLog,
"-- Determine powerflow bus type for each node");
166 for (
auto node :
mSystem.mNodes) {
167 bool connectedPV =
false;
168 bool connectedPQ =
false;
169 bool connectedVD =
false;
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) {
177 }
else if (std::shared_ptr<CPS::SP::Ph1::SynchronGenerator> gen =
178 std::dynamic_pointer_cast<CPS::SP::Ph1::SynchronGenerator>(
180 if (gen->mPowerflowBusType == CPS::PowerflowBusType::PV) {
182 }
else if (gen->mPowerflowBusType == CPS::PowerflowBusType::VD) {
184 }
else if (gen->mPowerflowBusType == CPS::PowerflowBusType::PQ) {
187 }
else if (std::shared_ptr<CPS::SP::Ph1::NetworkInjection> extnet =
188 std::dynamic_pointer_cast<CPS::SP::Ph1::NetworkInjection>(
190 if (extnet->mPowerflowBusType == CPS::PowerflowBusType::VD) {
192 }
else if (extnet->mPowerflowBusType == CPS::PowerflowBusType::PV) {
200 if (!connectedPV && connectedPQ && !connectedVD) {
202 mSLog,
"{}: only PQ type component connected -> set as PQ bus",
206 else if (!connectedPV && !connectedPQ && !connectedVD) {
207 SPDLOG_LOGGER_INFO(
mSLog,
"{}: no component connected -> set as PQ bus",
211 else if (connectedPV && !connectedPQ && !connectedVD) {
213 mSLog,
"{}: only PV type component connected -> set as PV bus",
217 else if (connectedPV && connectedPQ && !connectedVD) {
219 mSLog,
"{}: PV and PQ type component connected -> set as PV bus",
223 else if (!connectedPV && !connectedPQ && connectedVD) {
225 mSLog,
"{}: only VD type component connected -> set as VD bus",
229 else if (connectedPV && !connectedPQ && connectedVD) {
231 mSLog,
"{}: VD and PV type component connect -> set as VD bus",
235 else if (connectedPV && connectedPQ && connectedVD) {
237 mSLog,
"{}: VD, PV and PQ type component connect -> set as VD bus",
241 std::stringstream ss;
242 ss <<
"Node>>" << node->name()
243 <<
": combination of connected components is invalid";
244 throw std::invalid_argument(ss.str());
254 SPDLOG_LOGGER_INFO(
mSLog,
"#### Create index vectors for power flow solver:");
303 CPS::TopologicalNode::Ptr node) {
305 std::dynamic_pointer_cast<CPS::SP::Ph1::AvVoltageSourceInverterDQ>(
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();
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();
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());
335 SPDLOG_LOGGER_INFO(
mSLog,
"-- Determine base voltages for each node "
336 "according to connected components");
340 std::vector<UInt> zoneParent(
mSystem.mNodes.size());
341 for (UInt i = 0; i < zoneParent.size(); ++i)
343 auto findZone = [&](UInt node) -> UInt {
344 while (zoneParent[node] != node) {
345 zoneParent[node] = zoneParent[zoneParent[node]];
346 node = zoneParent[node];
350 auto uniteZones = [&](UInt a, UInt b) {
351 zoneParent[findZone(a)] = findZone(b);
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());
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);
376 if (std::abs(voltage) <= 1e-6)
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>(
384 auto &bucket = isAuthoritative ? authoritative : fallback;
385 bucket[zone].emplace_back(voltage, comp->name());
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) {
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());
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;
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;
426 if (!hasAuthoritative)
427 zoneVoltage[entry.first] = reference;
431 for (
auto node :
mSystem.mNodes) {
432 auto it = zoneVoltage.find(findZone(node->matrixNodeIndex()));
437 for (
auto &entry : zoneVoltage) {
438 auto it = zoneLoads.find(entry.first);
439 if (it == zoneLoads.end())
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(),
452 for (
auto node :
mSystem.mNodes) {
457 SPDLOG_LOGGER_WARN(
mSLog,
"No base voltage entry for {}", node->name());
463 if (std::abs(it->second) < 1e-6) {
464 SPDLOG_LOGGER_WARN(
mSLog,
"Zero base voltage for {}", node->name());
470 SPDLOG_LOGGER_INFO(
mSLog,
"Base voltage summary: missing={}, zero={}",
471 numMissing, numZero);
477 mExternalGrids[0]->modifyPowerFlowBusType(CPS::PowerflowBusType::VD);
481 if (gen->node(0)->name() == name) {
482 gen->modifyPowerFlowBusType(CPS::PowerflowBusType::VD);
486 throw std::invalid_argument(
"Invalid slack bus, no external grid or "
487 "synchronous generator attached");
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>(
501 gen->modifyPowerFlowBusType(powerFlowBusType);
508 if (
mBehaviour == Behaviour::Initialization) {
509 SPDLOG_LOGGER_INFO(
mSLog,
"-- Set solver behaviour to Initialization");
512 SPDLOG_LOGGER_INFO(
mSLog,
"-- Set component behaviour to Initialization");
513 for (
auto comp :
mSystem.mComponents) {
515 std::dynamic_pointer_cast<CPS::TopologicalPowerComp>(comp);
517 powerComp->setBehaviour(
518 TopologicalPowerComp::Behaviour::Initialization);
521 SPDLOG_LOGGER_INFO(
mSLog,
"-- Set solver behaviour to Simulation");
524 SPDLOG_LOGGER_INFO(
mSLog,
"-- Set component behaviour to PFSimulation");
525 for (
auto comp :
mSystem.mComponents) {
527 std::dynamic_pointer_cast<CPS::TopologicalPowerComp>(comp);
529 powerComp->setBehaviour(TopologicalPowerComp::Behaviour::PFSimulation);
537 mY = CPS::SparseMatrixComp(n, n);
538 for (
auto line :
mLines) {
539 line->pfApplyAdmittanceMatrixStamp(
mY);
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());
548 trans->pfApplyAdmittanceMatrixStamp(
mY);
551 shunt->pfApplyAdmittanceMatrixStamp(
mY);
555 throw std::invalid_argument(
"There are no bus");
566 if (!Math::isFinite(
mF(i))) {
567 SPDLOG_LOGGER_WARN(
mSLog,
"mF[{}] not finite (NaN/Inf)", i);
603 SPDLOG_LOGGER_DEBUG(
mSLog,
"Mismatch vector at iteration {}: \n {}", i,
mF);
620 Bool settled =
false;
630 if (converged && !settled) {
634 "Q-limit outer loop did not settle within {} iterations; "
635 "PV/PQ classification may still be oscillating",
643void PFSolver::SolveTask::execute(Real time, Int timeStepCount) {
651 return Task::List{std::make_shared<SolveTask>(*
this)};
void determinePFBusType()
Determine bus type for all buses.
void reclassifyBuses()
Re-derive index vectors and resize the system after PV<->PQ switching.
CPS::Real mBaseApparentPowerFallback
Fallback base apparent power if no generator or transformer rating is found.
Bool runNewtonRaphson()
Run a single Newton-Raphson solve with the current bus classification.
void resetToOriginalClassification()
Restore the original PV/PQ classification before a fresh solve.
std::vector< std::shared_ptr< CPS::SP::Ph1::Load > > mLoads
Vector of load components.
CPS::Bool mKeepLastSolution
Use last converged solution as initial guess.
CPS::TopologicalNode::List mPQBuses
Vector of nodes characterized as PQ buses.
UInt mNumPQBuses
Number of PQ nodes.
virtual void solveJacobianSystem()
Solve the linearized system mJ*mX = mF into mX; sparse subclass overrides.
UInt mNumVDBuses
Number of PV nodes.
Real mTolerance
Solver tolerance.
CPS::String logVector(std::vector< CPS::UInt > indexVector)
Logging for integer vectors.
std::vector< std::shared_ptr< CPS::SP::Ph1::PiLine > > mLines
Vector of line components.
CPS::Task::List getTasks() override
Get tasks for scheduler.
void assignMatrixNodeIndices()
Assignment of matrix indices for nodes.
std::vector< CPS::UInt > mVDBusIndices
Vector with indices of VD buses.
virtual void setUpJacobianStorage()
Allocate Jacobian storage; dense by default, sparse subclass overrides.
void initializeComponents()
Initialization of individual components.
std::vector< std::shared_ptr< CPS::SP::Ph1::SynchronGenerator > > mSynchronGenerators
Vector of synchronous generator components.
void propagateAndVerifyBaseVoltage()
Determine, verify and propagate each node's base voltage per electrical zone.
std::vector< CPS::UInt > mPQBusIndices
Vector with indices of PQ buses.
void modifyPowerFlowBusComponent(CPS::String name, CPS::PowerflowBusType powerFlowBusType)
Allows to modify the powerflow bus type of a specific component.
std::vector< std::shared_ptr< CPS::SP::Ph1::NetworkInjection > > mExternalGrids
Vector of external grid components.
std::vector< std::shared_ptr< CPS::SP::Ph1::SolidStateTransformer > > mSolidStateTransformers
Vector of solid state transformer components.
std::vector< CPS::UInt > mPVBusIndices
Vector with indices of PV buses.
void setVDNode(CPS::String name)
Set a node to VD using its name.
std::vector< std::shared_ptr< CPS::SP::Ph1::Transformer > > mTransformers
Vector of transformer components.
std::vector< std::shared_ptr< CPS::SP::Ph1::Shunt > > mShunts
Vector of shunt components.
CPS::Bool checkConvergence()
Check whether below tolerance.
CPS::Matrix mJ
Jacobian matrix.
CPS::UInt mMaxIterations
Maximum number of iterations.
CPS::Real componentBaseVoltage(CPS::TopologicalPowerComp::Ptr comp, CPS::TopologicalNode::Ptr node)
Base voltage a single component reports for node, or 0 if unknown.
CPS::Vector mX
Solution vector.
std::vector< std::shared_ptr< CPS::SP::Ph1::AvVoltageSourceInverterDQ > > mAverageVoltageSourceInverters
Vector of average voltage source inverters.
void rebuildBusIndexAggregates()
Rebuild index vectors + counts from the PQ/PV/VD node lists.
CPS::Real mBaseApparentPower
Base power of per-unit system.
void setBaseApparentPower()
Set apparent base power of per-unit system.
virtual void calculateMismatch()=0
Calculate mismatch.
CPS::SparseMatrixCompRow mY
Admittance matrix.
UInt mNumPVBuses
Number of PV nodes.
CPS::Real B(int i, int j)
Gets the imaginary part of admittance matrix element.
void composeAdmittanceMatrix()
Compose admittance matrix.
CPS::TopologicalNode::List mPQBusesOrig
Original PQ/PV classification (snapshot before Q-limit switching)
CPS::Bool isConverged
Convergence flag.
CPS::Vector mF
Vector of mismatch values.
CPS::UInt mIterations
Actual number of iterations.
CPS::UInt mMaxOuterIterations
Maximum number of Q-limit outer iterations.
virtual CPS::Bool enforceReactiveLimits()
Switch generators violating their Q limits between PV/PQ; base impl is a no-op.
PFSolver(CPS::String name, CPS::SystemTopology system, Real timeStep, CPS::Logger::Level logLevel)
Constructor to be used in simulation examples.
CPS::SystemTopology mSystem
System list.
virtual void calculateJacobian()=0
Calculate the Jacobian.
void setSolverAndComponentBehaviour(Solver::Behaviour behaviour) override
set solver and component to initialization or simulation behaviour
std::map< CPS::TopologicalNode::Ptr, CPS::Real > mBaseVoltageAtNode
Map providing determined base voltages for each node.
virtual void setSolution()=0
Set final solution.
void initialize() override
Initialization of the solver.
CPS::Real mBaseVoltageLooseTolerance
Relative tolerance for non-authoritative (e.g. Load) base-voltage candidates vs. the zone's rating.
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...
virtual void updateSolution()=0
Update solution in each iteration.
Bool solvePowerflow()
Solves the powerflow problem.
CPS::Bool mEnforceReactiveLimits
Enforce generator reactive-power limits via PV<->PQ outer-loop switching.
CPS::TopologicalNode::List mVDBuses
Vector of nodes characterized as VD buses.
CPS::TopologicalNode::List mPVBuses
Vector of nodes characterized as PV buses.
UInt mNumUnknowns
Number of unknowns, defining system dimension.
virtual void clearReactiveLimitState()
Clear Q-limit bookkeeping; overridden by PFSolverPowerPolar.
CPS::Real G(int i, int j)
Gets the real part of admittance matrix element.
std::vector< CPS::UInt > mPQPVBusIndices
Vector with indices of both PQ and PV buses.
Real mTimeStep
Time step for fixed step solvers.
Behaviour mBehaviour
Solver behaviour initialization or simulation.
CPS::Logger::Log mSLog
Logger.
Bool mInitFromNodesAndTerminals