-
Notifications
You must be signed in to change notification settings - Fork 18
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
Feature uncontrolled vs controlled charging #338
Changes from 9 commits
73b1ee3
26d8718
92f36d7
85fdf51
398520a
a40424f
bfc041e
ae2dc9b
cc30987
145c2a8
58175ea
3c84405
30df69a
5569fad
60a3e42
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
import pandas as pd | ||
import pycountry | ||
|
||
|
||
def scale_to_regional_resolution(df, region_country_mapping, populations): | ||
""" | ||
Create regional electricity demand for controlled charging. | ||
ASSUME all road transport is subnationally distributed in proportion to population. | ||
""" | ||
df_population_share = ( | ||
populations.loc[:, "population_sum"] | ||
.reindex(region_country_mapping.keys()) | ||
.groupby(by=region_country_mapping) | ||
.transform(lambda df: df / df.sum()) | ||
) | ||
|
||
regional_df = ( | ||
pd.DataFrame( | ||
index=df.index, | ||
data={ | ||
id: df[country_code] | ||
for id, country_code in region_country_mapping.items() | ||
}, | ||
) | ||
.mul(df_population_share) | ||
.rename(columns=lambda col_name: col_name.replace(".", "-")) | ||
) | ||
pd.testing.assert_series_equal(regional_df.sum(axis=1), df.sum(axis=1)) | ||
return regional_df | ||
|
||
|
||
def scale_to_national_resolution(df): | ||
df.columns.name = None | ||
return df | ||
|
||
|
||
def scale_to_continental_resolution(df): | ||
return df.sum(axis=1).to_frame("EUR") | ||
|
||
|
||
def convert_annual_distance_to_electricity_demand( | ||
path_to_controlled_annual_demand: str, | ||
power_scaling_factor: float, | ||
first_year: int, | ||
final_year: int, | ||
conversion_factors: dict[str, float], | ||
country_codes: list[str], | ||
): | ||
""" | ||
Convert annual distance driven demand to electricity demand for | ||
controlled charging accounting for conversion factors. | ||
""" | ||
df_energy_demand = ( | ||
pd.read_csv(path_to_controlled_annual_demand, index_col=[1, 2]) | ||
.xs(slice(first_year, final_year), level="year", drop_level=False) | ||
.assign(value=lambda x: x["value"] * x["vehicle_type"].map(conversion_factors)) | ||
.groupby(["country_code", "year"]) | ||
.sum() | ||
.loc[country_codes] | ||
.mul(power_scaling_factor) | ||
.squeeze() | ||
.unstack("country_code") | ||
) | ||
|
||
return -df_energy_demand | ||
|
||
|
||
if __name__ == "__main__": | ||
resolution = snakemake.wildcards.resolution | ||
|
||
path_to_controlled_annual_demand = snakemake.input.annual_controlled_demand | ||
power_scaling_factor = snakemake.params.power_scaling_factor | ||
first_year = snakemake.params.first_year | ||
final_year = snakemake.params.final_year | ||
conversion_factors = snakemake.params.conversion_factors | ||
path_to_output = snakemake.output[0] | ||
country_codes = ( | ||
[pycountry.countries.lookup(c).alpha_3 for c in snakemake.params.countries], | ||
) | ||
region_country_mapping = ( | ||
pd.read_csv(snakemake.input.locations, index_col=0) | ||
.loc[:, "country_code"] | ||
.to_dict() | ||
) | ||
populations = pd.read_csv(snakemake.input.populations, index_col=0) | ||
|
||
df = convert_annual_distance_to_electricity_demand( | ||
path_to_controlled_annual_demand, | ||
power_scaling_factor, | ||
first_year, | ||
final_year, | ||
conversion_factors, | ||
country_codes, | ||
) | ||
|
||
if resolution == "continental": | ||
df = scale_to_continental_resolution(df) | ||
elif resolution == "national": | ||
df = scale_to_national_resolution(df) | ||
elif resolution == "regional": | ||
df = scale_to_regional_resolution( | ||
df, region_country_mapping=region_country_mapping, populations=populations | ||
) | ||
else: | ||
raise ValueError("Input resolution is not recognised") | ||
df.T.to_csv(path_to_output, index_label=["id"]) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,29 +1,50 @@ | ||
techs: | ||
demand_road_transport_electrified: | ||
demand_road_transport_electrified_uncontrolled: | ||
essentials: | ||
name: 'Electrified road transport demand' | ||
name: 'Uncontrolled electrified road transport demand -- follows a timeseries' | ||
parent: demand | ||
carrier: electricity | ||
constraints: | ||
resource: file=demand/electrified-road-transport.csv | ||
resource: file=demand/uncontrolled-electrified-road-transport.csv | ||
|
||
demand_road_transport_historic_electrified: | ||
demand_road_transport_historic_electrified_uncontrolled: | ||
essentials: | ||
name: 'Removes historic electrified road transport demand' | ||
name: 'Removes historic electrified road transport demand -- assumed uncontrolled' | ||
parent: supply | ||
carrier: electricity | ||
constraints: | ||
resource: file=demand/road-transport-historic-electrification.csv | ||
resource: file=demand/uncontrolled-road-transport-historic-electrification.csv | ||
resource_min_use: 1 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should this be "force_resource"? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've updated it, indeed I think it makes more sense |
||
|
||
demand_road_transport_electrified_controlled: | ||
essentials: | ||
name: 'Controlled electrified road transport demand' | ||
parent: demand | ||
carrier: electricity | ||
constraints: | ||
force_resource: false | ||
resource: -.inf | ||
|
||
overrides: | ||
keep-historic-electricity-demand-from-road-transport: | ||
{% for id, location in locations.iterrows() %} | ||
{{ id }}.techs.demand_road_transport_historic_electrified.exists: False | ||
{{ id }}.techs.demand_road_transport_historic_electrified_uncontrolled.exists: False | ||
{% endfor %} | ||
|
||
{% for year in locations.columns %} | ||
{{ year }}_transport_controlled_electrified_demand: | ||
group_constraints: | ||
{% for location in locations.index %} | ||
{{ location }}_annual_controlled_electricity_demand: | ||
locs: [{{ location }}] | ||
techs: [demand_road_transport_electrified_controlled] | ||
carrier_con_equals: | ||
electricity: {{ locations.loc[location, year] }} # {{ (1 / scaling_factors.power) | unit("MWh") }} | ||
{% endfor %} | ||
{% endfor %} | ||
|
||
locations: | ||
{% for id, location in locations.iterrows() %} | ||
{{ id }}.techs.demand_road_transport_electrified: | ||
{{ id }}.techs.demand_road_transport_historic_electrified: | ||
{{ id }}.techs.demand_road_transport_electrified_uncontrolled: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Needs controlled tech here too. I'd maybe update to: {{ id }}.techs:
demand_road_transport_electrified_uncontrolled:
demand_road_transport_historic_electrified_uncontrolled:
demand_road_transport_electrified_controlled:
|
||
{{ id }}.techs.demand_road_transport_historic_electrified_uncontrolled: | ||
{% endfor %} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I haven't looked, but how close is this to a copy-paste of the
uncontrolled
python file? If it is very similar, we should consider using the two files with a controlled/uncontrolled flag that decides on the functions to use etc.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's fairly different, because the
uncontrolled
scripts generate timeseries per vehicle type which are then aggregated and rescaled per resolution. This last step is similar but still different because contains different data structures. Thecontrolled
script is actually closer to therescale.py
in the heat sector.