diff --git a/CHANGELOG.md b/CHANGELOG.md index 54e6afbc..83cd8bd4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ * **ADD** fully-electrified heat demand (#284). -* **ADD** fully-electrified road transportation (#270), (#271). A parameter allows to define the share of uncontrolled (timeseries) vs controlled charging (optimised) by the solver (PR #338). +* **ADD** fully-electrified road transportation (#270, #271, #358). A parameter allows to define the share of uncontrolled (timeseries) vs controlled charging (optimised) by the solver (#338). * **ADD** nuclear power plant technology with capacity limits. Capacity limits can be equal to today or be bound by a minimum and maximum capacity to represent an available range in future. In either case, capacities are allocated at a subnational resolution based on linear scaling from current capacity geolocations, using the JRC power plant database (#78). diff --git a/config/default.yaml b/config/default.yaml index ba76942a..3ee2db6b 100644 --- a/config/default.yaml +++ b/config/default.yaml @@ -19,7 +19,8 @@ data-sources: swiss-end-use: https://www.bfe.admin.ch/bfe/en/home/versorgung/statistik-und-geodaten/energiestatistiken/energieverbrauch-nach-verwendungszweck.exturl.html/aHR0cHM6Ly9wdWJkYi5iZmUuYWRtaW4uY2gvZGUvcHVibGljYX/Rpb24vZG93bmxvYWQvOTg1NA==.html swiss-energy-balance: https://www.bfe.admin.ch/bfe/en/home/versorgung/statistik-und-geodaten/energiestatistiken/gesamtenergiestatistik.exturl.html/aHR0cHM6Ly9wdWJkYi5iZmUuYWRtaW4uY2gvZGUvcHVibGljYX/Rpb24vZG93bmxvYWQvNzUxOQ==.html swiss-industry-energy-balance: https://www.bfe.admin.ch/bfe/en/home/versorgung/statistik-und-geodaten/energiestatistiken/teilstatistiken.exturl.html/aHR0cHM6Ly9wdWJkYi5iZmUuYWRtaW4uY2gvZGUvcHVibGljYX/Rpb24vZG93bmxvYWQvODc4OA==.html - ev-data: https://zenodo.org/record/6579421/files/ramp-ev-consumption-profiles.csv.gz?download=1 + controlled-ev-profiles: https://zenodo.org/record/6579421/files/ramp-ev-consumption-profiles.csv.gz?download=1 + uncontrolled-ev-profiles: https://sandbox.zenodo.org/records/45530/files/uncontrolled-charging-profiles.csv.gz?download=1 # TODO: convert into Zenodo repository gridded-temperature-data: https://zenodo.org/records/6557643/files/temperature.nc?download=1 gridded-10m-windspeed-data: https://zenodo.org/records/6557643/files/wind10m.nc?download=1 when2heat-params: https://zenodo.org/records/10965295/files/{dataset}?download=1 diff --git a/config/schema.yaml b/config/schema.yaml index de136978..d1ef3103 100644 --- a/config/schema.yaml +++ b/config/schema.yaml @@ -103,10 +103,14 @@ properties: type: string pattern: ^(https?|http?):\/\/.+ description: Web address of Swiss industry energy balance data. - ev-data: + controlled-ev-profiles: type: string pattern: ^(https?|http?):\/\/.+ description: Web address of electric vehicle data. + uncontrolled-ev-profiles: + type: string + pattern: ^(https?|http?):\/\/.+ + description: Web address of electric light-duty vehicle uncontrolled charging. gridded-temperature-data: type: string pattern: ^(https?|http?):\/\/.+ diff --git a/rules/transport.smk b/rules/transport.smk index 0d0016eb..dbf81f1c 100644 --- a/rules/transport.smk +++ b/rules/transport.smk @@ -2,15 +2,24 @@ rule download_transport_timeseries: - # TODO have correct timeseries data once RAMP has generated the new charging profile and it's been put on Zenodo message: "Get EV data from RAMP" params: - url = config["data-sources"]["ev-data"] + url = config["data-sources"]["controlled-ev-profiles"] conda: "../envs/shell.yaml" output: protected("data/automatic/ramp-ev-consumption-profiles.csv.gz") localrule: True shell: "curl -sSLo {output} {params.url}" +rule download_uncontrolled_timeseries: + # TODO: move into rule download_transport_timeseries once PR 356 is merged + message: "Get EV uncontrolled charging data from RAMP" + params: + url = config["data-sources"]["uncontrolled-ev-profiles"] + conda: "../envs/shell.yaml" + output: protected("data/automatic/ramp-ev-uncontrolled-charging-profiles.csv.gz") + localrule: True + shell: "curl -sSLo {output} {params.url}" + rule annual_transport_demand: message: "Calculate future transport energy demand based on JRC IDEES" @@ -52,7 +61,7 @@ rule create_uncontrolled_road_transport_timeseries: message: "Create timeseries for road transport demand (uncontrolled charging)" input: annual_data = "build/data/transport/annual-road-transport-distance-demand-uncontrolled.csv", - timeseries = "data/automatic/ramp-ev-consumption-profiles.csv.gz" + timeseries = "data/automatic/ramp-ev-uncontrolled-charging-profiles.csv.gz" params: first_year = config["scope"]["temporal"]["first-year"], final_year = config["scope"]["temporal"]["final-year"], @@ -73,7 +82,7 @@ use rule create_uncontrolled_road_transport_timeseries as create_uncontrolled_ro message: "Create timeseries for historic electrified road transport demand (uncontrolled charging)" input: annual_data = "build/data/transport/annual-road-transport-distance-demand-historic-electrification.csv", - timeseries = "data/automatic/ramp-ev-consumption-profiles.csv.gz", + timeseries = "data/automatic/ramp-ev-uncontrolled-charging-profiles.csv.gz", params: first_year = config["scope"]["temporal"]["first-year"], final_year = config["scope"]["temporal"]["final-year"], diff --git a/scripts/transport/road_transport_timeseries.py b/scripts/transport/road_transport_timeseries.py index 9f23f085..dd11c5d2 100644 --- a/scripts/transport/road_transport_timeseries.py +++ b/scripts/transport/road_transport_timeseries.py @@ -16,6 +16,7 @@ def create_road_transport_demand_timeseries( path_to_output: str, ) -> None: # Read annual road transport distance into panda dataframe + df_annual = ( pd.read_csv(path_to_annual_data, index_col=[0, 1, 2], parse_dates=[2]) .squeeze() @@ -30,7 +31,6 @@ def create_road_transport_demand_timeseries( if vehicle_type in ["passenger-cars", "motorcycles", "light-duty-vehicles"]: # Use RAMP time series profiles for small and light vehicles. - # TODO check if path_to_timeseries data is charging demand or transport demand df_timeseries = ( pd.read_csv(path_to_timeseries, index_col=[0, 1, 2], parse_dates=[0]) .xs(slice(first_year, final_year), level="year") @@ -39,9 +39,9 @@ def create_road_transport_demand_timeseries( .groupby(by=lambda idx: idx.year) .transform(lambda x: x / x.sum()) .pipe(fill_empty_country, country_neighbour_dict) + .tz_localize("UTC") .mul(df_annual) ) - elif vehicle_type in ["heavy-duty-vehicles", "coaches-and-buses"]: # ASSUME flat profiles for heavy transport. df_timeseries = df_annual.groupby(by=lambda idx: idx.year).transform( @@ -69,7 +69,10 @@ def create_road_transport_demand_timeseries( def fill_empty_country(df, country_neighbour_dict): for country, neighbours in country_neighbour_dict.items(): - assert country not in df.columns + if country in df.columns: + print(f"Country {country} is already in dataframe") + continue + print(f"Country {country} is not in dataframe, filling with neighbours") df[country] = df[neighbours].mean(axis=1) return df