Skip to main content

powerio_matrix/matrix/
lacpf.rs

1//! Linear AC power flow (LACPF) block matrix.
2//!
3//! At flat start `u* = 1 + j0`, the linearized power flow Jacobian is
4//!
5//! ```text
6//!   F(x*) = [[ G  -B  -I  0 ],
7//!            [-B  -G   0 -I ]],
8//! ```
9//!
10//! whose `2n × 2n` block (without the load injection identity columns) is
11//!
12//! ```text
13//!   J = [[ G  -B ],
14//!        [-B  -G ]].
15//! ```
16//!
17//! Satisfies `p = G ε - B θ`, `q = -B ε - G θ`. Indefinite (saddle point);
18//! emitted as a hard input alongside the SDDM B', B'', and ±Im(Y_bus).
19
20use sprs::CsMat;
21
22use crate::Result;
23use crate::indexed::IndexedNetwork;
24
25use super::BuildOptions;
26use super::ybus::build_ybus;
27
28pub fn build_lacpf(case: &IndexedNetwork, opts: &BuildOptions) -> Result<CsMat<f64>> {
29    let parts = build_ybus(case, opts)?;
30    let n = case.n();
31    let two_n = 2 * n;
32
33    // Walk both G and B once and emit the 4 blocks: [+G, -B; -B, -G].
34    // `build_ybus` already returns CSR, so iterate the parts directly rather
35    // than deep-copying through `to_csr()`.
36    let mut tri = sprs::TriMat::with_capacity((two_n, two_n), 2 * (parts.g.nnz() + parts.b.nnz()));
37
38    for (&v, (i, j)) in &parts.g {
39        tri.add_triplet(i, j, v); // top-left:  +G
40        tri.add_triplet(n + i, n + j, -v); // bottom-right: -G
41    }
42    for (&v, (i, j)) in &parts.b {
43        tri.add_triplet(i, n + j, -v); // top-right:    -B
44        tri.add_triplet(n + i, j, -v); // bottom-left:  -B
45    }
46
47    Ok(tri.to_csr())
48}