use std::cell::RefCell;
use std::collections::HashMap;
use std::collections::hash_map::Entry;
use super::map::{BRANCH_DEVICE_TYPE, LINE_CIRCUIT, derive_bus_kinds};
use crate::network::{
Branch, Bus, BusId, BusType, Extras, Generator, Load, Network, Shunt, SourceFormat,
};
use crate::{Error, Result};
const FMT: &str = "PowerWorld .pwb";
const MVA_BASE: f64 = 100.0;
const RESYNC_WINDOW: usize = 1024;
type Probe<T> = std::result::Result<T, &'static str>;
pub fn parse_pwb(bytes: &[u8], name_hint: Option<&str>) -> Result<Network> {
let header_constant = expect_header(bytes)?;
reject_unsupported_vintage(bytes)?;
let gen_variants = match header_constant {
338 | 368 | 425 => GenVariants {
plain: true,
reg: false,
simple_reg: false,
},
508 => GenVariants {
plain: true,
reg: true,
simple_reg: false,
},
554 => GenVariants {
plain: false,
reg: false,
simple_reg: true,
},
_ => GenVariants {
plain: false,
reg: true,
simple_reg: false,
},
};
let branch_count_can_include_trailer = header_constant == 554;
let bus_runs = RefCell::new(HashMap::new());
match search_table_chain(
bytes,
name_hint,
gen_variants,
branch_count_can_include_trailer,
&bus_runs,
false,
) {
Some(net) => net,
None => search_table_chain(
bytes,
name_hint,
gen_variants,
branch_count_can_include_trailer,
&bus_runs,
true,
)
.unwrap_or_else(|| {
Err(Error::FormatRead {
format: FMT,
message: "no table chain matches the validated .pwb layouts \
(buses, loads, generators, shunts, branches in sequence)"
.into(),
})
}),
}
}
#[derive(Clone, Copy)]
struct GenVariants {
plain: bool,
reg: bool,
simple_reg: bool,
}
#[expect(clippy::too_many_lines)]
fn search_table_chain(
bytes: &[u8],
name_hint: Option<&str>,
gen_variants: GenVariants,
branch_count_can_include_trailer: bool,
bus_runs: &RefCell<BusRuns>,
wide_bus_glue: bool,
) -> Option<Result<Network>> {
for (buses, bus_shunts, bus_end, last_bus_unk) in
bus_table_candidates(bytes, bus_runs, wide_bus_glue)
{
let Some(bus_ids) = BusIdSet::new(&buses) else {
continue; };
let mut best = None;
let bus_names = bus_name_map(&buses);
let load_runs = RefCell::new(HashMap::new());
let gen_runs = RefCell::new(HashMap::new());
let gen_reg_runs = RefCell::new(HashMap::new());
let gen_reg_simple_runs = RefCell::new(HashMap::new());
let shunt_runs = RefCell::new(HashMap::new());
let branch_runs = RefCell::new(HashMap::new());
let load_scan_end = resync_end(bytes, bus_end, last_bus_unk & 0x10 != 0);
for (loads, l_end) in device_table_candidates(
bytes,
bus_end..load_scan_end,
&bus_ids,
read_load_record,
&load_runs,
128,
12,
) {
let gen_candidates = gen_variants
.plain
.then(|| {
device_table_candidates(
bytes,
l_end..l_end.saturating_add(RESYNC_WINDOW),
&bus_ids,
read_gen_record,
&gen_runs,
128,
32,
)
})
.into_iter()
.flatten()
.chain(
gen_variants
.reg
.then(|| {
device_table_candidates(
bytes,
l_end..l_end.saturating_add(RESYNC_WINDOW),
&bus_ids,
read_gen_reg_record,
&gen_reg_runs,
128,
40,
)
})
.into_iter()
.flatten(),
)
.chain(
gen_variants
.simple_reg
.then(|| {
device_table_candidates(
bytes,
l_end..l_end.saturating_add(RESYNC_WINDOW),
&bus_ids,
read_gen_reg_simple_record,
&gen_reg_simple_runs,
128,
40,
)
})
.into_iter()
.flatten(),
);
for (generators, g_end) in gen_candidates {
if gen_table_continues(bytes, g_end, &bus_ids, gen_variants) {
continue;
}
if !bus_shunts.is_empty() {
if let Some(branches) = find_branch_table(
bytes,
g_end,
&bus_ids,
&bus_names,
&branch_runs,
branch_count_can_include_trailer,
) {
keep_best_chain(
&mut best,
chain_score(&loads, &bus_shunts, &branches, &generators),
checked_network(
name_hint,
buses.clone(),
loads.clone(),
bus_shunts.clone(),
branches,
generators.clone(),
),
);
}
}
for (shunts, s_end) in device_table_candidates(
bytes,
g_end..g_end.saturating_add(RESYNC_WINDOW),
&bus_ids,
read_shunt_record,
&shunt_runs,
48,
28,
) {
let Some(branches) = find_branch_table(
bytes,
s_end,
&bus_ids,
&bus_names,
&branch_runs,
branch_count_can_include_trailer,
) else {
continue;
};
let mut shunts = shunts;
extend_unique_shunts(&mut shunts, &bus_shunts);
keep_best_chain(
&mut best,
chain_score(&loads, &shunts, &branches, &generators),
checked_network(
name_hint,
buses.clone(),
loads.clone(),
shunts,
branches,
generators.clone(),
),
);
}
if let Some(branches) = find_branch_table(
bytes,
g_end,
&bus_ids,
&bus_names,
&branch_runs,
branch_count_can_include_trailer,
) {
keep_best_chain(
&mut best,
chain_score(&loads, &bus_shunts, &branches, &generators),
checked_network(
name_hint,
buses.clone(),
loads.clone(),
bus_shunts.clone(),
branches,
generators.clone(),
),
);
}
}
}
if let Some((_, net)) = best {
return Some(net);
}
}
None
}
fn keep_best_chain(
best: &mut Option<(usize, Result<Network>)>,
score: usize,
net: Result<Network>,
) {
let candidate_ok = net.is_ok();
let replace = match best.as_ref() {
None => true,
Some((best_score, best_net)) => match (best_net.is_ok(), candidate_ok) {
(false, true) => true,
(true, false) => false,
_ => score > *best_score,
},
};
if replace {
*best = Some((score, net));
}
}
fn chain_score(
loads: &[Load],
shunts: &[Shunt],
branches: &[Branch],
generators: &[Generator],
) -> usize {
loads.len() + shunts.len() + branches.len() + generators.len()
}
fn extend_unique_shunts(shunts: &mut Vec<Shunt>, extra: &[Shunt]) {
for shunt in extra {
if !shunts.iter().any(|existing| {
existing.bus == shunt.bus
&& (existing.g - shunt.g).abs() <= 1e-9
&& (existing.b - shunt.b).abs() <= 1e-9
}) {
shunts.push(shunt.clone());
}
}
}
fn gen_table_continues(
bytes: &[u8],
after: usize,
bus_ids: &BusIdSet,
variants: GenVariants,
) -> bool {
(after..after.saturating_add(RESYNC_WINDOW).min(bytes.len())).any(|p| {
(variants.plain && read_gen_record(bytes, p, bus_ids).is_ok())
|| (variants.reg && read_gen_reg_record(bytes, p, bus_ids).is_ok())
|| (variants.simple_reg && read_gen_reg_simple_record(bytes, p, bus_ids).is_ok())
})
}
fn checked_network(
name_hint: Option<&str>,
mut buses: Vec<Bus>,
loads: Vec<Load>,
shunts: Vec<Shunt>,
branches: Vec<Branch>,
generators: Vec<Generator>,
) -> Result<Network> {
derive_bus_kinds(&mut buses, &generators);
let net = Network {
name: name_hint.unwrap_or("case").to_string(),
base_mva: MVA_BASE,
buses,
loads,
shunts,
branches,
generators,
storage: Vec::new(),
hvdc: Vec::new(),
source_format: SourceFormat::PowerWorldBinary,
source: None,
};
net.check_references(FMT).map(|()| net)
}
struct Cur<'a> {
b: &'a [u8],
pos: usize,
}
impl<'a> Cur<'a> {
fn take(&mut self, n: usize) -> Probe<&'a [u8]> {
let end = self.pos.checked_add(n).ok_or("truncated record")?;
let s = self.b.get(self.pos..end).ok_or("truncated record")?;
self.pos = end;
Ok(s)
}
fn u8(&mut self) -> Probe<u8> {
Ok(self.take(1)?[0])
}
fn u16(&mut self) -> Probe<u16> {
Ok(u16::from_le_bytes(self.take(2)?.try_into().unwrap()))
}
fn u32(&mut self) -> Probe<u32> {
Ok(u32::from_le_bytes(self.take(4)?.try_into().unwrap()))
}
fn f32(&mut self) -> Probe<f64> {
Ok(f64::from(f32::from_le_bytes(
self.take(4)?.try_into().unwrap(),
)))
}
fn f64(&mut self) -> Probe<f64> {
Ok(f64::from_le_bytes(self.take(8)?.try_into().unwrap()))
}
fn string(&mut self, max: usize) -> Probe<&'a [u8]> {
let n = self.u32()? as usize;
if n > max {
return Err("string length exceeds the field maximum");
}
let s = self.take(n)?;
if !printable(s) {
return Err("string has non printable bytes");
}
Ok(s)
}
fn short_string(&mut self, max: usize) -> Probe<&'a [u8]> {
let n = self.u8()? as usize;
if n > max {
return Err("device ID length exceeds the field maximum");
}
let s = self.take(n)?;
if !printable(s) {
return Err("device ID has non printable bytes");
}
Ok(s)
}
fn short_string_2(&mut self) -> Probe<&'a [u8]> {
let n = self.u8()? as usize;
if n == 0 || n > 2 {
return Err("fixed capacity ID length not 1 or 2");
}
let text = self.take(2)?;
if !printable(&text[..n]) {
return Err("fixed capacity ID has non printable bytes");
}
Ok(&text[..n])
}
}
const BLOB_WINDOW: usize = 4 << 20;
fn resync_end(b: &[u8], after: usize, prev_bit4: bool) -> usize {
if prev_bit4 {
after.saturating_add(BLOB_WINDOW).min(b.len())
} else {
after.saturating_add(RESYNC_WINDOW).min(b.len())
}
}
fn printable(s: &[u8]) -> bool {
s.iter().all(|&c| (0x20..0x7f).contains(&c))
}
fn slice_at(b: &[u8], at: usize, n: usize) -> Option<&[u8]> {
at.checked_add(n).and_then(|end| b.get(at..end))
}
fn checked_offset(at: usize, add: usize) -> Probe<usize> {
at.checked_add(add).ok_or("truncated record")
}
fn count_fits(b: &[u8], first: usize, count: usize, min_record_len: usize) -> bool {
let Some(remaining) = b.len().checked_sub(first) else {
return false;
};
count
.checked_mul(min_record_len)
.is_some_and(|min_bytes| min_bytes <= remaining)
}
fn u32_at(b: &[u8], at: usize) -> Probe<u32> {
slice_at(b, at, 4)
.and_then(|s| <[u8; 4]>::try_from(s).ok())
.map(u32::from_le_bytes)
.ok_or("truncated record")
}
fn f32_at(b: &[u8], at: usize) -> Probe<f64> {
slice_at(b, at, 4)
.and_then(|s| <[u8; 4]>::try_from(s).ok())
.map(f32::from_le_bytes)
.map(f64::from)
.ok_or("truncated record")
}
fn string_at(b: &[u8], at: usize, max: usize) -> Probe<String> {
let n = u32_at(b, at)? as usize;
if n > max {
return Err("string length exceeds the field maximum");
}
let s = slice_at(b, checked_offset(at, 4)?, n).ok_or("truncated record")?;
if !printable(s) {
return Err("string has non printable bytes");
}
Ok(String::from_utf8_lossy(s).into_owned())
}
fn expect_header(b: &[u8]) -> Result<u64> {
const DECODED: [u64; 9] = [338, 368, 425, 483, 508, 537, 550, 551, 554];
let bad = || Error::FormatRead {
format: FMT,
message: "not a recognized PowerWorld binary case (header magic mismatch); \
only the validated .pwb layouts are read"
.into(),
};
if b.len() < 0x40 {
return Err(bad());
}
let word = |i: usize| u64::from_le_bytes(b[i * 8..i * 8 + 8].try_into().unwrap());
let (a, v, c) = (word(0), word(1), word(2));
if a != 15000 {
return Err(bad());
}
if c != 20 || !DECODED.contains(&v) {
return Err(unsupported_vintage(format!(
"header format words ({v}, {c}); the decoded eras are \
338/368/425/483/508/537/550/551/554 with 20"
)));
}
Ok(v)
}
fn reject_unsupported_vintage(b: &[u8]) -> Result<()> {
let scan = b.len().min(0x10000).saturating_sub(8);
let mut heads = 0usize;
let mut at = 0x20;
while at < scan {
let Ok((_, after)) = read_bus_head(b, at) else {
at += 1;
continue;
};
heads += 1;
if heads >= 2 {
return Ok(());
}
at = after;
}
Err(unsupported_vintage(
"no recognized bus record layout in the leading 64 KiB",
))
}
fn unsupported_vintage(detail: impl std::fmt::Display) -> Error {
Error::FormatRead {
format: FMT,
message: format!(
"unsupported PowerWorld .pwb vintage: {detail}; only the validated \
338/368/425/483/508/537/550/551/554 layouts are decoded (see docs/powerworld.md)"
),
}
}
enum BusIdSet {
Bitmap(Vec<u64>),
Sparse(Vec<usize>),
}
impl BusIdSet {
fn new(buses: &[Bus]) -> Option<Self> {
let max = buses.iter().map(|b| b.id.0).max().unwrap_or(0);
let words = max / 64 + 1;
if words > (buses.len() * 4).max(1024) {
let mut ids: Vec<usize> = buses.iter().map(|b| b.id.0).collect();
ids.sort_unstable();
if ids.windows(2).any(|w| w[0] == w[1]) {
return None;
}
return Some(Self::Sparse(ids));
}
let mut bits = vec![0u64; words];
for bus in buses {
let (w, bit) = (bus.id.0 / 64, 1u64 << (bus.id.0 % 64));
if bits[w] & bit != 0 {
return None;
}
bits[w] |= bit;
}
Some(Self::Bitmap(bits))
}
#[inline]
fn contains(&self, id: usize) -> bool {
match self {
Self::Bitmap(words) => words
.get(id / 64)
.is_some_and(|w| w & (1 << (id % 64)) != 0),
Self::Sparse(ids) => ids.binary_search(&id).is_ok(),
}
}
}
fn bus_name_map(buses: &[Bus]) -> HashMap<String, BusId> {
buses
.iter()
.filter_map(|bus| {
bus.name
.as_ref()
.map(|name| (name.trim().to_ascii_uppercase(), bus.id))
})
.collect()
}
struct Run<T> {
items: Vec<T>,
ends: Vec<usize>,
dead: bool,
}
impl<T: Clone> Run<T> {
fn start(item: T, end: usize) -> Self {
Run {
items: vec![item],
ends: vec![end],
dead: false,
}
}
fn prefix(
&mut self,
count: usize,
mut next: impl FnMut(usize, &T) -> Option<(T, usize)>,
) -> Option<(Vec<T>, usize)> {
if count == 0 {
return None; }
while !self.dead && self.items.len() < count {
let after = *self.ends.last().unwrap();
match next(after, self.items.last().unwrap()) {
Some((item, end)) => {
self.items.push(item);
self.ends.push(end);
}
None => self.dead = true,
}
}
(self.items.len() >= count).then(|| (self.items[..count].to_vec(), self.ends[count - 1]))
}
}
struct BusHead {
bus: Bus,
shunt: Option<Shunt>,
unk: u32,
}
fn known_bus_flags(unk: u32) -> bool {
unk & !0x3571 == 0x06
}
fn bus_family(unk: u32) -> u32 {
unk & !0x3551
}
type BusRunItem = (Bus, u32, Option<Shunt>);
type BusRuns = HashMap<usize, (Run<BusRunItem>, u32)>;
fn bus_table_candidates<'a>(
b: &'a [u8],
runs: &'a RefCell<BusRuns>,
wide_glue: bool,
) -> impl Iterator<Item = (Vec<Bus>, Vec<Shunt>, usize, u32)> + 'a {
let limit = b.len().saturating_sub(4).min(0x10000);
(0x20..limit).flat_map(move |at| {
let count = u32::from_le_bytes(b[at..at + 4].try_into().unwrap()) as usize;
let glues = if wide_glue {
(count != 0 && count < 256).then_some(49..=96)
} else {
let max_glue = if count >= 256 { 96 } else { 48 };
(count != 0 && count <= 2_000_000).then_some(0..=max_glue)
};
glues
.into_iter()
.flatten()
.filter_map(move |glue| {
let first = at.checked_add(4)?.checked_add(glue)?;
count_fits(b, first, count, 32)
.then(|| bus_run(b, runs, first, count))
.flatten()
})
.map(|(heads, end)| {
let last_unk = heads.last().map_or(0, |(_, unk, _)| *unk);
let shunts = heads
.iter()
.filter_map(|(bus, _, shunt)| {
shunt.clone().map(|mut shunt| {
shunt.bus = bus.id;
shunt
})
})
.collect();
(
heads.into_iter().map(|(bus, _, _)| bus).collect(),
shunts,
end,
last_unk,
)
})
})
}
fn bus_run(
b: &[u8],
runs: &RefCell<BusRuns>,
first: usize,
count: usize,
) -> Option<(Vec<BusRunItem>, usize)> {
let mut map = runs.borrow_mut();
let (run, family) = match map.entry(first) {
Entry::Occupied(e) => e.into_mut(),
Entry::Vacant(e) => {
let (head, end) = read_bus_head(b, first).ok()?;
let family = bus_family(head.unk);
e.insert((Run::start((head.bus, head.unk, head.shunt), end), family))
}
};
let family = *family;
run.prefix(count, |after, prev| {
(after..resync_end(b, after, prev.1 & 0x10 != 0)).find_map(|p| {
read_bus_head(b, p)
.ok()
.filter(|(h, _)| bus_family(h.unk) == family)
.map(|(h, end)| ((h.bus, h.unk, h.shunt), end))
})
})
}
fn read_bus_head(b: &[u8], at: usize) -> Probe<(BusHead, usize)> {
let mut c = Cur { b, pos: at };
let num = c.u32()? as usize;
if num == 0 || num > 99_999_999 {
return Err("implausible bus number");
}
let name_len = c.u32()? as usize;
if name_len == 0 {
return Err("empty bus name");
}
if name_len > 64 {
return Err("string length exceeds the field maximum");
}
let name = c.take(name_len)?;
let unk = c.u32()?;
if !known_bus_flags(unk) {
return Err("bus record flags not in the validated set");
}
if !printable(name) {
return Err("string has non printable bytes");
}
if unk & 1 == 0 {
let _extra = c.u16()?;
}
let kv = c.f32()?;
if !kv.is_finite() || !(0.0..=10_000.0).contains(&kv) {
return Err("implausible nominal kV");
}
let area = c.u32()? as usize;
let zone = c.u32()? as usize;
if area > 100_000_000 || zone > 100_000_000 {
return Err("implausible area/zone/BA number");
}
let after_zone = c.pos;
let mut with_ba = Cur { b, pos: after_zone };
let with_ba_result = (|| -> Probe<(f64, f64)> {
let ba = with_ba.u32()?;
if ba > 100_000_000 {
return Err("implausible area/zone/BA number");
}
read_bus_label_and_solution(&mut with_ba)
})();
let (vm, va_rad) = if let Ok(solution) = with_ba_result {
c.pos = with_ba.pos;
solution
} else {
let mut old = Cur { b, pos: after_zone };
let solution = read_bus_label_and_solution(&mut old)?;
c.pos = old.pos;
solution
};
if !vm.is_finite() || !(0.0..=10.0).contains(&vm) || !va_rad.is_finite() || va_rad.abs() > 100.0
{
return Err("implausible voltage solution");
}
let bus = Bus {
id: BusId(num),
kind: BusType::Pq,
vm,
va: va_rad.to_degrees(),
base_kv: kv,
vmax: 1.1,
vmin: 0.9,
area,
zone,
name: Some(String::from_utf8_lossy(name).into_owned()),
extras: Extras::new(),
};
let shunt = bus_tail_shunt(b, c.pos, BusId(num));
Ok((BusHead { bus, shunt, unk }, c.pos))
}
fn bus_tail_shunt(b: &[u8], after_head: usize, bus: BusId) -> Option<Shunt> {
let g_pu = b
.get(after_head.checked_add(1)?..after_head.checked_add(5)?)
.and_then(|s| <[u8; 4]>::try_from(s).ok())
.map(f32::from_le_bytes)
.map(f64::from)
.filter(|g| g.is_finite() && g.abs() <= 1.0e6)
.unwrap_or(0.0);
let b_pu = b
.get(after_head.checked_add(5)?..after_head.checked_add(9)?)
.and_then(|s| <[u8; 4]>::try_from(s).ok())
.map(f32::from_le_bytes)
.map(f64::from)?;
if !b_pu.is_finite() || b_pu.abs() > 1.0e6 || (g_pu.abs() <= 1e-9 && b_pu.abs() <= 1e-9) {
return None;
}
let mut extras = Extras::new();
extras.insert(
"ShuntID".into(),
serde_json::Value::String("BusShunt".into()),
);
Some(Shunt {
bus,
g: g_pu * MVA_BASE,
b: b_pu * MVA_BASE,
in_service: true,
extras,
})
}
fn read_bus_label_and_solution(c: &mut Cur<'_>) -> Probe<(f64, f64)> {
let _label = c.string(64)?;
let vm = c.f64()?;
let va_rad = c.f64()?;
Ok((vm, va_rad))
}
trait ReadRecord<T>: Fn(&[u8], usize, &BusIdSet) -> Probe<(T, usize)> + Copy {}
impl<T, F: Fn(&[u8], usize, &BusIdSet) -> Probe<(T, usize)> + Copy> ReadRecord<T> for F {}
fn read_device_head<T>(
b: &[u8],
at: usize,
bus_ids: &BusIdSet,
read: fn(&mut Cur, BusId, &[u8]) -> Probe<T>,
) -> Probe<(T, usize)> {
let mut c = Cur { b, pos: at };
let bus = c.u32()? as usize;
if !bus_ids.contains(bus) {
return Err("record references an unknown bus");
}
let id = c.short_string(8)?;
if id.is_empty() {
return Err("empty device ID");
}
let v = read(&mut c, BusId(bus), id)?;
Ok((v, c.pos))
}
fn read_load_record(b: &[u8], at: usize, bus_ids: &BusIdSet) -> Probe<(Load, usize)> {
read_device_head(b, at, bus_ids, read_load)
}
fn read_gen_record(b: &[u8], at: usize, bus_ids: &BusIdSet) -> Probe<(Generator, usize)> {
read_device_head(b, at, bus_ids, read_gen)
}
fn read_shunt_record(b: &[u8], at: usize, bus_ids: &BusIdSet) -> Probe<(Shunt, usize)> {
read_device_head(b, at, bus_ids, read_shunt)
}
fn device_table_candidates<'a, T: Clone + 'a>(
b: &'a [u8],
scan: std::ops::Range<usize>,
bus_ids: &'a BusIdSet,
read: impl ReadRecord<T> + 'a,
runs: &'a RefCell<HashMap<usize, Run<T>>>,
max_glue: usize,
min_record_len: usize,
) -> impl Iterator<Item = (Vec<T>, usize)> + 'a {
let limit = scan.end.min(b.len().saturating_sub(4));
(scan.start..limit).flat_map(move |at| {
let count = u32::from_le_bytes(b[at..at + 4].try_into().unwrap()) as usize;
let glues = (count != 0 && count <= 10_000_000).then_some(0..=max_glue);
glues.into_iter().flatten().filter_map(move |glue| {
let first = at.checked_add(4)?.checked_add(glue)?;
count_fits(b, first, count, min_record_len)
.then(|| device_run(b, runs, first, count, bus_ids, read))
.flatten()
})
})
}
fn device_run<T: Clone>(
b: &[u8],
runs: &RefCell<HashMap<usize, Run<T>>>,
first: usize,
count: usize,
bus_ids: &BusIdSet,
read: impl ReadRecord<T>,
) -> Option<(Vec<T>, usize)> {
let mut map = runs.borrow_mut();
let run = match map.entry(first) {
Entry::Occupied(e) => e.into_mut(),
Entry::Vacant(e) => {
let (item, end) = read(b, first, bus_ids).ok()?;
e.insert(Run::start(item, end))
}
};
run.prefix(count, |after, _| {
(after..after.saturating_add(RESYNC_WINDOW).min(b.len()))
.find_map(|p| read(b, p, bus_ids).ok())
})
}
fn read_load(c: &mut Cur, bus: BusId, id: &[u8]) -> Probe<Load> {
let record_start = c.pos - (4 + 1) - id.len(); let flag = c.u8()?;
if flag > 1 {
return Err("load status byte not in the validated set");
}
let mut p = c.f32()? * MVA_BASE;
let mut q = c.f32()? * MVA_BASE;
let mut in_service = true;
if flag == 0 && p.abs() < 1e-30 && q.abs() < 1e-30 {
let early_p = f32_at(c.b, checked_offset(record_start, 25)?)? * MVA_BASE;
let early_q = f32_at(c.b, checked_offset(record_start, 29)?)? * MVA_BASE;
let late_p = f32_at(c.b, checked_offset(record_start, 33)?)? * MVA_BASE;
let late_q = f32_at(c.b, checked_offset(record_start, 37)?)? * MVA_BASE;
let early_is_marker = (early_p - MVA_BASE).abs() <= 1e-6 && early_q.abs() <= 1e-30;
let (alt_p, alt_q, end) = if early_is_marker {
(late_p, late_q, checked_offset(record_start, 41)?)
} else {
(early_p, early_q, checked_offset(record_start, 33)?)
};
if alt_p.abs() > 1e-30 || alt_q.abs() > 1e-30 {
p = alt_p;
q = alt_q;
in_service = !early_is_marker;
}
c.pos = c.pos.max(end);
}
if !p.is_finite() || !q.is_finite() || p.abs() > 1.0e6 || q.abs() > 1.0e6 {
return Err("implausible load power");
}
let mut extras = Extras::new();
extras.insert(
"LoadID".into(),
serde_json::Value::String(String::from_utf8_lossy(id).into_owned()),
);
Ok(Load {
bus,
p,
q,
in_service,
extras,
})
}
fn read_gen(c: &mut Cur, bus: BusId, id: &[u8]) -> Probe<Generator> {
let record_start = c.pos - (4 + 1) - id.len(); let mut chosen = None;
for anchor in [9usize, 10, 11, 12] {
let mut probe = Cur {
b: c.b,
pos: checked_offset(record_start, anchor)?,
};
if let Ok(vals) = read_gen_f32_block(&mut probe) {
chosen = Some((vals, probe.pos));
break;
}
}
let Some((v, end)) = chosen else {
return Err("generator record does not match the validated layouts");
};
c.pos = end;
Ok(gen_from_block(bus, &v, true))
}
fn read_gen_f32_block(c: &mut Cur) -> Probe<[f64; 8]> {
let mut v = [0.0f64; 8];
for (i, slot) in v.iter_mut().enumerate() {
let x = c.f32()?;
if !x.is_finite() || x.abs() >= 1.0e6 {
return Err("generator record does not match the validated layouts");
}
if (i == 4 && !(0.5..=1.6).contains(&x)) || (i == 5 && !(0.1..=1.0e5).contains(&x)) {
return Err("generator record does not match the validated layouts");
}
*slot = x;
}
Ok(v)
}
fn gen_from_block(bus: BusId, v: &[f64; 8], in_service: bool) -> Generator {
Generator {
bus,
pg: v[0] * MVA_BASE,
qg: v[1] * MVA_BASE,
qmax: v[2] * MVA_BASE,
qmin: v[3] * MVA_BASE,
vg: v[4],
mbase: v[5],
pmax: v[6] * MVA_BASE,
pmin: v[7] * MVA_BASE,
in_service,
cost: None,
caps: Default::default(),
}
}
fn read_gen_reg_record(b: &[u8], at: usize, bus_ids: &BusIdSet) -> Probe<(Generator, usize)> {
let mut c = Cur { b, pos: at };
let bus = c.u32()? as usize;
if !bus_ids.contains(bus) {
return Err("record references an unknown bus");
}
let reg = c.u32()? as usize;
if !bus_ids.contains(reg) {
return Err("regulated bus is not a known bus");
}
let _id = c.short_string_2()?;
if c.u8()? != 1 {
return Err("generator record lead byte not 1");
}
let _ = c.u8()?; let pres = c.u8()?;
if pres & !0x23 != 0 {
return Err("generator presence byte not in the validated set");
}
if pres & 0x22 == 0x22 {
return Err("generator presence bits 1 and 5 together are unobserved");
}
for bit in [0x01, 0x20] {
if pres & bit != 0 {
let v = c.f32()?;
if !v.is_finite() || v.abs() > 1.0e6 {
return Err("implausible presence gated generator value");
}
}
}
if pres & 2 != 0 {
let _ = c.u8()?;
}
let v = read_gen_f32_block(&mut c)?;
read_gen_reg_tail(&mut c, bus, &v)
}
fn read_gen_reg_simple_record(
b: &[u8],
at: usize,
bus_ids: &BusIdSet,
) -> Probe<(Generator, usize)> {
let mut c = Cur { b, pos: at };
let bus = c.u32()? as usize;
if !bus_ids.contains(bus) {
return Err("record references an unknown bus");
}
let reg = c.u32()? as usize;
if !bus_ids.contains(reg) {
return Err("regulated bus is not a known bus");
}
let _id = c.short_string_2()?;
if c.u8()? != 0 || c.u8()? != 0 {
return Err("generator record separator bytes not zero");
}
let v = read_gen_f32_block(&mut c)?;
read_gen_reg_tail(&mut c, bus, &v)
}
fn read_gen_reg_tail(c: &mut Cur<'_>, bus: usize, v: &[f64; 8]) -> Probe<(Generator, usize)> {
if c.u8()? != 0 {
return Err("generator record separator byte not zero");
}
let status = c.u8()?;
if status & !0x01 != 0x08 {
return Err("generator status byte not in the validated set");
}
let rmpct = c.f32()?;
if !rmpct.is_finite() || !(0.0..=1000.0).contains(&rmpct) {
return Err("implausible remote regulation percentage");
}
Ok((gen_from_block(BusId(bus), v, status & 1 == 1), c.pos))
}
fn read_shunt(c: &mut Cur, bus: BusId, id: &[u8]) -> Probe<Shunt> {
let record_start = c.pos - (4 + 1) - id.len(); let mut probe = Cur {
b: c.b,
pos: checked_offset(record_start, 24)?,
};
let b_mvar = probe.f32()? * MVA_BASE;
if !b_mvar.is_finite() || b_mvar.abs() > 1.0e6 {
return Err("implausible shunt MVAr");
}
c.pos = probe.pos;
let mut extras = Extras::new();
extras.insert(
"ShuntID".into(),
serde_json::Value::String(String::from_utf8_lossy(id).into_owned()),
);
Ok(Shunt {
bus,
g: 0.0,
b: b_mvar,
in_service: true,
extras,
})
}
fn known_branch_flags(flags: u16) -> bool {
flags & !0x44B3 == 0x004C
}
fn find_branch_table(
b: &[u8],
from: usize,
bus_ids: &BusIdSet,
bus_names: &HashMap<String, BusId>,
runs: &RefCell<HashMap<usize, Run<(Branch, u16)>>>,
count_can_include_trailer: bool,
) -> Option<Vec<Branch>> {
let limit = from
.saturating_add(RESYNC_WINDOW * 2)
.min(b.len().saturating_sub(4));
for at in from..limit {
let count = u32::from_le_bytes(b[at..at + 4].try_into().unwrap()) as usize;
if count == 0 || count > 10_000_000 {
continue;
}
let Some(first) = (at.saturating_add(4)..at.saturating_add(64).min(b.len()))
.find(|&p| read_branch_head(b, p, bus_ids, bus_names).is_ok())
else {
continue;
};
let counts = [
Some(count),
(count_can_include_trailer && count > 1).then_some(count - 1),
];
for effective_count in counts.into_iter().flatten() {
if !count_fits(b, at.saturating_add(4), effective_count, 24) {
continue;
}
if let Some((branches, after)) =
branch_run(b, runs, first, effective_count, bus_ids, bus_names)
{
let last_bit4 = branches.last().is_some_and(|(_, flags)| flags & 0x10 != 0);
let continues = (after..resync_end(b, after, last_bit4))
.any(|p| read_branch_head(b, p, bus_ids, bus_names).is_ok());
if !continues {
return Some(branches.into_iter().map(|(br, _)| br).collect());
}
}
}
}
None
}
fn branch_run(
b: &[u8],
runs: &RefCell<HashMap<usize, Run<(Branch, u16)>>>,
first: usize,
count: usize,
bus_ids: &BusIdSet,
bus_names: &HashMap<String, BusId>,
) -> Option<(Vec<(Branch, u16)>, usize)> {
let mut map = runs.borrow_mut();
let run = match map.entry(first) {
Entry::Occupied(e) => e.into_mut(),
Entry::Vacant(e) => {
let (br, end, flags) = read_branch_head(b, first, bus_ids, bus_names).ok()?;
e.insert(Run::start((br, flags), end))
}
};
run.prefix(count, |after, prev| {
(after..resync_end(b, after, prev.1 & 0x10 != 0)).find_map(|p| {
read_branch_head(b, p, bus_ids, bus_names)
.ok()
.map(|(br, end, flags)| ((br, flags), end))
})
})
}
#[allow(clippy::many_single_char_names)] fn read_branch_head(
b: &[u8],
at: usize,
bus_ids: &BusIdSet,
bus_names: &HashMap<String, BusId>,
) -> Probe<(Branch, usize, u16)> {
read_step_up_transformer_head(b, at, bus_ids, bus_names)
.or_else(|_| read_standard_branch_head(b, at, bus_ids))
}
#[allow(clippy::many_single_char_names)] fn read_standard_branch_head(
b: &[u8],
at: usize,
bus_ids: &BusIdSet,
) -> Probe<(Branch, usize, u16)> {
let mut c = Cur { b, pos: at };
let from = branch_endpoint(&mut c)?;
let to = branch_endpoint(&mut c)?;
if !bus_ids.contains(from) || !bus_ids.contains(to) || from == to {
return Err("branch references unknown buses");
}
let flags = c.u16()?;
if !known_branch_flags(flags) {
return Err("branch record flags not in the validated set");
}
let circuit = if flags & 1 == 0 {
Some(c.short_string_2()?)
} else {
None
};
let r = c.f32()?;
let x = c.f32()?;
let b_chg = c.f32()?;
for v in [r, x, b_chg] {
if !v.is_finite() || v.abs() > 1.0e4 {
return Err("implausible branch impedance");
}
}
let _g = c.f32()?;
let inline = if flags & 2 == 0 { 3 } else { 2 };
let mut rates = [0.0f64; 3];
for slot in rates.iter_mut().take(inline) {
let v = c.f32()?;
if !v.is_finite() || !(0.0..=1.0e6).contains(&v) {
return Err("implausible branch rating");
}
*slot = v * MVA_BASE;
}
let tail_start = c.pos;
let tag = c.u32()?;
let (device, tap) = match tag {
12 => read_modern_branch_tail(&mut c)?,
5 => read_legacy_branch_tail(&mut c, tail_start)?,
_ => return Err("branch tail tag not in the validated set"),
};
let mut extras = Extras::new();
extras.insert(
LINE_CIRCUIT.into(),
serde_json::Value::String(circuit.map_or_else(
|| " 1".to_string(),
|s| String::from_utf8_lossy(s).into_owned(),
)),
);
extras.insert(
BRANCH_DEVICE_TYPE.into(),
serde_json::Value::String(device.into()),
);
let br = Branch {
from: BusId(from),
to: BusId(to),
r,
x,
b: b_chg,
rate_a: rates[0],
rate_b: rates[1],
rate_c: rates[2],
tap,
shift: 0.0,
in_service: true,
angmin: -360.0,
angmax: 360.0,
extras,
};
Ok((br, c.pos, flags))
}
fn branch_endpoint(c: &mut Cur<'_>) -> Probe<usize> {
let raw = i32::from_le_bytes(c.take(4)?.try_into().unwrap());
raw.checked_abs()
.and_then(|id| (id > 0).then_some(id as usize))
.ok_or("invalid branch endpoint")
}
fn read_step_up_transformer_head(
b: &[u8],
at: usize,
bus_ids: &BusIdSet,
bus_names: &HashMap<String, BusId>,
) -> Probe<(Branch, usize, u16)> {
if at < 8 {
return Err("step up transformer anchor before record");
}
let from = u32_at(b, at)? as usize;
if !bus_ids.contains(from) {
return Err("step up transformer high side bus is unknown");
}
let nominal = f32_at(b, at - 8)?;
if !nominal.is_finite() || (nominal - 100.0).abs() > 1e-3 {
return Err("step up transformer nominal anchor missing");
}
let x_device = f32_at(b, checked_offset(at, 17)?)?;
let mbase = f32_at(b, checked_offset(at, 197)?)?;
if !x_device.is_finite()
|| !(0.0..=100.0).contains(&x_device)
|| !mbase.is_finite()
|| !(0.1..=1.0e5).contains(&mbase)
{
return Err("step up transformer impedance anchor missing");
}
let name_at = checked_offset(at, 356)?;
let name = string_at(b, name_at, 64)?;
let Some(gen_name) = name.split_whitespace().next() else {
return Err("step up transformer name is empty");
};
if !name.to_ascii_uppercase().contains("STEP UP") {
return Err("step up transformer name anchor missing");
}
let to = bus_names
.get(&format!("GEN {}", gen_name.to_ascii_uppercase()))
.copied()
.ok_or("step up transformer low side bus is unknown")?;
if to.0 == from {
return Err("step up transformer has identical endpoints");
}
let mut extras = Extras::new();
extras.insert(LINE_CIRCUIT.into(), serde_json::Value::String(" 1".into()));
extras.insert(
BRANCH_DEVICE_TYPE.into(),
serde_json::Value::String("Transformer".into()),
);
let br = Branch {
from: BusId(from),
to,
r: 0.0,
x: x_device * mbase / MVA_BASE,
b: 0.0,
rate_a: 0.0,
rate_b: 0.0,
rate_c: 0.0,
tap: 1.0,
shift: 0.0,
in_service: true,
angmin: -360.0,
angmax: 360.0,
extras,
};
Ok((
br,
checked_offset(checked_offset(name_at, 4)?, name.len())?,
0,
))
}
fn read_modern_branch_tail(c: &mut Cur<'_>) -> Probe<(&'static str, f64)> {
for _ in 0..11 {
let v = c.f32()?;
if !v.is_finite() || v.abs() > 1.0e6 {
return Err("implausible branch rating block value");
}
}
if c.u8()? != 0 {
return Err("branch record separator byte not zero");
}
match c.u8()? {
0x01 => Ok(("Line", 0.0)),
0x00 => {
let tap = c.f32()?;
if !tap.is_finite() || !(0.2..=5.0).contains(&tap) {
return Err("implausible transformer tap");
}
Ok(("Transformer", tap))
}
_ => Err("branch kind marker not in the validated set"),
}
}
fn read_legacy_branch_tail(c: &mut Cur<'_>, tail_start: usize) -> Probe<(&'static str, f64)> {
for _ in 0..4 {
if c.u32()? != 0 {
return Err("legacy branch tail marker is not zero filled");
}
}
let tap = tail_start
.checked_add(22)
.and_then(|at| slice_at(c.b, at, 4))
.and_then(|s| <[u8; 4]>::try_from(s).ok())
.map_or(0.0, |raw| f64::from(f32::from_le_bytes(raw)));
if tap.is_finite() && (0.2..=5.0).contains(&tap) && (tap - 1.0).abs() > 1e-6 {
Ok(("Transformer", tap))
} else {
Ok(("Line", 0.0))
}
}
#[cfg(test)]
mod tests {
use super::*;
fn empty_network(name: &str) -> Network {
Network {
name: name.to_string(),
base_mva: MVA_BASE,
buses: Vec::new(),
loads: Vec::new(),
shunts: Vec::new(),
branches: Vec::new(),
generators: Vec::new(),
storage: Vec::new(),
hvdc: Vec::new(),
source_format: SourceFormat::PowerWorldBinary,
source: None,
}
}
#[test]
fn best_chain_prefers_valid_chain_over_higher_scoring_error() {
let mut best = None;
keep_best_chain(&mut best, 100, Err(unsupported_vintage("bad candidate")));
keep_best_chain(&mut best, 1, Ok(empty_network("valid")));
let (_, net) = best.unwrap();
assert!(net.is_ok());
}
#[test]
fn alternate_load_record_reads_late_p_and_q() {
let mut bytes = vec![0u8; 41];
bytes[6] = 0;
bytes[25..29].copy_from_slice(&1.0f32.to_le_bytes());
bytes[29..33].copy_from_slice(&0.0f32.to_le_bytes());
bytes[33..37].copy_from_slice(&0.5f32.to_le_bytes());
bytes[37..41].copy_from_slice(&0.25f32.to_le_bytes());
let mut c = Cur { b: &bytes, pos: 6 };
let load = read_load(&mut c, BusId(1), b"1").unwrap();
assert!((load.p - 50.0).abs() < 1e-9);
assert!((load.q - 25.0).abs() < 1e-9);
assert_eq!(c.pos, 41);
}
}