Skip to main content

powerio_matrix/synth/
tree.rs

1//! Random spanning tree topology. Produces a singular Laplacian B' (rank n-1).
2
3use rand::Rng;
4use rand::SeedableRng;
5use rand_chacha::ChaCha8Rng;
6
7use crate::network::{Branch, Bus, BusId, BusType, Extras, Network};
8
9use super::SynthSpec;
10
11pub fn generate_tree(spec: &SynthSpec) -> Network {
12    let n = spec.n.max(2);
13    let mut rng = ChaCha8Rng::seed_from_u64(spec.seed);
14    let buses = make_buses(n);
15
16    // For each new node k in [1, n), connect it to a uniformly chosen ancestor.
17    let mut branches = Vec::with_capacity(n - 1);
18    for k in 1..n {
19        let parent = rng.random_range(0..k);
20        branches.push(make_branch(parent + 1, k + 1, spec, &mut rng));
21    }
22
23    net(format!("synth_tree_n{n}"), buses, branches)
24}
25
26/// Wrap synthesized buses and branches into an in-memory [`Network`]: no loads,
27/// shunts, generators, or source document.
28pub(super) fn net(name: String, buses: Vec<Bus>, branches: Vec<Branch>) -> Network {
29    Network::in_memory(name, 100.0, buses, branches)
30}
31
32/// Build `n` synthetic buses with ids `1..=n` and bus 1 designated reference.
33/// Every synthetic case needs exactly one reference bus, or the DC sensitivity
34/// and DC-OPF paths reject it with `ReferenceBusCount { found: 0 }`. Centralized
35/// here so each topology generator can't forget it. Requires `n >= 1`.
36pub(crate) fn make_buses(n: usize) -> Vec<Bus> {
37    let mut buses: Vec<Bus> = (0..n).map(|i| make_bus(i + 1)).collect();
38    buses[0].kind = BusType::Ref;
39    buses
40}
41
42pub(crate) fn make_bus(id: usize) -> Bus {
43    Bus {
44        id: BusId(id),
45        kind: BusType::Pq,
46        vm: 1.0,
47        va: 0.0,
48        base_kv: 345.0,
49        vmax: 1.1,
50        vmin: 0.9,
51        area: 1,
52        zone: 1,
53        name: None,
54        extras: Extras::new(),
55    }
56}
57
58pub(crate) fn make_branch(
59    from: usize,
60    to: usize,
61    spec: &SynthSpec,
62    rng: &mut ChaCha8Rng,
63) -> Branch {
64    // Log-uniform reactance around mean_x; resistance = r_over_x * x.
65    let log_low = (spec.mean_x * 0.5).ln();
66    let log_high = (spec.mean_x * 2.0).ln();
67    let log_x: f64 = rng.random_range(log_low..log_high);
68    let x = log_x.exp().max(1e-6);
69    let r = spec.r_over_x * x;
70    Branch {
71        from: BusId(from),
72        to: BusId(to),
73        r,
74        x,
75        b: 0.0,
76        rate_a: 0.0,
77        rate_b: 0.0,
78        rate_c: 0.0,
79        tap: 0.0,
80        shift: 0.0,
81        in_service: true,
82        angmin: -360.0,
83        angmax: 360.0,
84        extras: Extras::new(),
85    }
86}