use std::collections::BTreeMap;
use powerio_matrix::BalancedNetwork;
const IMPEDANCE_QUANTUM: f64 = 1e-5;
const POWER_QUANTUM: f64 = 1e-2;
const BASE_MVA_QUANTUM: f64 = 1e-3;
fn quantize(x: f64, q: f64) -> i64 {
if x.is_finite() {
(x / q).round() as i64
} else {
i64::MIN
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize)]
pub struct Fingerprint {
pub buses: usize,
pub base_mva: i64,
pub degrees: Vec<usize>,
pub impedances: Vec<i64>,
pub hvdc: usize,
pub load_p: i64,
pub load_q: i64,
}
impl Fingerprint {
#[must_use]
pub fn of(net: &BalancedNetwork) -> Self {
let mut degree: BTreeMap<usize, usize> = net.buses.iter().map(|b| (b.id.0, 0)).collect();
let mut impedances = Vec::new();
for br in &net.branches {
*degree.entry(br.from.0).or_default() += 1;
*degree.entry(br.to.0).or_default() += 1;
impedances.push(quantize(br.r.hypot(br.x), IMPEDANCE_QUANTUM));
}
impedances.sort_unstable();
let mut degrees: Vec<usize> = degree.into_values().collect();
degrees.sort_unstable();
Self {
buses: net.buses.len(),
base_mva: quantize(net.base_mva, BASE_MVA_QUANTUM),
degrees,
impedances,
hvdc: net.hvdc.len(),
load_p: quantize(net.loads.iter().map(|l| l.p).sum(), POWER_QUANTUM),
load_q: quantize(net.loads.iter().map(|l| l.q).sum(), POWER_QUANTUM),
}
}
#[must_use]
pub fn of_distribution(net: &powerio_dist::MulticonductorNetwork) -> Self {
let mut counts: BTreeMap<String, usize> = BTreeMap::new();
for (a, b) in net
.lines
.iter()
.map(|l| (l.bus_from.as_str(), l.bus_to.as_str()))
.chain(
net.switches
.iter()
.map(|s| (s.bus_from.as_str(), s.bus_to.as_str())),
)
{
*counts.entry(a.to_string()).or_default() += 1;
*counts.entry(b.to_string()).or_default() += 1;
}
let mut degrees: Vec<usize> = counts.into_values().collect();
degrees.sort_unstable();
Self {
buses: net.buses.len(),
base_mva: 0,
degrees,
impedances: Vec::new(),
hvdc: 0,
load_p: quantize(
net.loads.iter().flat_map(|l| l.p_nom.iter()).sum(),
POWER_QUANTUM,
),
load_q: quantize(
net.loads.iter().flat_map(|l| l.q_nom.iter()).sum(),
POWER_QUANTUM,
),
}
}
#[must_use]
pub fn primary(&self) -> PrimaryKey {
PrimaryKey {
buses: self.buses,
base_mva: self.base_mva,
hvdc: self.hvdc,
degrees: self.degrees.clone(),
}
}
#[must_use]
pub fn agrees_with(&self, other: &Self) -> bool {
let close = |a: i64, b: i64, tolerance: i64| (a - b).abs() <= tolerance;
self.impedances.len() == other.impedances.len()
&& std::iter::zip(&self.impedances, &other.impedances)
.all(|(a, b)| close(*a, *b, slack(*a)))
&& close(self.load_p, other.load_p, slack(self.load_p))
&& close(self.load_q, other.load_q, slack(self.load_q))
}
}
fn slack(quantized: i64) -> i64 {
1.max(quantized.abs() / 10_000)
}
#[must_use]
pub fn same_data(a: &BalancedNetwork, b: &BalancedNetwork) -> bool {
let branches = |net: &BalancedNetwork| {
let mut v: Vec<[i64; 5]> = net
.branches
.iter()
.map(|br| {
[
quantize(br.r, IMPEDANCE_QUANTUM),
quantize(br.x, IMPEDANCE_QUANTUM),
quantize(br.b, IMPEDANCE_QUANTUM),
quantize(br.effective_tap(), IMPEDANCE_QUANTUM),
quantize(br.shift, IMPEDANCE_QUANTUM),
]
})
.collect();
v.sort_unstable();
v
};
let generators = |net: &BalancedNetwork| {
let mut v: Vec<[i64; 4]> = net
.generators
.iter()
.map(|g| {
[
quantize(g.pmax, POWER_QUANTUM),
quantize(g.pmin, POWER_QUANTUM),
quantize(g.qmax, POWER_QUANTUM),
quantize(g.qmin, POWER_QUANTUM),
]
})
.collect();
v.sort_unstable();
v
};
let loads = |net: &BalancedNetwork| {
let mut v: Vec<[i64; 2]> = net
.loads
.iter()
.map(|l| [quantize(l.p, POWER_QUANTUM), quantize(l.q, POWER_QUANTUM)])
.collect();
v.sort_unstable();
v
};
branches(a) == branches(b) && generators(a) == generators(b) && loads(a) == loads(b)
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct PrimaryKey {
pub buses: usize,
pub base_mva: i64,
pub hvdc: usize,
pub degrees: Vec<usize>,
}