12#include <dpsim/PFSolverPowerPolar.h>
20 CPS::Logger::Level logLevel)
21 :
PFSolver(name, system, timeStep, logLevel) {}
24 bool keep_last_solution) {
29 const UInt n =
mSystem.mNodes.size();
31 bool can_keep = keep_last_solution && mHasLastConvergedSolution &&
32 mLastConvergedV.size() == n && mLastConvergedD.size() == n;
34 SPDLOG_LOGGER_INFO(
mSLog,
35 "PF initialization: keep_last_solution={}, can_keep={}",
36 keep_last_solution, can_keep);
42 sol_V = mLastConvergedV;
43 sol_D = mLastConvergedD;
47 for (
auto comp :
mSystem.mComponents) {
48 if (
auto load = std::dynamic_pointer_cast<CPS::SP::Ph1::Load>(comp)) {
49 if (load->use_profile)
57 std::dynamic_pointer_cast<CPS::SP::Ph1::SynchronGenerator>(comp)) {
64 UInt idx = pq->matrixNodeIndex();
74 for (
auto comp :
mSystem.mComponentsAtNode[pq]) {
75 if (
auto load = std::dynamic_pointer_cast<CPS::SP::Ph1::Load>(comp)) {
76 sol_P(idx) -= load->attributeTyped<CPS::Real>(
"P_pu")->get();
77 sol_Q(idx) -= load->attributeTyped<CPS::Real>(
"Q_pu")->get();
78 }
else if (
auto sst = std::dynamic_pointer_cast<
80 sol_P(idx) -= sst->getNodalInjection(pq).real();
81 sol_Q(idx) -= sst->getNodalInjection(pq).imag();
82 }
else if (
auto vsi = std::dynamic_pointer_cast<
89 std::dynamic_pointer_cast<CPS::SP::Ph1::SynchronGenerator>(
91 sol_P(idx) += gen->attributeTyped<CPS::Real>(
"P_set_pu")->get();
92 sol_Q(idx) += gen->attributeTyped<CPS::Real>(
"Q_set_pu")->get();
102 UInt idx = pv->matrixNodeIndex();
112 for (
auto comp :
mSystem.mComponentsAtNode[pv]) {
113 if (
auto gen = std::dynamic_pointer_cast<CPS::SP::Ph1::SynchronGenerator>(
115 sol_P(idx) += gen->attributeTyped<CPS::Real>(
"P_set_pu")->get();
116 sol_V(idx) = gen->attributeTyped<CPS::Real>(
"V_set_pu")->get();
117 }
else if (
auto load =
118 std::dynamic_pointer_cast<CPS::SP::Ph1::Load>(comp)) {
119 sol_P(idx) -= load->attributeTyped<CPS::Real>(
"P_pu")->get();
120 sol_Q(idx) -= load->attributeTyped<CPS::Real>(
"Q_pu")->get();
121 }
else if (
auto vsi = std::dynamic_pointer_cast<
125 }
else if (
auto extnet =
126 std::dynamic_pointer_cast<CPS::SP::Ph1::NetworkInjection>(
128 sol_P(idx) += extnet->attributeTyped<CPS::Real>(
"p_inj")->get() /
130 sol_V(idx) = extnet->attributeTyped<CPS::Real>(
"V_set_pu")->get();
140 UInt idx = vd->matrixNodeIndex();
147 for (
auto comp :
mSystem.mComponentsAtNode[vd]) {
149 std::dynamic_pointer_cast<CPS::SP::Ph1::NetworkInjection>(comp)) {
150 sol_V(idx) = extnet->attributeTyped<CPS::Real>(
"V_set_pu")->get();
151 }
else if (
auto load =
152 std::dynamic_pointer_cast<CPS::SP::Ph1::Load>(comp)) {
153 sol_P(idx) -= load->attributeTyped<CPS::Real>(
"P_pu")->get();
154 sol_Q(idx) -= load->attributeTyped<CPS::Real>(
"Q_pu")->get();
155 }
else if (
auto gen =
156 std::dynamic_pointer_cast<CPS::SP::Ph1::SynchronGenerator>(
158 sol_P(idx) += gen->attributeTyped<CPS::Real>(
"P_set_pu")->get();
159 sol_Q(idx) += gen->attributeTyped<CPS::Real>(
"Q_set_pu")->get();
160 sol_V(idx) = gen->attributeTyped<CPS::Real>(
"V_set_pu")->get();
180 for (UInt a = 0; a < npqpv; ++a) {
183 mF(a) = Pesp.coeff(k) -
P(k);
187 mF(a + npqpv) = Qesp.coeff(k) -
Q(k);
200 for (UInt a = 0; a < npqpv; ++a) {
203 mJ.coeffRef(a, a) = -
Q(k) -
B(k, k) *
sol_V.coeff(k) *
sol_V.coeff(k);
206 for (UInt b = 0; b < npqpv; ++b) {
213 mJ.coeffRef(a, b) = val;
221 for (UInt a = 0; a < npqpv; ++a) {
226 mJ.coeffRef(a + da, a + db) =
238 mJ.coeffRef(a + da, b + db) = val;
250 mJ.coeffRef(a + da, a + db) =
254 for (UInt b = 0; b < npqpv; ++b) {
262 mJ.coeffRef(a + da, b + db) = -val;
274 mJ.coeffRef(a + da, a + db) =
286 mJ.coeffRef(a + da, b + db) = val;
297 const double maxDVpu = 0.1;
298 const double maxDThetaRad = 0.2;
302 for (UInt a = 0; a < npqpv; ++a) {
303 double dTheta = std::abs(
mX.coeff(a));
304 if (dTheta > maxDThetaRad)
305 scale = std::min(scale, maxDThetaRad / dTheta);
308 double dVrel = std::abs(
mX.coeff(npqpv + b));
310 scale = std::min(scale, maxDVpu / dVrel);
313 for (UInt a = 0; a < npqpv; ++a) {
315 sol_D(k) += scale *
mX.coeff(a);
318 sol_V(k) *= (1.0 + scale *
mX.coeff(a + npqpv));
321 for (
auto node :
mSystem.mNodes) {
322 UInt idx = node->matrixNodeIndex();
330 SPDLOG_LOGGER_WARN(
mSLog,
"Not converged within {} iterations",
333 SPDLOG_LOGGER_WARN(
mSLog,
"Writing last iterate to result state "
334 "(not stored as warm-start solution).");
340 mLastConvergedV =
sol_V;
341 mLastConvergedD =
sol_D;
342 mHasLastConvergedSolution =
true;
347 SPDLOG_LOGGER_INFO(
mSLog,
"Solution written to result state:");
349 SPDLOG_LOGGER_INFO(
mSLog,
"Name\tP\t\tQ\t\tV\t\tD");
351 for (
auto node :
mSystem.mNodes) {
352 UInt idx = node->matrixNodeIndex();
355 mSLog,
"{}\t{}\t{}\t{}\t{}",
362 for (UInt i = 0; i <
mSystem.mNodes.size(); ++i) {
367 for (
auto node :
mSystem.mNodes) {
368 auto simNode = std::dynamic_pointer_cast<CPS::SimNode<CPS::Complex>>(node);
370 UInt idx = node->matrixNodeIndex();
382 for (
auto line :
mLines) {
384 v(0) =
sol_V_complex.coeff(line->node(0)->matrixNodeIndex());
385 v(1) =
sol_V_complex.coeff(line->node(1)->matrixNodeIndex());
387 VectorComp current = line->Y_element() * v;
389 VectorComp flow_on_branch = v.array() * current.conjugate().array();
390 line->updateBranchFlow(current, flow_on_branch);
394 v(0) =
sol_V_complex.coeff(trafo->node(0)->matrixNodeIndex());
395 v(1) =
sol_V_complex.coeff(trafo->node(1)->matrixNodeIndex());
397 VectorComp current = trafo->Y_element() * v;
399 VectorComp flow_on_branch = v.array() * current.conjugate().array();
400 trafo->updateBranchFlow(current, flow_on_branch);
405 for (
auto node :
mSystem.mNodes) {
406 std::list<std::shared_ptr<CPS::SP::Ph1::PiLine>> lines;
407 for (
auto comp :
mSystem.mComponentsAtNode[node]) {
408 if (std::shared_ptr<CPS::SP::Ph1::PiLine> line =
409 std::dynamic_pointer_cast<CPS::SP::Ph1::PiLine>(comp)) {
410 line->storeNodalInjection(
sol_S_complex.coeff(node->matrixNodeIndex()));
411 lines.push_back(line);
416 for (
auto comp :
mSystem.mComponentsAtNode[node]) {
417 if (std::shared_ptr<CPS::SP::Ph1::Transformer> trafo =
418 std::dynamic_pointer_cast<CPS::SP::Ph1::Transformer>(comp)) {
419 trafo->storeNodalInjection(
430 for (UInt j = 0; j <
mSystem.mNodes.size(); ++j) {
434 return sol_V.coeff(k) * val;
439 for (UInt j = 0; j <
mSystem.mNodes.size(); ++j) {
443 return sol_V.coeff(k) * val;
448 auto node_idx = topoNode->matrixNodeIndex();
451 CPS::Complex I(0.0, 0.0);
452 for (UInt j = 0; j <
mSystem.mNodes.size(); ++j)
455 CPS::Complex S =
sol_Vcx(node_idx) * conj(I);
458 CPS::Complex Sgen = S;
459 for (
auto comp :
mSystem.mComponentsAtNode[topoNode]) {
460 if (
auto loadPtr = std::dynamic_pointer_cast<CPS::SP::Ph1::Load>(comp)) {
461 Sgen += CPS::Complex(**(loadPtr->mActivePowerPerUnit),
462 **(loadPtr->mReactivePowerPerUnit));
467 for (
auto comp :
mSystem.mComponentsAtNode[topoNode]) {
469 std::dynamic_pointer_cast<CPS::SP::Ph1::NetworkInjection>(comp)) {
475 std::dynamic_pointer_cast<CPS::SP::Ph1::SynchronGenerator>(
483 sol_P(node_idx) = S.real();
484 sol_Q(node_idx) = S.imag();
489 auto node_idx = topoNode->matrixNodeIndex();
492 CPS::Complex I(0.0, 0.0);
493 for (UInt j = 0; j <
mSystem.mNodes.size(); ++j)
496 CPS::Complex S =
sol_Vcx(node_idx) * conj(I);
499 CPS::Complex Sgen = S;
500 for (
auto comp :
mSystem.mComponentsAtNode[topoNode]) {
501 if (
auto loadPtr = std::dynamic_pointer_cast<CPS::SP::Ph1::Load>(comp)) {
502 Sgen += CPS::Complex(**(loadPtr->mActivePowerPerUnit),
503 **(loadPtr->mReactivePowerPerUnit));
508 for (
auto comp :
mSystem.mComponentsAtNode[topoNode]) {
510 std::dynamic_pointer_cast<CPS::SP::Ph1::SynchronGenerator>(
518 sol_Q(node_idx) = S.imag();
525 for (
auto comp :
mSystem.mComponentsAtNode[node])
526 if (
auto load = std::dynamic_pointer_cast<CPS::SP::Ph1::Load>(comp))
527 q += load->attributeTyped<CPS::Real>(
"Q_pu")->get();
532 CPS::TopologicalNode::Ptr node) {
533 UInt k = node->matrixNodeIndex();
534 CPS::Complex I(0.0, 0.0);
535 for (UInt j = 0; j <
mSystem.mNodes.size(); ++j)
538 CPS::Complex S =
sol_Vcx(k) * conj(I);
544 auto busLimits = [&](CPS::TopologicalNode::Ptr node, CPS::Real &qMaxPU,
545 CPS::Real &qMinPU, CPS::Real &vSetPU) ->
bool {
549 bool hasGen =
false, anyFinite =
false;
550 for (
auto comp :
mSystem.mComponentsAtNode[node]) {
551 if (
auto gen = std::dynamic_pointer_cast<CPS::SP::Ph1::SynchronGenerator>(
553 CPS::Real gMax = gen->attributeTyped<CPS::Real>(
"Q_max_pu")->get();
554 CPS::Real gMin = gen->attributeTyped<CPS::Real>(
"Q_min_pu")->get();
557 vSetPU = gen->attributeTyped<CPS::Real>(
"V_set_pu")->get();
560 if (CPS::Math::isFinite(gMax) || CPS::Math::isFinite(gMin))
564 return hasGen && anyFinite;
567 auto frozen = [&](CPS::TopologicalNode::Ptr node) {
573 std::vector<std::tuple<CPS::TopologicalNode::Ptr, bool, CPS::Real>> toPQ;
574 std::vector<CPS::TopologicalNode::Ptr> toPV;
580 CPS::Real qMaxPU, qMinPU, vSetPU;
581 if (!busLimits(node, qMaxPU, qMinPU, vSetPU))
584 if (CPS::Math::isFinite(qMaxPU) && qGen > qMaxPU)
585 toPQ.emplace_back(node,
true, qMaxPU);
586 else if (CPS::Math::isFinite(qMinPU) && qGen < qMinPU)
587 toPQ.emplace_back(node,
false, qMinPU);
592 auto node = kv.first;
593 bool atMax = kv.second;
596 CPS::Real qMaxPU, qMinPU, vSetPU;
597 if (!busLimits(node, qMaxPU, qMinPU, vSetPU))
599 CPS::Real v =
sol_V(node->matrixNodeIndex());
600 if (atMax && v > vSetPU)
601 toPV.push_back(node);
602 else if (!atMax && v < vSetPU)
603 toPV.push_back(node);
607 for (
auto &c : toPQ) {
608 auto node = std::get<0>(c);
609 bool atMax = std::get<1>(c);
610 CPS::Real qLimPU = std::get<2>(c);
614 UInt idx = node->matrixNodeIndex();
616 sol_Q(idx) = Qesp(idx);
620 mSLog,
"Q-limit: bus {} frozen at {} after {} switches", node->name(),
623 SPDLOG_LOGGER_INFO(
mSLog,
"Q-limit: PV bus {} -> PQ pinned at {}",
624 node->name(), atMax ?
"Qmax" :
"Qmin");
628 for (
auto &node : toPV) {
632 CPS::Real qMaxPU, qMinPU, vSetPU;
633 busLimits(node, qMaxPU, qMinPU, vSetPU);
634 sol_V(node->matrixNodeIndex()) = vSetPU;
637 SPDLOG_LOGGER_INFO(
mSLog,
"Q-limit: PQ bus {} -> PV (constraint relaxed)",
641 return !toPQ.empty() || !toPV.empty();
652 auto node_idx = topoNode->matrixNodeIndex();
655 CPS::Complex I(0.0, 0.0);
656 for (UInt j = 0; j <
mSystem.mNodes.size(); ++j)
658 CPS::Complex S =
sol_Vcx(node_idx) * conj(I);
661 CPS::Real V =
sol_V.coeff(node_idx);
662 for (
auto comp :
mSystem.mComponentsAtNode[topoNode])
663 if (
auto shuntPtr = std::dynamic_pointer_cast<CPS::SP::Ph1::Shunt>(comp))
665 S += std::pow(V, 2) * Complex(-**(shuntPtr->mConductancePerUnit),
666 **(shuntPtr->mSusceptancePerUnit));
669 sol_P(node_idx) = S.real();
670 sol_Q(node_idx) = S.imag();
675 sol_P = CPS::Vector(n);
676 sol_Q = CPS::Vector(n);
677 sol_V = CPS::Vector(n);
678 sol_D = CPS::Vector(n);
void resetToOriginalClassification()
Restore the original PV/PQ classification before a fresh solve.
CPS::TopologicalNode::List mPQBuses
Vector of nodes characterized as PQ buses.
UInt mNumPQBuses
Number of PQ nodes.
std::vector< std::shared_ptr< CPS::SP::Ph1::PiLine > > mLines
Vector of line components.
std::vector< std::shared_ptr< CPS::SP::Ph1::Transformer > > mTransformers
Vector of transformer components.
CPS::Matrix mJ
Jacobian matrix.
CPS::Vector mX
Solution vector.
CPS::Bool solutionInitialized
Flag whether solution vectors are initialized.
CPS::Real mBaseApparentPower
Base power of per-unit system.
CPS::SparseMatrixCompRow mY
Admittance matrix.
CPS::UInt mMaxQLimitSwitchesPerBus
Maximum number of PV<->PQ switches per bus before it is frozen (anti-oscillation)
CPS::Bool solutionComplexInitialized
Flag whether complex solution vectors are initialized.
UInt mNumPVBuses
Number of PV nodes.
CPS::Real B(int i, int j)
Gets the imaginary part of admittance matrix element.
CPS::Bool isConverged
Convergence flag.
CPS::Vector mF
Vector of mismatch values.
CPS::UInt mIterations
Actual number of iterations.
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.
std::map< CPS::TopologicalNode::Ptr, CPS::Real > mBaseVoltageAtNode
Map providing determined base voltages for each node.
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.
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.
CPS::Vector sol_P
Solution vector of active power.
void clearReactiveLimitState() override
Clear the Q-limit conversion bookkeeping between solves.
void resize_complex_sol(CPS::Int n)
Resize complex solution vector.
void calculateBranchFlow()
Calculate branch flows from current solution and store them in line and transformer components.
CPS::Real generatorReactivePowerPerUnit(CPS::TopologicalNode::Ptr node)
Generator reactive-power injection at a bus [pu], used by the Q-limit check.
void calculateQAtPVBuses()
Calculate the reactive power at all PV buses from current solution.
CPS::VectorComp sol_S_complex
Solution vector of representing sol_P and sol_Q as complex quantity.
PFSolverPowerPolar(CPS::String name, const CPS::SystemTopology &system, CPS::Real timeStep, CPS::Logger::Level logLevel)
Constructor to be used in simulation examples.
CPS::Real loadReactivePowerPerUnit(CPS::TopologicalNode::Ptr node)
Total local load reactive power at a bus [pu].
CPS::Vector sol_D
Solution vector of voltage angle.
CPS::Real sol_Vi(CPS::UInt k)
Calculate imaginary part of voltage from sol_V and sol_D.
std::map< CPS::TopologicalNode::Ptr, CPS::UInt > mQLimitSwitchCount
Total PV<->PQ switches per bus over the solve (anti-oscillation cap)
void calculatePAndQAtSlackBus()
Calculate P and Q at slack bus from current solution.
CPS::Vector sol_Q
Solution vector of reactive power.
CPS::Real Q(CPS::UInt k)
Calculate the reactive power at a bus from current solution.
void calculateMismatch() override
Calculate mismatch.
CPS::Bool enforceReactiveLimits() override
Q-limit PV<->PQ switching pass (overrides the base no-op)
CPS::Complex sol_Vcx(CPS::UInt k)
Calculate complex voltage from sol_V and sol_D.
void generateInitialSolution(Real time, bool keep_last_solution=false) override
Generate initial solution for current time step.
std::map< CPS::TopologicalNode::Ptr, bool > mQLimitConvertedAtMax
Buses switched PV->PQ by the Q-limit loop -> pinned at Qmax (true) or Qmin (false)
void calculateJacobian() override
Calculate the Jacobian.
void updateSolution() override
Update solution in each iteration.
void resize_sol(CPS::Int n)
Resize solution vector.
CPS::Real sol_Vr(CPS::UInt k)
Calculate real part of voltage from sol_V and sol_D.
CPS::Real P(CPS::UInt k)
Calculate active power at a bus from current solution.
void calculateNodalInjection()
Calculate nodal power injections and store them in first line or transformer (in case no line is conn...
CPS::Vector sol_V
Solution vector of voltage magnitude.
void calculatePAndQInjectionPQBuses()
Calculate complex power flowing from this node to the other nodes.
CPS::VectorComp sol_V_complex
Solution vector of representing sol_V and sol_D as complex quantity.
void setSolution() override
Set final solution.
CPS::Logger::Log mSLog
Logger.