energy_penalty() applies the capture energy penalty to only one of six technologies

Aperta
#283 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub

Nessuno ha ancora preso questa issue.

Valutazione

Difficoltà
3/5
Tempo stimato
1-2 giorni
Idoneità per principianti
68/100
Tipo di issue
Bug
Chiarezza
Specificata chiaramente
Stato di attività
Attiva
Stack tecnologico
python
Ambito
build-system, data

Direzione di ricerca

Iniziare in scripts/compile_cost_assumptions.py, all’interno di energy_penalty(), e seguire il ciclo delle sei tecnologie, i rami di dispatch e l’assegnazione duplicata di VOM. Verificare che ogni tecnologia di cattura del carbonio elencata venga elaborata, che il biogas utilizzi il ramo previsto e che i valori generati in outputs/costs_2050.csv includano gli aggiustamenti attesi per investimento, VOM ed efficienza.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

bug

energy_penalty() exits after the first technology: five of six CC technologies never get the capture energy penalty

Note: Claude found this issue, so below is a summary by it.

Summary

In scripts/compile_cost_assumptions.py, energy_penalty() loops over six carbon-capture technologies and adds an auxiliary steam boiler to cover the heat demand of the capture unit, rescaling investment, VOM, efficiency and (for CHPs) heat efficiency accordingly.

The loop body contains a break where pass appears to have been intended. central solid biomass CHP CC is the first item in the list and has a non-zero VOM, so the loop exits during the first iteration — after the investment update but before the VOM, efficiency and efficiency-heat updates.

The net effect on the published outputs/costs_*.csv:

  • central solid biomass CHP CC receives the investment uplift but keeps its unpenalized electrical and heat efficiency and its unadjusted VOM.
  • waste CHP CC, solid biomass boiler steam CC, direct firing solid fuels CC, direct firing gas CC and biogas CC receive nothing at all — their investment, VOM and efficiency are identical to their non-CC counterparts.

Downstream this makes carbon capture cheaper and more efficient than the function intends, which matters for any study comparing BECCS routes against each other or against unabated conversion.

Location

scripts/compile_cost_assumptions.py, in energy_penalty() — the if begins at line 3313 on current master:

        if cost_dataframe.loc[(tech_name, "VOM"), "value"]:
            break
        else:
            cost_dataframe.loc[(tech_name, "VOM"), "value"] = 0.0

Everything after this block — the VOM rescaling, efficiency = eta_main, and the if "CHP" in tech_name heat-efficiency block — is unreachable for every technology whose VOM is non-zero, and unreachable for all subsequent technologies regardless.

The same code is present at least as far back as v0.11.0, so the outputs have been affected for a long time.

Evidence in the shipped outputs

The source field records which lines executed, because the function overwrites it with "Combination of <tech> and <boiler>". From outputs/costs_2050.csv:

technology parameter value source
central solid biomass CHP CC investment 6003.1610 Combination of central solid biomass CHP CC and solid biomass boiler steam
central solid biomass CHP CC efficiency 0.2652 Danish Energy Agency, inputs/technology_data_for_el_and_dh.xlsx
central solid biomass CHP CC VOM 6.2350 Danish Energy Agency, inputs/technology_data_for_el_and_dh.xlsx
waste CHP CC investment 9442.6408 Danish Energy Agency, inputs/technology_data_for_el_and_dh.xlsx

The first row shows the investment line ran; the second and third show the loop had already exited; the fourth shows the second technology was never reached. waste CHP CC, solid biomass boiler steam CC, direct firing solid fuels CC, direct firing gas CC and biogas CC all carry investment and efficiency values identical to their non-CC rows.

Magnitude

Re-running the intended arithmetic on costs_2050.csv (capture heat-input 0.66 MWh/tCO₂; boiler efficiencies 0.90 solid biomass / 0.94 gas; CO₂ intensities 0.3667 solid biomass, 0.198 gas):

technology investment now intended Δ efficiency now intended Δ
central solid biomass CHP CC 6003 6003 (already applied) 0.2652 0.2090 −21.2 %
waste CHP CC 9443 12 741 +34.9 % 0.2165 0.1706 −21.2 %
solid biomass boiler steam CC 679 1044 +53.8 % 0.9000 0.7093 −21.2 %
direct firing solid fuels CC 279 518 +85.9 % 1.0000 0.7881 −21.2 %
direct firing gas CC 19 29 +53.5 % 1.0000 0.8779 −12.2 %
biogas CC 1091 1250 +14.6 % 1.0000 0.8779 −12.2 %

Heat efficiencies also change for the two CHPs: central solid biomass CHP CC 0.8294 → 0.9433, waste CHP CC 0.7625 → 0.8906.

So the shipped cost data understates capture-inclusive investment by 15–86 % for five technologies, and overstates the conversion efficiency of all six by 12–21 %.

Secondary issues in the same function

1. The elif "biogas" branch is unreachable. The dispatch is ordered

if "powerboost" in tech_name: ...
elif "gas" in tech_name: ...
elif "biogas" in tech_name: ...
else: ...

"gas" in "biogas CC" is True, so biogas CC is always caught by the "gas" branch and takes the natural-gas CO₂ intensity (0.198 tCO₂/MWh) instead of its own CO2 stored (0.1447 tCO₂/MWh). Once the break is fixed, this changes biogas CC from investment 1250 / efficiency 0.8779 to 1207 / 0.9078. Testing "biogas" before "gas" would fix it.

2. The "powerboost" branch is dead. central solid biomass CHP powerboost CC is defined in the sheet mappings but is absent from the technology list the loop iterates over, so the branch never runs.

3. The heat-efficiency block hardcodes the solid-biomass CO₂ intensity.

if "CHP" in tech_name:
    cost_dataframe.loc[(tech_name, "efficiency-heat"), "value"] = (
        cost_dataframe.loc[(tech_name, "efficiency-heat"), "value"] * scalingFactor
        + cost_dataframe.loc[("solid biomass", "CO2 intensity"), "value"] * (...)
    )

This uses solid biomass rather than the branch's own co2_capture, which is wrong for waste CHP CC (whose capture is applied to the oil CO₂ intensity downstream in PyPSA-Eur).

4. feedstock is assigned in three branches and never read, and is not assigned in the "biogas" branch — harmless today, but it suggests the dispatch was refactored at some point and the break is a leftover from that.

Suggested fix

-        if cost_dataframe.loc[(tech_name, "VOM"), "value"]:
-            break
-        else:
+        if not cost_dataframe.loc[(tech_name, "VOM"), "value"]:
             cost_dataframe.loc[(tech_name, "VOM"), "value"] = 0.0

and, for the dispatch:

         if "powerboost" in tech_name:
             ...
-        elif "gas" in tech_name:
-            ...
         elif "biogas" in tech_name:
             boiler = "gas boiler steam"
             co2_capture = cost_dataframe.loc[(tech_name, "CO2 stored"), "value"]
+        elif "gas" in tech_name:
+            ...
         else:
             ...

There is also a duplicated VOM assignment block later in the function — the identical rescaling appears twice, once immediately after the break and again after the efficiency-heat block. It is currently harmless because the loop never reaches either, but once the break is fixed the second occurrence would apply the rescaling a second time, so one of them should go.

Lingua principale
Python
Stelle
130
Fork
59
Metriche di merge delle PR
Nessuna PR unita negli ultimi 30g

Guida per i contributori

Nessuna guida per i contributori indicizzata per questo repository

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di PyPSA/technology-data

Tutte le issue di PyPSA/technology-data

Issue simili

Altre issue su Python

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.