use crate::network::PowerNetwork;
use crate::powerflow::result::BranchFlow;
use num_complex::Complex64;
pub fn compute_branch_flows(
network: &PowerNetwork,
voltages_pu: &[f64],
angles_rad: &[f64],
) -> Vec<BranchFlow> {
let v: Vec<Complex64> = voltages_pu
.iter()
.zip(angles_rad.iter())
.map(|(&m, &a)| Complex64::from_polar(m, a))
.collect();
let mut flows = Vec::with_capacity(network.branches.len());
for (branch_index, branch) in network.branches.iter().enumerate() {
let i = match network.bus_index(branch.from_bus) {
Ok(idx) => idx,
Err(_) => continue,
};
let j = match network.bus_index(branch.to_bus) {
Ok(idx) => idx,
Err(_) => continue,
};
if !branch.status {
flows.push(BranchFlow {
branch_index,
from_bus: branch.from_bus,
to_bus: branch.to_bus,
p_from_mw: 0.0,
q_from_mvar: 0.0,
p_to_mw: 0.0,
q_to_mvar: 0.0,
p_loss_mw: 0.0,
q_loss_mvar: 0.0,
loading_pct: 0.0,
});
continue;
}
let ys = Complex64::new(branch.r, branch.x).inv();
let tap = branch.tap_complex();
let tap_conj = tap.conj();
let tap_mag_sq = tap.norm_sqr();
let bc = Complex64::new(0.0, branch.b / 2.0);
let i_from = v[i] * (ys / tap_mag_sq + bc) + v[j] * (-ys / tap_conj);
let s_from = v[i] * i_from.conj();
let i_to = v[i] * (-ys / tap) + v[j] * (ys + bc);
let s_to = v[j] * i_to.conj();
let p_from = s_from.re * network.base_mva;
let q_from = s_from.im * network.base_mva;
let p_to = s_to.re * network.base_mva;
let q_to = s_to.im * network.base_mva;
let s_from_mva = s_from.norm() * network.base_mva;
let loading_pct = if branch.rate_a > 0.0 {
s_from_mva / branch.rate_a * 100.0
} else {
0.0
};
flows.push(BranchFlow {
branch_index,
from_bus: branch.from_bus,
to_bus: branch.to_bus,
p_from_mw: p_from,
q_from_mvar: q_from,
p_to_mw: p_to,
q_to_mvar: q_to,
p_loss_mw: p_from + p_to,
q_loss_mvar: q_from + q_to,
loading_pct,
});
}
flows
}
pub fn compute_dc_branch_flows(network: &PowerNetwork, angles_rad: &[f64]) -> Vec<BranchFlow> {
let mut flows = Vec::with_capacity(network.branches.len());
for (branch_index, branch) in network.branches.iter().enumerate() {
let i = match network.bus_index(branch.from_bus) {
Ok(idx) => idx,
Err(_) => continue,
};
let j = match network.bus_index(branch.to_bus) {
Ok(idx) => idx,
Err(_) => continue,
};
if !branch.status {
flows.push(BranchFlow {
branch_index,
from_bus: branch.from_bus,
to_bus: branch.to_bus,
p_from_mw: 0.0,
q_from_mvar: 0.0,
p_to_mw: 0.0,
q_to_mvar: 0.0,
p_loss_mw: 0.0,
q_loss_mvar: 0.0,
loading_pct: 0.0,
});
continue;
}
let tap = branch.effective_tap();
let p_ij = (angles_rad[i] - angles_rad[j]) / (branch.x * tap);
let p_ij_mw = p_ij * network.base_mva;
let loading_pct = if branch.rate_a > 0.0 {
p_ij_mw.abs() / branch.rate_a * 100.0
} else {
0.0
};
flows.push(BranchFlow {
branch_index,
from_bus: branch.from_bus,
to_bus: branch.to_bus,
p_from_mw: p_ij_mw,
q_from_mvar: 0.0,
p_to_mw: -p_ij_mw, q_to_mvar: 0.0,
p_loss_mw: 0.0,
q_loss_mvar: 0.0,
loading_pct,
});
}
flows
}
#[cfg(test)]
mod tests {
use super::*;
use crate::network::branch::Branch;
use crate::network::bus::{Bus, BusType};
use crate::network::topology::Generator;
fn make_2bus_net() -> PowerNetwork {
let mut net = PowerNetwork::new(100.0);
net.buses.push({
let mut b = Bus::new(1, BusType::Slack);
b.vm = 1.0;
b
});
net.buses.push({
let mut b = Bus::new(2, BusType::PQ);
b.vm = 1.0;
b.pd = crate::units::Power(50.0);
b.qd = crate::units::ReactivePower(10.0);
b
});
net.branches.push(Branch {
from_bus: 1,
to_bus: 2,
r: 0.01,
x: 0.1,
b: 0.02,
rate_a: 100.0,
rate_b: 100.0,
rate_c: 100.0,
tap: 0.0,
shift: 0.0,
status: true,
});
net.generators.push(Generator {
bus_id: 1,
pg: 0.0,
qg: 0.0,
qmax: 999.0,
qmin: -999.0,
vg: 1.0,
mbase: 100.0,
status: true,
pmax: 999.0,
pmin: 0.0,
});
net
}
#[test]
fn test_ac_branch_flows_count() {
let net = make_2bus_net();
let v_mag = vec![1.0_f64; net.bus_count()];
let v_ang = vec![0.0_f64; net.bus_count()];
let flows = compute_branch_flows(&net, &v_mag, &v_ang);
assert_eq!(flows.len(), 1);
assert_eq!(flows[0].branch_index, 0);
assert_eq!(flows[0].from_bus, 1);
assert_eq!(flows[0].to_bus, 2);
}
#[test]
fn test_dc_branch_flows_lossless() {
let net = make_2bus_net();
let angles = vec![0.0_f64, -0.05];
let flows = compute_dc_branch_flows(&net, &angles);
assert_eq!(flows.len(), 1);
let flow = &flows[0];
assert!((flow.p_from_mw - 50.0).abs() < 1e-6);
assert!((flow.p_loss_mw).abs() < 1e-12);
assert!((flow.p_from_mw + flow.p_to_mw).abs() < 1e-10);
}
#[test]
fn test_branch_loading_pct_computed() {
let net = make_2bus_net();
let angles = vec![0.0_f64, -0.05];
let flows = compute_dc_branch_flows(&net, &angles);
assert!((flows[0].loading_pct - 50.0).abs() < 1e-6);
}
#[test]
fn test_open_branch_zeroed() {
let mut net = make_2bus_net();
net.branches[0].status = false;
let v_mag = vec![1.0_f64; net.bus_count()];
let v_ang = vec![0.0_f64; net.bus_count()];
let flows = compute_branch_flows(&net, &v_mag, &v_ang);
assert_eq!(flows.len(), 1);
let f = &flows[0];
assert_eq!(f.p_from_mw, 0.0);
assert_eq!(f.loading_pct, 0.0);
}
}