Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generalize generator types #656

Merged
merged 6 commits into from
Sep 9, 2022
Merged

Generalize generator types #656

merged 6 commits into from
Sep 9, 2022

Conversation

rouille
Copy link
Collaborator

@rouille rouille commented Jul 4, 2022

Pull Request doc

Purpose

Generalize generator type since these will depend on the selected grid model.

What the code is doing

Define constants for generators (such as color, label, emission, etc.) for each supported grid model.

Testing

Existing unit tests.

Where to look

  • The powersimdata.network.constants.plants module is where everything is defined
  • Other modules are refactored to generalize generator types

Usage Example/Visuals

N/A

Time estimate

45min

@rouille rouille self-assigned this Jul 4, 2022
@rouille rouille changed the title Ben/gentype Generalize generator types Jul 4, 2022
@rouille rouille requested a review from danlivengood July 7, 2022 17:19
@rouille
Copy link
Collaborator Author

rouille commented Aug 2, 2022

@FabianHofmann, @chrstphtrs, if you could help @danlivengood filling out the efficiency and emission values for PyPSA-Eur (currently set to None) that would be fantastic.

@danlivengood
Copy link

danlivengood commented Aug 2, 2022

What I'm seeing thus far is:
OCGT, from the DEA data sheet tab 52
efficiency = 0.41
carbon_per_mwh = didn't find this one in DEA - could use the IPCC report
carbon_per_mmbtu = didn't find this one in DEA - could use the EPA calculator
nox_per_mwh = 0.173 (converted the 2020 ctrl value of 48 g/GJ using 1 MWh = 0.277778 GJ, same value for 2030 ctrl and 2050 ctrl)
so2_per_mwh = 0.002 (converted the 2020 ctrl value of 0.43 g/GJ using 1 MWh = 0.277778 GJ, same value for 2030 ctrl and 2050 ctrl)

CCGT, from the DEA data sheet tab 5
efficiency = 0.58
carbon_per_mwh = didn't find this one in DEA - could use the IPCC report
carbon_per_mmbtu = didn't find this one in DEA - could use the EPA calculator
nox_per_mwh = 0.054 (converted the 2020 ctrl value of 15 g/GJ using 1 MWh = 0.277778 GJ, value decreases to 10 g/GJ for 2030 ctrl and 8 g/GJ for 2050 ctrl)
so2_per_mwh = 0

For the others:
Are the lignite values you use different from the coal values?
Are the coal values we have in powersimdata/network/constants/plants.py sufficiently close to your values?
Are the DFO (oil) values we have sufficiently close to your values for oil?
Do you include biomass data in your constants? If so, which of the DEA tabs do you use?

@rouille rouille force-pushed the ben/gentype branch 2 times, most recently from cef207b to 0bae8af Compare August 5, 2022 02:41
@chrstphtrs
Copy link
Contributor

Hi Dan,

It looks like you need to differentiate your constants (emissions, efficiency etc.). Split ng into ocgt and ccgt, and (for a European model) split coal into hard_coal and lignite.

Some general notes:

  • For efficiency from DEA, use "annual average" values rather than "name plate"
  • Think about which year to apply as default values: 2015 likely useful for existing capacity, for capacity expansion 2030 or 2050
  • CO2 emissions intensity is a constant given by fuel type (negligible variance over time) in terms of its energy content, so tCO2/TJ or kgCO2/MWh_th (e.g. natural gas 55.8 tCO2/TJ = 201 kgCO2/MWh_th)
  • Two different technologies using the same fuel (e.g. OCGT and CCGT running with natural gas) can have different CO2 emissions per MWh_el based on their efficiency: kgCO2/MWh_el = kgCO2/MWh_th / efficiency_tech (so 347 kgCO2/MWh_el for CCGT at 58% efficiency, 490 kgCO2/MWh_el for OCGT at 41% efficiency)
  • The variable carbon_per_mmbtu is somewhat misleading as it is not analogous to carbon_per_mwh: the latter specifies the carbon dioxide (CO2) emissions intensity of a fuel (in CO2/energy unit, see above), the former the carbon (C) content (in C/energy unit, which is factorized with the "carbon dioxide intensity of carbon" (ratio of the molecular weight of carbon dioxide to carbon, always 44 kg CO2/12 kg C), to get the carbon dioxide emissions intensity of the fuel)
  • Try to use one source for CO2 emissions intensity of all fuel types (e.g. this one for Germany, page 52: https://www.umweltbundesamt.de/publikationen/co2-emission-factors-for-fossil-fuels-0)
  • CO2 emissions of coal vary with type and mining location. Especially lignite sometimes has significantly higher emissions intensity. This is the case in Germany/Europe, which is why we have separate emissions values for lignite and hard coal in PyPSA-Eur. In the US, emissions intensities are more comparable, so one emissions factor for both hard coal (already an aggregate of bituminous, sub-bituminous, anthracite coal) and lignite is reasonable.

Editing your notes from above (>>):
OCGT, from the DEA data sheet tab 52 >> yes!
efficiency = 0.41 >> use annual average, decide about year
carbon_per_mwh = didn't find this one in DEA - could use the IPCC report >> use emissions intensity for natural gas, divide by OCGT efficiency. In theory, if plants have individual efficiencies, their corresponding individual carbon_per_mwh should be calculated by the model. But if your carbon accounting is set up with average values, the average (default) efficiency for each technology type will do.
carbon_per_mmbtu = didn't find this one in DEA - could use the EPA calculator >> this is a constant for each fuel type, e.g. 14.43 kg C/mmbtu for natural gas according to EPA: https://www.epa.gov/energy/greenhouse-gases-equivalencies-calculator-calculations-and-references
nox_per_mwh = 0.173 (converted the 2020 ctrl value of 48 g/GJ using 1 MWh = 0.277778 GJ, same value for 2030 ctrl and 2050 ctrl) >> yes
so2_per_mwh = 0.002 (converted the 2020 ctrl value of 0.43 g/GJ using 1 MWh = 0.277778 GJ, same value for 2030 ctrl and 2050 ctrl) >> yes

CCGT, from the DEA data sheet tab 5 >> yes, use "05 Gas turb. CC, steam extract." as CCGT plants tend to be > 100MW
>> other values same as above

For the others:
Are the lignite values you use different from the coal values? >> for Germany/Europe, yes (see UBA, table 23)
Are the coal values we have in powersimdata/network/constants/plants.py sufficiently close to your values? >> efficiency 0.33 ok, carbon_per_mwh should be consistently calculated with emissions intensity of fuel and efficiency of technology. We don't track nox, so2, carbon_per_mmbtu.
Are the DFO (oil) values we have sufficiently close to your values for oil? >> depends on assumptions for efficiency of the technology where DFO is used. We have 270 kgCO2/MWh_th for light heating oil (=DFO, from 74 tCO2/TJ in UBA, table 23), for a peaker plant with 35% efficiency (e.g. DEA "50 Diesel engine farm") this would be 771 kg/MWh_el. Some (older) peakers also use heavy fuel oil, which would be 79.7 tCO2/TJ, 287 kgCO2/MWh_th and 820 kgCO2/MWh_el (at 35% efficiency). We don't use this either.
Do you include biomass data in your constants? If so, which of the DEA tabs do you use? >> We have 300 kgCO2/MWh_th. For a large biomass power plant, use a mix of "09a Wood Chips extract. plant" and "09b Wood Pellets extract. plant" (e.g. 41.6% efficiency annual average, 2015).

@rouille rouille force-pushed the ben/gentype branch 2 times, most recently from 7149e43 to 9e167b1 Compare August 5, 2022 19:44
@danlivengood
Copy link

Hi Chris,
First, an easy one (I think)
It looks like you need to differentiate your constants (emissions, efficiency etc.). Split ng into ocgt and ccgt, and (for a European model) split coal into hard_coal and lignite.
For the europe_tub model, we do split them. See lines 336-368 of powersimdata/network/constants/plants.py
They're only combined for the tamu and hifld models

@danlivengood
Copy link

For efficiency from DEA, use "annual average" values rather than "name plate" - Ah! Good call.
Think about which year to apply as default values: 2015 likely useful for existing capacity, for capacity expansion 2030 or 2050
For now, we're just focused on the values needed for a PCM run for the current state of PyPSA-Eur, so we'll use the 2015 values.
We have an improvement planned to allow for these values to change relative to the simulated year, so we'll later collect projections and have that in the PSD data.

@danlivengood
Copy link

Our carbon_per_mwh is perhaps a bit misleading - that is relative to mwh_el not mwh_th. Hence, we have 469 kgCO2/MWh_el for all of the ng plants in the tamu and hifld grids. We'll be sure to capture the efficiency values for OCGT and CCGT in the PyPSA_Eur constants

@rouille rouille force-pushed the ben/gentype branch 6 times, most recently from 300610c to 5f7a61b Compare August 15, 2022 22:17
@rouille rouille marked this pull request as ready for review August 15, 2022 22:18
@rouille
Copy link
Collaborator Author

rouille commented Aug 15, 2022

This is ready for review. I believe I got all hard coded generator types. Also, I have changed the default Pmin value when adding a new generator via the change table.

@rouille rouille requested a review from lanesmith August 18, 2022 20:15
@rouille rouille force-pushed the ben/gentype branch 2 times, most recently from 2c02d65 to bbeeb40 Compare August 19, 2022 21:27
@rouille rouille force-pushed the ben/gentype branch 5 times, most recently from b88e407 to 76e08a3 Compare September 2, 2022 20:16
@rouille
Copy link
Collaborator Author

rouille commented Sep 2, 2022

All the references for efficiency and emission have been added. it looks like this in the docs:

Efficiency
Screen Shot 2022-09-02 at 2 01 18 PM
Emission
Screen Shot 2022-09-02 at 2 02 41 PM

Copy link
Collaborator

@lanesmith lanesmith left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me. See my comments for some typo fixes in the docs.

Comment on lines 200 to 202
onshore_wind_type = {
v: k for k, v in grid.model_immutables.plants["type2label"].items()
}["Onshore Wind"]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm trying to understand the logic here, we do have "label2type" returned in get_plants function which is populated into model_immutables.plants, right? Also, we have "Onshore Wind" label only for Europe grid, no?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right that we have label2type and there is no need to revert the dict. For some reason, I believed I renamed the label for USA but it seems I did not. It should be:

    def __init__(self):
        self.type2label = {
            "nuclear": "Nuclear",
            "geothermal": "Geothermal",
            "coal": "Coal",
            "dfo": "Diesel Fuel Oil",
            "hydro": "Hydro",
            "ng": "Natural Gas",
            "solar": "Solar",
            "wind": "Onshore Wind",
            "wind_offshore": "Offshore Wind",
            "biomass": "Biomass",
            "other": "Other",
            "storage": "Storage",
        }
        self.curtailable2label = {
            "solar": "Solar Curtailment",
            "wind": "Onshore Wind Curtailment",
            "wind_offshore": "Offshore Wind Curtailment",
        }

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment on lines 8 to 10
pmin_by_type={"hydro": None},
pmin_by_id=None,
curtailable={"solar", "wind"},
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure whether we would like to keep mutable defaults here.

Copy link
Collaborator Author

@rouille rouille Sep 7, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was trying to keep the original behavior of the function since we added hydro to the list of curtailable resources.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I see where you come from. Just wonder whether we should move mutable defaults into if logic in the function as we did it elsewhere, such as pmin_by_type = pmin_by_type if pmin_by_type else {"hydro": None}.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Copy link
Collaborator

@BainanXia BainanXia left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice. Thanks!

@rouille rouille merged commit 3b321bb into develop Sep 9, 2022
@rouille rouille deleted the ben/gentype branch September 9, 2022 21:16
@jenhagg jenhagg mentioned this pull request Dec 10, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
refactor Code that is being refactored
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants