Matrix overview & indexing
This page is the reference hub for the matrix types: what each one stores and costs to build, how element indexing (A[row, column]) resolves, and which accessors read a matrix's data, axes, lookups, reference buses, reduction data, and provenance.
All matrix types are concrete subtypes of the abstract supertype PowerNetworkMatrix{T} <: AbstractArray{T, 2} (src/PowerNetworkMatrix.jl). Because they are AbstractArray{T,2} subtypes, they support the standard array interface (size, axes, getindex, iteration), but indexing is overloaded so that rows and columns are addressed by domain identifiers (bus numbers and arc tuples) rather than by raw integer positions.
Matrix taxonomy
Every matrix stores two structural fields:
axes: a 2-tuple of vectors listing the identifiers (arc tuples and/or bus numbers) for each dimension.lookup: a 2-tuple of dictionaries mapping those identifiers to integer positions into the storeddata.
The storage form is one of three kinds:
- Dense — the full matrix is materialized as a
Matrix{Float64}(or, forYbus, aSparseMatrixCSC). - Sparse — stored as a
SparseMatrixCSC. - Virtual / lazy — no full matrix is stored; rows are computed on demand and cached in an LRU row cache. Use these past large-system limits instead of the dense forms. Virtual matrices are not serializable.
Build cost is for a network of $N_b$ buses and $N_a$ arcs.
| Matrix | Rows | Columns | Storage | Build cost | Represents |
|---|---|---|---|---|---|
IncidenceMatrix | arc tuples | bus numbers | sparse | $O(N_a)$ | signed bus–arc topology (+1 from-bus, -1 to-bus) |
AdjacencyMatrix | bus numbers | bus numbers | sparse | $O(N_a)$ | signed bus–bus connectivity (Ybus sparsity pattern) |
Ybus | bus numbers | bus numbers | sparse (complex) | $O(N_a)$ | nodal admittance (topology + electrical parameters) |
ArcAdmittanceMatrix | arc tuples | bus numbers | sparse (complex) | $O(N_a)$, with Ybus | off-diagonal Ybus entries; built as part of Ybus |
BA_Matrix | bus numbers | arc tuples | sparse | $O(N_a)$ | $B A$: incidence weighted by branch susceptance |
ABA_Matrix | bus numbers | bus numbers | sparse | $O(N_a)$, plus factorization | $A^\top B A$ DC susceptance matrix; optionally factorized |
PTDF | arc tuples | bus numbers | dense | $O(N_b^3)$ | power transfer distribution factors |
LODF | arc tuples | arc tuples | dense | $O(N_a \cdot N_b^2)$ | line outage distribution factors |
VirtualPTDF | arc tuples | bus numbers | virtual | one solve per row, on demand | lazy per-row PTDF |
VirtualLODF | arc tuples | arc tuples | virtual | one solve per row, on demand | lazy per-row LODF |
VirtualMODF | arc tuples | bus numbers | virtual | one solve per row, on demand | post-modification / post-contingency PTDF rows |
Notes on the taxonomy:
Ybusis complex-valued (YBUS_ELTYPE, i.e.ComplexF32); all other numeric matrices are real (Float64).IncidenceMatrix/AdjacencyMatrixstore signedInt8topology.PTDFandLODFstore their data transposed internally;getindexandget_ptdf_data/get_lodf_datahide this so callers always see the standard(row, column)orientation.- There is no dense
MODFtype — onlyVirtualMODF. Contrast with PTDF/LODF, which have both dense and virtual forms. ArcAdmittanceMatrixis produced as a byproduct of buildingYbus(via a construction keyword) rather than being independently constructed by typical users.- The dense build costs are why
PTDF/LODFare the matrices to avoid at scale; see Computational Considerations for the sparsity and sparsification trade-offs behind that.
Every type is constructed by calling it on a System — PTDF(sys), Ybus(sys), and so on — and the lazy forms build and index exactly like their materialized counterparts. Full constructor signatures and keyword arguments are in the public API reference; worked examples are in the Introduction tutorial, and How to Build Multiple Matrices Without Repeating Work covers passing already-built matrices (Ybus, incidence, BA) to the constructors that accept them so shared intermediates are computed once.
Arc-tuple indexing
Matrices that involve branches identify each branch by an arc tuple — a Tuple{Int, Int} of the form (from_bus_number, to_bus_number) giving the directed connection between two buses. Arc tuples, rather than branch-name strings, are the canonical branch identifier: they are compact and unambiguous, they match the mathematical formulation in which a branch is defined by its two endpoint buses, and they survive network reductions, where named branches may be merged or eliminated but the surviving equivalent arc keeps a well-defined endpoint pair.
How A[row, column] resolves
Indexing is fully overloaded on PowerNetworkMatrix (src/PowerNetworkMatrix.jl). A[row, column] calls to_index(A, row, column), which maps each supplied identifier to an integer position through the per-dimension lookup dictionary (via the internal lookup_index helper), then reads the underlying data.
The accepted element types for row and column, and how each resolves:
| Index value | Resolves via | Supported |
|---|---|---|
Int (bus number) | direct lookup[i] | ✅ |
arc tuple (from, to)::Tuple{Int,Int} | direct lookup[i] | ✅ |
PowerSystems.ACBus | lookup_index specialization → Base.to_index(bus) = get_number(bus) | ✅ |
PowerSystems.Arc | lookup_index specialization → Base.to_index(arc) = get_arc_tuple(arc) | ✅ |
branch-name String | dedicated getindex on PTDF / LODF / VirtualPTDF (maps name → arc via reduction data, applies parallel/aggregation multiplier) | ✅ (PTDF/LODF/VirtualPTDF only) |
Colon (:) | returns the whole row/column | ✅ |
PowerNetworkMatrixKey | A[k] splats k.I back into A[k.I...] | ✅ |
raw Int position pair | dense positional fast path (A.data[…]) | ✅ |
PowerSystems.ACBranch | — | ❌ raises KeyError |
Only PTDF, LODF, and VirtualPTDF accept branch-name String indices, and LODF requires one on both dimensions. Name indexing is retained for backward compatibility but is slower and less direct than arc-tuple indexing.
When network reductions (e.g. RadialReduction, DegreeTwoReduction) are applied, eliminated branches are absent from the matrix, and indexing with an arc tuple that was reduced away raises an error. Enumerate the surviving identifiers with get_axes.
Examples
using PowerNetworkMatrices
import PowerSystems
import PowerSystemCaseBuilder
sys = PowerSystemCaseBuilder.build_system(
PowerSystemCaseBuilder.PSITestSystems,
"c_sys5",
)
ptdf = PTDF(sys)
ybus = Ybus(sys)[ Info: Loaded time series from storage file existing=/home/runner/.julia/packages/PowerSystemCaseBuilder/zuCHR/data/serialized_system/8584b9e729c8aa68ee5405660c6258cde1f67ed3b68f114823707912e9a0d16c/c_sys5_time_series_storage.h5 new=/tmp/jl_vASH2u compression=InfrastructureSystems.CompressionSettings(false, InfrastructureSystems.CompressionTypesModule.CompressionTypes.DEFLATE = 1, 3, true)
[ Info: Finding subnetworks via iterative union find
[ Info: Finding subnetworks via iterative union find# By bus number and arc tuple (canonical):
ptdf[(2, 3), 1]0.1939166033667965# By PowerSystems component objects: an Arc row and an ACBus column
bus1 = first(
b for b in PowerSystems.get_components(PowerSystems.ACBus, sys) if
PowerSystems.get_number(b) == 1
)
branch = first(PowerSystems.get_components(PowerSystems.ACBranch, sys))
ptdf[PowerSystems.get_arc(branch), bus1]0.36849525887737145# By branch name (PTDF/LODF/VirtualPTDF only):
ptdf[PowerSystems.get_name(branch), 1]
# NOT allowed — raises KeyError:
# ptdf[branch, 1] # a PowerSystems.ACBranch object0.36849525887737145# Whole row / column with a Colon:
ptdf[:, 1] # column for bus 16-element Vector{Float64}:
0.19391660336679648
0.36849525887737145
0.19391660336679656
0.1939166033667965
-0.36849525887737133
0.43758813775583216Axes and lookups
get_axes returns mat.axes and get_lookup returns mat.lookup, each a 2-tuple ordered (dimension 1, dimension 2). The axis vector lists identifiers (bus numbers as Int, arcs as Tuple{Int,Int}) in position order; the matching lookup maps each identifier back to its integer position in data. These are the authoritative way to enumerate valid indices — especially after a reduction, where some arcs/buses are no longer present — and are defined for every matrix type.
get_axes(ptdf) # (bus-number vector, arc-tuple vector)([1, 2, 3, 4, 5], [(1, 2), (1, 5), (3, 4), (2, 3), (4, 5), (1, 4)])The dimension-specific accessors get_bus_axis / get_arc_axis / get_bus_lookup / get_arc_lookup select the correct dimension without the caller knowing which index (1 or 2) is the bus or arc dimension for a given matrix type. They are defined only for the dimensions a matrix actually has.
See also
- How to Diagnose a Disconnected Network — testing whether the network is connected and enumerating electrical islands.
- How to Define and Apply Contingencies —
ArcModification,ShuntModification,NetworkModification,ContingencySpec, and the Woodbury tooling. AutoToleranceand How to Choose a Linear Solver — the sparsificationtoland the linear-solver backends.to_hdf5/from_hdf5— HDF5 persistence (PTDF only).- Full public API — the curated autodocs for every exported symbol, including the reduction specs,
NetworkReductionData, and the aggregated-branch rating functions. - Internals — the KLU and Accelerate solver submodules.