pub mod ac_sensitivity;
pub mod batch;
pub mod bde;
pub mod branch_flow;
pub mod constrained;
pub mod continuation;
pub mod dc;
pub mod dispatch;
pub mod injection_hessian;
pub mod jacobian;
pub mod json;
pub mod linear;
pub mod measurement;
pub mod network;
pub mod outerloop;
pub mod qv;
pub mod ratings;
pub mod roots;
pub mod scenario;
pub mod se;
pub mod shortcircuit;
pub mod solver;
pub mod switches;
pub mod topology;
pub mod types;
use json::NetworkData;
use linear::{
btheta::{DcIslandStatus, DcSolution, dc_power_flow},
impedance::{LinearIslandStatus, LinearReport, linear_power_flow},
};
use network::{ShuntAdm, YBus, build_ybus, linear_initial_guess, stamp_shunts};
use solver::{IslandReport, JacobianBackend, PersistentSolver, SolveStats, SolveStatus};
use types::{Bus, Line, Transformer};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub enum PowerFlowMethod {
#[default]
NewtonRaphson,
Dc,
LinearImpedance,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub enum PowerFlowInit {
Flat,
#[default]
LinearImpedance,
Dc,
}
#[derive(Clone, Debug)]
pub struct PowerFlowOptions {
pub tol: f64,
pub max_iter: usize,
pub backend: JacobianBackend,
pub method: PowerFlowMethod,
pub init: PowerFlowInit,
pub enforce_q_limits: bool,
pub distribute_slack: Option<crate::outerloop::SlackDistribution>,
pub area_interchange: Option<crate::outerloop::AreaDefinition>,
pub control_taps: bool,
pub control_remote_voltage: bool,
pub tap_max_shift: i32,
pub max_outer_iter: usize,
pub dc: crate::linear::DcOptions,
}
impl Default for PowerFlowOptions {
fn default() -> Self {
Self {
tol: 1e-6,
max_iter: 20,
backend: JacobianBackend::default(),
method: PowerFlowMethod::default(),
init: PowerFlowInit::default(),
enforce_q_limits: false,
distribute_slack: None,
area_interchange: None,
control_taps: false,
control_remote_voltage: false,
tap_max_shift: 3,
max_outer_iter: 10,
dc: crate::linear::DcOptions::default(),
}
}
}
#[derive(Debug)]
pub struct PowerFlowReport {
pub buses: Vec<Bus>,
pub islands: Vec<IslandReport>,
pub stats: SolveStats,
pub dc: Option<DcSolution>,
pub linear: Option<LinearReport>,
pub outer: Option<OuterLoopOutcome>,
}
#[derive(Clone, Copy, Debug, Default)]
pub struct TapData<'a> {
pub changers: &'a [Option<types::TapChanger>],
pub regulation: &'a [outerloop::TapRegulation],
}
impl TapData<'_> {
pub fn none() -> Self {
Self::default()
}
pub fn is_empty(&self) -> bool {
self.changers.is_empty() || self.regulation.is_empty()
}
}
#[derive(Clone, Copy, Debug, Default)]
pub struct RemoteControlData<'a> {
pub machines: &'a [types::RegulatingMachine],
}
impl RemoteControlData<'_> {
pub fn none() -> Self {
Self::default()
}
pub fn is_empty(&self) -> bool {
self.machines.iter().all(|m| m.at_bus == m.controls_bus)
}
}
#[derive(Clone, Debug, Default)]
pub struct OuterLoopOutcome {
pub report: Option<outerloop::OuterLoopReport>,
pub q_limit_switches: Vec<usize>,
pub slack: Option<outerloop::SlackDistributionReport>,
pub area: Option<outerloop::AreaInterchangeReport>,
pub taps: Vec<outerloop::ControllerReport>,
pub remote: Vec<outerloop::RemoteControlReport>,
pub retyped: Vec<(usize, types::BusType, types::BusType)>,
pub transformers: Vec<Transformer>,
pub changers: Vec<Option<types::TapChanger>>,
}
impl PowerFlowReport {
fn empty(buses: Vec<Bus>, status: SolveStatus) -> Self {
Self {
buses,
islands: Vec::new(),
stats: SolveStats {
status,
mismatch_history: Vec::new(),
},
dc: None,
linear: None,
outer: None,
}
}
}
pub fn run_power_flow_analysis(network_data: NetworkData) -> PowerFlowReport {
let ybus = build_ybus(network_data.buses.len(), &network_data.lines, &[]);
run_power_flow_analysis_from_ybus(network_data.buses, ybus)
}
pub fn run_power_flow(
buses: Vec<Bus>,
lines: &[Line],
transformers: &[Transformer],
shunts: &[ShuntAdm],
taps: TapData<'_>,
opts: PowerFlowOptions,
) -> PowerFlowReport {
run_power_flow_with_remote(
buses,
lines,
transformers,
shunts,
taps,
RemoteControlData::none(),
opts,
)
}
pub fn run_power_flow_with_remote(
mut buses: Vec<Bus>,
lines: &[Line],
transformers: &[Transformer],
shunts: &[ShuntAdm],
taps: TapData<'_>,
remote: RemoteControlData<'_>,
opts: PowerFlowOptions,
) -> PowerFlowReport {
match opts.method {
PowerFlowMethod::Dc => {
let solution = dc_power_flow(&mut buses, lines, transformers, opts.dc);
let status = if solution
.islands
.iter()
.any(|i| i.status == DcIslandStatus::Singular)
{
SolveStatus::Singular
} else {
SolveStatus::Converged
};
let mut report = PowerFlowReport::empty(buses, status);
report.dc = Some(solution);
report
}
PowerFlowMethod::LinearImpedance => {
let ybus = finished_ybus(buses.len(), lines, transformers, shunts);
let solution = linear_power_flow(&mut buses, &ybus);
let status = if solution
.islands
.iter()
.any(|i| i.status == LinearIslandStatus::Singular)
{
SolveStatus::Singular
} else {
SolveStatus::Converged
};
let mut report = PowerFlowReport::empty(buses, status);
report.linear = Some(solution);
report
}
PowerFlowMethod::NewtonRaphson => {
let ybus = finished_ybus(buses.len(), lines, transformers, shunts);
initialize_newton(&mut buses, lines, transformers, &ybus, opts.init, opts.dc);
newton_with_loops(buses, lines, transformers, shunts, taps, remote, ybus, opts)
}
}
}
pub fn initialize_newton(
buses: &mut [Bus],
lines: &[Line],
transformers: &[Transformer],
ybus: &network::YBusSparse,
init: PowerFlowInit,
dc: crate::linear::DcOptions,
) {
match init {
PowerFlowInit::Flat => {}
PowerFlowInit::LinearImpedance => linear_initial_guess(buses, ybus),
PowerFlowInit::Dc => {
let saved: Vec<(types::BusType, f64, f64, f64)> = buses
.iter()
.map(|b| (b.bus_type, b.p_spec, b.q_spec, b.voltage_mag))
.collect();
dc_power_flow(buses, lines, transformers, dc);
for (bus, (bus_type, p_spec, q_spec, voltage_mag)) in buses.iter_mut().zip(saved) {
bus.bus_type = bus_type;
bus.p_spec = p_spec;
bus.q_spec = q_spec;
bus.voltage_mag = voltage_mag;
}
}
}
}
#[allow(clippy::too_many_arguments)]
#[allow(clippy::too_many_arguments)]
fn newton_with_loops(
mut buses: Vec<Bus>,
lines: &[Line],
transformers: &[Transformer],
shunts: &[ShuntAdm],
taps: TapData<'_>,
remote: RemoteControlData<'_>,
mut ybus: network::YBusSparse,
opts: PowerFlowOptions,
) -> PowerFlowReport {
let wants_taps = opts.control_taps && !taps.is_empty();
let wants_remote = opts.control_remote_voltage && !remote.is_empty();
if !opts.enforce_q_limits
&& opts.distribute_slack.is_none()
&& opts.area_interchange.is_none()
&& !wants_taps
&& !wants_remote
{
let mut solver = PersistentSolver::new(opts.backend);
let (islands, stats) = solver.solve_with_stats(&mut buses, &ybus, opts.tol, opts.max_iter);
return PowerFlowReport {
buses,
islands,
stats,
dc: None,
linear: None,
outer: None,
};
}
assert!(
!(opts.distribute_slack.is_some() && opts.area_interchange.is_some()),
"distribute_slack and area_interchange cannot both be set: area interchange subsumes \
distributed slack (one area with a zero target *is* distributed slack), so running both \
would dispatch the same generators twice"
);
let mut transformers = transformers.to_vec();
let mut changers = taps.changers.to_vec();
let mut slack = opts
.distribute_slack
.clone()
.map(outerloop::DistributedSlack::new);
let mut area = opts
.area_interchange
.clone()
.map(outerloop::AreaInterchange::new);
let mut qlim = opts.enforce_q_limits.then(outerloop::ReactiveLimits::new);
let mut remote_loop =
wants_remote.then(|| outerloop::RemoteVoltageControl::new(remote.machines));
let mut phase =
wants_taps.then(|| outerloop::PhaseControl::new().max_tap_shift(opts.tap_max_shift));
let mut voltage = wants_taps
.then(|| outerloop::TransformerVoltageControl::new().max_tap_shift(opts.tap_max_shift));
let (islands, report) = {
let mut ctx = outerloop::SolveContext::new(&mut buses, &mut ybus)
.with_branches(lines, &mut transformers, shunts)
.with_taps(&mut changers, taps.regulation);
let mut list: Vec<&mut dyn outerloop::OuterLoop> = Vec::new();
if let Some(l) = remote_loop.as_mut() {
list.push(l);
}
if let Some(l) = area.as_mut() {
list.push(l);
}
if let Some(l) = slack.as_mut() {
list.push(l);
}
if let Some(l) = qlim.as_mut() {
list.push(l);
}
if let Some(l) = phase.as_mut() {
list.push(l);
}
if let Some(l) = voltage.as_mut() {
list.push(l);
}
outerloop::solve_with_loops(
&mut ctx,
opts.tol,
opts.max_iter,
opts.backend,
&mut list,
opts.max_outer_iter,
)
};
let mut outcome = OuterLoopOutcome {
report: Some(report),
q_limit_switches: qlim
.as_ref()
.map(|l| l.switches().to_vec())
.unwrap_or_default(),
slack: slack.map(outerloop::DistributedSlack::into_report),
area: area.map(outerloop::AreaInterchange::into_report),
taps: Vec::new(),
remote: remote_loop
.as_ref()
.map(|l| l.report_against(&buses))
.unwrap_or_default(),
retyped: remote_loop
.as_ref()
.map(|l| l.retyped().to_vec())
.unwrap_or_default(),
transformers: Vec::new(),
changers: Vec::new(),
};
if let Some(l) = voltage.as_ref() {
outcome
.taps
.extend(l.report(taps.regulation, &changers).controllers);
}
if let Some(l) = phase.as_ref() {
outcome
.taps
.extend(l.report(taps.regulation, &changers).controllers);
}
if wants_taps {
outcome.transformers = transformers;
outcome.changers = changers;
}
let status = if islands.iter().all(|i| {
!matches!(
i.status,
solver::IslandStatus::Singular | solver::IslandStatus::MaxIterationsReached
)
}) {
SolveStatus::Converged
} else if islands
.iter()
.any(|i| i.status == solver::IslandStatus::Singular)
{
SolveStatus::Singular
} else {
SolveStatus::MaxIterationsReached
};
PowerFlowReport {
buses,
islands,
stats: SolveStats {
status,
mismatch_history: Vec::new(),
},
dc: None,
linear: None,
outer: Some(outcome),
}
}
fn finished_ybus(
n: usize,
lines: &[Line],
transformers: &[Transformer],
shunts: &[ShuntAdm],
) -> network::YBusSparse {
let mut ybus = build_ybus(n, lines, transformers);
stamp_shunts(&mut ybus, shunts);
ybus.finish()
}
pub fn run_power_flow_analysis_from_ybus(mut buses: Vec<Bus>, ybus: YBus) -> PowerFlowReport {
let ybus = ybus.finish();
linear_initial_guess(&mut buses, &ybus);
let mut solver = PersistentSolver::new(JacobianBackend::default());
let (islands, stats) = solver.solve_with_stats(&mut buses, &ybus, 1e-6, 20);
PowerFlowReport {
buses,
islands,
stats,
dc: None,
linear: None,
outer: None,
}
}