Skip to content

[BUG] pv_rooftop_to_buildings writes status-quo scenario rows twice, doubling PV capacity #1489

Description

@MoritzSchloesser

Summary

pv_rooftop_to_buildings() writes every status-quo-scenario row to
supply.egon_power_plants_pv_roof_building twice. The rows are exact duplicates, down to
the same gens_id, so the scenario's PV capacity is exactly 2x overstated.

Observed on a Schleswig-Holstein run with --scenarios: [status2024, eGon2035]:

  scenario  |  rows  | distinct_b | sum_cap  | sum_cap_dedup
------------+--------+------------+----------+---------------
 eGon2035   | 222414 |     222414 | 2733.567 |      2733.567
 status2024 | 233700 |     116850 | 3722.069 |      1861.035

eGon2035 is unaffected. Only status-quo scenarios are duplicated.

Example of a duplicated pair — identical in every column including gens_id:

 index  |  scenario  | bus_id | building_id |     gens_id     | capacity | voltage_level
--------+------------+--------+-------------+-----------------+----------+---------------
 158674 | status2024 |   1666 |      985847 | SEE907528493492 |   0.1083 |             7
  41824 | status2024 |   1666 |      985847 | SEE907528493492 |   0.1083 |             7

Cause

In pv_rooftop_buildings.py, the accumulator all_buildings_gdf is seeded with the
status-quo rows before the scenario loop starts
(:2430):

all_buildings_gdf = (
    desagg_mastr_gdf.assign(scenario=status_quo)   # status2024 rows already in
    .reset_index()
    .rename(columns={"geometry": "geom"})
    .set_geometry("geom")
)

scenario_buildings_gdf_sq = all_buildings_gdf.copy()

The loop then iterates over SCENARIOS, which includes status2024, and appends each
scenario's rows to that same accumulator (:2440, :2473):

for scenario in SCENARIOS:
    if scenario == status_quo:
        scenario_buildings_gdf = scenario_buildings_gdf_sq.copy()   # the same rows again
    ...
    all_buildings_gdf = gpd.GeoDataFrame(
        pd.concat([all_buildings_gdf, scenario_buildings_gdf], ignore_index=True),
        crs=scenario_buildings_gdf.crs,
        geometry="geom",
    )

On the status2024 iteration the seed set is appended a second time. eGon2035 takes the
else branch, where allocate_scenarios() builds a fresh set that was never used as the seed,
which is why only status-quo scenarios are affected.

The bug can only fire when a status-quo scenario is in SCENARIOS, so runs predating status-quo
scenarios never hit it.

Downstream impact

  • storages/home_batteries.py:127-139 sums capacity per bus_id from this table to size
    home batteries. For status-quo scenarios that sum is doubled, so battery capacities derived
    from it are wrong.
  • sanity_checks.py:681 (sanitycheck_pv_rooftop_buildings) reads the whole table with
    SELECT *. Any per-scenario totals or building-level comparisons it makes are affected.
  • heat_supply/individual_heating.py (determine_buildings_with_hp_in_mv_grid) queries this
    table to weight buildings with PV at 1.5x when distributing heat pumps. The duplicated rows put
    duplicate entries into the weights index, so np.random.choice could select the same building
    twice; both copies were then allocated capacity and the second was silently dropped by
    drop_duplicates("building_id") in the bulk export, losing that capacity from the grid's budget
    with no rescaling. This caused a ~0.3-0.5 % conservation shortfall in
    demand.egon_hp_capacity_buildings for status2024 (~1.68 MW of 349.42 MW in one run) and made
    results non-reproducible between runs. A defensive dedup has been added on the heat-pump side,
    but the underlying data is still wrong for every other consumer.

Suggested fix

Either seed the accumulator empty and let the loop populate it:

all_buildings_gdf = gpd.GeoDataFrame(
    columns=scenario_buildings_gdf_sq.columns,
    crs=scenario_buildings_gdf_sq.crs,
    geometry="geom",
)

or keep the seed and skip the redundant append on the status-quo iteration.

A UNIQUE constraint on (scenario, gens_id) — or on (scenario, building_id, gens_id) — would
turn a recurrence into a hard failure rather than a silent doubling.

Reproducing

Run electricity_supply.power_plants.pv_rooftop_buildings.pv-rooftop-to-buildings with any
status-quo scenario configured, then:

SELECT scenario, count(*) AS rows, count(DISTINCT building_id) AS distinct_buildings
FROM supply.egon_power_plants_pv_roof_building
GROUP BY 1;

Status-quo scenarios show rows = 2 x distinct_buildings.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

🐛 bugSomething isn't working

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions