use num_complex::Complex;
use serde::{Deserialize, Serialize};
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
pub struct Event {
pub time: f64,
pub kind: EventKind,
}
impl Event {
pub fn new(time: f64, kind: EventKind) -> Self {
Self { time, kind }
}
pub fn bolted_fault(time: f64, bus: usize) -> Self {
Self::new(
time,
EventKind::BusFault {
bus,
y: Complex::new(1e6, 0.0),
},
)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum EventKind {
BusFault { bus: usize, y: Complex<f64> },
ClearFault { bus: usize },
BranchTrip { branch: usize },
BranchClose { branch: usize },
UnitTrip { unit: usize },
UnitClose { unit: usize },
LoadStep { bus: usize, ds: Complex<f64> },
}
impl EventKind {
pub fn touches_network(&self) -> bool {
true
}
pub fn bus(&self) -> Option<usize> {
match *self {
EventKind::BusFault { bus, .. }
| EventKind::ClearFault { bus }
| EventKind::LoadStep { bus, .. } => Some(bus),
EventKind::BranchTrip { .. }
| EventKind::BranchClose { .. }
| EventKind::UnitTrip { .. }
| EventKind::UnitClose { .. } => None,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum EventError {
BusOutOfRange { bus: usize, n_bus: usize },
BranchOutOfRange { branch: usize, n_branch: usize },
UnitOutOfRange { unit: usize, n_unit: usize },
}
impl std::fmt::Display for EventError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
EventError::BusOutOfRange { bus, n_bus } => {
write!(
f,
"event names bus {bus}, but the network has {n_bus} buses"
)
}
EventError::BranchOutOfRange { branch, n_branch } => {
write!(
f,
"event names branch {branch}, but the network has {n_branch} branches"
)
}
EventError::UnitOutOfRange { unit, n_unit } => {
write!(
f,
"event names unit {unit}, but the system has {n_unit} devices"
)
}
}
}
}
impl std::error::Error for EventError {}
#[derive(Clone, Debug, PartialEq)]
pub enum DynamicsWarning {
DeadIsland { buses: Vec<usize>, time: f64 },
Skipped { time: f64, error: EventError },
RelayDropped { id: String, detail: String },
}
impl std::fmt::Display for DynamicsWarning {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
DynamicsWarning::DeadIsland { buses, time } => write!(
f,
"at t = {time}: {} bus(es) have no machine and no voltage reference, \
starting at bus {}",
buses.len(),
buses.first().copied().unwrap_or(0)
),
DynamicsWarning::Skipped { time, error } => {
write!(f, "at t = {time}: {error}")
}
DynamicsWarning::RelayDropped { id, detail } => {
write!(f, "relay {id}: {detail}; dropped")
}
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
#[serde(tag = "watch", rename_all = "snake_case")]
pub enum Watch {
BusVoltage { bus: usize },
UnitSpeed { unit: usize },
UnitAngleExcursion { unit: usize },
}
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
#[serde(tag = "when", rename_all = "snake_case")]
pub enum Trigger {
Below { threshold: f64 },
Above { threshold: f64 },
}
impl Trigger {
pub fn residual(&self, value: f64) -> f64 {
match *self {
Trigger::Below { threshold } => threshold - value,
Trigger::Above { threshold } => value - threshold,
}
}
pub fn holds(&self, value: f64) -> bool {
self.residual(value) > 0.0
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Relay {
pub id: String,
#[serde(flatten)]
pub watch: Watch,
#[serde(flatten)]
pub trigger: Trigger,
pub delay: f64,
pub action: EventKind,
#[serde(default)]
pub repeating: bool,
}
impl Relay {
pub fn under_voltage(
id: impl Into<String>,
bus: usize,
threshold: f64,
delay: f64,
action: EventKind,
) -> Self {
Self {
id: id.into(),
watch: Watch::BusVoltage { bus },
trigger: Trigger::Below { threshold },
delay,
action,
repeating: false,
}
}
pub fn over_speed(
id: impl Into<String>,
unit: usize,
threshold: f64,
delay: f64,
action: EventKind,
) -> Self {
Self {
id: id.into(),
watch: Watch::UnitSpeed { unit },
trigger: Trigger::Above { threshold },
delay,
action,
repeating: false,
}
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct RelayAction {
pub id: String,
pub crossed_at: f64,
pub fired_at: f64,
pub action: EventKind,
}