Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

PSS SINCAL

SINCAL is one native project format capable of holding both balanced and conductor-resolved networks. Select the electrical profile explicitly. The current balanced reader produces the existing BalancedNetwork; it does not infer balance from equal phase values or automatically reduce unbalanced data. The explicit multiconductor profile produces MulticonductorNetwork through powerio-tnd; neither profile silently retries the other family.

Build feature

Both readers, the byte-exact native echo and the experimental writer are the sincal cargo feature of powerio-tnd, enabled by default by powerio, so the CLI, the Python wheel, the C ABI and the browser converter include them. The feature brings in SQLite through powerio-tnd, which a wasm32-unknown-unknown build compiles only with a C compiler that has a WebAssembly backend. A facade built with default-features = false needs no C compiler: grid_formats lists no SINCAL format, ParseOptions and EmitOptions have no SINCAL field, and a SINCAL source or target fails with REQUEST.SINCAL.FEATURE_DISABLED (REQUEST.DIST_FORMAT.SINCAL_FEATURE_DISABLED from powerio-dist used directly). resolve_format still describes the tokens, because format metadata is not a build probe.

Balanced reader

The sincal-balanced input token selects positive-sequence load flow. The current profile accepts native SQLite schemas 14.8, 15.5 and 16.0 and .sinx archives, with one base variant. Rust also accepts explicitly acquired schema-11.5 Access records and selected absolute daily snapshots, as described below. It covers buses, positive-sequence lines, static loads, external sources, converter injections and two-winding transformers with fixed common taps, plus fixed capacitor banks. Ideal external sources support absolute and relative source/terminal voltage prescriptions and voltage-only limits. Open terminals and inactive equipment remain in the network. Unknown required modes fail with table, native record ID and field context.

let options = powerio::ParseOptions::default().format("sincal-balanced")?;
let module = powerio::parse_with_options("case.sinx", &options)?;
assert!(matches!(module.value(), powerio::PioValue::BalancedNetwork(_)));
powerio::emit(&module, "matpower", "case.m")?;
Ok::<(), Box<dyn std::error::Error>>(())
powerio summary case.sinx --from sincal-balanced
powerio convert case.sinx --from sincal-balanced --to matpower -o case.m
powerio serialize case.sinx --from sincal-balanced -o case.pio.json

Python uses powerio.parse(path, format="sincal-balanced"). The C ABI uses the existing pio_parse format argument and pio_value_balanced_network typed accessor. No new network type or binding ABI is introduced.

Bare sincal and an undeclared .sinx require profile selection. Explicit selection does not authorize discarding incompatible phase-resolved data: unsupported terminal or load modes are errors, with no fallback to another family. The native model’s source-format identity is sincal; the retained source records the selected input profile as sincal-balanced.

Multiconductor reader

Use sincal-multiconductor to select the conductor-resolved reader. A symmetric operating point still produces MulticonductorNetwork. The verified electrical profiles include schema-14.8/15.0 native SQLite/archive inputs and schema-11.5/12.8 Access acquisition records. Shared structural admission of other versions does not imply their electrical support. Unsupported equipment or missing required sequence data rejects the complete parse; no partial feeder is returned. The documented global Flag_LFZ0=2 missing-zero-sequence policy is supported for three-phase sequence lines and the verified ideal-source profile. Explicit zero-sequence inputs retain precedence; applied defaults are recorded. This does not enable missing-data supplementation for transformers or finite sources. Nominal delta–delta transformers support one, two or three installed coils, including reversed polarity and open terminals. Winding selectors expand to actual phase pairs; no unused phase is invented. Partial mixed-winding transformers and inconsistent native core-loss inputs still reject.

powerio summary case.db --from sincal-multiconductor
powerio convert case.db --from sincal-multiconductor --to pmd-json -o case.json
powerio convert case.db --from sincal-multiconductor --to sincal-multiconductor -o copy.db

Python’s existing parse(..., format="sincal-multiconductor") and the C ABI’s existing pio_parse route native SQLite/archive inputs to their established multiconductor typed accessors. No ABI entry point or network family is added.

The Rust facade exposes explicit variant/snapshot/acquisition selection through ParseOptions.sincal_multiconductor. Python uses dist.SincalReadOptions, and the CLI exposes the same selections on summary, convert and serialize. Access parsing requires the original MDB plus the optional helper’s acquired tables, supplied as a relative source companion. Parsing does not run MDB Tools. The recorded original length and SHA-256 must match the MDB; this catches mismatched input files, but is not an attestation that caller-supplied table contents are authentic.

let mut selection = powerio::dist::SincalReadOptions::default();
selection.variant = Some(1);
selection.snapshot_hours = Some(12.0);
selection.acquired_tables = Some("acquired.json".into());
let mut options = powerio::ParseOptions::default().format("sincal-multiconductor")?;
options.sincal_multiconductor = Some(selection);
let module = powerio::parse_with_options("project/original.mdb", &options)?;
assert!(matches!(module.value(), powerio::PioValue::MulticonductorNetwork(_)));
Ok::<(), Box<dyn std::error::Error>>(())

Create project/acquired.json with evals/sincal/import_access.py. In-memory sources supply the companion with Source::with_named_buffer; file companions stay under the source’s acquisition root. For example:

import powerio
from powerio.dist import SincalReadOptions

module = powerio.parse(
    "project/original.mdb", format="sincal-multiconductor",
    sincal_multiconductor=SincalReadOptions(
        variant=1, snapshot_hours=12, acquired_tables="acquired.json"),
)
powerio summary project/original.mdb --from sincal-multiconductor \
  --sincal-variant 1 --sincal-snapshot-hours 12 \
  --sincal-acquired-tables acquired.json

Python memory inputs supply named_buffers={"acquired.json": records_bytes}. They never read companions from disk. File inputs may explicitly widen the root with acquisition_root=... in Python or --acquisition-root in the CLI; the root must contain the primary and referenced files. The normal root is the primary file’s parent directory. Julia uses the same keywords with its exported SincalReadOptions:

using PowerIO
module_ = parse("project/original.mdb"; format="sincal-multiconductor",
    sincal_multiconductor=SincalReadOptions(
        variant=1, snapshot_hours=12, acquired_tables="acquired.json"))

Julia memory/IO inputs take named_buffers=Dict("acquired.json" => records_bytes). C uses pio_parse_with_options with borrowed PioParseOptions and PioSincalReadOptions; zero initialization leaves optional values absent, and presence flags distinguish an omitted snapshot from explicit midnight. The existing pio_parse remains unchanged. For memory inputs, pio_source_from_memory_with_buffers copies the primary and named companions before returning; caller buffers may then be released. These additions require the matching development library and Julia companion. Existing ABI 7 layouts and typed network accessors are unchanged.

Legacy UI-manipulator references on loads retain their IDs as provenance. Their edits are already stored in the load input fields, so stored power factors apply once; the UI definition is not evaluated again. This follows Siemens Release Notes 21.0, pp. 4–6. Conflicting daily timestamps remain errors.

Active daily profiles require an explicit snapshot; no midnight default is assumed. In schema 11.5, absolute P/Q profiles replace aggregate powers, while common relative-factor profiles scale the defined powers, preserving unequal Wye or delta branches and their voltage dependence. Relative profiles currently require the documented direct-scaling coefficient settings: Power_a1=0, Power_b1=0, Reduce_a2=0, Reduce_b2=1. They accept finite nonnegative factors, linear or step interpolation and cyclic repetition. Separate P/Q-factor modes, topology-dependent coincidence and absolute-profile allocation to unequal branches remain unsupported. Generic time-series and inherited variants remain under development.

Schema-11.5 NULL transformer tap status uses the documented fixed-status default and records that interpretation. Active controllers and unresolved partial mixed-winding circuits still reject; the default does not supply missing physics.

Fixed reactor and capacitor banks retain their phase connections, losses, grounding and open/inactive states as typed shunts and switches. Supported three-phase grounded banks retain separate positive- and zero-sequence inputs. Explicit neutral impedances, automatic regulators and stepped direct zero-sequence impedances still require additional mapping. Documented optional schema-11.5 defaults are recorded; required ratings are never filled in. The Access acquisition helper includes both bank tables by default. For schema 11.5, a NULL voltage-level line/cable temperature uses its documented 20 C default. The selected field is recorded in network.defaulted; explicit temperatures retain their correction, and missing columns or modern NULLs fail. The same legacy profile admits documented defaults for optional line flags, dielectric losses, parallel counts, rating factors, rated frequency and active temperature coefficients. Required impedances and sequence declarations remain strict. Ideal connections record their applied defaults as well.

Every SINCAL parse shares a fixed budget of SQLite work. Both readers read terminals once per parse and group them by element, so the work per element does not grow with the size of the terminal table: a native-width feeder with 1,000 lines reads within the budget. A project that does exceed it fails with a message saying it exceeds the reader’s bounded query work, never with partial output.

Access MDB inputs use the core primary-file limit, which defaults to 1 GiB; POWERIO_MAX_PRIMARY_BYTES can set an explicit bound. CSIRO09 is 69,181,440 bytes, and its validation uses that exact, tighter bound. Native SQLite/.sinx acquisition and acquired table documents retain their separate fixed 64 MiB limits. These are distinct limits; the library does not silently raise them.

Fidelity and output

In the balanced profile, native physical quantities use a declared 100 MVA internal conversion base. The multiconductor model uses its usual SI units. Both readers correct cable resistance for the voltage-level temperature in every schema. The balanced reader refuses a line built from LineSeg sections, as the multiconductor reader does. Both readers apply Line.fr as a factor on the thermal current Ith: the line’s current limit is Ith * ParSys * fr, and fr does not change its impedance. Disabled native generator capability limits become unbounded typed limits, not zero capability. Diagnostics identify this and data outside the chosen snapshot. Disabled modern profile references do not replace static powers. Active profiles require a supported adapter and explicit snapshot selection; currently only the declared schema-11.5 absolute daily profile is accepted. Fault, dynamic, protection, economic, untyped diagram and stored-result data remain in the retained source. Selected drawing coordinates are typed as described below. Cross-format output reports their omission.

An unchanged module can emit sincal or its matching explicit profile token to reproduce its primary native bytes exactly. A SQLite input echoes SQLite bytes; an archive echoes archive bytes; an explicitly acquired Access case echoes the original MDB, never the intermediate tables. An explicit output profile for the other network family is refused. Use an appropriate destination filename:

powerio convert case.sinx --from sincal-balanced --to sincal-balanced -o copy.sinx

This is source echo, not a fresh writer: format metadata reports can_emit=false. Editing the value removes its retained native source. PowerIO IR retains typed values and provenance, not the original native project bytes. Edited, constructed and IR-restored modules therefore refuse native output through ordinary emit. Fresh writing requires the explicit experimental option below.

Access/MDB acquisition currently uses the explicit optional helper under evals/sincal; ordinary library parsing never starts external programs. Its internal typed records are not native SQLite output. The Rust balanced selection API accepts them as a companion to their original MDB.

Experimental fresh writer (Rust)

emit_with_options accepts an explicit sincal_experimental request. It always writes the current typed value, even when an unchanged module retains native source bytes. The result has Fidelity::Canonical, experimental warnings and loss diagnostics. Default options are equivalent to ordinary emit, including byte-exact echo. Unsupported physics or packaging errors produce no partial file, and existing destination files are not overwritten.

let module = powerio::parse_with_options(
    "case.sinx", &powerio::ParseOptions::default().format("sincal-balanced")?,
)?;
let mut request = powerio::SincalExperimentalOptions::default();
request.container =
    powerio::SincalContainer::archive_with("converted", powerio::ArchiveOptions::default());
let mut options = powerio::EmitOptions::default();
options.sincal_experimental = Some(request);
let result = powerio::emit_with_options(&module, "sincal", &options, "fresh.sinx")?;
assert_eq!(result.fidelity(), powerio::Fidelity::Canonical);
for diagnostic in result.diagnostics() {
    eprintln!("{}: {}", diagnostic.code(), diagnostic.message());
}
Ok::<(), Box<dyn std::error::Error>>(())

SincalContainer::Sqlite (the default) produces a single SQLite database. SincalContainer::archive_with produces deterministic candidate .sinx packaging containing that database and manifests. ArchiveOptions has no fields yet; it starts from Default so packaging options can be added later without breaking callers. The struct variant SincalContainer::Archive { project_name } is kept for compatibility and packages the same bytes. The project name accepts 1–128 ASCII letters, digits, underscores or hyphens. The destination filename does not select the container. Neither path writes an Access database, desktop .sin file, diagrams or results. Native SINCAL open/save/calculation remains an external acceptance gate.

The module’s existing electrical type selects the backend. BalancedNetwork uses the transmission writer. MulticonductorNetwork uses the distribution writer and requires request.nominal_ll_volts: a map from every typed bus ID to a finite positive line-line voltage in volts. Transformer auxiliary buses that are eliminated during primitive reconstruction span two voltage levels; their entries may be omitted. No source voltage, bound or metadata value is silently interpreted as bus nominal voltage. A balanced network rejects this map. Explicit sincal-balanced and sincal-multiconductor targets reject a value from the other family; the bare sincal target uses the typed family. Neither electrical family is transformed implicitly or retried through the other backend.

The initial balanced subset covers ideal reference sources, fixed PQ injections, static loads, same-voltage lines and fixed two-winding transformers. The multiconductor subset covers ideal positive-sequence sources with grounded or isolated floating reference, unequal phase and phase-pair static loads, sequence-representable lines and finite DD or solid DY/YD transformers with fixed taps. Supported native transformer primitives can be rewritten from their actual conductor matrices, including after IR restoration and compatible edits. Reconstruction reports its canonical nameplate parameter base explicitly. External neutral circuits, other transformer/core modes, general shunts, controls and profiles are not yet covered by this writer subset.

Open switches on canonical phase selections retain separate endpoints and explicit open native terminal states. Optional uniform ampacities are preserved. Native readback may introduce auxiliary buses and ideal connections; closed-switch collapse can omit those connections’ ratings on a subsequent fresh write, with loss diagnostics. Neutral switching, conductor permutations and nonuniform switch ratings remain unsupported.

This opt-in is currently exposed by the Rust facade. CLI, Python and C emission continue to use their existing ordinary emission path. can_emit remains false because the experimental subset is not a universal native writer. Fresh multiconductor output is verified through the public reader described above. Neither internal readback nor independent electrical validation establishes native SINCAL desktop acceptance.

Validation

The reader maps all 15 nodes and 32 equipment records of the small licensed SimBench native case. Rust tests cover malformed/unsupported input, service states, open terminals, typed routing, binary echo and IR behavior. The evals/sincal/check_balanced_simbench.py harness checks actual Rust output against paired publisher CSV inputs and a fresh pandapower 3.5.6 calculation, including derived nonzero taps on either winding. Stored native results are separate evidence; no SINCAL desktop execution has been performed.

Three external cases also map completely: IEEE18 (18 native nodes, 44 elements), IEEE33 (33 nodes, 67 elements), and the student study (40 nodes, 84 elements). Their fresh independent pandapower comparisons agree within 4.5e-12 pu in complex bus voltage. The student file’s saved static load powers are zero with profiles disabled; its historical profile results are not the state parsed here. The external validation report records this distinction. No external native model files are vendored.

Additional schemas, active profiles, variants and corpus cases remain under development. Native fixtures require redistribution rights; external research models are not silently copied into the test suite.

The public multiconductor facade maps all 688 CSIRO09 elements at five selected daily snapshots. Its original powers are symmetric; five separately labelled unequal-delta-branch stress cases exercise asymmetric behavior. Independent OpenDSS comparisons cover every phase at 617 energized native nodes and four isolated nodes, with maximum voltage difference below 0.000372 V. The finite OpenDSS source approximation is checked separately. The same public path checks original MDB echo and IR value preservation; see evals/sincal/csiro09-public.json. A second published native case, Truong’s schema-15.0 12-bus model, maps all 45 elements, including 33 unequal single-phase loads. Independent OpenDSS comparisons cover all 36 phase voltages and 66 line-terminal currents/powers; maximum voltage error is below 0.000006 V. Native source echo, IR, generic matrix and PF-instance checks also pass; see evals/sincal/truong12-public.json. Its source has no redistribution license and remains external. These are two complete conductor-resolved cases, not complete corpus coverage or native SINCAL desktop acceptance. The 12-bus case does not close the larger-case review gate.

Explicit balanced Access snapshots

The balanced reader also accepts schema-11.5 Access acquisition records through powerio_tnd::formats::sincal::SincalBalancedReadOptions, carried by powerio::ParseOptions::sincal_balanced. Select sincal-balanced explicitly, set variant and snapshot_hours when required, and set acquired_tables to the relative companion containing import_access.py output. Memory sources attach that companion with Source::with_named_buffer; file sources use the acquisition root. Parsing verifies the original MDB header, byte count and SHA-256 against those records and never invokes MDB Tools. Include NetworkGroup and NetworkGroupTrans in acquisition so balanced interchange controls are checked.

CSIRO19 is checked at seven daily snapshots through this public path. Profiles currently admit absolute daily P/Q with linear or stepped interpolation. Modern active profiles, inherited variants and other profile modes still reject. Legacy voltage/temperature defaults are narrowly scoped and retained in component metadata. The CLI uses the same selection flags for either explicit family:

powerio serialize original.mdb --from sincal-balanced --sincal-variant 1 \
  --sincal-snapshot-hours 6 --sincal-acquired-tables acquired.json

The acquired companion must be beneath the source directory or the explicitly selected --acquisition-root. Unknown or missing profiles never trigger a balanced/multiconductor fallback. Python exposes SincalBalancedReadOptions through parse(..., sincal_balanced=...); Julia uses the same type and keyword. C uses PioSincalBalancedReadOptions through PioParseOptions.sincal_balanced. The separate distribution selection type remains SincalReadOptions.

import powerio as pio

module = pio.parse("project/original.mdb", format="sincal-balanced",
    sincal_balanced=pio.SincalBalancedReadOptions(
        variant=1, snapshot_hours=12, acquired_tables="acquired.json"))
pio.serialize(module, "case.pio.json")

Experimental legacy source-control compatibility

Rust SincalReadOptions.assume_inactive_source_controls = true and the CLI --sincal-assume-inactive-source-controls allow schema-11.5 NULL values in Infeeder.Flag_LfLimit, Flag_LfCtrl, Flag_Qctrl, Flag_Macro and Kr to be interpreted as zero/inactive. The default remains strict. Python and Julia expose the same boolean on SincalReadOptions; C exposes it on PioSincalReadOptions. It is unavailable on balanced reader options. Active controls, missing columns and missing electrical parameters still reject.

powerio summary project/original.mdb --from sincal-multiconductor \
  --sincal-acquired-tables acquired.json --sincal-variant 1 \
  --sincal-snapshot-hours 12 --sincal-assume-inactive-source-controls

An applied assumption emits READ.DIST.SINCAL_ASSUMED_INACTIVE_SOURCE_CONTROLS. The affected component and fields survive IR serialization in extras.sincal_compatibility_assumptions. Original MDB echo remains byte exact. These assumptions admit the complete CSIRO12 feeder, with independent OpenDSS checks, but do not establish what native SINCAL does with these NULLs.

Native node records with no declared equipment conductors are retained in extras.sincal_unconnected_nodes, with READ.DIST.SINCAL_UNCONNECTED_NODES, instead of inventing phases. Nodes attached to open or inactive equipment remain electrical buses; unsourced islands still require explicit downstream handling.

Fidelity findings and working trial routes

Both readers report source-only tables with row counts and an explicit scope (selected native variant or all rows). Enumerated source-only harmonic and reliability fields are named separately; this is not an exhaustive field-level schema audit. Access tables excluded during acquisition have unknown counts, reported as such. Findings have structured diagnostic details, survive IR, and are attached to ordinary cross-format loss warnings. The inventory is a parse-time description, not a copy of the native payload.

Balanced schema defaults identify affected components and values. Distribution defaults identify components and fields in sincal_defaulted_fields extras; experimental assumptions remain separately identified. Retained native bytes are absent from IR, and neither IR nor edited values can recreate native echo.

The installed-wheel trial harness evals/sincal/check_trial_workflows.py checks these public paths on external models. All four cases preserve original source echo and typed IR. Matrix and numerical evidence is in the separate case-specific Rust/oracle harnesses.

Case / selected profilePF instance preparationOrdinary target emission and reparse
CSIRO19 / balanced, noon snapshotPassMATPOWER pass, with loss diagnostics
Truong12 / multiconductorPassDSS, PMD JSON and BMOPF JSON pass, with loss diagnostics
CSIRO09 / multiconductor, noon snapshotRefused: unsourced native islandsRefused: reference-terminal voltage sources
CSIRO12 / multiconductor, noon snapshot, compatibility opt-inPassRefused: reference-terminal voltage sources

Emission/reparse here establishes a usable format path, not numerical equality of the exported target. The OpenDSS numerical oracles independently construct circuits from native SINCAL fields; they do not validate PowerIO’s target exports. PF preparation constructs a calculation instance; PowerIO does not solve it.

For distribution Access cases, keep the original and acquired records together:

import powerio as pio

module = pio.parse("project/original.mdb", format="sincal-multiconductor",
    sincal_multiconductor=pio.dist.SincalReadOptions(
        variant=1, snapshot_hours=12, acquired_tables="acquired.json",
        assume_inactive_source_controls=True))  # CSIRO12 only; inspect diagnostics
print(pio.diagnostic_records(module.diagnostics))
pio.serialize(module, "case.pio.json")
instance = module.to_mc_ac_pf_instance()  # CSIRO12 succeeds; CSIRO09 rejects islands

CSIRO09’s primary file is 69,181,440 bytes. The checked command sets POWERIO_MAX_PRIMARY_BYTES=69181440 explicitly; the default input limit remains unchanged. The native models stay external, subject to their own data rights.

Drawing coordinates and line routes

Both reader families populate the existing bus-location and line-route fields from native graphic tables. A declared schematic view (GraphicAreaTile.Flag=2) uses Diagram space; other views remain Unknown. This profile does not infer WGS84, a projected CRS, or the meaning of Node.lat/lon and Node.hr/hh. Use them as native layouts unless you independently establish the coordinate reference system.

A point-shaped node keeps its source point. An extended busbar uses a derived midpoint; extras.sincal_geometry preserves its two native endpoints. Lines use native terminal identities and attachment positions, with ordered bends on both ports (NoPoint=1..n), following the Siemens database-interface manual. Gaps, duplicate orders and collapsed routes are diagnosed and omitted. Conflicting drawings, invalid optional records and multiple areas do not change the electrical model. No position is fabricated for generated auxiliary buses.

SQLite databases and .sinx archives supply graphics directly. For Access, include optional graphics in the explicitly acquired companion:

python evals/sincal/import_access.py model.mdb acquired.json --include-graphics

Keep any additional electrical table selections your case already requires. Electrical-only companions still work; omitted graphics are reported. READ.SINCAL.GEOMETRY or READ.DIST.SINCAL_GEOMETRY reports the mapping counts, coordinate space and any omissions. View identity, origin, page dimensions, scales and optional CRS/reference fields are retained as native values in the module’s SINCAL view metadata extension and survive IR (the full extension key is documented in the implementation guide below). No origin or scale transform is applied; page centimetres do not define a metre-space canvas. Graphical style and artwork remain only in source. Unchanged native echo stays byte-exact; IR retains mapped geometry. Fresh experimental SINCAL output still omits it with a diagnostic.

Existing extraction paths work without new options or binding types:

powerio geo extract case.sinx --from sincal-balanced -o case.geo.json
powerio geo extract case.db --from sincal-multiconductor -o case.geo.json

For Access or another input requiring snapshot/compatibility selections, first serialize the selected module to IR, then extract that IR’s geometry. Canonical geo JSON carries Diagram or Unknown space; it is not an earth-referenced GIS layer. Full native view metadata is retained on the module and in IR, rather than on the extracted geometry layer. Rust uses BalancedNetwork::to_geo_layer() or powerio::dist_geo::to_dist_geo_layer(&network).

Validation covers SimBench, CSIRO09/12/19 and the externally held Truong model: 862 typed locations and 674 routes, including three native bends. All preserve electrical fields, native echo and IR. CSIRO12 retains its existing explicit compatibility assumptions. Additional shared-decoder checks cover CSIRO03 (774 routes, up to 40 vertices) and CSIRO13 (874 routes); these are geometry-only checks and do not claim complete electrical parsing of either case. See the implementation and evidence for case accounting and the deferred georeferencing and desktop acceptance gates.