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.
Summary
pv_rooftop_to_buildings()writes every status-quo-scenario row tosupply.egon_power_plants_pv_roof_buildingtwice. The rows are exact duplicates, down tothe same
gens_id, so the scenario's PV capacity is exactly 2x overstated.Observed on a Schleswig-Holstein run with
--scenarios: [status2024, eGon2035]:eGon2035is unaffected. Only status-quo scenarios are duplicated.Example of a duplicated pair — identical in every column including
gens_id:Cause
In
pv_rooftop_buildings.py, the accumulatorall_buildings_gdfis seeded with thestatus-quo rows before the scenario loop starts (
:2430):The loop then iterates over
SCENARIOS, which includesstatus2024, and appends eachscenario's rows to that same accumulator (
:2440,:2473):On the
status2024iteration the seed set is appended a second time.eGon2035takes theelsebranch, whereallocate_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-quoscenarios never hit it.
Downstream impact
storages/home_batteries.py:127-139sumscapacityperbus_idfrom this table to sizehome 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 withSELECT *. 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 thistable to weight buildings with PV at 1.5x when distributing heat pumps. The duplicated rows put
duplicate entries into the
weightsindex, sonp.random.choicecould select the same buildingtwice; 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 budgetwith no rescaling. This caused a ~0.3-0.5 % conservation shortfall in
demand.egon_hp_capacity_buildingsfor status2024 (~1.68 MW of 349.42 MW in one run) and maderesults 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:
or keep the seed and skip the redundant append on the status-quo iteration.
A
UNIQUEconstraint on(scenario, gens_id)— or on(scenario, building_id, gens_id)— wouldturn a recurrence into a hard failure rather than a silent doubling.
Reproducing
Run
electricity_supply.power_plants.pv_rooftop_buildings.pv-rooftop-to-buildingswith anystatus-quo scenario configured, then:
Status-quo scenarios show
rows = 2 x distinct_buildings.