Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 12 additions & 7 deletions docs/dss-geometry-safety.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,30 +15,35 @@ a geometry-defined line.

Until Carson/geometry impedance calculation is implemented:

1. Detect a `Line` that references `geometry=`, `spacing=`, or `wires=`.
1. Detect a `Line` that references `geometry=`, `spacing=`, `wires=`,
`cncables=`, or `tscables=`.
2. Do not synthesize a `DistLineCode` from OpenDSS `Line` factory defaults.
3. Do not infer/fabricate conductor count from the balanced `phases` default.
4. Emit a **parse-time diagnostic** identifying the deferred geometry source.
5. Preserve the original source object so same-format DSS echo remains lossless.
6. Keep explicit `linecode=` behavior unchanged.
7. A one-conductor SWER geometry must never become a three-conductor normalized line.

The preferred long-term representation is an explicit unresolved/deferred line
reference in the canonical model. If that representation is not yet available,
the reader should fail closed rather than manufacture electrical parameters.
PowerIO represents this state explicitly: the `DistLine` remains in the
network, with its source-derived terminal maps and `linecode: None`. Parsing
records an error diagnostic because the typed network is not electrically
complete. A same-format DSS echo still returns the retained source exactly;
analysis and semantic format writers refuse the unresolved impedance, and no
writer supplies a placeholder linecode or electrical default.

## Regression cases

The regression harness in `powerio-dist/tests/dss_geometry_safety.rs` covers:

- a 3-phase overhead `LineGeometry` backed by `WireData`;
- a single-conductor SWER `LineGeometry`;
- a 4-conductor `LineSpacing` plus `wires` definition;
- parse-time diagnostics for deferred geometry;
- protection against OpenDSS factory-default `R1` leaking into a geometry line.

The tests are currently `#[ignore]` because they encode the target invariant while
the canonical model's deferred-line representation is being finalized. They are
intended to become ordinary tests in the implementation PR.
These are ordinary regression tests. They also verify that retained DSS echoes
exactly and that every semantic output path reports unresolved impedance as an
error instead of inventing data.

## Upstream context

Expand Down
2 changes: 1 addition & 1 deletion docs/src/distribution.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ A multiconductor network is the conductor level distribution model. OpenDSS and
using PowerIO
feeder = parse_file("IEEE13Nodeckt.dss") # PioModule{MulticonductorNetwork}
net = feeder.value
net.data.lines[1] # terminal maps, linecode reference
net.data.lines[1] # terminal maps, optional linecode reference
net.data.linecodes[1] # per length impedance matrices, SI units
diagnostics(feeder) # what the reader kept, assumed, or refused
```
Expand Down
2 changes: 1 addition & 1 deletion powerio-dist/src/bmopf/read.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1045,7 +1045,7 @@ impl Reader<'_> {
bus_to: string(o.get("bus_to")),
terminal_map_from: strings(o.get("terminal_map_from")),
terminal_map_to: strings(o.get("terminal_map_to")),
linecode,
linecode: Some(linecode),
length,
route: None,
i_max: floats(o.get("i_max")),
Expand Down
12 changes: 11 additions & 1 deletion powerio-dist/src/bmopf/write.rs
Original file line number Diff line number Diff line change
Expand Up @@ -806,9 +806,19 @@ impl Writer {
if !net.lines().is_empty() {
let mut lines = Map::new();
for l in net.lines() {
let Some(linecode) = l.linecode.as_deref() else {
self.warn(
&C::EMIT_MULTICONDUCTOR_IMPEDANCE_UNRESOLVED,
format!(
"line {} omitted from BMOPF output: its impedance is unresolved",
l.name
),
);
continue;
};
let mut o = Map::new();
o.insert("length".into(), self.num(l.length, "line length"));
o.insert("linecode".into(), json!(l.linecode));
o.insert("linecode".into(), json!(linecode));
o.insert("bus_from".into(), json!(l.bus_from));
o.insert("bus_to".into(), json!(l.bus_to));
o.insert("terminal_map_from".into(), json!(l.terminal_map_from));
Expand Down
11 changes: 11 additions & 0 deletions powerio-dist/src/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,11 @@ pub mod codes {
"the reader stopped following includes at the nesting depth limit";
READ_DSS_LINECODE_UNKNOWN = "READ.DSS.LINECODE_UNKNOWN", Warning,
"a line names a linecode the case does not declare";
/// Geometry-backed impedance remains in retained OpenDSS input but is
/// absent from the typed network until line constants are calculated.
/// Severity `Error`: electrical analysis of that network is unsafe.
READ_DSS_IMPEDANCE_UNRESOLVED = "READ.DSS.IMPEDANCE_UNRESOLVED", Error,
"a line's retained conductor geometry has not been lowered to impedance matrices";

// EMIT.DSS: what the canonical dss writer cannot state.
EMIT_DSS_FIELD_DROPPED = "EMIT.DSS.FIELD_DROPPED", Warning,
Expand Down Expand Up @@ -230,6 +235,12 @@ pub mod codes {
"a line polyline was dropped because the target has no polyline field";
EMIT_MULTICONDUCTOR_SIDECAR_DROPPED = "EMIT.MULTICONDUCTOR.SIDECAR_DROPPED", Warning,
"a companion file the case text refers to was not written";
EMIT_MULTICONDUCTOR_IMPEDANCE_UNRESOLVED =
"EMIT.MULTICONDUCTOR.IMPEDANCE_UNRESOLVED", Error,
"a line with unresolved impedance was omitted instead of receiving fabricated electrical values";
VALIDATE_MULTICONDUCTOR_IMPEDANCE_UNRESOLVED =
"VALIDATE.MULTICONDUCTOR.IMPEDANCE_UNRESOLVED", Error,
"a line has no resolved impedance and cannot be used for electrical analysis";

// Failures.
READ_DIST_IO_FAILED = "READ.DIST.IO_FAILED", Error,
Expand Down
Loading
Loading