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}