pub mod arnoldi;
#[cfg(feature = "smallsignal-arpack")]
pub mod arpack;
pub mod dae;
#[cfg(feature = "iidm")]
pub mod dyd;
pub mod dyr;
pub mod events;
pub mod init;
pub mod integrator;
pub mod json;
pub mod models;
pub mod shift;
pub mod smallsignal;
use num_complex::Complex;
use crate::network::{
ShuntAdm, YBusSparse, build_ybus_with_outages, connected_components, stamp_shunts,
};
use crate::solver::JacobianBackend;
use crate::types::{Line, Transformer};
use dae::DaePattern;
use models::DynamicModel;
pub use events::{
DynamicsWarning, Event, EventError, EventKind, Relay, RelayAction, Trigger, Watch,
};
pub use init::{BuildError, DeviceSpec, SystemSpec, build};
pub use json::{DynamicsData, DynamicsDocument, DynamicsError};
#[derive(Clone, Debug)]
pub struct DynamicsOptions {
pub end_time: f64,
pub step: f64,
pub tol: f64,
pub max_newton: usize,
pub damping_steps: usize,
pub events: Vec<Event>,
pub relays: Vec<events::Relay>,
pub backend: JacobianBackend,
}
impl Default for DynamicsOptions {
fn default() -> Self {
Self {
end_time: 10.0,
step: 0.005,
tol: 1e-9,
max_newton: 20,
damping_steps: 2,
events: Vec::new(),
relays: Vec::new(),
backend: JacobianBackend::default(),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum DynamicsStatus {
Completed,
NewtonFailed { time: f64 },
Singular { time: f64 },
}
#[derive(Clone, Debug, Default)]
pub struct Trajectory {
pub time: Vec<f64>,
pub names: Vec<String>,
pub rows: Vec<Vec<f64>>,
}
impl Trajectory {
pub fn new(names: Vec<String>) -> Self {
Self {
time: Vec::new(),
names,
rows: Vec::new(),
}
}
pub(crate) fn push(&mut self, t: f64, x: &[f64], v: &[Complex<f64>]) {
let mut row = Vec::with_capacity(self.names.len());
row.extend_from_slice(x);
for vi in v {
row.push(vi.norm());
}
for vi in v {
row.push(vi.arg());
}
self.time.push(t);
self.rows.push(row);
}
pub fn column(&self, name: &str) -> Option<usize> {
self.names.iter().position(|n| n == name)
}
pub fn series(&self, name: &str) -> Option<Vec<f64>> {
let col = self.column(name)?;
Some(self.rows.iter().map(|r| r[col]).collect())
}
}
#[derive(Clone, Debug)]
pub struct DynamicsReport {
pub trajectory: Trajectory,
pub status: DynamicsStatus,
pub steps: usize,
pub newton_iterations: usize,
pub events_applied: usize,
pub warnings: Vec<DynamicsWarning>,
pub relay_actions: Vec<events::RelayAction>,
}
pub struct DynamicSystem {
pub(crate) models: Vec<Box<dyn DynamicModel>>,
pub(crate) ids: Vec<String>,
pub(crate) ybus: YBusSparse,
pub(crate) pattern: DaePattern,
pub(crate) v_fixed: Vec<Complex<f64>>,
pub(crate) lines: Vec<Line>,
pub(crate) transformers: Vec<Transformer>,
pub(crate) shunts: Vec<ShuntAdm>,
pub(crate) outaged: Vec<bool>,
pub(crate) load_y: Vec<Complex<f64>>,
pub(crate) fault_y: Vec<Complex<f64>>,
pub(crate) x0: Vec<f64>,
pub(crate) v0: Vec<Complex<f64>>,
}
impl std::fmt::Debug for DynamicSystem {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("DynamicSystem")
.field("devices", &self.ids)
.field("n_bus", &self.v0.len())
.field("n_states", &self.x0.len())
.field("nnz", &self.pattern.nnz())
.finish()
}
}
impl DynamicSystem {
pub fn n_bus(&self) -> usize {
self.v0.len()
}
pub fn n_states(&self) -> usize {
self.x0.len()
}
pub fn state(&self) -> &[f64] {
&self.x0
}
pub fn state_mut(&mut self) -> &mut [f64] {
&mut self.x0
}
pub fn voltages(&self) -> &[Complex<f64>] {
&self.v0
}
pub fn differential_state_names(&self) -> Vec<String> {
let mut names = Vec::with_capacity(self.x0.len());
for (d, model) in self.models.iter().enumerate() {
for state in model.state_names() {
names.push(format!("{}.{}", self.ids[d], state));
}
}
names
}
pub fn observable_names(&self) -> Vec<String> {
let layout = self.pattern.layout();
let mut names = Vec::with_capacity(layout.n());
for (d, model) in self.models.iter().enumerate() {
for state in model.state_names() {
names.push(format!("{}.{}", self.ids[d], state));
}
}
for i in 0..layout.n_bus {
names.push(format!("bus{i}.vmag"));
}
for i in 0..layout.n_bus {
names.push(format!("bus{i}.vang"));
}
names
}
pub(crate) fn latch(&self, x: &[f64], v: &[Complex<f64>]) {
let layout = self.pattern.layout();
for (d, model) in self.models.iter().enumerate() {
let (off, len, bus) = (layout.dev_offset[d], layout.dev_len[d], layout.dev_bus[d]);
model.latch(&x[off..off + len], v[bus]);
}
}
pub(crate) fn project(&self, x: &mut [f64]) {
let layout = self.pattern.layout();
for (d, model) in self.models.iter().enumerate() {
let (off, len) = (layout.dev_offset[d], layout.dev_len[d]);
model.project(&mut x[off..off + len]);
}
}
pub(crate) fn derivatives_at(&self, x: &[f64], v: &[Complex<f64>], out: &mut [f64]) {
let layout = self.pattern.layout();
for (d, model) in self.models.iter().enumerate() {
let (off, len, bus) = (layout.dev_offset[d], layout.dev_len[d], layout.dev_bus[d]);
model.derivatives(&x[off..off + len], v[bus], &mut out[off..off + len]);
}
}
pub(crate) fn reassemble(&mut self) {
let n = self.v0.len();
let mut y = build_ybus_with_outages(n, &self.lines, &self.transformers, &self.outaged);
stamp_shunts(&mut y, &self.shunts);
for i in 0..n {
y.add(i, i, self.load_y[i] + self.fault_y[i]);
}
let layout = self.pattern.layout();
for (d, model) in self.models.iter().enumerate() {
if let Some(yn) = model.norton_admittance() {
let bus = layout.dev_bus[d];
y.add(bus, bus, yn);
}
}
self.ybus = y.finish();
}
pub(crate) fn connected_devices(&self) -> Vec<bool> {
self.models.iter().map(|m| m.is_connected()).collect()
}
pub(crate) fn dead_islands(&self) -> Vec<Vec<usize>> {
let layout = self.pattern.layout();
let mut alive = vec![false; layout.n_bus];
let connected = self.connected_devices();
for (d, &bus) in layout.dev_bus.iter().enumerate() {
alive[bus] |= connected[d];
}
for (i, alive) in alive.iter_mut().enumerate() {
*alive |= self.pattern.is_fixed(i);
}
connected_components(&self.ybus)
.into_iter()
.filter(|component| !component.iter().any(|&b| alive[b]))
.collect()
}
pub(crate) fn watched(
&self,
watch: events::Watch,
x: &[f64],
v: &[Complex<f64>],
reference: &[f64],
) -> Option<f64> {
let layout = self.pattern.layout();
match watch {
events::Watch::BusVoltage { bus } => v.get(bus).map(|value| value.norm()),
events::Watch::UnitSpeed { unit } => {
let offset = *layout.dev_offset.get(unit)?;
let omega = self.models.get(unit)?.speed_index()?;
x.get(offset + omega).copied()
}
events::Watch::UnitAngleExcursion { unit } => {
let offset = *layout.dev_offset.get(unit)?;
let omega = self.models.get(unit)?.speed_index()?;
let delta = offset + omega.checked_sub(1)?;
Some((x.get(delta)? - reference.get(delta)?).abs())
}
}
}
pub fn max_derivative(&self) -> f64 {
let mut f = vec![0.0; self.x0.len()];
self.derivatives_at(&self.x0, &self.v0, &mut f);
f.iter().fold(0.0f64, |m, d| m.max(d.abs()))
}
}
pub fn settle(system: &mut DynamicSystem, opts: &DynamicsOptions) -> integrator::StepOutcome {
integrator::solve_algebraic(system, opts.backend.lu_backend(), opts.tol, opts.max_newton)
}
pub fn run_dynamics(system: &mut DynamicSystem, opts: &DynamicsOptions) -> DynamicsReport {
integrator::integrate(system, opts)
}