DPsim
Loading...
Searching...
No Matches
PFSolverPowerPolar.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 <algorithm>
10#include <tuple>
11
12#include <dpsim/PFSolverPowerPolar.h>
13
14using namespace DPsim;
15using namespace CPS;
16
18 const CPS::SystemTopology &system,
19 CPS::Real timeStep,
20 CPS::Logger::Level logLevel)
21 : PFSolver(name, system, timeStep, logLevel) {}
22
24 bool keep_last_solution) {
25 // Undo Q-limit PV<->PQ conversions left over from a previous solve.
28
29 const UInt n = mSystem.mNodes.size();
30
31 bool can_keep = keep_last_solution && mHasLastConvergedSolution &&
32 mLastConvergedV.size() == n && mLastConvergedD.size() == n;
33
34 SPDLOG_LOGGER_INFO(mSLog,
35 "PF initialization: keep_last_solution={}, can_keep={}",
36 keep_last_solution, can_keep);
37
38 resize_sol(n);
40
41 if (can_keep) {
42 sol_V = mLastConvergedV;
43 sol_D = mLastConvergedD;
44 }
45
46 // update components
47 for (auto comp : mSystem.mComponents) {
48 if (auto load = std::dynamic_pointer_cast<CPS::SP::Ph1::Load>(comp)) {
49 if (load->use_profile)
50 load->updatePQ(time);
51
52 load->calculatePerUnitParameters(mBaseApparentPower,
53 mSystem.mSystemOmega);
54 }
55
56 if (auto gen =
57 std::dynamic_pointer_cast<CPS::SP::Ph1::SynchronGenerator>(comp)) {
58 gen->calculatePerUnitParameters(mBaseApparentPower, mSystem.mSystemOmega);
59 }
60 }
61
62 // PQ buses
63 for (auto pq : mPQBuses) {
64 UInt idx = pq->matrixNodeIndex();
65
66 sol_P(idx) = 0.0;
67 sol_Q(idx) = 0.0;
68
69 if (!can_keep) {
70 sol_V(idx) = 1.0;
71 sol_D(idx) = 0.0;
72 }
73
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<
84 sol_P(idx) +=
85 vsi->attributeTyped<CPS::Real>("P_ref")->get() / mBaseApparentPower;
86 sol_Q(idx) +=
87 vsi->attributeTyped<CPS::Real>("Q_ref")->get() / mBaseApparentPower;
88 } else if (auto gen =
89 std::dynamic_pointer_cast<CPS::SP::Ph1::SynchronGenerator>(
90 comp)) {
91 sol_P(idx) += gen->attributeTyped<CPS::Real>("P_set_pu")->get();
92 sol_Q(idx) += gen->attributeTyped<CPS::Real>("Q_set_pu")->get();
93 }
94 }
95
96 sol_S_complex(idx) = CPS::Complex(sol_P(idx), sol_Q(idx));
97 sol_V_complex(idx) = Math::polar(sol_V(idx), sol_D(idx));
98 }
99
100 // PV buses
101 for (auto pv : mPVBuses) {
102 UInt idx = pv->matrixNodeIndex();
103
104 sol_P(idx) = 0.0;
105 sol_Q(idx) = 0.0;
106
107 if (!can_keep) {
108 sol_D(idx) = 0.0;
109 sol_V(idx) = 1.0;
110 }
111
112 for (auto comp : mSystem.mComponentsAtNode[pv]) {
113 if (auto gen = std::dynamic_pointer_cast<CPS::SP::Ph1::SynchronGenerator>(
114 comp)) {
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<
123 sol_P(idx) +=
124 vsi->attributeTyped<CPS::Real>("P_ref")->get() / mBaseApparentPower;
125 } else if (auto extnet =
126 std::dynamic_pointer_cast<CPS::SP::Ph1::NetworkInjection>(
127 comp)) {
128 sol_P(idx) += extnet->attributeTyped<CPS::Real>("p_inj")->get() /
130 sol_V(idx) = extnet->attributeTyped<CPS::Real>("V_set_pu")->get();
131 }
132 }
133
134 sol_S_complex(idx) = CPS::Complex(sol_P(idx), sol_Q(idx));
135 sol_V_complex(idx) = Math::polar(sol_V(idx), sol_D(idx));
136 }
137
138 // VD / slack buses
139 for (auto vd : mVDBuses) {
140 UInt idx = vd->matrixNodeIndex();
141
142 sol_P(idx) = 0.0;
143 sol_Q(idx) = 0.0;
144 sol_D(idx) = 0.0;
145 sol_V(idx) = 1.0;
146
147 for (auto comp : mSystem.mComponentsAtNode[vd]) {
148 if (auto extnet =
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>(
157 comp)) {
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();
161 }
162 }
163
164 sol_S_complex(idx) = CPS::Complex(sol_P(idx), sol_Q(idx));
165 sol_V_complex(idx) = Math::polar(sol_V(idx), sol_D(idx));
166 }
167
168 solutionInitialized = true;
170
171 Pesp = sol_P;
172 Qesp = sol_Q;
173}
174
176 UInt npqpv = mNumPQBuses + mNumPVBuses;
177 UInt k;
178 mF.setZero();
179
180 for (UInt a = 0; a < npqpv; ++a) {
181 // For PQ and PV buses calculate active power mismatch
182 k = mPQPVBusIndices[a];
183 mF(a) = Pesp.coeff(k) - P(k);
184
185 //only for PQ buses calculate reactive power mismatch
186 if (a < mNumPQBuses)
187 mF(a + npqpv) = Qesp.coeff(k) - Q(k);
188 }
189}
190
192 UInt npqpv = mNumPQBuses + mNumPVBuses;
193 double val;
194 UInt k, j;
195 UInt da, db;
196
197 mJ.setZero();
198
199 //J1
200 for (UInt a = 0; a < npqpv; ++a) { //rows
201 k = mPQPVBusIndices[a];
202 //diagonal
203 mJ.coeffRef(a, a) = -Q(k) - B(k, k) * sol_V.coeff(k) * sol_V.coeff(k);
204
205 //non diagonal elements
206 for (UInt b = 0; b < npqpv; ++b) {
207 if (b != a) {
208 j = mPQPVBusIndices[b];
209 val = sol_V.coeff(k) * sol_V.coeff(j) *
210 (G(k, j) * sin(sol_D.coeff(k) - sol_D.coeff(j)) -
211 B(k, j) * cos(sol_D.coeff(k) - sol_D.coeff(j)));
212 //if (val != 0.0)
213 mJ.coeffRef(a, b) = val;
214 }
215 }
216 }
217
218 //J2
219 da = 0;
220 db = npqpv;
221 for (UInt a = 0; a < npqpv; ++a) { //rows
222 k = mPQPVBusIndices[a];
223 //diagonal
224 //std::cout << "J2D:" << (a + da) << "," << (a + db) << std::endl;
225 if (a < mNumPQBuses)
226 mJ.coeffRef(a + da, a + db) =
227 P(k) + G(k, k) * sol_V.coeff(k) * sol_V.coeff(k);
228
229 //non diagonal elements
230 for (UInt b = 0; b < mNumPQBuses; ++b) {
231 if (b != a) {
232 j = mPQPVBusIndices[b];
233 val = sol_V.coeff(k) * sol_V.coeff(j) *
234 (G(k, j) * cos(sol_D.coeff(k) - sol_D.coeff(j)) +
235 B(k, j) * sin(sol_D.coeff(k) - sol_D.coeff(j)));
236 //if (val != 0.0)
237 //std::cout << "J2ij:" << (a + da) << "," << (b + db) << std::endl;
238 mJ.coeffRef(a + da, b + db) = val;
239 }
240 }
241 }
242
243 //J3
244 da = npqpv;
245 db = 0;
246 for (UInt a = 0; a < mNumPQBuses; ++a) { //rows
247 k = mPQPVBusIndices[a];
248 //diagonal
249 //std::cout << "J3:" << (a + da) << "," << (a + db) << std::endl;
250 mJ.coeffRef(a + da, a + db) =
251 P(k) - G(k, k) * sol_V.coeff(k) * sol_V.coeff(k);
252
253 //non diagonal elements
254 for (UInt b = 0; b < npqpv; ++b) {
255 if (b != a) {
256 j = mPQPVBusIndices[b];
257 val = sol_V.coeff(k) * sol_V.coeff(j) *
258 (G(k, j) * cos(sol_D.coeff(k) - sol_D.coeff(j)) +
259 B(k, j) * sin(sol_D.coeff(k) - sol_D.coeff(j)));
260 //if (val != 0.0)
261 //std::cout << "J3:" << (a + da) << "," << (b + db) << std::endl;
262 mJ.coeffRef(a + da, b + db) = -val;
263 }
264 }
265 }
266
267 //J4
268 da = npqpv;
269 db = npqpv;
270 for (UInt a = 0; a < mNumPQBuses; ++a) { //rows
271 k = mPQPVBusIndices[a];
272 //diagonal
273 //std::cout << "J4:" << (a + da) << "," << (a + db) << std::endl;
274 mJ.coeffRef(a + da, a + db) =
275 Q(k) - B(k, k) * sol_V.coeff(k) * sol_V.coeff(k);
276
277 //non diagonal elements
278 for (UInt b = 0; b < mNumPQBuses; ++b) {
279 if (b != a) {
280 j = mPQPVBusIndices[b];
281 val = sol_V.coeff(k) * sol_V.coeff(j) *
282 (G(k, j) * sin(sol_D.coeff(k) - sol_D.coeff(j)) -
283 B(k, j) * cos(sol_D.coeff(k) - sol_D.coeff(j)));
284 if (val != 0.0) {
285 //std::cout << "J4:" << (a + da) << "," << (b + db) << std::endl;
286 mJ.coeffRef(a + da, b + db) = val;
287 }
288 }
289 }
290 }
291}
292
294 UInt npqpv = mNumPQBuses + mNumPVBuses;
295
296 // Scale the whole step by one factor to bound the max change without altering direction.
297 const double maxDVpu = 0.1; // max |dV| per step [pu]
298 const double maxDThetaRad = 0.2; // max |dTheta| per step [rad]
299
300 // mX: [0,npqpv) angle incr (PQ+PV), then rel. voltage dV/V (PQ only).
301 double scale = 1.0;
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);
306 }
307 for (UInt b = 0; b < mNumPQBuses; ++b) {
308 double dVrel = std::abs(mX.coeff(npqpv + b));
309 if (dVrel > maxDVpu)
310 scale = std::min(scale, maxDVpu / dVrel);
311 }
312
313 for (UInt a = 0; a < npqpv; ++a) {
314 UInt k = mPQPVBusIndices[a];
315 sol_D(k) += scale * mX.coeff(a);
316 // additive-relative update, consistent with the Jacobian
317 if (a < mNumPQBuses)
318 sol_V(k) *= (1.0 + scale * mX.coeff(a + npqpv));
319 }
320
321 for (auto node : mSystem.mNodes) {
322 UInt idx = node->matrixNodeIndex();
323 sol_V_complex(idx) = Math::polar(sol_V(idx), sol_D(idx));
324 }
325}
326
328
329 if (!isConverged) {
330 SPDLOG_LOGGER_WARN(mSLog, "Not converged within {} iterations",
332
333 SPDLOG_LOGGER_WARN(mSLog, "Writing last iterate to result state "
334 "(not stored as warm-start solution).");
335 } else {
339
340 mLastConvergedV = sol_V;
341 mLastConvergedD = sol_D;
342 mHasLastConvergedSolution = true;
343
344 SPDLOG_LOGGER_INFO(mSLog, "Converged in {} iterations", mIterations);
345 }
346
347 SPDLOG_LOGGER_INFO(mSLog, "Solution written to result state:");
348
349 SPDLOG_LOGGER_INFO(mSLog, "Name\tP\t\tQ\t\tV\t\tD");
350
351 for (auto node : mSystem.mNodes) {
352 UInt idx = node->matrixNodeIndex();
353
354 SPDLOG_LOGGER_INFO(
355 mSLog, "{}\t{}\t{}\t{}\t{}",
356 std::dynamic_pointer_cast<CPS::SimNode<CPS::Complex>>(node)->name(),
357 sol_P[idx], sol_Q[idx], sol_V[idx], sol_D[idx]);
358 }
359
360 mSLog->flush();
361
362 for (UInt i = 0; i < mSystem.mNodes.size(); ++i) {
363 sol_S_complex(i) = CPS::Complex(sol_P.coeff(i), sol_Q.coeff(i));
364 sol_V_complex(i) = Math::polar(sol_V.coeff(i), sol_D.coeff(i));
365 }
366
367 for (auto node : mSystem.mNodes) {
368 auto simNode = std::dynamic_pointer_cast<CPS::SimNode<CPS::Complex>>(node);
369
370 UInt idx = node->matrixNodeIndex();
371
372 simNode->setVoltage(sol_V_complex(idx) * mBaseVoltageAtNode[node]);
373
374 simNode->setPower(sol_S_complex(idx) * mBaseApparentPower);
375 }
376
379}
380
382 for (auto line : mLines) {
383 VectorComp v(2);
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);
391 }
392 for (auto trafo : mTransformers) {
393 VectorComp v(2);
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);
401 }
402}
403
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);
412 break;
413 }
414 }
415 if (lines.empty()) {
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(
420 sol_S_complex.coeff(node->matrixNodeIndex()));
421 break;
422 }
423 }
424 }
425 }
426}
427
429 Real val = 0.0;
430 for (UInt j = 0; j < mSystem.mNodes.size(); ++j) {
431 val += sol_V.coeff(j) * (G(k, j) * cos(sol_D.coeff(k) - sol_D.coeff(j)) +
432 B(k, j) * sin(sol_D.coeff(k) - sol_D.coeff(j)));
433 }
434 return sol_V.coeff(k) * val;
435}
436
438 Real val = 0.0;
439 for (UInt j = 0; j < mSystem.mNodes.size(); ++j) {
440 val += sol_V.coeff(j) * (G(k, j) * sin(sol_D.coeff(k) - sol_D.coeff(j)) -
441 B(k, j) * cos(sol_D.coeff(k) - sol_D.coeff(j)));
442 }
443 return sol_V.coeff(k) * val;
444}
445
447 for (auto topoNode : mVDBuses) {
448 auto node_idx = topoNode->matrixNodeIndex();
449
450 // Net nodal injection into the network: S_inj = V * conj(YV)
451 CPS::Complex I(0.0, 0.0);
452 for (UInt j = 0; j < mSystem.mNodes.size(); ++j)
453 I += mY.coeff(node_idx, j) * sol_Vcx(j);
454
455 CPS::Complex S = sol_Vcx(node_idx) * conj(I);
456
457 // Generator/source power: S_gen = S_inj + S_load
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));
463 }
464 }
465
466 // Update connected VD source/generator with actual generated power
467 for (auto comp : mSystem.mComponentsAtNode[topoNode]) {
468 if (auto extnetPtr =
469 std::dynamic_pointer_cast<CPS::SP::Ph1::NetworkInjection>(comp)) {
470 extnetPtr->updatePowerInjection(Sgen * mBaseApparentPower);
471 break;
472 }
473
474 if (auto sgPtr =
475 std::dynamic_pointer_cast<CPS::SP::Ph1::SynchronGenerator>(
476 comp)) {
477 sgPtr->updatePowerInjection(Sgen * mBaseApparentPower);
478 break;
479 }
480 }
481
482 // Store net nodal injection, not generator power
483 sol_P(node_idx) = S.real();
484 sol_Q(node_idx) = S.imag();
485 }
486}
488 for (auto topoNode : mPVBuses) {
489 auto node_idx = topoNode->matrixNodeIndex();
490
491 // Net nodal injection into the network: S_inj = V * conj(YV)
492 CPS::Complex I(0.0, 0.0);
493 for (UInt j = 0; j < mSystem.mNodes.size(); ++j)
494 I += mY.coeff(node_idx, j) * sol_Vcx(j);
495
496 CPS::Complex S = sol_Vcx(node_idx) * conj(I);
497
498 // Generator power: S_gen = S_inj + S_load
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));
504 }
505 }
506
507 // Update PV generator with actual generator Q
508 for (auto comp : mSystem.mComponentsAtNode[topoNode]) {
509 if (auto sgPtr =
510 std::dynamic_pointer_cast<CPS::SP::Ph1::SynchronGenerator>(
511 comp)) {
512 sgPtr->updateReactivePowerInjection(Sgen * mBaseApparentPower);
513 break;
514 }
515 }
516
517 // Store net nodal Q injection, not generator Q
518 sol_Q(node_idx) = S.imag();
519 }
520}
521
522CPS::Real
523PFSolverPowerPolar::loadReactivePowerPerUnit(CPS::TopologicalNode::Ptr node) {
524 CPS::Real q = 0.0;
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();
528 return q;
529}
530
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)
536 I += mY.coeff(k, j) * sol_Vcx(j);
537 // Net nodal injection S = generator - load; add load back for generator Q.
538 CPS::Complex S = sol_Vcx(k) * conj(I);
539 return S.imag() + loadReactivePowerPerUnit(node);
540}
541
543 // Returns false if the bus has no generator or no finite Q limit.
544 auto busLimits = [&](CPS::TopologicalNode::Ptr node, CPS::Real &qMaxPU,
545 CPS::Real &qMinPU, CPS::Real &vSetPU) -> bool {
546 qMaxPU = 0.0;
547 qMinPU = 0.0;
548 vSetPU = 0.0;
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>(
552 comp)) {
553 CPS::Real gMax = gen->attributeTyped<CPS::Real>("Q_max_pu")->get();
554 CPS::Real gMin = gen->attributeTyped<CPS::Real>("Q_min_pu")->get();
555 qMaxPU += gMax;
556 qMinPU += gMin;
557 vSetPU = gen->attributeTyped<CPS::Real>("V_set_pu")->get();
558 hasGen = true;
559 // isFinite is bit-pattern based, so it's immune to -Ofast/-ffast-math.
560 if (CPS::Math::isFinite(gMax) || CPS::Math::isFinite(gMin))
561 anyFinite = true;
562 }
563 }
564 return hasGen && anyFinite;
565 };
566
567 auto frozen = [&](CPS::TopologicalNode::Ptr node) {
568 auto it = mQLimitSwitchCount.find(node);
569 return it != mQLimitSwitchCount.end() &&
570 it->second >= mMaxQLimitSwitchesPerBus;
571 };
572
573 std::vector<std::tuple<CPS::TopologicalNode::Ptr, bool, CPS::Real>> toPQ;
574 std::vector<CPS::TopologicalNode::Ptr> toPV;
575
576 // PV -> PQ: voltage-controlling generators that hit a reactive limit.
577 for (auto node : mPVBuses) {
578 if (frozen(node))
579 continue;
580 CPS::Real qMaxPU, qMinPU, vSetPU;
581 if (!busLimits(node, qMaxPU, qMinPU, vSetPU))
582 continue;
583 CPS::Real qGen = generatorReactivePowerPerUnit(node);
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);
588 }
589
590 // PQ -> PV: pinned generators whose constraint is no longer binding.
591 for (auto &kv : mQLimitConvertedAtMax) {
592 auto node = kv.first;
593 bool atMax = kv.second;
594 if (frozen(node))
595 continue;
596 CPS::Real qMaxPU, qMinPU, vSetPU;
597 if (!busLimits(node, qMaxPU, qMinPU, vSetPU))
598 continue;
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);
604 }
605
606 // Apply PV -> PQ switches (pin reactive injection at the limit).
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);
611 mPVBuses.erase(std::remove(mPVBuses.begin(), mPVBuses.end(), node),
612 mPVBuses.end());
613 mPQBuses.push_back(node);
614 UInt idx = node->matrixNodeIndex();
615 Qesp(idx) = qLimPU - loadReactivePowerPerUnit(node);
616 sol_Q(idx) = Qesp(idx);
617 mQLimitConvertedAtMax[node] = atMax;
619 SPDLOG_LOGGER_WARN(
620 mSLog, "Q-limit: bus {} frozen at {} after {} switches", node->name(),
621 atMax ? "Qmax" : "Qmin", mQLimitSwitchCount[node]);
622 else
623 SPDLOG_LOGGER_INFO(mSLog, "Q-limit: PV bus {} -> PQ pinned at {}",
624 node->name(), atMax ? "Qmax" : "Qmin");
625 }
626
627 // Apply PQ -> PV switches (restore voltage control).
628 for (auto &node : toPV) {
629 mPQBuses.erase(std::remove(mPQBuses.begin(), mPQBuses.end(), node),
630 mPQBuses.end());
631 mPVBuses.push_back(node);
632 CPS::Real qMaxPU, qMinPU, vSetPU;
633 busLimits(node, qMaxPU, qMinPU, vSetPU);
634 sol_V(node->matrixNodeIndex()) = vSetPU;
635 mQLimitConvertedAtMax.erase(node);
636 ++mQLimitSwitchCount[node];
637 SPDLOG_LOGGER_INFO(mSLog, "Q-limit: PQ bus {} -> PV (constraint relaxed)",
638 node->name());
639 }
640
641 return !toPQ.empty() || !toPV.empty();
642}
643
648
650 // calculates apparent power injection at PQ buses flowing to other nodes (i.e. S_inj_to_other = S_inj - S_shunt, with S_inj = S_gen - S_load)
651 for (auto topoNode : mPQBuses) {
652 auto node_idx = topoNode->matrixNodeIndex();
653
654 // calculate power flowing out of the node into the admittance matrix (i.e. S_inj)
655 CPS::Complex I(0.0, 0.0);
656 for (UInt j = 0; j < mSystem.mNodes.size(); ++j)
657 I += mY.coeff(node_idx, j) * sol_Vcx(j);
658 CPS::Complex S = sol_Vcx(node_idx) * conj(I);
659
660 // Subtracting shunt power to obtain power injection flowing from this node to the other nodes (i.e. S_inj_to_other)
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))
664 // capacitive susceptance is positive --> q is injected into the node
665 S += std::pow(V, 2) * Complex(-**(shuntPtr->mConductancePerUnit),
666 **(shuntPtr->mSusceptancePerUnit));
667
668 // TODO: check whether S_inj_to_other should be stored in sol_P and sol_Q or rather S_inj
669 sol_P(node_idx) = S.real();
670 sol_Q(node_idx) = S.imag();
671 }
672}
673
675 sol_P = CPS::Vector(n);
676 sol_Q = CPS::Vector(n);
677 sol_V = CPS::Vector(n);
678 sol_D = CPS::Vector(n);
679 sol_P.setZero(n);
680 sol_Q.setZero(n);
681 sol_V.setZero(n);
682 sol_D.setZero(n);
683}
684
686 sol_S_complex = CPS::VectorComp(n);
687 sol_V_complex = CPS::VectorComp(n);
688 sol_S_complex.setZero(n);
689 sol_V_complex.setZero(n);
690}
691
692CPS::Real PFSolverPowerPolar::sol_Vr(UInt k) {
693 return sol_V.coeff(k) * cos(sol_D.coeff(k));
694}
695
696CPS::Real PFSolverPowerPolar::sol_Vi(UInt k) {
697 return sol_V.coeff(k) * sin(sol_D.coeff(k));
698}
699
700CPS::Complex PFSolverPowerPolar::sol_Vcx(UInt k) {
701 return CPS::Complex(sol_Vr(k), sol_Vi(k));
702}
void resetToOriginalClassification()
Restore the original PV/PQ classification before a fresh solve.
Definition PFSolver.cpp:286
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
std::vector< std::shared_ptr< CPS::SP::Ph1::PiLine > > mLines
Vector of line components.
Definition PFSolver.h:72
std::vector< std::shared_ptr< CPS::SP::Ph1::Transformer > > mTransformers
Vector of transformer components.
Definition PFSolver.h:62
CPS::Matrix mJ
Jacobian matrix.
Definition PFSolver.h:53
CPS::Vector mX
Solution vector.
Definition PFSolver.h:55
CPS::Bool solutionInitialized
Flag whether solution vectors are initialized.
Definition PFSolver.h:106
CPS::Real mBaseApparentPower
Base power of per-unit system.
Definition PFSolver.h:100
CPS::SparseMatrixCompRow mY
Admittance matrix.
Definition PFSolver.h:50
CPS::UInt mMaxQLimitSwitchesPerBus
Maximum number of PV<->PQ switches per bus before it is frozen (anti-oscillation)
Definition PFSolver.h:94
CPS::Bool solutionComplexInitialized
Flag whether complex solution vectors are initialized.
Definition PFSolver.h:108
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
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
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
std::map< CPS::TopologicalNode::Ptr, CPS::Real > mBaseVoltageAtNode
Map providing determined base voltages for each node.
Definition PFSolver.h:81
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
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
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.
Definition Solver.h:45