Skip to content

Add crosssection_escalation: larger LV cross-section instead of cable stacking on overload (#377) - #730

Open
nader-00 wants to merge 3 commits into
devfrom
feature/377-crosssection-escalation
Open

Add crosssection_escalation: larger LV cross-section instead of cable stacking on overload (#377)#730
nader-00 wants to merge 3 commits into
devfrom
feature/377-crosssection-escalation

Conversation

@nader-00

Copy link
Copy Markdown
Collaborator

Description

Adds an opt-in LV overload reinforcement measure: trying a larger cable
cross-section (up to 2 parallel cables) via select_cable() instead of
falling back to many parallel standard cables, wherever the existing
overload logic would otherwise install more than 2 parallel standard lines.

Part of the broader effort in #377 (add/improve grid reinforcement methods),
addressing the "too many parallel lines" critique directly.

Problem

reinforce_lines_overloading() today always upgrades an overloaded LV line
to the one configured standard cable type, adding as many parallel systems of
that type as needed (ceil(apparent_power / standard_capacity)). For
sufficiently overloaded lines this results in cable stacks of 3, sometimes up
to 9, parallel standard cables — in real grids a larger cross-section would
usually be chosen instead, and it's also already what select_cable() (an
existing, previously unused eDisGo helper) is built to do: pick the smallest
cable that carries a given apparent power at a given maximum parallel count.

Solution

  • New crosssection_escalation: bool = False parameter on reinforce_grid()
    (and passed through reinforce_lines_overloading() /
    _reinforce_lines_overloading_per_grid_level()). LV only in this PR — see
    Scope below.
  • For LV lines where the existing logic would install more than 2
    parallel standard cables, select_cable(..., max_cables=2) is tried first.
    If a cross-section up to 2 parallel cables suffices, that type/count is
    used instead of the standard-type stack. If not (or if the result would
    ever fall below the standard type's I_max_th — defensively checked,
    should be unreachable by construction), falls back to today's behaviour
    unchanged.
  • crosssection_escalation_existing: bool = False (requires
    crosssection_escalation=True): extends the same escalation attempt to LV
    lines that are already the standard cross-section with more than 2
    parallel systems — a separate code path
    (_add_parallel_standard_lines()), not reachable by the main flag alone
    (see the two-commit history / Root-cause finding below for why this needed
    a second, explicit extension rather than being automatic).
  • No changes to equipment_changes logging, no changes to the cost
    calculation — an escalated cross-section is logged exactly like today's
    standard-type replacement (equipment_changes reads type_info live from
    the topology after the change, independent of how that type was chosen).
  • crosssection_escalation_existing=False, crosssection_escalation=False
    (both defaults): reinforcement is bit-identical to current dev (see
    Validation below) — this PR changes no default behaviour.

Root-cause finding that shaped this PR (two commits, not one)

The first integration run (base feature only) landed only 12 of 42 known
LV cable stacks (num_parallel > 2) at n<=2, far short of an offline
estimate of ~40/42. Root-cause diagnosis found 21 of the 30 shortfall cases
(70%) were not physics (impossible to escalate) but an architecture gap:
the escalation logic lived exclusively in _replace_by_parallel_standard_ lines(), which a line only reaches if it is not yet the standard type
at overload-check time. Lines that were already standard type with
num_parallel > 1 — as 100% of the 21 gap cases were, straight from the raw
ding0 input, not from any prior eDisGo decision — are handled by a
completely separate function, _add_parallel_standard_lines(), which the
base feature never touches (confirmed structurally: it's a nested closure
only reachable from inside _reinforce_lines_overloading_per_grid_level(),
and reinforce_lines_voltage_issues() — the voltage path — never calls it
at all, so this extension cannot be reached from voltage-driven
reinforcement). crosssection_escalation_existing closes that gap using the
same select_cable(max_cables=2) call and the same >2 threshold, applied
to the line's current total capacity (num_parallel_current x standard_single_capacity, which for an already-standard line is exactly its
measured apparent power — no under-dimensioning risk, no design choice, a
direct consequence of the data model only storing one type_info +
num_parallel per line).

One notable consequence, called out explicitly: unlike the base feature
(which only ever replaces eDisGo's own, not-yet-built reinforcement
decisions), crosssection_escalation_existing can also replace cable stacks
that are part of the original ding0 input topology — real, already
"laid" infrastructure in the model, not a hypothetical planning decision.
The cost model treats both cases identically, but this is a conceptually
stronger intervention and is named as its own consideration here rather than
left implicit in the diff.

Cost-model caveat (must be read alongside the validation numbers below)

eDisGo's line cost model is flat: costs_cable_lv is a single kEUR/km value
for all LV cable types (costs.py), multiplied only by length and
quantity — never by cross-section. A cross-section upgrade at the same
parallel count therefore never costs more in this model, by construction.
The measured cost savings below are partly an artifact of the same cost-
model limitation that motivates the "too many parallel lines" critique in
#377 in the first place (no cross-section/trenching-cost differentiation).
A more realistic cost model (material cost rising with cross-section) would
likely shrink the advantage of escalation, though probably not reverse it —
trenching cost is incurred once regardless of parallel count either way
(confirmed: earthwork cost in this package is already per-line, not
per-parallel-system), and fewer parallel systems also mean fewer joints/bay
positions in practice, which the current cost model ignores entirely (to the
detriment of today's cable-stacking practice, not of this PR).

Scope

LV only for this PR. The code path (_reinforce_lines_overloading_per_grid_ level()) is symmetric for MV and LV, but all 42 validated cable stacks are
LV — there is no MV evidence from this validation. Enabling MV would be
unvalidated extrapolation.

Validation

  • Unit tests: test_reinforce_lines_overloading_crosssection_escalation and
    test_reinforce_lines_overloading_crosssection_escalation_existing (new),
    plus all existing tests in test_reinforce_measures.py — 8/8 passed.
  • Regression: crosssection_escalation=False (default) confirmed
    bit-identical to dev (transformers_df, topology_lines_df,
    equipment_changes, grid_expansion_costs, compared via
    pandas.testing.assert_frame_equal on the standard ding0_test_network_1
    reinforcement scenario).
  • Integration against the 42 known LV cable stacks (num_parallel > 2)
    across all 10 real test networks:
    • Base feature alone: 12/42 land at n<=2 (not the offline-estimated ~40/42
      — see root-cause finding above).
    • With crosssection_escalation_existing: 27/42 land at n<=2 — 15 of
      the 21 architecture-gap cases now succeed; the remaining 6 (plus 9 other
      cases) reflect genuine impedance feedback (changing a line's type/count
      shifts the load-flow distribution enough that the offline, single-pass
      estimate no longer holds) rather than an escalation-logic gap. Reported
      as a validation number, not asserted as a hard cost/count threshold —
      the exact figure depends on grid data and would break on unrelated
      config changes if hard-coded into a test.

Type of change

  • New feature (non-breaking change which adds functionality)

Checklist:

  • New and adjusted code is formatted using the pre-commit hooks
  • New and adjusted code includes type hinting now
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • The Read the Docs documentation is compiling correctly
  • If new packages are needed, I added them to setup.py etc.
  • I have added new features to the corresponding whatsnew file

nader-00 added 3 commits July 21, 2026 12:11
reinforce_lines_overloading() currently only stacks parallel standard
lines to solve overloading, which #377 flags as sometimes producing an
implausibly large number of parallel cables where a larger LV
cross-section would carry the same load. This adds an opt-in
crosssection_escalation flag (default False): for LV lines that would
otherwise need more than two parallel standard lines,
select_cable() is tried first (up to two parallel cables, never below
the standard cross-section), falling back to today's behaviour
otherwise.
)

crosssection_escalation only ever reached lines handled by
_replace_by_parallel_standard_lines(); lines that were already the
standard cross-section (e.g. native multi-cable ding0 lines) were
scaled up via _add_parallel_standard_lines() instead, which never
attempted escalation. This adds crosssection_escalation_existing
(requires crosssection_escalation=True) to also cover that path, using
the same >2-parallel-lines gate and select_cable() logic. Unlike the
base flag, this can replace cross-sections that were part of the
original input topology, not only eDisGo's own prior reinforcement.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant