Skip to content

[BUG] District heating demand assignment ignores grid boundaries, leading to unrealistic heat pump dispatch #622

@joda9

Description

@joda9

Summary

When running eDisGo standalone (without eTraGo), district heating demand is fully assigned to heat pumps located inside the ding0 grid district — even when the underlying district heating network extends well beyond the grid boundary. As a result, heat pumps inside the grid are required to cover the entire district heating demand, including the parts physically located outside the modeled grid. In real-world dispatch, eTraGo would distribute this demand across multiple supply units (other heat pumps, CHPs, peak boilers) along the broader heating network. Without that coupling, eDisGo produces unrealistic heat pump capacities and load profiles.

Reproduction

  • Grid: ding0_grid_id = 30891 (north of Hamburg)
  • Scenario: eGon2035
  • Workflow:
    1. EDisGo(ding0_grid=...)
    2. edisgo.import_heat_pumps(scenario="eGon2035", import_types=["central_heat_pumps"], engine=...)
    3. Inspect edisgo.heat_pump.heat_demand_df and the resulting loads_active_power of the central heat pump.
  • Observation: a single central heat pump in grid 30891 ends up with a peak active power demand of ~800 MW, far above the installed unit capacity and any plausible local heat sink. The reason is that the entire egon_district_heating_areas / demand.egon_demandregio_zensus_electricity-derived district heat demand for the heating area linked to that heat pump is fully attributed to the grid, ignoring the geometric overlap between the heating area and the MV grid district.

Expected behavior

Heat demand assigned to heat pumps that supply district heating areas should reflect only the share of the heat demand whose physical heat sinks (buildings, residential/CTS heat consumers) lie inside the ding0 MV grid district — or, more generally, the local share of the heat pump's nominal contribution to that district heating area.

Proposed fix

Add a weighting step when populating heat_pump.heat_demand_df for central_heat_pumps:

  1. For each district heating area linked to a central heat pump in the grid:
    • Determine the geometric intersection of the heating area polygon with the MV grid district polygon (edisgo.topology.grid_district["geom"]).
    • Weight the demand by either:
      • the share of heat-demanding buildings/zensus cells of that heating area that fall inside the grid district (preferred, demand-weighted), or
      • the share of the heating area's area inside the grid district (geometric fallback).
  2. Apply the weight to the imported heat demand time series before it is written into heat_pump.heat_demand_df.
  3. Optionally also scale the heat pump's nominal capacity (p_set) by the same factor, so that pre-reinforcement worst-case calculations stay consistent.

This keeps the standalone eDisGo workflow self-contained while removing the most severe over-counting. The "true" allocation across multiple supply units along a district heating network would still require eTraGo, but a geometric/demand-based weighting brings results into a realistic order of magnitude.

Why this matters

Without the fix, downstream steps are systematically biased:

  • pm_optimize solves an OPF with a load that physically cannot be served locally, leading to non-convergence or absurd dispatch.
  • reinforce / enhanced_reinforce_grid over-dimensions the grid, sometimes by orders of magnitude, and reports inflated grid expansion costs.
  • Comparative studies between grids are not robust, because the bias depends on how much of the linked district heating area extends outside the grid district — which is grid-specific.

Suggested API

A new parameter for import_heat_pumps, e.g.:

edisgo.import_heat_pumps(
    scenario="eGon2035",
    import_types=["central_heat_pumps"],
    engine=db_engine,
    weight_district_heating_demand="building_share",  # or "area_share" / None
)

with None preserving the current (unweighted) behavior for backwards compatibility.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions