pub mod fortescue;
pub mod network;
pub mod solver;
use num_complex::Complex;
pub use fortescue::SequenceValue;
pub use network::{ScNetwork3Ph, ScSource};
pub use solver::{
BranchResult, FaultResult, NodeResult, ShortCircuitError, ShortCircuitReport, ShuntResult,
SourceResult, short_circuit,
};
pub(crate) const PHASE_ANG: [f64; 3] = [
0.0,
-2.0 * std::f64::consts::PI / 3.0,
2.0 * std::f64::consts::PI / 3.0,
];
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum FaultType {
ThreePhase,
SinglePhaseToGround,
TwoPhase,
TwoPhaseToGround,
}
impl FaultType {
pub fn from_code(code: i8) -> Result<Self, ShortCircuitError> {
match code {
0 => Ok(Self::ThreePhase),
1 => Ok(Self::SinglePhaseToGround),
2 => Ok(Self::TwoPhase),
3 => Ok(Self::TwoPhaseToGround),
other => Err(ShortCircuitError::UnknownFaultType(other)),
}
}
pub fn default_phase(self) -> FaultPhase {
match self {
Self::ThreePhase => FaultPhase::Abc,
Self::SinglePhaseToGround => FaultPhase::A,
Self::TwoPhase | Self::TwoPhaseToGround => FaultPhase::Bc,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum FaultPhase {
Abc,
A,
B,
C,
Ab,
Ac,
Bc,
}
impl FaultPhase {
pub fn from_code(code: i8) -> Result<Option<Self>, ShortCircuitError> {
match code {
-1 => Ok(None),
0 => Ok(Some(Self::Abc)),
1 => Ok(Some(Self::A)),
2 => Ok(Some(Self::B)),
3 => Ok(Some(Self::C)),
4 => Ok(Some(Self::Ab)),
5 => Ok(Some(Self::Ac)),
6 => Ok(Some(Self::Bc)),
other => Err(ShortCircuitError::UnknownFaultPhase(other)),
}
}
pub(crate) fn indices(self) -> (Option<usize>, Option<usize>) {
match self {
Self::Abc => (None, None),
Self::A => (Some(0), None),
Self::B => (Some(1), None),
Self::C => (Some(2), None),
Self::Ab => (Some(0), Some(1)),
Self::Ac => (Some(0), Some(2)),
Self::Bc => (Some(1), Some(2)),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub enum VoltageScaling {
#[default]
Maximum,
Minimum,
}
pub fn c_factor(u_rated: f64, scaling: VoltageScaling) -> f64 {
match scaling {
VoltageScaling::Maximum => 1.10,
VoltageScaling::Minimum if u_rated <= 1000.0 => 0.95,
VoltageScaling::Minimum => 1.00,
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum FaultAdmittance {
Bolted,
Finite(Complex<f64>),
}
#[derive(Clone, Copy, Debug)]
pub struct Fault {
pub id: u64,
pub bus: usize,
pub fault_type: FaultType,
pub fault_phase: FaultPhase,
pub y_fault: FaultAdmittance,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct ShortCircuitOptions {
pub scaling: VoltageScaling,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_voltage_scaling_factor_follows_the_iec_table() {
assert_eq!(c_factor(400.0, VoltageScaling::Maximum), 1.10);
assert_eq!(c_factor(150_000.0, VoltageScaling::Maximum), 1.10);
assert_eq!(c_factor(400.0, VoltageScaling::Minimum), 0.95);
assert_eq!(c_factor(1000.0, VoltageScaling::Minimum), 0.95);
assert_eq!(c_factor(1000.1, VoltageScaling::Minimum), 1.00);
assert_eq!(c_factor(150_000.0, VoltageScaling::Minimum), 1.00);
}
#[test]
fn two_phase_faults_default_to_bc() {
assert_eq!(FaultType::TwoPhase.default_phase(), FaultPhase::Bc);
assert_eq!(FaultType::TwoPhaseToGround.default_phase(), FaultPhase::Bc);
assert_eq!(FaultType::ThreePhase.default_phase(), FaultPhase::Abc);
assert_eq!(
FaultType::SinglePhaseToGround.default_phase(),
FaultPhase::A
);
}
#[test]
fn the_default_phase_sentinel_is_not_mistaken_for_a_phase() {
assert_eq!(FaultPhase::from_code(-1).unwrap(), None);
assert_eq!(FaultPhase::from_code(0).unwrap(), Some(FaultPhase::Abc));
assert!(FaultPhase::from_code(9).is_err());
}
#[test]
fn phase_indices_match_the_reference_mapping() {
assert_eq!(FaultPhase::Abc.indices(), (None, None));
assert_eq!(FaultPhase::A.indices(), (Some(0), None));
assert_eq!(FaultPhase::C.indices(), (Some(2), None));
assert_eq!(FaultPhase::Ab.indices(), (Some(0), Some(1)));
assert_eq!(FaultPhase::Ac.indices(), (Some(0), Some(2)));
assert_eq!(FaultPhase::Bc.indices(), (Some(1), Some(2)));
}
}