Different Jacobian Algorithms #166
luke-kiernan
started this conversation in
General
Replies: 1 comment
|
I made some revisions and rebased the
J_nonzeros = reinterpret(ComplexF64, SparseArrays.nonzeros(Jv))
for col in 1:num_buses
Yb_val_range = nzrange(Yb, col)
J_Vm_start = div(Jv.colptr[2 * col - 1] + 1, 2)
J_Vm_val_range = Yb_val_range .+ (J_Vm_start - Yb_val_range.start)
J_Va_start = div(Jv.colptr[2 * col] + 1, 2)
J_Va_val_range = Yb_val_range .+ (J_Va_start - Yb_val_range.start)
# do something with the ranges
endI could write J_complex = reinterpret(ComplexF64, Jv)
for col in 1:num_buses
Yb_val_range = nzrange(Yb, col)
J_Vm_val_range = nzrange(J_complex, 2*col-1)
J_Va_val_range = nzrange(J_complex, 2*col)
# do something with the ranges
endand the reindexing would be handled behind the scenes. [Explicitly: the data width doubled, so |
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Current data layout
Our Jacobian matrix is stored as a
SparseMatrixCSC, of dimensions 2n-by-2n, where n is the number of buses. Each 2x2 block is structurally nonzero if and only if there's a branch between that pair of buses. If any one entry in a 2x2 block is structurally nonzero, then all 4 are structurally nonzero. This means we're storing a few extra zeros, but the tradeoff is that our sparse structure doesn't depend on the bus types (so PV/PQ switching isn't a problem).When writing code, I frequently find myself referring to the following chart:
xFRows in
Jcorrespond to entries inF, columns to entries inx, so the above also describes the structure of J. If that isn't clear, I wrote down a sample Jacobian matrix for a 3-bus system in the docstring for_create_jacobian_matrix_structure.Iterating over Neighbors
This is what we're currently using. There's been some refactoring for performance reasons, but the core algorithm amounts to
Note that this works across-then-down, when down-then-across cooperates much better with CSC format. The no-frills loop can be found here. Main performance improvements since then:
Valof the bus type of thebus_to, for compilation reasons.sum()over the other neighbors. Since we're iterating over the neighbors anyway, total it up as we go.Valobjects at runtime is slow: instead of havingVal(bus_type), spell out the 3 possibilities in anif-elseifso that it'sVal(constant).2-pass algorithm
This is described in Algorithm 1 of the paper "Bus Admittance Matrix Revisited: Performance Challenges on Modern Computers": it iterates directly over the sparse structure. However, it stores the angle derivatives and voltage magnitude derivatives in 2 separate CSC matrices,
dS_ΘanddS_V. The sparse structure ofdS_ΘanddS_Vis the same as that of the Ybus matrix: their initialization step of "copy the contents ofYbustodS_Θ" is linear memory access, hence very fast. With our data layout, we don't have that feature.Nevertheless, I adapted their general algorithm on the


lk/2-pass-jacbianbranch. Profiling the Jacobian calls alone, I get that it's about 2x faster than our current implementation. Running theTrustRegionmethod on a 10k bus system, it's a 15% reduction in total runtime, albeit with a 15% increase in memory usage. I've eliminated all unnecessary allocations in my implementation of their algorithm, but I haven't experimented with further performance tweaks.Matrix algorithm
Worth mentioning for readibility and mathematical elegance: the matpower docs have a nice derivation using chain rule. Implemented a while back: poor performance, like 10x slower than the current method. However, it's still used in testing and can be found in
test/test_utils/legacy_pf.jl.All reactions