#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct NodeIdx(pub usize);
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct BusIdx(pub usize);
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct SwitchIdx(pub usize);
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum SwitchKind {
Generic,
Breaker,
Disconnector,
LoadBreakSwitch,
DisconnectingCircuitBreaker,
GroundDisconnector,
Jumper,
Cut,
Fuse,
Junction,
}
impl SwitchKind {
pub fn is_operable(self) -> bool {
!matches!(self, SwitchKind::Junction)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Switch {
pub kind: SwitchKind,
pub nodes: [NodeIdx; 2],
pub open: bool,
pub in_service: bool,
}
impl Switch {
pub fn conducting(&self) -> bool {
self.in_service && !self.open
}
}
#[derive(Clone, Debug, Default)]
pub struct NodeBreakerTopology {
pub n_nodes: usize,
pub switches: Vec<Switch>,
pub busbars: Vec<NodeIdx>,
}
impl NodeBreakerTopology {
pub fn new(n_nodes: usize) -> Self {
Self {
n_nodes,
switches: Vec::new(),
busbars: Vec::new(),
}
}
pub fn add_switch(&mut self, switch: Switch) -> SwitchIdx {
debug_assert!(switch.nodes[0].0 < self.n_nodes && switch.nodes[1].0 < self.n_nodes);
self.switches.push(switch);
SwitchIdx(self.switches.len() - 1)
}
pub fn conducting_count(&self) -> usize {
self.switches.iter().filter(|s| s.conducting()).count()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn switch(kind: SwitchKind, a: usize, b: usize, open: bool, in_service: bool) -> Switch {
Switch {
kind,
nodes: [NodeIdx(a), NodeIdx(b)],
open,
in_service,
}
}
#[test]
fn conducting_requires_both_in_service_and_closed() {
assert!(switch(SwitchKind::Breaker, 0, 1, false, true).conducting());
assert!(!switch(SwitchKind::Breaker, 0, 1, true, true).conducting());
assert!(!switch(SwitchKind::Breaker, 0, 1, false, false).conducting());
assert!(!switch(SwitchKind::Breaker, 0, 1, true, false).conducting());
}
#[test]
fn only_a_junction_is_inoperable() {
assert!(!SwitchKind::Junction.is_operable());
for kind in [
SwitchKind::Generic,
SwitchKind::Breaker,
SwitchKind::Disconnector,
SwitchKind::LoadBreakSwitch,
SwitchKind::DisconnectingCircuitBreaker,
SwitchKind::GroundDisconnector,
SwitchKind::Jumper,
SwitchKind::Cut,
SwitchKind::Fuse,
] {
assert!(kind.is_operable(), "{kind:?}");
}
}
#[test]
fn add_switch_returns_a_usable_index_and_counts_conducting() {
let mut topo = NodeBreakerTopology::new(4);
let a = topo.add_switch(switch(SwitchKind::Breaker, 0, 1, false, true));
let b = topo.add_switch(switch(SwitchKind::Disconnector, 1, 2, true, true));
assert_eq!((a, b), (SwitchIdx(0), SwitchIdx(1)));
assert_eq!(topo.conducting_count(), 1);
}
}