DPsim
Loading...
Searching...
No Matches
PFSolverPowerPolarSparse.cpp
1// SPDX-FileCopyrightText: 2026 Institute for Automation of Complex Power Systems, EONERC, RWTH Aachen University
2// SPDX-License-Identifier: MPL-2.0
3
4#include <dpsim/PFSolverPowerPolarSparse.h>
5
6#if defined(WITH_KLU)
7#include <dpsim/KLUAdapter.h>
8#elif defined(WITH_SPARSE)
9#include <dpsim/SparseLUAdapter.h>
10#else
11#include <dpsim/DenseLUAdapter.h>
12#endif
13
14using namespace DPsim;
15using namespace CPS;
16
18 CPS::String name, const CPS::SystemTopology &system, CPS::Real timeStep,
19 CPS::Logger::Level logLevel)
20 : PFSolverPowerPolar(name, system, timeStep, logLevel) {}
21
23 return G(k, j) != 0.0 || B(k, j) != 0.0;
24}
25
27 UInt npqpv = mNumPQBuses + mNumPVBuses;
28 std::vector<Eigen::Triplet<Real>> triplets;
29
30 // Pattern mirrors the bus admittance sparsity; off-diagonal entries exist only
31 // between connected buses. Blocks: J1 dTheta/dTheta, J2 dTheta/dV, J3 dQ/dTheta,
32 // J4 dQ/dV (V increments only for PQ buses).
33 for (UInt a = 0; a < npqpv; ++a) {
34 UInt k = mPQPVBusIndices[a];
35 triplets.emplace_back(a, a, 0.0); // J1 diagonal
36 if (a < mNumPQBuses)
37 triplets.emplace_back(a, a + npqpv, 0.0); // J2 diagonal
38 for (UInt b = 0; b < npqpv; ++b) { // J1 off-diagonal
39 if (b != a && isConnected(k, mPQPVBusIndices[b]))
40 triplets.emplace_back(a, b, 0.0);
41 }
42 for (UInt b = 0; b < mNumPQBuses; ++b) { // J2 off-diagonal
43 if (b != a && isConnected(k, mPQPVBusIndices[b]))
44 triplets.emplace_back(a, b + npqpv, 0.0);
45 }
46 }
47 for (UInt a = 0; a < mNumPQBuses; ++a) {
48 UInt k = mPQPVBusIndices[a];
49 triplets.emplace_back(a + npqpv, a, 0.0); // J3 diagonal
50 triplets.emplace_back(a + npqpv, a + npqpv, 0.0); // J4 diagonal
51 for (UInt b = 0; b < npqpv; ++b) { // J3 off-diagonal
52 if (b != a && isConnected(k, mPQPVBusIndices[b]))
53 triplets.emplace_back(a + npqpv, b, 0.0);
54 }
55 for (UInt b = 0; b < mNumPQBuses; ++b) { // J4 off-diagonal
56 if (b != a && isConnected(k, mPQPVBusIndices[b]))
57 triplets.emplace_back(a + npqpv, b + npqpv, 0.0);
58 }
59 }
60
62 mJsparse.setFromTriplets(triplets.begin(), triplets.end());
63 mJsparse.makeCompressed();
64}
65
67 if (mNumUnknowns == 0)
68 return;
69
71
72#if defined(WITH_KLU)
73 mLinearSolver = std::make_shared<KLUAdapter>(mSLog);
74#elif defined(WITH_SPARSE)
75 mLinearSolver = std::make_shared<SparseLUAdapter>(mSLog);
76#else
77 mLinearSolver = std::make_shared<DenseLUAdapter>(mSLog);
78#endif
79
80 // Analyze the fixed pattern once; values are refactorized each iteration.
82}
83
85 UInt npqpv = mNumPQBuses + mNumPVBuses;
86 double val;
87 UInt k, j;
88
89 // Pattern is fixed; clear values and refill the existing entries in place.
90 mJsparse.coeffs().setZero();
91
92 // J1: dP/dTheta
93 for (UInt a = 0; a < npqpv; ++a) {
94 k = mPQPVBusIndices[a];
95 mJsparse.coeffRef(a, a) = -Q(k) - B(k, k) * sol_V.coeff(k) * sol_V.coeff(k);
96 for (UInt b = 0; b < npqpv; ++b) {
97 if (b == a)
98 continue;
99 j = mPQPVBusIndices[b];
100 if (!isConnected(k, j))
101 continue;
102 val = sol_V.coeff(k) * sol_V.coeff(j) *
103 (G(k, j) * sin(sol_D.coeff(k) - sol_D.coeff(j)) -
104 B(k, j) * cos(sol_D.coeff(k) - sol_D.coeff(j)));
105 mJsparse.coeffRef(a, b) = val;
106 }
107 }
108
109 // J2: dP/dV
110 for (UInt a = 0; a < npqpv; ++a) {
111 k = mPQPVBusIndices[a];
112 if (a < mNumPQBuses)
113 mJsparse.coeffRef(a, a + npqpv) =
114 P(k) + G(k, k) * sol_V.coeff(k) * sol_V.coeff(k);
115 for (UInt b = 0; b < mNumPQBuses; ++b) {
116 if (b == a)
117 continue;
118 j = mPQPVBusIndices[b];
119 if (!isConnected(k, j))
120 continue;
121 val = sol_V.coeff(k) * sol_V.coeff(j) *
122 (G(k, j) * cos(sol_D.coeff(k) - sol_D.coeff(j)) +
123 B(k, j) * sin(sol_D.coeff(k) - sol_D.coeff(j)));
124 mJsparse.coeffRef(a, b + npqpv) = val;
125 }
126 }
127
128 // J3: dQ/dTheta
129 for (UInt a = 0; a < mNumPQBuses; ++a) {
130 k = mPQPVBusIndices[a];
131 mJsparse.coeffRef(a + npqpv, a) =
132 P(k) - G(k, k) * sol_V.coeff(k) * sol_V.coeff(k);
133 for (UInt b = 0; b < npqpv; ++b) {
134 if (b == a)
135 continue;
136 j = mPQPVBusIndices[b];
137 if (!isConnected(k, j))
138 continue;
139 val = sol_V.coeff(k) * sol_V.coeff(j) *
140 (G(k, j) * cos(sol_D.coeff(k) - sol_D.coeff(j)) +
141 B(k, j) * sin(sol_D.coeff(k) - sol_D.coeff(j)));
142 mJsparse.coeffRef(a + npqpv, b) = -val;
143 }
144 }
145
146 // J4: dQ/dV
147 for (UInt a = 0; a < mNumPQBuses; ++a) {
148 k = mPQPVBusIndices[a];
149 mJsparse.coeffRef(a + npqpv, a + npqpv) =
150 Q(k) - B(k, k) * sol_V.coeff(k) * sol_V.coeff(k);
151 for (UInt b = 0; b < mNumPQBuses; ++b) {
152 if (b == a)
153 continue;
154 j = mPQPVBusIndices[b];
155 if (!isConnected(k, j))
156 continue;
157 val = sol_V.coeff(k) * sol_V.coeff(j) *
158 (G(k, j) * sin(sol_D.coeff(k) - sol_D.coeff(j)) -
159 B(k, j) * cos(sol_D.coeff(k) - sol_D.coeff(j)));
160 mJsparse.coeffRef(a + npqpv, b + npqpv) = val;
161 }
162 }
163}
164
166 // Full numeric factorization with pivoting on the first iteration of each run;
167 // refactorize (reuse ordering + symbolic) afterwards. mIterations is 0 only on
168 // the first iteration (set after the solve in PFSolver::solvePowerflow).
169 if (mIterations == 0)
170 mLinearSolver->factorize(mJsparse);
171 else
172 mLinearSolver->refactorize(mJsparse);
173
174 Matrix rhs = mF;
175 mX = mLinearSolver->solve(rhs);
176}
UInt mNumPQBuses
Number of PQ nodes.
Definition PFSolver.h:24
CPS::Vector mX
Solution vector.
Definition PFSolver.h:52
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:465
CPS::Vector mF
Vector of mismatch values.
Definition PFSolver.h:54
CPS::UInt mIterations
Actual number of iterations.
Definition PFSolver.h:85
UInt mNumUnknowns
Number of unknowns, defining system dimension.
Definition PFSolver.h:30
CPS::Real G(int i, int j)
Gets the real part of admittance matrix element.
Definition PFSolver.cpp:463
std::vector< CPS::UInt > mPQPVBusIndices
Vector with indices of both PQ and PV buses.
Definition PFSolver.h:44
PFSolverPowerPolar(CPS::String name, const CPS::SystemTopology &system, CPS::Real timeStep, CPS::Logger::Level logLevel)
Constructor to be used in simulation examples.
CPS::Vector sol_D
Solution vector of voltage angle.
CPS::Real Q(CPS::UInt k)
Calculate the reactive power at a bus from current solution.
CPS::Real P(CPS::UInt k)
Calculate active power at a bus from current solution.
CPS::Vector sol_V
Solution vector of voltage magnitude.
std::shared_ptr< DirectLinearSolver > mLinearSolver
Direct linear solver reused across iterations (symbolic factorization computed once)
CPS::SparseMatrixRow mJsparse
Sparse Jacobian (row-major, fixed pattern, values updated in place each iteration)
bool isConnected(CPS::UInt k, CPS::UInt j)
Whether buses k and j are connected (off-diagonal Jacobian entry exists)
void setUpJacobianStorage() override
Build the fixed sparsity pattern, create the linear solver and analyze the pattern once.
std::vector< std::pair< CPS::UInt, CPS::UInt > > mVariableSystemMatrixEntries
Empty list: PF uses full refactorization, not partial refactorization.
void calculateJacobian() override
Fill the sparse Jacobian values in place (pattern unchanged)
PFSolverPowerPolarSparse(CPS::String name, const CPS::SystemTopology &system, CPS::Real timeStep, CPS::Logger::Level logLevel)
Constructor to be used in simulation examples.
void solveJacobianSystem() override
Factorize (first iteration of a run) or refactorize, then solve.
void buildJacobianPattern()
Construct the structural sparsity pattern of the Jacobian from the bus admittance matrix.
CPS::Logger::Log mSLog
Logger.
Definition Solver.h:45