use std::collections::HashMap;
use powerio_core::ComponentId;
use crate::network::Load;
use crate::network::{
BalancedNetwork, BranchRatingSet, BusId, LoadingLimits, TapChangerKind,
TapChangerRegulationMode, TransformerControl, TransformerControlMode,
};
pub(super) fn current_limit_mva(current_a: f64, nominal_kv: f64) -> f64 {
current_a / 1000.0 * 3f64.sqrt() * nominal_kv
}
pub(super) fn sorted_current_rates(limits: &LoadingLimits, nominal_kv: f64) -> Vec<f64> {
let mut rates = limits
.permanent_limit
.into_iter()
.chain(limits.temporary_limits.iter().map(|limit| limit.value))
.filter(|value| value.is_finite() && *value > 0.0 && *value < f64::MAX)
.map(|value| current_limit_mva(value, nominal_kv))
.collect::<Vec<_>>();
rates.sort_by(f64::total_cmp);
rates
}
pub(super) fn rate_branches_from_current_limits(
net: &BalancedNetwork,
) -> Option<(BalancedNetwork, usize)> {
let detailed = net.detailed_connectivity().as_deref();
let bus_kv = net
.buses()
.iter()
.map(|bus| (bus.id, bus.base_kv))
.collect::<HashMap<BusId, f64>>();
let mut rated = Vec::new();
for (index, branch) in net.branches().iter().enumerate() {
if branch.rate_a != 0.0
|| branch.rate_b != 0.0
|| branch.rate_c != 0.0
|| !branch.rating_sets.is_empty()
{
continue;
}
let side_kv = |side: u8| {
let bus = if side == 1 { branch.from } else { branch.to };
bus_kv.get(&bus).copied().unwrap_or(0.0)
};
let exact = detailed.and_then(|detailed| {
let component = ComponentId::new("branch", branch.uid.as_deref()?).ok()?;
(1..=2).find_map(|side| {
detailed
.operational_limit_groups
.iter()
.filter(|group| {
group.equipment == component && group.terminal == side && group.selected
})
.find_map(|group| group.current_limits.as_ref())
.map(|limits| sorted_current_rates(limits, side_kv(side)))
.filter(|rates| !rates.is_empty())
})
});
let rates = exact.or_else(|| {
let ratings = branch.current_ratings?;
let limits = LoadingLimits {
permanent_limit: Some(ratings.c_rating_a),
permanent_limit_name: None,
temporary_limits: [ratings.c_rating_b, ratings.c_rating_c]
.into_iter()
.map(|value| crate::network::TemporaryLimit {
name: String::new(),
value,
acceptable_duration_seconds: 0,
fictitious: false,
})
.collect(),
};
Some(sorted_current_rates(&limits, side_kv(1))).filter(|rates| !rates.is_empty())
});
if let Some(rates) = rates {
rated.push((index, rates));
}
}
if rated.is_empty() {
return None;
}
let count = rated.len();
let mut projected = net.clone();
let branches = projected.branches_mut();
for (index, rates) in rated {
let branch = &mut branches[index];
let rate = |slot: usize| rates.get(slot).copied().unwrap_or(0.0);
branch.rate_a = rate(0);
branch.rate_b = rate(1);
branch.rate_c = rate(2);
branch.rating_sets = rates
.iter()
.enumerate()
.skip(3)
.map(|(slot, value)| BranchRatingSet::new(format!("RATE{}", slot + 1), *value))
.collect();
branch.current_ratings = None;
}
Some((projected, count))
}
const TAP_TOLERANCE: f64 = 1e-9;
#[derive(Debug, Clone, PartialEq)]
pub(super) struct TapControl {
pub kind: TapChangerKind,
pub steps: Vec<f64>,
pub position: usize,
pub inserted_present: bool,
pub regulation_mode: Option<TapChangerRegulationMode>,
pub regulating: bool,
pub regulation_value: Option<f64>,
pub target_deadband: Option<f64>,
}
pub(super) fn tap_control_from_psse(
control: &TransformerControl,
present: f64,
regulated_kv: f64,
) -> Option<TapControl> {
let kind = match control.mode {
TransformerControlMode::Fixed
| TransformerControlMode::Voltage
| TransformerControlMode::ReactiveFlow => TapChangerKind::Ratio,
TransformerControlMode::ActiveFlow => TapChangerKind::Phase,
_ => return None,
};
if control.ntp <= 1 || !control.tap_min.is_finite() || !control.tap_max.is_finite() {
return None;
}
let (low, high) = if control.tap_min <= control.tap_max {
(control.tap_min, control.tap_max)
} else {
(control.tap_max, control.tap_min)
};
let increment = (high - low) / f64::from(control.ntp - 1);
let mut steps = (0..control.ntp)
.map(|step| low + increment * f64::from(step))
.collect::<Vec<_>>();
let on_step = steps
.iter()
.position(|value| (value - present).abs() <= TAP_TOLERANCE);
let inserted_present = on_step.is_none();
let position = on_step.unwrap_or_else(|| {
let position = steps
.iter()
.position(|value| *value > present)
.unwrap_or(steps.len());
steps.insert(position, present);
position
});
let middle = f64::midpoint(control.band_min, control.band_max);
let width = control.band_max - control.band_min;
let (regulation_mode, regulation_value, target_deadband) = match control.mode {
TransformerControlMode::Voltage if regulated_kv > 0.0 => (
Some(TapChangerRegulationMode::Voltage),
Some(middle * regulated_kv),
Some(width * regulated_kv),
),
TransformerControlMode::ReactiveFlow => (
Some(TapChangerRegulationMode::ReactivePower),
Some(middle),
Some(width),
),
TransformerControlMode::ActiveFlow => (
Some(TapChangerRegulationMode::ActivePower),
Some(middle),
Some(width),
),
_ => (None, None, None),
};
let regulating = control.enabled
&& regulation_mode.is_some()
&& target_deadband.is_some_and(|deadband| deadband >= 0.0)
&& (regulation_mode != Some(TapChangerRegulationMode::Voltage)
|| regulation_value.is_some_and(|value| value > 0.0));
Some(TapControl {
kind,
steps,
position,
inserted_present,
regulation_mode,
regulating,
regulation_value,
target_deadband,
})
}
pub(super) fn psse_control_from_tap(
tap: &TapControl,
controlled_bus: Option<BusId>,
regulated_kv: f64,
) -> Option<TransformerControl> {
if tap.steps.len() <= 1 {
return None;
}
let mode = match (tap.kind, tap.regulation_mode) {
(TapChangerKind::Ratio, Some(TapChangerRegulationMode::Voltage)) => {
TransformerControlMode::Voltage
}
(TapChangerKind::Ratio, Some(TapChangerRegulationMode::ReactivePower)) => {
TransformerControlMode::ReactiveFlow
}
(TapChangerKind::Phase, Some(TapChangerRegulationMode::ActivePower)) => {
TransformerControlMode::ActiveFlow
}
_ => TransformerControlMode::Fixed,
};
let mut control = TransformerControl::new(mode);
control.enabled = mode != TransformerControlMode::Fixed && tap.regulating;
control.controlled_bus = controlled_bus;
control.tap_min = tap.steps.iter().copied().fold(f64::INFINITY, f64::min);
control.tap_max = tap.steps.iter().copied().fold(f64::NEG_INFINITY, f64::max);
control.ntp = u32::try_from(tap.steps.len()).unwrap_or(u32::MAX);
if let Some(value) = tap.regulation_value {
let deadband = tap.target_deadband.unwrap_or(0.0);
let scale = match mode {
TransformerControlMode::Voltage if regulated_kv > 0.0 => Some(regulated_kv),
TransformerControlMode::ReactiveFlow | TransformerControlMode::ActiveFlow => Some(1.0),
_ => None,
};
if let Some(scale) = scale {
control.band_min = (value - deadband / 2.0) / scale;
control.band_max = (value + deadband / 2.0) / scale;
}
}
Some(control)
}
pub(super) fn storage_as_loads(net: &BalancedNetwork) -> Option<(BalancedNetwork, usize)> {
if net.storage().is_empty() {
return None;
}
let mut projected = net.clone();
let storage = std::mem::take(projected.storage_mut());
let count = storage.len();
projected
.loads_mut()
.extend(storage.into_iter().map(|unit| {
let mut load = Load::new(unit.bus, -unit.ps, -unit.qs);
load.in_service = unit.in_service;
load
}));
Some((projected, count))
}