Skip to content

Fix/reinforce lines voltage issues all segments - #738

Open
joda9 wants to merge 25 commits into
edisgo_run_edisgofrom
fix/reinforce-lines-voltage-issues-all-segments
Open

Fix/reinforce lines voltage issues all segments#738
joda9 wants to merge 25 commits into
edisgo_run_edisgofrom
fix/reinforce-lines-voltage-issues-all-segments

Conversation

@joda9

@joda9 joda9 commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator

Description

Please include a summary of the change and which issue is fixed.

Fixes # (issue)

Type of change

Please delete options that are not relevant.

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)

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 the setup.py, and if needed the rtd_requirements.txt, the eDisGo_env.yml and the eDisGo_env_dev.yml.
  • I have added new features to the corresponding whatsnew file

Birgit Schachler and others added 25 commits April 2, 2023 16:22
- engine() without arguments now auto-detects: egon-data database if a
  configuration file exists (EGON_DATA_CONFIG or
  ~/.ssh/egon-data.configuration.yaml), otherwise OEP; choice is logged
- configuration file without ssh-tunnel section connects directly
  instead of raising KeyError
- engine_from_settings(): strict source vocabulary (egon-data/oep/
  empty), unknown values raise instead of silently falling back
- pass ssh_pkey as str — sshtunnel silently ignores pathlib.Path and
  falls back to default keys, failing gateway auth
- SSH keepalive + pool_pre_ping/pool_recycle keep pooled connections
  usable across long multi-grid runs
- pin paramiko < 4.0 (DSSKey removed in 4.x breaks sshtunnel)
- conftest: pin test engine to OEP so auto-detection cannot redirect
  the suite, replace hardcoded home path with default_config_path()
- Main.jl: use the solution whenever the solver proves optimality or
  reports a feasible primal point; computing an IIS on a feasible model
  raises Gurobi error 10015 and killed the run. The SOC tightness check
  now runs on every used primal point (a loose relaxation matters for
  suboptimal solutions too); termination status is passed through to
  opf_results.status
- pm_optimize: split a non-contiguous time index (e.g. from automatic
  timestep selection) into contiguous intervals and optimize each
  separately — storage/heat state does not carry across a gap. Results
  are merged over the full index, per-interval solve reports land in
  opf_results.interval_results, infeasible intervals raise after
  feasible results are stored
- from_powermodels: write results only onto the current interval's
  time steps instead of the whole frame
- align overlying-grid SOC series and EV flexibility bands to the
  analysis time index (year shift + reindex) via new
  tools.align_series_to_timeindex
egon_etrago_bus/egon_etrago_link live in schema "grid" in a local
egon-data database; only the OEP resolves them via the alias mapping
keyed on "supply". Heat pump import against a local database failed
with UndefinedTable before.
YAML/JSON-driven task pipelines with a single entry point
run_edisgo(config): registered tasks with requires/provides metadata,
a static pre-run validator, extends-based config merging, and three
bundled presets (worst_case, flex_opf, overlying_grid_opf).

- flexibilities: top-level carrier list drives both import_flex and
  the optimize task's flexible selection
- timestep selection split into set_timeindex (manual, early) and
  select_critical_timesteps (auto scoring, late)
- database source resolved via engine_from_settings (auto-detect,
  oep, egon-data)
- overlying-grid data via run_edisgo(..., overlying_grid_data=...)
  from eTraGo or CSVs, incl. new OverlyingGrid attributes
- ORM mapping fix for egon-data tables/views without primary key
Cherry-picked from edisgo_run_edisgo (b83c884), which built this on top
of dozens of commits this branch doesn't have. Adds spatial_reduce /
spatial_restore tasks bracketing optimize, so the OPF can run on a
spatially-reduced grid while reinforcement still runs on the full
topology, plus the underlying spatial_complexity_reduction algorithm and
its tests.

Dropped edisgo.EDisGo.run_pipeline() from the same squashed source commit
since it calls edisgo.run._run_pipeline_on, which doesn't exist on this
branch's simpler run_edisgo() entry point, and nothing in the spatial
feature depends on it.
Cherry-picked from edisgo_run_edisgo (dc1249b). Supersedes the
task-local reduce_timeseries_data_to_given_timeindex workaround added in
the previous commit: get_flexibility_bands now year-aligns and trims its
own returned/stored bands to edisgo_obj.timeseries.timeindex, so any
caller (not just this pipeline) gets correct bands.

Renamed the ported spatial-reduction preset from uc6_spatial_reduction.yaml
to spatial_reduction_opf.yaml, and its example runner from run_example_06.py
to examples/run_spatial_reduction_example.py, to follow this branch's
<name>[_opf].yaml / examples/ convention (worst_case, flex_opf,
overlying_grid_opf) instead of edisgo_run_edisgo's ucN_ numbering.

Adapted the preset's pipeline to this branch's timestep-selection API: it
previously used edisgo_run_edisgo's separate uc5 feature (a dual-mode
select_timesteps task switched via position:/mode:), which this branch
doesn't have. Hardwired to the manual case the preset already shipped
with, via this branch's set_timeindex task (parameters passed as step
params, not via a top-level timeseries_selection block); the post_grid
auto-selection step was dropped since it would have no-opped here anyway.
get_database_alias_dictionaries built a fresh edisgo.io.db.engine() with no
arguments to read the data.edut_00 name-mapping table. With no arguments
that call is in auto-detect mode: in any environment that has an egon-data
configuration file (e.g. ~/.ssh/egon-data.configuration.yaml) it resolves to
the local egon-data database and opens an SSH tunnel, instead of the OEP the
caller is actually importing from.

data.edut_00 only exists on the OEP, so the lookup then failed with
NoSuchTableError: edut_00 against the local database — breaking every
*_oedb code path (all OEDB tests) even when a valid OEP engine/token was
passed in. The stray auto-detected tunnel is also what produced the endless
paramiko "keepalive@lag.net" logging errors, since it is never closed.

Thread the engine that import_tables_from_oep already holds (guaranteed to
be the OEP engine inside the "openenergyplatform" branch) through
_ensure_db_mappings_loaded to get_database_alias_dictionaries, so the alias
dictionary is read from the same OEP connection as the mapped tables.
Engine() auto-detect remains only as the fallback when no engine is passed.
…handling

Two fixes surfaced by running the temporally + spatially reduced OPF
(overlying_grid_opf_spatial) through the full eGo/eTraGo chain:

- powermodels_io.from_powermodels: the HV-requirement relative-error
  calculation (opf_version 3/4) compared the per-interval result frame
  (indexed by the interval's timeindex) against hv_flex_dict built over
  the full reduced index, raising a shape broadcast error (e.g. 168 vs
  336) for any multi-interval run. Align the requirement series to the
  result frame's index (.loc[df2.index]); a no-op for a single full run.

- Main.jl: broaden has_solution to also accept NEARLY_FEASIBLE_POINT
  (numerically loose but valid primal points), and wrap compute_conflict!
  (IIS) in a try/catch so a feasible-but-misreported model no longer hard-
  crashes with "Gurobi Error 10015: Cannot compute IIS on a feasible model".
Combined "full" use case: overlying_grid_opf (egon time series, overlying-
grid requirements, automatic critical-timestep selection = temporal
reduction, powermodels OPF) PLUS the spatial_reduce / spatial_restore
bracket around optimize, so the OPF runs on a spatially reduced grid while
reinforcement runs on the full topology. Both reductions are togglable.
ssh_tunnel() started an SSHTunnelForwarder but only returned its local
port — the forwarder was never referenced again, so the tunnel and its
keepalive daemon thread leaked for the whole life of the process and were
never stopped. Register an atexit hook so every tunnel is torn down
cleanly at process exit (the tunnel still stays open during the run, as
the engine is cached and reused across tasks/grids).

Also raise the paramiko.transport logger to WARNING when the tunnel is
created: paramiko logs every keepalive request at DEBUG from that daemon
thread, which — before this cleanup — could fire after pytest/interpreter
had closed the log streams, flooding the end of a test run with harmless
"ValueError: I/O operation on closed file" logging errors.
- spatial_reduction_opf.yaml: replace the hardcoded /home/gurobi overlying
  path with a placeholder, and fix the database source ("local" is not a
  valid pipeline source — use "egon-data") so the shipped preset works.
- run_spatial_reduction_example.py: use placeholder paths instead of a
  developer's local ding0/overlying directories.
- drop CONTEXT.md and docs_notes/ (internal design/grilling notes, one of
  which leaked a local repo path) — not intended for publication.
edisgo/opf/eDisGo_OPF.jl was missing from every built artifact, so
pm_optimize() could never work in a non-editable installation. It fails at
powermodels_opf.py:415, which launches julia with an absolute path derived
from __file__ -- fine in a source checkout, a missing file in site-packages.
This has been broken since at least v0.2.1, so the OPF has never run from a
released eDisGo. It matters now because eGo pulls eDisGo as
"edisgo @ git+https://github.com/openego/eDisGo.git@dev", which pip builds
into a real wheel.

package_data patterns are resolved relative to the package they are listed
under, and edisgo/opf is a package of its own (unlike edisgo/config,
edisgo/equipment and edisgo/run/presets, which are plain data directories
inside the edisgo package). The Julia tree therefore needs its own
"edisgo.opf" key; it was unreachable from the existing "edisgo" entry.

Nobody noticed because everything installs editable during development and
the test workflow installs from the checkout, never from a built artifact. So
add a Packaging workflow that builds both artifacts and checks them:

- .github/scripts/check_packaged_data.py asserts that the Julia sources, the
  config files, the equipment tables and the run presets are all present in
  the wheel and the sdist. Runnable locally before a release. It also warns
  about the egg-info trap: a stale SOURCES.txt keeps packing files from
  earlier builds, which makes a broken sdist look complete.
- the workflow additionally installs the wheel into a fresh venv and resolves
  Main.jl the way powermodels_opf.py does at runtime, asserting the directory
  is writable too, since Main.jl runs Pkg.instantiate() in place.

Verified: without the package_data key the check fails on both artifacts
(0 of 16 Julia files); with it the wheel and sdist carry all 16, and the
installed layout resolves Main.jl.
The OEDB tests hit the live OEP, which intermittently returns 503/504
(oedialect ConnectionException) or refuses the connection outright
(requests ConnectionError). These transient outages fail otherwise-green
CI runs.

- add pytest-rerunfailures to the Linux basic + coverage install steps.
  It is declared in setup.py's dev extras, which the Linux jobs do not
  install; the Windows job gets it through eDisGo_env_dev.yml, which pins
  "-e .[dev]".
- retry only network failures: --reruns 2 --reruns-delay 15
  --only-rerun "Connection(Error|Exception)|Timeout", so genuine
  assertion/logic failures still fail immediately. Only two tests are
  marked runonlinux, so the OEP-hitting tests run on Windows as well and
  want the same treatment.

The Windows invocation stays on a single line. That step runs in
PowerShell (the job sets no `defaults: shell:`), where "\" is not a line
continuation, so a wrapped command aborts before pytest even starts:

    ParserError: Missing expression after unary operator '--'.
…0.3.0

The changelog stopped at the documentation restructuring and covered none of
the work merged since: the whole edisgo.run pipeline framework (#731), the
spatial complexity reduction integration (#706), the OPF rework towards
feasible non-optimal solutions and per-interval solving, the reworked database
access, the get_flexibility_bands time-index scoping and the SPDX license
headers (#607).

Also sets the release date.
- setup.py: version 0.3.0 (was 0.3.0dev, which normalised to 0.3.0.dev0), and
  fill in the metadata PyPI has been missing entirely: python_requires
  (>=3.10,<3.13 -- numpy is pinned to 1.26.4, which has no 3.13 wheels, and
  the test matrix covers 3.10 to 3.12), trove classifiers and project_urls.
- authors: Jonas Danke and Moritz Schlösser are maintaining eDisGo now, so
  they lead the author field and are named as maintainers; the previous
  contributors stay listed. Maike Held was missing from it entirely despite
  being the second-largest contributor.
- AUTHORS.md and CITATION.cff are new. Neither existed, so there was no
  complete credit list anywhere outside the git history, and GitHub had
  nothing to render for "Cite this repository".
- .mailmap consolidates the identities people committed under: git counted 43
  distinct authors for 19 actual people. Two commits were authored with Jonas'
  name but Kilian's address, so that entry matches on both fields rather than
  reassigning all of Kilian's commits.
- doc/conf.py: extend the stale copyright year.

Verified: twine check passes on both artifacts, the wheel METADATA carries the
new author/maintainer/classifiers/Requires-Python, and CITATION.cff parses
with all required CFF 1.2.0 fields present.
Co-authored-by: Moritz Schloesser <moritz.schloesser@rl-institut.de>
Co-authored-by: joda9 <66819219+joda9@users.noreply.github.com>
The three RLI thesis PDFs had gone missing from the website and were
restored from backup under year-based upload paths (2021/09, 2022/09,
2023/09). The old 2024/09 paths now return 404, failing the docs
linkcheck CI.
* docs(whatsnew): document the run pipeline, OPF and database work for 0.3.0

The changelog stopped at the documentation restructuring and covered none of
the work merged since: the whole edisgo.run pipeline framework (#731), the
spatial complexity reduction integration (#706), the OPF rework towards
feasible non-optimal solutions and per-interval solving, the reworked database
access, the get_flexibility_bands time-index scoping and the SPDX license
headers (#607).

Also sets the release date.

* chore(release): 0.3.0 metadata, authors and citation info

- setup.py: version 0.3.0 (was 0.3.0dev, which normalised to 0.3.0.dev0), and
  fill in the metadata PyPI has been missing entirely: python_requires
  (>=3.10,<3.13 -- numpy is pinned to 1.26.4, which has no 3.13 wheels, and
  the test matrix covers 3.10 to 3.12), trove classifiers and project_urls.
- authors: Jonas Danke and Moritz Schlösser are maintaining eDisGo now, so
  they lead the author field and are named as maintainers; the previous
  contributors stay listed. Maike Held was missing from it entirely despite
  being the second-largest contributor.
- AUTHORS.md and CITATION.cff are new. Neither existed, so there was no
  complete credit list anywhere outside the git history, and GitHub had
  nothing to render for "Cite this repository".
- .mailmap consolidates the identities people committed under: git counted 43
  distinct authors for 19 actual people. Two commits were authored with Jonas'
  name but Kilian's address, so that entry matches on both fields rather than
  reassigning all of Kilian's commits.
- doc/conf.py: extend the stale copyright year.

Verified: twine check passes on both artifacts, the wheel METADATA carries the
new author/maintainer/classifiers/Requires-Python, and CITATION.cff parses
with all required CFF 1.2.0 fields present.

* Update eDisGo_OPF.jl Manifest for Julia 1.12 compatibility

StaticArrays 1.5.12 (pinned in the old manifest) fails to precompile
on Julia >=1.11 due to Base's AbstractTriangular gaining a type
parameter. Pkg.update() resolved newer, compatible versions of
StaticArrays and its dependents (JuMP, PowerModels, MathOptInterface,
Gurobi.jl, etc.).

* Bump Julia to 1.12 in CI, compat and install docs

The updated eDisGo_OPF.jl Manifest was resolved with Julia 1.12 and pins
stdlibs that do not exist in earlier versions (e.g. JuliaSyntaxHighlighting,
StyledStrings, LinearAlgebra 1.12). Main.jl runs Pkg.instantiate() on that
checked-in manifest, so the CI job pinned to Julia 1.6 could not resolve it
and test_pm_optimize failed.

Keeping both 1.6 and 1.12 working is not possible with a single checked-in
manifest, so the declared Julia version is raised everywhere it appears.
The date stamped in the 0.3.0 metadata commit was a placeholder from before
the dev -> master merge landed. Set both CITATION.cff and the changelog to
the day the release is actually published, so the tagged tree and the Zenodo
citation metadata agree with the PyPI upload date.
config_opf_julia.cfg still pointed at julia-1.1.0/bin while the bundled
eDisGo_OPF.jl package targets Julia 1.12, and the configuration reference
includes the file verbatim, so the stale path was published in the docs right
next to an installation page asking for 1.12.

julia_bin has not been read since pm_optimize() started resolving julia from
the system PATH, so the reference no longer claims the file points to the
binary in use, and the file itself says what it is. Keeping the section rather
than dropping the file, so config directories carrying it stay loadable.
reinforce_lines_voltage_issues() determines the node at two thirds of the
feeder length (node_2_3) and normally disconnects the line there and
reconnects it to the station. If node_2_3 turns out to be the feeder
representative - the bus directly connected to the station - no line can
be disconnected and the measure falls back to reinforcing lines instead.

In that fallback only the single line between the station and the
representative was reinforced, not the remaining lines on the path to the
critical node. Since the voltage deviation at the critical node is
generally dominated by the segments further away from the station, the
measure frequently did not change the reported voltage issue at all. Grid
reinforcement then repeated the same ineffective measure in every
iteration and aborted with MaximumIterationError, typically reporting a
residual deviation in the per mille range.

LVGrid_5 of the test grid shows this: for a voltage issue at
Bus_BranchTee_LVGrid_5_2, node_2_3 is moved back to the representative
Bus_BranchTee_LVGrid_5_1. Only Line_50000003 was reinforced, which is
0.56 m long, while Line_50000002 - 30 m of NAYY 4x1x35 causing nearly all
of the voltage drop - was left untouched.

All lines on the path from the station to the critical node are now
reinforced. Feeders consisting of a single line are unaffected.

Short feeders are affected most, which is why the error shows up
predominantly in grids that went through spatial complexity reduction,
where node_2_3 frequently coincides with the representative.

Test expectations for the number of equipment changes and grid expansion
costs are updated accordingly, as more lines are reinforced per measure.
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.

2 participants