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

refactory energy calculation #185

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
75 changes: 1 addition & 74 deletions co2calculator/calculate.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,8 @@
calc_co2_bicycle,
calc_co2_bus,
calc_co2_car,
calc_co2_ferry,
calc_co2_motorbike,
calc_co2_pedelec,
calc_co2_plane,
calc_co2_train,
calc_co2_tram,
)
Expand All @@ -25,13 +23,11 @@
CarFuel,
BusFuel,
TrainFuel,
ElectricityFuel,
HeatingFuel,
Unit,
TransportationMode,
CountryCode2,
)
from .data_handlers import EmissionFactors, ConversionFactors
from .distances import create_distance_request, get_distance, range_categories
from .parameters import (
ElectricityEmissionParameters,
HeatingEmissionParameters,
Expand All @@ -43,75 +39,6 @@
conversion_factors = ConversionFactors()


def calc_co2_electricity(
consumption: float,
fuel_type: ElectricityFuel = None,
country_code: CountryCode2 = None,
own_share: float = 1,
) -> Kilogram:
"""Function to compute electricity emissions

:param consumption: energy consumption
:param fuel_type: energy (mix) used for electricity [production fuel mix, residual fuel mix]
:param country_code: 2 Letter ISO country code
:param energy_share: the research group's approximate share of the total electricity energy consumption
:type consumption: float
:type fuel_type: str
:type country_code: str
:type own_share: float
:return: total emissions of electricity energy consumption
:rtype: Kilogram
"""

# Validate parameters
params_extracted = {k: v for k, v in locals().items() if v is not None}
params = ElectricityEmissionParameters(**params_extracted)

# Get the co2 factor
co2e = emission_factors.get(params.dict())

return consumption * own_share * co2e


def calc_co2_heating(
consumption: float,
fuel_type: HeatingFuel,
unit: Unit = None,
own_share: float = 1.0,
) -> Kilogram:
"""Function to compute heating emissions

:param consumption: energy consumption
:param fuel_type: fuel type used for heating
[coal, district_heating, electricity, gas, heat_pump_air,
heat_pump_ground, liquid_gas, oil, pellet, solar, woodchips]
:param unit: unit of energy consumption [kwh, kg, l, m^3]
:param own_share: share of building area used by research group
:type consumption: float
:type fuel_type: str
:type unit: str
:type own_share: float
:return: total emissions of heating energy consumption
:rtype: Kilogram
"""
# Validate parameters
assert 0 < own_share <= 1
params_extracted = {k: v for k, v in locals().items() if v is not None}
params = HeatingEmissionParameters(**params_extracted)

# Get the co2 factor
co2e = emission_factors.get(params.dict())

if unit is not Unit.KWH:
# Get the conversion factor
conversion_factor = conversion_factors.get(fuel_type=fuel_type, unit=unit)
consumption_kwh = consumption * conversion_factor
else:
consumption_kwh = consumption

return consumption_kwh * co2e * own_share


def calc_co2_trip(
distance: Kilometer | None,
transportation_mode: TransportationMode,
Expand Down
3 changes: 2 additions & 1 deletion co2calculator/data_handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,9 @@ def get(self, fuel_type, unit):
:return:
:rtype:
"""
print(f'fuel_type == "{fuel_type.value}" & unit == "{unit}"')
Copy link
Collaborator

Choose a reason for hiding this comment

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

shall this print statement be kept here?

selected_factors = self.conversion_factors.query(
f'fuel_type=="{fuel_type}" & unit=="{unit}"'
f'fuel_type == "{fuel_type.value}" & unit == "{unit}"'
)
if selected_factors.empty:
raise ConversionFactorNotFound(
Expand Down
Empty file.
85 changes: 85 additions & 0 deletions co2calculator/energy/calculate_energy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
"""Function colleciton to calculate energy type co2 emissions"""

from typing import Union
from co2calculator.constants import Unit
from co2calculator.data_handlers import ConversionFactors, EmissionFactors
from co2calculator.parameters import (
ElectricityEmissionParameters,
ElectricityParameters,
HeatingEmissionParameters,
HeatingParameters,
)
from co2calculator._types import Kilogram

emission_factors = EmissionFactors()
conversion_factors = ConversionFactors()


def calc_co2_heating(
consumption: float, options: Union[HeatingParameters, dict]
) -> Kilogram:
"""Function to compute heating emissions

:param consumption: energy consumption
:param options: parameters for heating emissions calculation
:type consumption: float
:type options: HeatingParameters | dict
:return: total emissions of heating energy consumption
:rtype: Kilogram
"""
# Validate parameters
if options is None:
options = {}
params = HeatingParameters(**options)
emission_params = HeatingEmissionParameters(
**params.heating_emission_parameters.dict()
)

# Get the co2 factor
co2e = emission_factors.get(emission_params.dict())

if params.unit is not Unit.KWH:
print(emission_params.fuel_type, params.unit)
# Get the conversion factor
conversion_factor = conversion_factors.get(
fuel_type=emission_params.fuel_type, unit=params.unit
)

consumption_kwh = consumption * conversion_factor
else:
consumption_kwh = consumption

# co2 equivalents for heating and electricity refer to a consumption of 1 TJ
# so consumption needs to be converted to TJ
Copy link
Collaborator

Choose a reason for hiding this comment

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

These comments can be removed as we are not converting to TJ anymore

return consumption_kwh * params.area_share * co2e


def calc_co2_electricity(
consumption: float, options: Union[ElectricityParameters, dict]
) -> Kilogram:
"""Function to compute electricity emissions

:param consumption: energy consumption
:param fuel_type: energy (mix) used for electricity [german_energy_mix, solar]
:param energy_share: the research group's approximate share of the total electricity energy consumption
:type consumption: float
:type fuel_type: str
:type energy_share: float
:return: total emissions of electricity energy consumption
:rtype: Kilogram
"""

# Validate parameters
if options is None:
options = {}
params = ElectricityParameters(**options)
emission_params = ElectricityEmissionParameters(
**params.electricity_emission_parameters.dict()
)

# Get the co2 factor
co2e = emission_factors.get(emission_params.dict())

# co2 equivalents for heating and electricity refer to a consumption of 1 TJ
# so consumption needs to be converted to TJ
return consumption * params.energy_share / co2e
Copy link
Collaborator

Choose a reason for hiding this comment

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

should be multiplication not division! (I think we had this fixed somewhere before already?)

and comments can be removed, as we are not converting to TJ anymore (with the new emission factors)

35 changes: 33 additions & 2 deletions co2calculator/parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
HeatingFuel,
EmissionCategory,
CountryCode2,
Unit,
)
from typing import Union

Expand Down Expand Up @@ -157,7 +158,9 @@ class ElectricityEmissionParameters(BaseModel):

category: EmissionCategory = EmissionCategory.ELECTRICITY
fuel_type: Union[ElectricityFuel, str] = ElectricityFuel.PRODUCTION_FUEL_MIX
country_code: CountryCode2 # TODO: Shall we set a default? Or add a watning if not provided?
country_code: Union[
CountryCode2, str
] = "DE" # TODO: Shall we set a default? Or add a warning if not provided?

@validator("fuel_type", allow_reuse=True)
def check_fueltype(cls, v):
Expand All @@ -167,15 +170,43 @@ def check_fueltype(cls, v):
return ElectricityFuel(v)


class ElectricityParameters(BaseModel):
electricity_emission_parameters: ElectricityEmissionParameters
energy_share: float = 1.0

@validator("energy_share", allow_reuse=True)
def check_energy_share(cls, v):
assert 0 <= v <= 1
return v


class HeatingEmissionParameters(BaseModel):

category: EmissionCategory = EmissionCategory.HEATING
fuel_type: Union[HeatingFuel, str] = HeatingFuel.GAS
country_code: str = "global"
country_code: Union[CountryCode2, str] = "global"

@validator("fuel_type", allow_reuse=True)
def check_fueltype(cls, v):
if isinstance(v, str):
assert v.lower() in (item.value for item in HeatingFuel)
v = v.lower()
return HeatingFuel(v)


class HeatingParameters(BaseModel):
heating_emission_parameters: HeatingEmissionParameters
unit: Unit
area_share: float = 1.0

@validator("unit", allow_reuse=True)
def check_unit(cls, v):
if isinstance(v, str):
assert v.lower() in (item.value for item in Unit)
v = v.lower()
return Unit(v)

@validator("area_share", allow_reuse=True)
def check_area_share(cls, v):
assert 0 <= v <= 1
return v
2 changes: 1 addition & 1 deletion data/conversion_factors_heating.csv
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
1,liquid_gas,kg,14.1
2,coal,kg,6.0
3,pellet,kg,5.4
4,woodchips,kg,5.2
4,wood chips,kg,5.2
Copy link
Collaborator

Choose a reason for hiding this comment

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

I would keep it as one word - or alternatively "wood_chips" (similar to "liquid_gas"

5,gas,m^3,10.8
2 changes: 1 addition & 1 deletion data/emission_factors_heating.csv
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@
2,global,heating,"UK, Department for Business, Energy & Industrial Strategy",LPG,kWh,liquid_gas,kg/kWh,0.21
3,global,heating,"UK, Department for Business, Energy & Industrial Strategy",fuel oil,kWh,oil,kg/kWh,0.27
4,global,heating,"UK, Department for Business, Energy & Industrial Strategy",wood pellets,kWh,pellet,kg/kWh,0.01074
5,global,heating,"UK, Department for Business, Energy & Industrial Strategy",wood chips,kWh,woodchips,kg/kWh,0.01074
5,global,heating,"UK, Department for Business, Energy & Industrial Strategy",wood chips,kWh,wood chips,kg/kWh,0.01074
2 changes: 1 addition & 1 deletion data/test_data_users/heating.csv
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
group_id;building;timestamp;consumption;fuel_type;unit;group_share
1;;2018-01-01;250;woodchips;kg;1.0
1;;2018-01-01;250;wood chips;kg;1.0
2;;2018-01-01;29653;gas;kWh;0.5
3;;2018-01-01;47569;oil;kWh;1.0
4;;2018-01-01;35124;district_heating;kWh;0.2
Expand Down
1 change: 1 addition & 0 deletions tests/functional/test_data_code_compliance.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ def test_defaults(default_parameters):
"""Test if default parameters are available in the csv files"""

# Get the emission factor for the default parameter combination
print(default_parameters().dict())
co2e = emission_factors.get(default_parameters().dict())
assert isinstance(
co2e, float
Expand Down
34 changes: 0 additions & 34 deletions tests/unit/test_calculate.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,40 +80,6 @@ def test_calc_co2_trip_ignore_error_on_custom_emission_factor():
assert result == 10


def test_heating_woodchips():
"""Test co2e calculation for heating: woodchips"""
# Given parameters
fuel_type = "wood chips"
consumption = 250
unit = "kg"
co2e_kg_expected = 13.962

# Calculate co2e
co2e = candidate.calc_co2_heating(
consumption=consumption, unit=unit, fuel_type=fuel_type
)

# Check if expected result matches calculated result
assert co2e == pytest.approx(co2e_kg_expected, rel=0.01)


def test_electricity():
"""Test co2e calculation for electricity"""
# Given parameters
fuel_type = "production fuel mix"
country = "FR"
consumption_kwh = 10000
co2e_kg_expected = 620.7

# Calculate co2e
co2e = candidate.calc_co2_electricity(
consumption=consumption_kwh, fuel_type=fuel_type, country_code=country
)

# Check if expected result matches calculated result
assert co2e == pytest.approx(co2e_kg_expected, rel=0.01)


@pytest.mark.skip(
reason="Failing right now, but units will change anyways. let's check after the co2factors are updated"
)
Expand Down
44 changes: 44 additions & 0 deletions tests/unit/test_calculate_energy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"""tests for energy calculation"""
import co2calculator.energy.calculate_energy as energy
import pytest


def test_heating_woodchips():
"""Test co2e calculation for heating: wood chips"""
# Given parameters
consumption = 250
co2e_kg_expected = 13.962
func_options = {
# Given parameters
"heating_emission_parameters": {
"fuel_type": "wood chips" # emission factor: 9322 kg/TJ
},
"unit": "kg", # conversion factor to kWh = 5.4
}
# Calculate co2e
co2e = energy.calc_co2_heating(consumption=consumption, options=func_options)

# Check if expected result matches calculated result
assert co2e == pytest.approx(co2e_kg_expected, rel=0.01)


def test_electricity():
"""Test co2e calculation for electricity"""
# Given parameters
consumption_kwh = 10000
co2e_kg_expected = 22265

func_options = {
"electricity_emission_parameters": {
"fuel_type": "production fuel mix",
"country_code": "DE",
}
}

# Calculate co2e
co2e = energy.calc_co2_electricity(
consumption=consumption_kwh, options=func_options
)

# Check if expected result matches calculated result
assert co2e == pytest.approx(co2e_kg_expected, rel=0.01)
Loading