21 :
PFSolver(name, system, timeStep, logLevel) {}
24 bool keep_last_solution) {
34 SPDLOG_LOGGER_INFO(
mSLog,
35 "PF initialization: keep_last_solution={}, can_keep={}",
36 keep_last_solution, can_keep);
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)) {
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>(
102 UInt idx = pv->matrixNodeIndex();
112 for (
auto comp :
mSystem.mComponentsAtNode[pv]) {
113 if (
auto gen = std::dynamic_pointer_cast<CPS::SP::Ph1::SynchronGenerator>(
117 }
else if (
auto load =
118 std::dynamic_pointer_cast<CPS::SP::Ph1::Load>(comp)) {
121 }
else if (
auto vsi = std::dynamic_pointer_cast<
125 }
else if (
auto extnet =
126 std::dynamic_pointer_cast<CPS::SP::Ph1::NetworkInjection>(
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)) {
155 }
else if (
auto gen =
156 std::dynamic_pointer_cast<CPS::SP::Ph1::SynchronGenerator>(
180 for (
UInt a = 0; a < npqpv; ++a) {
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).");
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{}",
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());
391 line->updateBranchFlow(current, flow_on_branch);
395 v(0) =
sol_V_complex.coeff(trafo->node(0)->matrixNodeIndex());
396 v(1) =
sol_V_complex.coeff(trafo->node(1)->matrixNodeIndex());
402 trafo->updateBranchFlow(current, flow_on_branch);
407 for (
auto node :
mSystem.mNodes) {
408 std::list<std::shared_ptr<CPS::SP::Ph1::PiLine>> lines;
409 for (
auto comp :
mSystem.mComponentsAtNode[node]) {
410 if (std::shared_ptr<CPS::SP::Ph1::PiLine> line =
411 std::dynamic_pointer_cast<CPS::SP::Ph1::PiLine>(comp)) {
412 line->storeNodalInjection(
sol_S_complex.coeff(node->matrixNodeIndex()));
413 lines.push_back(line);
418 for (
auto comp :
mSystem.mComponentsAtNode[node]) {
419 if (std::shared_ptr<CPS::SP::Ph1::Transformer> trafo =
420 std::dynamic_pointer_cast<CPS::SP::Ph1::Transformer>(comp)) {
421 trafo->storeNodalInjection(
436 return sol_V.coeff(k) * val;
445 return sol_V.coeff(k) * val;
450 auto node_idx = topoNode->matrixNodeIndex();
461 for (
auto comp :
mSystem.mComponentsAtNode[topoNode]) {
462 if (
auto loadPtr = std::dynamic_pointer_cast<CPS::SP::Ph1::Load>(comp)) {
464 **(loadPtr->mReactivePowerPerUnit));
469 for (
auto comp :
mSystem.mComponentsAtNode[topoNode]) {
471 std::dynamic_pointer_cast<CPS::SP::Ph1::NetworkInjection>(comp)) {
477 std::dynamic_pointer_cast<CPS::SP::Ph1::SynchronGenerator>(
485 sol_P(node_idx) = S.real();
486 sol_Q(node_idx) = S.imag();
491 auto node_idx = topoNode->matrixNodeIndex();
502 for (
auto comp :
mSystem.mComponentsAtNode[topoNode]) {
503 if (
auto loadPtr = std::dynamic_pointer_cast<CPS::SP::Ph1::Load>(comp)) {
505 **(loadPtr->mReactivePowerPerUnit));
510 for (
auto comp :
mSystem.mComponentsAtNode[topoNode]) {
512 std::dynamic_pointer_cast<CPS::SP::Ph1::SynchronGenerator>(
520 sol_Q(node_idx) = S.imag();
527 for (
auto comp :
mSystem.mComponentsAtNode[node])
528 if (
auto load = std::dynamic_pointer_cast<CPS::SP::Ph1::Load>(comp))
529 q += load->attributeTyped<
CPS::Real>(
"Q_pu")->get();
535 UInt k = node->matrixNodeIndex();
551 bool hasGen =
false, anyFinite =
false;
552 for (
auto comp :
mSystem.mComponentsAtNode[node]) {
553 if (
auto gen = std::dynamic_pointer_cast<CPS::SP::Ph1::SynchronGenerator>(
559 vSetPU = gen->attributeTyped<
CPS::Real>(
"V_set_pu")->get();
566 return hasGen && anyFinite;
575 std::vector<std::tuple<CPS::TopologicalNode::Ptr, bool, CPS::Real>> toPQ;
576 std::vector<CPS::TopologicalNode::Ptr> toPV;
583 if (!busLimits(node, qMaxPU, qMinPU, vSetPU))
587 toPQ.emplace_back(node,
true, qMaxPU);
589 toPQ.emplace_back(node,
false, qMinPU);
594 auto node = kv.first;
595 bool atMax = kv.second;
599 if (!busLimits(node, qMaxPU, qMinPU, vSetPU))
602 if (atMax && v > vSetPU)
603 toPV.push_back(node);
604 else if (!atMax && v < vSetPU)
605 toPV.push_back(node);
609 for (
auto &c : toPQ) {
610 auto node = std::get<0>(c);
611 bool atMax = std::get<1>(c);
616 UInt idx = node->matrixNodeIndex();
622 mSLog,
"Q-limit: bus {} frozen at {} after {} switches", node->name(),
625 SPDLOG_LOGGER_INFO(
mSLog,
"Q-limit: PV bus {} -> PQ pinned at {}",
626 node->name(), atMax ?
"Qmax" :
"Qmin");
630 for (
auto &node : toPV) {
635 busLimits(node, qMaxPU, qMinPU, vSetPU);
636 sol_V(node->matrixNodeIndex()) = vSetPU;
639 SPDLOG_LOGGER_INFO(
mSLog,
"Q-limit: PQ bus {} -> PV (constraint relaxed)",
643 return !toPQ.empty() || !toPV.empty();
654 auto node_idx = topoNode->matrixNodeIndex();
664 for (
auto comp :
mSystem.mComponentsAtNode[topoNode])
665 if (
auto shuntPtr = std::dynamic_pointer_cast<CPS::SP::Ph1::Shunt>(comp))
667 S += std::pow(V, 2) *
Complex(-**(shuntPtr->mConductancePerUnit),
668 **(shuntPtr->mSusceptancePerUnit));
671 sol_P(node_idx) = S.real();
672 sol_Q(node_idx) = S.imag();
spdlog::level::level_enum Level
static Complex polar(Real abs, Real phase)
static auto elementwiseProduct(const Eigen::MatrixBase< DerivedA > &a, const Eigen::MatrixBase< DerivedB > &b)
Elementwise product of two same-shaped Eigen expressions.
static bool isFinite(Real value)
std::shared_ptr< TopologicalNode > Ptr
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::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 mLastConvergedV
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.
bool mHasLastConvergedSolution
CPS::Real Q(CPS::UInt k)
Calculate the reactive power at a bus from current solution.
CPS::Vector mLastConvergedD
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.
Eigen::Matrix< Complex, Eigen::Dynamic, 1 > VectorComp
Dense vector for complex numbers.
Eigen::Matrix< Real, Eigen::Dynamic, 1 > Vector
Dense vector for real numbers.
std::complex< Real > Complex