Skip to main content

powerio_tx/format/powerworld/
mod.rs

1//! Parse and emit PowerWorld auxiliary `.aux` files.
2//!
3//! The implementation is layered. The AUX grammar decoder produces the
4//! generic [`AuxFile`] — every `DATA` and `SCRIPT` section, with field lists,
5//! value rows, and `SUBDATA` blocks intact — and knows the grammar from the
6//! official format guide: legacy and concise headers, comma delimited (CSV)
7//! sections, multiline field lists and value rows, `//` comments, quoting,
8//! and `variablename:location` field suffixes. On top of it, the [`BalancedNetwork`]
9//! mapping consumes the power flow core types (Bus, Load, Shunt, Gen,
10//! Branch) by field name, so column order and extra columns don't matter.
11//! Object types outside the core stay reachable through [`aux_sections`] and
12//! survive the same format round trip byte for byte when the parsed module is
13//! passed to [`crate::emit`].
14//!
15//! The serializer emits `DATA (Object, [fields]) { … }` blocks for the core
16//! types, values in MW/MVAr/degrees, status as `Closed`/`Open`. Generator
17//! cost, HVDC, and storage are not represented and are reported on emission.
18//!
19//! `.pwb` binary cases are parsed but cannot be emitted; see that module for
20//! the decoded vintages and parity evidence. `.pwd` files carry no case data,
21//! only the diagram, and read as a diagram space layer.
22//!
23//! [`BalancedNetwork`]: crate::network::BalancedNetwork
24
25mod auxiliary;
26pub(in crate::format) mod map;
27mod objects;
28mod pwb;
29mod pwd;
30
31#[cfg(test)]
32mod tests;
33
34pub use auxiliary::{AuxFile, AuxObject, AuxRow, AuxScript, AuxSection, AuxSubData};
35
36pub use map::aux_sections;
37pub(crate) use map::write_powerworld;
38pub use objects::{Contingency, contingencies, rating_set_names};
39pub(crate) use pwb::parse_pwb_collecting;
40pub use pwd::{PwdDisplay, PwdSubstation};
41
42#[doc(hidden)]
43pub use auxiliary::{emit_aux as __emit_aux, parse_aux as __parse_aux};
44#[doc(hidden)]
45pub use pwb::{parse_pwb as __parse_pwb, parse_pwb_with_warnings as __parse_pwb_with_warnings};
46#[doc(hidden)]
47pub use pwd::{
48    parse_pwd as __parse_pwd, parse_pwd_display as __parse_pwd_display,
49    parse_pwd_file as __parse_pwd_file, parse_pwd_layer as __parse_pwd_layer,
50};
51
52use crate::network::Extras;
53
54/// Drop a retained device id that states exactly the positional default the
55/// aux writer's allocator would hand element `index` anyway.
56///
57/// Shared by both PowerWorld readers on purpose. The aux reader and the binary
58/// reader must agree on what counts as a default, or one keeps an id the other
59/// drops and a pwb → aux leg reports the disagreement as a conversion loss.
60/// Trimmed before comparing: PowerWorld pads ids for display, and a padded
61/// default is still the default.
62///
63/// `keys` are the field aliases one id can arrive under; an explicit
64/// non-default id is kept verbatim, padding included.
65pub(super) fn drop_positional_id(extras: &mut Extras, keys: &[&str], index: usize) {
66    let default = (index + 1).to_string();
67    for key in keys {
68        if extras
69            .get(*key)
70            .and_then(serde_json::Value::as_str)
71            .is_some_and(|v| v.trim() == default)
72        {
73            extras.remove(*key);
74        }
75    }
76}