From b70aa59dce9576ede6d69b5d716073520e4a218c Mon Sep 17 00:00:00 2001 From: lisazeyen Date: Fri, 1 Mar 2024 11:32:12 +0100 Subject: [PATCH 01/34] safe transport demand in unit kinetic energy --- scripts/build_transport_demand.py | 52 ++++++++++++++----------------- 1 file changed, 23 insertions(+), 29 deletions(-) diff --git a/scripts/build_transport_demand.py b/scripts/build_transport_demand.py index de561e3f..7e54b567 100644 --- a/scripts/build_transport_demand.py +++ b/scripts/build_transport_demand.py @@ -19,13 +19,16 @@ logger = logging.getLogger(__name__) def build_nodal_transport_data(fn, pop_layout): + # get numbers of car and fuel efficieny per country transport_data = pd.read_csv(fn, index_col=0) - + + # break number of cars down to nodal level based on population density nodal_transport_data = transport_data.loc[pop_layout.ct].fillna(0.0) nodal_transport_data.index = pop_layout.index nodal_transport_data["number cars"] = ( pop_layout["fraction"] * nodal_transport_data["number cars"] ) + # fill missing fuel efficiency with average data nodal_transport_data.loc[ nodal_transport_data["average fuel efficiency"] == 0.0, "average fuel efficiency", @@ -35,10 +38,14 @@ def build_nodal_transport_data(fn, pop_layout): def build_transport_demand(traffic_fn, airtemp_fn, nodes, nodal_transport_data): - ## Get overall demand curve for all vehicles - - traffic = pd.read_csv(traffic_fn, skiprows=2, usecols=["count"]).squeeze("columns") - + """ + returns transport demand per bus in unit kinetic energy. + """ + # averaged weekly counts from the year 2010-2015 + traffic = pd.read_csv(traffic_fn, skiprows=2, + usecols=["count"]).squeeze("columns") + + # create annual profile take account time zone + summer time transport_shape = generate_periodic_profiles( dt_index=snapshots, nodes=nodes, @@ -46,15 +53,6 @@ def build_transport_demand(traffic_fn, airtemp_fn, nodes, nodal_transport_data): ) transport_shape = transport_shape / transport_shape.sum() - # electric motors are more efficient, so alter transport demand - - plug_to_wheels_eta = options["bev_plug_to_wheel_efficiency"] - battery_to_wheels_eta = plug_to_wheels_eta * options["bev_charge_efficiency"] - - efficiency_gain = ( - nodal_transport_data["average fuel efficiency"] / battery_to_wheels_eta - ) - # get heating demand for correction to demand time series temperature = xr.open_dataarray(airtemp_fn).to_pandas() @@ -67,16 +65,8 @@ def build_transport_demand(traffic_fn, airtemp_fn, nodes, nodal_transport_data): options["ICE_upper_degree_factor"], ) - dd_EV = transport_degree_factor( - temperature, - options["transport_heating_deadband_lower"], - options["transport_heating_deadband_upper"], - options["EV_lower_degree_factor"], - options["EV_upper_degree_factor"], - ) - + # divide out the heating/cooling demand from ICE totals - # and multiply back in the heating/cooling demand for EVs ice_correction = (transport_shape * (1 + dd_ICE)).sum() / transport_shape.sum() energy_totals_transport = ( @@ -87,8 +77,7 @@ def build_transport_demand(traffic_fn, airtemp_fn, nodes, nodal_transport_data): return ( (transport_shape.multiply(energy_totals_transport) * 1e6 * nyears) - .divide(efficiency_gain * ice_correction) - .multiply(1 + dd_EV) + .divide(nodal_transport_data["average fuel efficiency"] * ice_correction) ) @@ -125,11 +114,14 @@ def bev_availability_profile(fn, snapshots, nodes, options): """ Derive plugged-in availability for passenger electric vehicles. """ + # car count in typical week traffic = pd.read_csv(fn, skiprows=2, usecols=["count"]).squeeze("columns") - + # maximum share plugged-in availability for passenger electric vehicles avail_max = options["bev_avail_max"] + # average share plugged-in availability for passenger electric vehicles avail_mean = options["bev_avail_mean"] + # linear scaling, highest when traffic is lowest, decreases if traffic increases avail = avail_max - (avail_max - avail_mean) * (traffic - traffic.min()) / ( traffic.mean() - traffic.min() ) @@ -149,7 +141,9 @@ def bev_availability_profile(fn, snapshots, nodes, options): def bev_dsm_profile(snapshots, nodes, options): dsm_week = np.zeros((24 * 7,)) - + + # assuming that at a certain time ("bev_dsm_restriction_time") EVs have to + # be charged to a minimum value (defined in bev_dsm_restriction_value) dsm_week[(np.arange(0, 7, 1) * 24 + options["bev_dsm_restriction_time"])] = options[ "bev_dsm_restriction_value" ] @@ -160,7 +154,7 @@ def bev_dsm_profile(snapshots, nodes, options): weekly_profile=dsm_week, ) - +#%% if __name__ == "__main__": if "snakemake" not in globals(): from _helpers import mock_snakemake @@ -168,7 +162,7 @@ if __name__ == "__main__": snakemake = mock_snakemake( "build_transport_demand", simpl="", - clusters=48, + clusters=37, ) configure_logging(snakemake) set_scenario_config(snakemake) From 548a99f273c2e1860d28cfa2d32469959ffe915e Mon Sep 17 00:00:00 2001 From: lisazeyen Date: Fri, 1 Mar 2024 15:19:00 +0100 Subject: [PATCH 02/34] restructure to links without temperature correction --- scripts/prepare_sector_network.py | 347 +++++++++++++++++------------- 1 file changed, 194 insertions(+), 153 deletions(-) diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index 9a9db36f..67b3fd7d 100755 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -24,6 +24,7 @@ from _helpers import ( ) from add_electricity import calculate_annuity, sanitize_carriers, sanitize_locations from build_energy_totals import build_co2_totals, build_eea_co2, build_eurostat_co2 +from build_transport_demand import transport_degree_factor from networkx.algorithms import complement from networkx.algorithms.connectivity.edge_augmentation import k_edge_augmentation from prepare_network import maybe_adjust_costs_and_potentials @@ -1502,11 +1503,156 @@ def add_storage_and_grids(n, costs): ) +def check_land_transport_shares(shares): + # Sums up the shares, ignoring None values + total_share = sum(filter(None, shares)) + if total_share != 1: + logger.warning( + f"Total land transport shares sum up to {total_share:.2%}," + "corresponding to increased or decreased demand assumptions." + ) + +def add_EVs(n, nodes, avail_profile, dsm_profile, p_set, electric_share, + number_cars, temperature): + + n.add("Carrier", "Li ion") + + n.madd( + "Bus", + nodes, + suffix=" EV battery", + location=nodes, + carrier="Li ion", + unit="MWh_el", + ) + + # temperature correction for EVs + dd_EV = transport_degree_factor( + temperature, + options["transport_heating_deadband_lower"], + options["transport_heating_deadband_upper"], + options["EV_lower_degree_factor"], + options["EV_upper_degree_factor"], + ) + + profile = p_set/p_set.max() + efficiency = costs.at['Battery electric (passenger cars)', 'efficiency'] + + n.madd( + "Link", + nodes, + suffix=" land transport EV", + bus0=nodes + " EV battery", + bus1=nodes + " land transport", + carrier="land transport EV", + efficiency=efficiency, + p_min_pu=profile, + p_max_pu=profile, + p_nom=electric_share*p_set.max()/efficiency, + p_nom_extendable=False, + ) + + + p_nom = number_cars * options.get("bev_charge_rate", 0.011) * electric_share + + n.madd( + "Link", + nodes, + suffix=" BEV charger", + bus0=nodes, + bus1=nodes + " EV battery", + p_nom=p_nom, + carrier="BEV charger", + p_max_pu=avail_profile[nodes], + efficiency=options.get("bev_charge_efficiency", 0.9), + ) + + if options["v2g"]: + n.madd( + "Link", + nodes, + suffix=" V2G", + bus1=nodes, + bus0=nodes + " EV battery", + p_nom=p_nom, + carrier="V2G", + p_max_pu=avail_profile[nodes], + efficiency=options.get("bev_charge_efficiency", 0.9), + ) + + if options["bev_dsm"]: + e_nom = ( + number_cars + * options.get("bev_energy", 0.05) + * options["bev_availability"] + * electric_share + ) + + n.madd( + "Store", + nodes, + suffix=" battery storage", + bus=nodes + " EV battery", + carrier="battery storage", + e_cyclic=True, + e_nom=e_nom, + e_max_pu=1, + e_min_pu=dsm_profile[nodes], + ) + +def add_fuel_cell_cars(n, nodes, p_set, fuel_cell_share, temperature): + + efficiency = options["transport_fuel_cell_efficiency"] + profile = p_set / p_set.max() + p_nom = fuel_cell_share * p_set / efficiency + + n.madd( + "Link", + nodes, + suffix=" land transport fuel cell", + bus0=spatial.h2.nodes, + bus1=nodes + " land transport", + carrier="land transport fuel cell", + efficiency=efficiency, + p_nom_extendable=False, + p_nom=p_nom, + p_min_pu=profile, + p_max_pu=profile, + ) + + +def add_ice_cars(n, nodes, p_set, ice_share, temperature): + + add_carrier_buses(n, "oil") + + ice_efficiency = options["transport_internal_combustion_efficiency"] + + if not options["regional_oil_demand"]: + p_nom = ice_share * p_set.sum(axis=1).max() / ice_efficiency + else: + p_nom = ice_share * p_set.max() / ice_efficiency + + + n.madd( + "Link", + nodes + " land transport ICE", + bus0=spatial.oil.nodes, + bus1=nodes + " land transport", + bus2=["co2 atmosphere"], + carrier="land transport oil", + efficiency=ice_efficiency, + efficiency2=costs.at["oil", "CO2 intensity"], + p_nom_extendable=True, + # p_nom=p_nom, + capital_cost=1e4, + ) + def add_land_transport(n, costs): # TODO options? logger.info("Add land transport") - + + # read in transport demand in units kinetic energy transport = pd.read_csv( snakemake.input.transport_demand, index_col=0, parse_dates=True ) @@ -1519,159 +1665,54 @@ def add_land_transport(n, costs): dsm_profile = pd.read_csv( snakemake.input.dsm_profile, index_col=0, parse_dates=True ) + + # exogenous share of passenger car type + engine_types = ["fuel_cell", "electric", "ice"] + shares = pd.Series() + for engine in engine_types: + shares[engine] = get(options[f"land_transport_{engine}_share"], + investment_year) + logger.info(f"{engine} share: {shares[engine]*100}%") + + check_land_transport_shares(shares) + + nodes = spatial.nodes + + # Add load for transport demand + n.add("Carrier", "land transport demand") - fuel_cell_share = get(options["land_transport_fuel_cell_share"], investment_year) - electric_share = get(options["land_transport_electric_share"], investment_year) - ice_share = get(options["land_transport_ice_share"], investment_year) - - total_share = fuel_cell_share + electric_share + ice_share - if total_share != 1: - logger.warning( - f"Total land transport shares sum up to {total_share:.2%}, corresponding to increased or decreased demand assumptions." - ) - - logger.info(f"FCEV share: {fuel_cell_share*100}%") - logger.info(f"EV share: {electric_share*100}%") - logger.info(f"ICEV share: {ice_share*100}%") - - nodes = pop_layout.index - - if electric_share > 0: - n.add("Carrier", "Li ion") - - n.madd( - "Bus", + n.madd("Bus", nodes, - suffix=" EV battery", location=nodes, - carrier="Li ion", - unit="MWh_el", - ) + suffix=" land transport", + carrier="land transport demand", + unit="MWh_kinetic") + + p_set = transport[nodes] + + # add demand + n.madd( + "Load", + nodes, + suffix=" land transport", + bus=nodes + " land transport", + carrier="land transport demand", + p_set=p_set, + ) + + # temperature for correction factor for heating/cooling + temperature = xr.open_dataarray(snakemake.input.temp_air_total).to_pandas() + + if shares["electric"] > 0: + add_EVs(n, nodes, avail_profile, dsm_profile, p_set, shares["electric"], + number_cars, temperature) + + if shares["fuel_cell"] > 0: + add_fuel_cell_cars(n, nodes, p_set, shares["fuel_cell"], temperature) - p_set = ( - electric_share - * ( - transport[nodes] - + cycling_shift(transport[nodes], 1) - + cycling_shift(transport[nodes], 2) - ) - / 3 - ) - - n.madd( - "Load", - nodes, - suffix=" land transport EV", - bus=nodes + " EV battery", - carrier="land transport EV", - p_set=p_set, - ) - - p_nom = number_cars * options.get("bev_charge_rate", 0.011) * electric_share - - n.madd( - "Link", - nodes, - suffix=" BEV charger", - bus0=nodes, - bus1=nodes + " EV battery", - p_nom=p_nom, - carrier="BEV charger", - p_max_pu=avail_profile[nodes], - efficiency=options.get("bev_charge_efficiency", 0.9), - # These were set non-zero to find LU infeasibility when availability = 0.25 - # p_nom_extendable=True, - # p_nom_min=p_nom, - # capital_cost=1e6, #i.e. so high it only gets built where necessary - ) - - if electric_share > 0 and options["v2g"]: - n.madd( - "Link", - nodes, - suffix=" V2G", - bus1=nodes, - bus0=nodes + " EV battery", - p_nom=p_nom, - carrier="V2G", - p_max_pu=avail_profile[nodes], - efficiency=options.get("bev_charge_efficiency", 0.9), - ) - - if electric_share > 0 and options["bev_dsm"]: - e_nom = ( - number_cars - * options.get("bev_energy", 0.05) - * options["bev_availability"] - * electric_share - ) - - n.madd( - "Store", - nodes, - suffix=" battery storage", - bus=nodes + " EV battery", - carrier="battery storage", - e_cyclic=True, - e_nom=e_nom, - e_max_pu=1, - e_min_pu=dsm_profile[nodes], - ) - - if fuel_cell_share > 0: - n.madd( - "Load", - nodes, - suffix=" land transport fuel cell", - bus=nodes + " H2", - carrier="land transport fuel cell", - p_set=fuel_cell_share - / options["transport_fuel_cell_efficiency"] - * transport[nodes], - ) - - if ice_share > 0: - add_carrier_buses(n, "oil") - - ice_efficiency = options["transport_internal_combustion_efficiency"] - - p_set_land_transport_oil = ( - ice_share - / ice_efficiency - * transport[nodes].rename(columns=lambda x: x + " land transport oil") - ) - - if not options["regional_oil_demand"]: - p_set_land_transport_oil = p_set_land_transport_oil.sum(axis=1).to_frame( - name="EU land transport oil" - ) - - n.madd( - "Bus", - spatial.oil.land_transport, - location=spatial.oil.demand_locations, - carrier="land transport oil", - unit="land transport", - ) - - n.madd( - "Load", - spatial.oil.land_transport, - bus=spatial.oil.land_transport, - carrier="land transport oil", - p_set=p_set_land_transport_oil, - ) - - n.madd( - "Link", - spatial.oil.land_transport, - bus0=spatial.oil.nodes, - bus1=spatial.oil.land_transport, - bus2="co2 atmosphere", - carrier="land transport oil", - efficiency2=costs.at["oil", "CO2 intensity"], - p_nom_extendable=True, - ) + if shares["ice"] > 0: + add_ice_cars(n, nodes, p_set, shares["ice"], temperature) + def build_heat_demand(n): @@ -3565,19 +3606,19 @@ def lossy_bidirectional_links(n, carrier, efficiencies={}): -compression_per_1000km * n.links.loc[carrier_i, "length_original"] / 1e3 ) - +#%% if __name__ == "__main__": if "snakemake" not in globals(): from _helpers import mock_snakemake snakemake = mock_snakemake( "prepare_sector_network", - configfiles="test/config.overnight.yaml", + # configfiles="test/config.overnight.yaml", simpl="", opts="", clusters="37", ll="v1.0", - sector_opts="CO2L0-24H-T-H-B-I-A-dist1", + sector_opts="730H-T-H-B-I-A-dist1", planning_horizons="2030", ) From bed2ef43d5b222bad6bb5c5c65361815b03746c3 Mon Sep 17 00:00:00 2001 From: lisazeyen Date: Fri, 1 Mar 2024 15:47:52 +0100 Subject: [PATCH 03/34] fix p_nom ICE --- scripts/prepare_sector_network.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index 67b3fd7d..ca763d1e 100755 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -1627,24 +1627,22 @@ def add_ice_cars(n, nodes, p_set, ice_share, temperature): ice_efficiency = options["transport_internal_combustion_efficiency"] - if not options["regional_oil_demand"]: - p_nom = ice_share * p_set.sum(axis=1).max() / ice_efficiency - else: - p_nom = ice_share * p_set.max() / ice_efficiency + p_nom = ice_share * p_set.max() / ice_efficiency + suffix = " land transport ICE" + p_nom.rename(lambda x: x + suffix, inplace=True) n.madd( "Link", - nodes + " land transport ICE", + nodes + suffix, bus0=spatial.oil.nodes, bus1=nodes + " land transport", bus2=["co2 atmosphere"], carrier="land transport oil", efficiency=ice_efficiency, efficiency2=costs.at["oil", "CO2 intensity"], - p_nom_extendable=True, - # p_nom=p_nom, - capital_cost=1e4, + p_nom_extendable=False, + p_nom=p_nom, ) def add_land_transport(n, costs): From 8efe43f35f685b58bc20d497da4d68c508b84de8 Mon Sep 17 00:00:00 2001 From: lisazeyen Date: Mon, 4 Mar 2024 08:34:08 +0100 Subject: [PATCH 04/34] add temperature correction --- scripts/prepare_sector_network.py | 43 ++++++++++++++++++++++++++----- scripts/solve_network.py | 6 ++--- 2 files changed, 39 insertions(+), 10 deletions(-) diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index ca763d1e..db93ede8 100755 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -1535,8 +1535,13 @@ def add_EVs(n, nodes, avail_profile, dsm_profile, p_set, electric_share, options["EV_upper_degree_factor"], ) + temp_eff = 1 / (1+dd_EV) + profile = p_set/p_set.max() - efficiency = costs.at['Battery electric (passenger cars)', 'efficiency'] + + car_efficiency = costs.at['Battery electric (passenger cars)', 'efficiency'] + + efficiency = car_efficiency * temp_eff n.madd( "Link", @@ -1602,9 +1607,21 @@ def add_EVs(n, nodes, avail_profile, dsm_profile, p_set, electric_share, def add_fuel_cell_cars(n, nodes, p_set, fuel_cell_share, temperature): - efficiency = options["transport_fuel_cell_efficiency"] + # correction factors for vehicle heating + cooling + dd_ICE = transport_degree_factor( + temperature, + options["transport_heating_deadband_lower"], + options["transport_heating_deadband_upper"], + options["ICE_lower_degree_factor"], + options["ICE_upper_degree_factor"], + ) + temp_eff = 1 / (1+dd_ICE) + car_efficiency = options["transport_fuel_cell_efficiency"] + efficiency = car_efficiency * temp_eff + + profile = p_set / p_set.max() - p_nom = fuel_cell_share * p_set / efficiency + p_nom = fuel_cell_share * p_set / car_efficiency n.madd( "Link", @@ -1624,10 +1641,22 @@ def add_fuel_cell_cars(n, nodes, p_set, fuel_cell_share, temperature): def add_ice_cars(n, nodes, p_set, ice_share, temperature): add_carrier_buses(n, "oil") - - ice_efficiency = options["transport_internal_combustion_efficiency"] - p_nom = ice_share * p_set.max() / ice_efficiency + # correction factors for vehicle heating + cooling + dd_ICE = transport_degree_factor( + temperature, + options["transport_heating_deadband_lower"], + options["transport_heating_deadband_upper"], + options["ICE_lower_degree_factor"], + options["ICE_upper_degree_factor"], + ) + temp_eff = 1 / (1+dd_ICE) + + car_efficiency = options["transport_internal_combustion_efficiency"] + + efficiency = car_efficiency * temp_eff + + p_nom = ice_share * p_set.max() / car_efficiency suffix = " land transport ICE" p_nom.rename(lambda x: x + suffix, inplace=True) @@ -1639,7 +1668,7 @@ def add_ice_cars(n, nodes, p_set, ice_share, temperature): bus1=nodes + " land transport", bus2=["co2 atmosphere"], carrier="land transport oil", - efficiency=ice_efficiency, + efficiency=efficiency, efficiency2=costs.at["oil", "CO2 intensity"], p_nom_extendable=False, p_nom=p_nom, diff --git a/scripts/solve_network.py b/scripts/solve_network.py index 7e53e606..9ba38d0b 100644 --- a/scripts/solve_network.py +++ b/scripts/solve_network.py @@ -921,19 +921,19 @@ def solve_network(n, config, solving, **kwargs): return n - +#%% if __name__ == "__main__": if "snakemake" not in globals(): from _helpers import mock_snakemake snakemake = mock_snakemake( "solve_sector_network", - configfiles="../config/test/config.perfect.yaml", + # configfiles="../config/test/config.perfect.yaml", simpl="", opts="", clusters="37", ll="v1.0", - sector_opts="CO2L0-1H-T-H-B-I-A-dist1", + sector_opts="730H-T-H-B-I-A-dist1", planning_horizons="2030", ) configure_logging(snakemake) From 4ffa702d304c372afcb1f1875c8eba58063487e4 Mon Sep 17 00:00:00 2001 From: lisazeyen Date: Mon, 4 Mar 2024 08:49:44 +0100 Subject: [PATCH 05/34] temperature correction in extra function --- scripts/prepare_sector_network.py | 79 ++++++++++++++++--------------- 1 file changed, 42 insertions(+), 37 deletions(-) diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index db93ede8..33c06afc 100755 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -1512,6 +1512,25 @@ def check_land_transport_shares(shares): "corresponding to increased or decreased demand assumptions." ) +def get_temp_efficency(car_efficiency, temperature, deadband_lw, deadband_up, + degree_factor_lw, degree_factor_up): + """ + Correct temperature depending on heating and cooling for respective car + type. + """ + # temperature correction for EVs + dd = transport_degree_factor( + temperature, + deadband_lw, + deadband_up, + degree_factor_lw, + degree_factor_up, + ) + + temp_eff = 1 / (1+dd) + + return car_efficiency * temp_eff + def add_EVs(n, nodes, avail_profile, dsm_profile, p_set, electric_share, number_cars, temperature): @@ -1526,22 +1545,16 @@ def add_EVs(n, nodes, avail_profile, dsm_profile, p_set, electric_share, unit="MWh_el", ) - # temperature correction for EVs - dd_EV = transport_degree_factor( - temperature, - options["transport_heating_deadband_lower"], - options["transport_heating_deadband_upper"], - options["EV_lower_degree_factor"], - options["EV_upper_degree_factor"], - ) - - temp_eff = 1 / (1+dd_EV) - - profile = p_set/p_set.max() - car_efficiency = costs.at['Battery electric (passenger cars)', 'efficiency'] - efficiency = car_efficiency * temp_eff + # temperature corrected efficiency + efficiency = get_temp_efficency(car_efficiency, temperature, + options["transport_heating_deadband_lower"], + options["transport_heating_deadband_upper"], + options["EV_lower_degree_factor"], + options["EV_upper_degree_factor"]) + + profile = p_set/p_set.max() n.madd( "Link", @@ -1553,7 +1566,7 @@ def add_EVs(n, nodes, avail_profile, dsm_profile, p_set, electric_share, efficiency=efficiency, p_min_pu=profile, p_max_pu=profile, - p_nom=electric_share*p_set.max()/efficiency, + p_nom=electric_share*p_set.max()/car_efficiency, p_nom_extendable=False, ) @@ -1607,21 +1620,18 @@ def add_EVs(n, nodes, avail_profile, dsm_profile, p_set, electric_share, def add_fuel_cell_cars(n, nodes, p_set, fuel_cell_share, temperature): - # correction factors for vehicle heating + cooling - dd_ICE = transport_degree_factor( - temperature, - options["transport_heating_deadband_lower"], - options["transport_heating_deadband_upper"], - options["ICE_lower_degree_factor"], - options["ICE_upper_degree_factor"], - ) - temp_eff = 1 / (1+dd_ICE) car_efficiency = options["transport_fuel_cell_efficiency"] - efficiency = car_efficiency * temp_eff + + # temperature corrected efficiency + efficiency = get_temp_efficency(car_efficiency, temperature, + options["transport_heating_deadband_lower"], + options["transport_heating_deadband_upper"], + options["ICE_lower_degree_factor"], + options["ICE_upper_degree_factor"]) profile = p_set / p_set.max() - p_nom = fuel_cell_share * p_set / car_efficiency + p_nom = fuel_cell_share * p_set.max() / car_efficiency n.madd( "Link", @@ -1642,19 +1652,14 @@ def add_ice_cars(n, nodes, p_set, ice_share, temperature): add_carrier_buses(n, "oil") - # correction factors for vehicle heating + cooling - dd_ICE = transport_degree_factor( - temperature, - options["transport_heating_deadband_lower"], - options["transport_heating_deadband_upper"], - options["ICE_lower_degree_factor"], - options["ICE_upper_degree_factor"], - ) - temp_eff = 1 / (1+dd_ICE) - car_efficiency = options["transport_internal_combustion_efficiency"] - efficiency = car_efficiency * temp_eff + # temperature corrected efficiency + efficiency = get_temp_efficency(car_efficiency, temperature, + options["transport_heating_deadband_lower"], + options["transport_heating_deadband_upper"], + options["ICE_lower_degree_factor"], + options["ICE_upper_degree_factor"]) p_nom = ice_share * p_set.max() / car_efficiency suffix = " land transport ICE" From 04d3164d7f88a050acc363bccae52246d82ef950 Mon Sep 17 00:00:00 2001 From: lisazeyen Date: Mon, 4 Mar 2024 09:41:22 +0100 Subject: [PATCH 06/34] adjust p_nom + profile --- scripts/prepare_sector_network.py | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index 33c06afc..6aba1bd5 100755 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -1553,20 +1553,23 @@ def add_EVs(n, nodes, avail_profile, dsm_profile, p_set, electric_share, options["transport_heating_deadband_upper"], options["EV_lower_degree_factor"], options["EV_upper_degree_factor"]) + suffix = " land transport EV" - profile = p_set/p_set.max() + p_nom = electric_share * p_set.div(efficiency).max() + + profile = p_set.div(efficiency)/p_set.div(efficiency).max() n.madd( "Link", nodes, - suffix=" land transport EV", + suffix=suffix, bus0=nodes + " EV battery", bus1=nodes + " land transport", carrier="land transport EV", efficiency=efficiency, p_min_pu=profile, p_max_pu=profile, - p_nom=electric_share*p_set.max()/car_efficiency, + p_nom=p_nom, p_nom_extendable=False, ) @@ -1629,14 +1632,16 @@ def add_fuel_cell_cars(n, nodes, p_set, fuel_cell_share, temperature): options["ICE_lower_degree_factor"], options["ICE_upper_degree_factor"]) + suffix = " land transport fuel cell" + + p_nom = fuel_cell_share * p_set.div(efficiency).max() - profile = p_set / p_set.max() - p_nom = fuel_cell_share * p_set.max() / car_efficiency + profile = p_set.div(efficiency)/p_set.div(efficiency).max() n.madd( "Link", nodes, - suffix=" land transport fuel cell", + suffix=suffix, bus0=spatial.h2.nodes, bus1=nodes + " land transport", carrier="land transport fuel cell", @@ -1660,15 +1665,15 @@ def add_ice_cars(n, nodes, p_set, ice_share, temperature): options["transport_heating_deadband_upper"], options["ICE_lower_degree_factor"], options["ICE_upper_degree_factor"]) - - p_nom = ice_share * p_set.max() / car_efficiency suffix = " land transport ICE" - p_nom.rename(lambda x: x + suffix, inplace=True) - + p_nom = ice_share * p_set.div(efficiency).max() + + n.madd( "Link", - nodes + suffix, + nodes, + suffix=suffix, bus0=spatial.oil.nodes, bus1=nodes + " land transport", bus2=["co2 atmosphere"], From 4242a0841ce56c34e3c25ceddb7f7c01984e966c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 4 Mar 2024 08:44:51 +0000 Subject: [PATCH 07/34] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- scripts/build_transport_demand.py | 20 ++- scripts/prepare_sector_network.py | 202 +++++++++++++++++------------- scripts/solve_network.py | 3 +- 3 files changed, 129 insertions(+), 96 deletions(-) diff --git a/scripts/build_transport_demand.py b/scripts/build_transport_demand.py index 7e54b567..a13c8df9 100644 --- a/scripts/build_transport_demand.py +++ b/scripts/build_transport_demand.py @@ -21,7 +21,7 @@ logger = logging.getLogger(__name__) def build_nodal_transport_data(fn, pop_layout): # get numbers of car and fuel efficieny per country transport_data = pd.read_csv(fn, index_col=0) - + # break number of cars down to nodal level based on population density nodal_transport_data = transport_data.loc[pop_layout.ct].fillna(0.0) nodal_transport_data.index = pop_layout.index @@ -39,12 +39,11 @@ def build_nodal_transport_data(fn, pop_layout): def build_transport_demand(traffic_fn, airtemp_fn, nodes, nodal_transport_data): """ - returns transport demand per bus in unit kinetic energy. + Returns transport demand per bus in unit kinetic energy. """ # averaged weekly counts from the year 2010-2015 - traffic = pd.read_csv(traffic_fn, skiprows=2, - usecols=["count"]).squeeze("columns") - + traffic = pd.read_csv(traffic_fn, skiprows=2, usecols=["count"]).squeeze("columns") + # create annual profile take account time zone + summer time transport_shape = generate_periodic_profiles( dt_index=snapshots, @@ -65,7 +64,6 @@ def build_transport_demand(traffic_fn, airtemp_fn, nodes, nodal_transport_data): options["ICE_upper_degree_factor"], ) - # divide out the heating/cooling demand from ICE totals ice_correction = (transport_shape * (1 + dd_ICE)).sum() / transport_shape.sum() @@ -75,9 +73,8 @@ def build_transport_demand(traffic_fn, airtemp_fn, nodes, nodal_transport_data): - pop_weighted_energy_totals["electricity rail"] ) - return ( - (transport_shape.multiply(energy_totals_transport) * 1e6 * nyears) - .divide(nodal_transport_data["average fuel efficiency"] * ice_correction) + return (transport_shape.multiply(energy_totals_transport) * 1e6 * nyears).divide( + nodal_transport_data["average fuel efficiency"] * ice_correction ) @@ -141,7 +138,7 @@ def bev_availability_profile(fn, snapshots, nodes, options): def bev_dsm_profile(snapshots, nodes, options): dsm_week = np.zeros((24 * 7,)) - + # assuming that at a certain time ("bev_dsm_restriction_time") EVs have to # be charged to a minimum value (defined in bev_dsm_restriction_value) dsm_week[(np.arange(0, 7, 1) * 24 + options["bev_dsm_restriction_time"])] = options[ @@ -154,7 +151,8 @@ def bev_dsm_profile(snapshots, nodes, options): weekly_profile=dsm_week, ) -#%% + +# %% if __name__ == "__main__": if "snakemake" not in globals(): from _helpers import mock_snakemake diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index 6aba1bd5..e7bd105b 100755 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -1505,15 +1505,22 @@ def add_storage_and_grids(n, costs): def check_land_transport_shares(shares): # Sums up the shares, ignoring None values - total_share = sum(filter(None, shares)) + total_share = sum(filter(None, shares)) if total_share != 1: logger.warning( f"Total land transport shares sum up to {total_share:.2%}," "corresponding to increased or decreased demand assumptions." ) -def get_temp_efficency(car_efficiency, temperature, deadband_lw, deadband_up, - degree_factor_lw, degree_factor_up): + +def get_temp_efficency( + car_efficiency, + temperature, + deadband_lw, + deadband_up, + degree_factor_lw, + degree_factor_up, +): """ Correct temperature depending on heating and cooling for respective car type. @@ -1526,14 +1533,23 @@ def get_temp_efficency(car_efficiency, temperature, deadband_lw, deadband_up, degree_factor_lw, degree_factor_up, ) - - temp_eff = 1 / (1+dd) - + + temp_eff = 1 / (1 + dd) + return car_efficiency * temp_eff -def add_EVs(n, nodes, avail_profile, dsm_profile, p_set, electric_share, - number_cars, temperature): - + +def add_EVs( + n, + nodes, + avail_profile, + dsm_profile, + p_set, + electric_share, + number_cars, + temperature, +): + n.add("Carrier", "Li ion") n.madd( @@ -1544,35 +1560,37 @@ def add_EVs(n, nodes, avail_profile, dsm_profile, p_set, electric_share, carrier="Li ion", unit="MWh_el", ) - - car_efficiency = costs.at['Battery electric (passenger cars)', 'efficiency'] - - # temperature corrected efficiency - efficiency = get_temp_efficency(car_efficiency, temperature, - options["transport_heating_deadband_lower"], - options["transport_heating_deadband_upper"], - options["EV_lower_degree_factor"], - options["EV_upper_degree_factor"]) + + car_efficiency = costs.at["Battery electric (passenger cars)", "efficiency"] + + # temperature corrected efficiency + efficiency = get_temp_efficency( + car_efficiency, + temperature, + options["transport_heating_deadband_lower"], + options["transport_heating_deadband_upper"], + options["EV_lower_degree_factor"], + options["EV_upper_degree_factor"], + ) suffix = " land transport EV" - + p_nom = electric_share * p_set.div(efficiency).max() - - profile = p_set.div(efficiency)/p_set.div(efficiency).max() - + + profile = p_set.div(efficiency) / p_set.div(efficiency).max() + n.madd( "Link", nodes, suffix=suffix, - bus0=nodes + " EV battery", + bus0=nodes + " EV battery", bus1=nodes + " land transport", carrier="land transport EV", - efficiency=efficiency, + efficiency=efficiency, p_min_pu=profile, p_max_pu=profile, p_nom=p_nom, p_nom_extendable=False, ) - p_nom = number_cars * options.get("bev_charge_rate", 0.011) * electric_share @@ -1600,7 +1618,7 @@ def add_EVs(n, nodes, avail_profile, dsm_profile, p_set, electric_share, p_max_pu=avail_profile[nodes], efficiency=options.get("bev_charge_efficiency", 0.9), ) - + if options["bev_dsm"]: e_nom = ( number_cars @@ -1608,7 +1626,7 @@ def add_EVs(n, nodes, avail_profile, dsm_profile, p_set, electric_share, * options["bev_availability"] * electric_share ) - + n.madd( "Store", nodes, @@ -1620,56 +1638,62 @@ def add_EVs(n, nodes, avail_profile, dsm_profile, p_set, electric_share, e_max_pu=1, e_min_pu=dsm_profile[nodes], ) - + + def add_fuel_cell_cars(n, nodes, p_set, fuel_cell_share, temperature): - + car_efficiency = options["transport_fuel_cell_efficiency"] - - # temperature corrected efficiency - efficiency = get_temp_efficency(car_efficiency, temperature, - options["transport_heating_deadband_lower"], - options["transport_heating_deadband_upper"], - options["ICE_lower_degree_factor"], - options["ICE_upper_degree_factor"]) - + + # temperature corrected efficiency + efficiency = get_temp_efficency( + car_efficiency, + temperature, + options["transport_heating_deadband_lower"], + options["transport_heating_deadband_upper"], + options["ICE_lower_degree_factor"], + options["ICE_upper_degree_factor"], + ) + suffix = " land transport fuel cell" - + p_nom = fuel_cell_share * p_set.div(efficiency).max() - - profile = p_set.div(efficiency)/p_set.div(efficiency).max() - + + profile = p_set.div(efficiency) / p_set.div(efficiency).max() + n.madd( "Link", nodes, suffix=suffix, bus0=spatial.h2.nodes, bus1=nodes + " land transport", - carrier="land transport fuel cell", - efficiency=efficiency, + carrier="land transport fuel cell", + efficiency=efficiency, p_nom_extendable=False, p_nom=p_nom, p_min_pu=profile, p_max_pu=profile, ) - - + + def add_ice_cars(n, nodes, p_set, ice_share, temperature): - + add_carrier_buses(n, "oil") - + car_efficiency = options["transport_internal_combustion_efficiency"] - - # temperature corrected efficiency - efficiency = get_temp_efficency(car_efficiency, temperature, - options["transport_heating_deadband_lower"], - options["transport_heating_deadband_upper"], - options["ICE_lower_degree_factor"], - options["ICE_upper_degree_factor"]) + + # temperature corrected efficiency + efficiency = get_temp_efficency( + car_efficiency, + temperature, + options["transport_heating_deadband_lower"], + options["transport_heating_deadband_upper"], + options["ICE_lower_degree_factor"], + options["ICE_upper_degree_factor"], + ) suffix = " land transport ICE" - - p_nom = ice_share * p_set.div(efficiency).max() - - + + p_nom = ice_share * p_set.div(efficiency).max() + n.madd( "Link", nodes, @@ -1683,12 +1707,13 @@ def add_ice_cars(n, nodes, p_set, ice_share, temperature): p_nom_extendable=False, p_nom=p_nom, ) - + + def add_land_transport(n, costs): # TODO options? logger.info("Add land transport") - + # read in transport demand in units kinetic energy transport = pd.read_csv( snakemake.input.transport_demand, index_col=0, parse_dates=True @@ -1702,54 +1727,62 @@ def add_land_transport(n, costs): dsm_profile = pd.read_csv( snakemake.input.dsm_profile, index_col=0, parse_dates=True ) - + # exogenous share of passenger car type engine_types = ["fuel_cell", "electric", "ice"] shares = pd.Series() for engine in engine_types: - shares[engine] = get(options[f"land_transport_{engine}_share"], - investment_year) + shares[engine] = get(options[f"land_transport_{engine}_share"], investment_year) logger.info(f"{engine} share: {shares[engine]*100}%") - - check_land_transport_shares(shares) - + + check_land_transport_shares(shares) + nodes = spatial.nodes - + # Add load for transport demand n.add("Carrier", "land transport demand") - n.madd("Bus", - nodes, - location=nodes, - suffix=" land transport", - carrier="land transport demand", - unit="MWh_kinetic") - + n.madd( + "Bus", + nodes, + location=nodes, + suffix=" land transport", + carrier="land transport demand", + unit="MWh_kinetic", + ) + p_set = transport[nodes] - - # add demand + + # add demand n.madd( "Load", nodes, suffix=" land transport", bus=nodes + " land transport", carrier="land transport demand", - p_set=p_set, + p_set=p_set, ) - + # temperature for correction factor for heating/cooling temperature = xr.open_dataarray(snakemake.input.temp_air_total).to_pandas() - + if shares["electric"] > 0: - add_EVs(n, nodes, avail_profile, dsm_profile, p_set, shares["electric"], - number_cars, temperature) - + add_EVs( + n, + nodes, + avail_profile, + dsm_profile, + p_set, + shares["electric"], + number_cars, + temperature, + ) + if shares["fuel_cell"] > 0: add_fuel_cell_cars(n, nodes, p_set, shares["fuel_cell"], temperature) if shares["ice"] > 0: add_ice_cars(n, nodes, p_set, shares["ice"], temperature) - def build_heat_demand(n): @@ -3643,7 +3676,8 @@ def lossy_bidirectional_links(n, carrier, efficiencies={}): -compression_per_1000km * n.links.loc[carrier_i, "length_original"] / 1e3 ) -#%% + +# %% if __name__ == "__main__": if "snakemake" not in globals(): from _helpers import mock_snakemake diff --git a/scripts/solve_network.py b/scripts/solve_network.py index 9ba38d0b..d1ca16d7 100644 --- a/scripts/solve_network.py +++ b/scripts/solve_network.py @@ -921,7 +921,8 @@ def solve_network(n, config, solving, **kwargs): return n -#%% + +# %% if __name__ == "__main__": if "snakemake" not in globals(): from _helpers import mock_snakemake From f2ba96e1483e4c9dc7f756164fbcbef0d1d414c8 Mon Sep 17 00:00:00 2001 From: lisazeyen Date: Mon, 4 Mar 2024 11:24:43 +0100 Subject: [PATCH 08/34] add color for land transport demand --- config/config.default.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/config.default.yaml b/config/config.default.yaml index ad4144d5..aa662839 100644 --- a/config/config.default.yaml +++ b/config/config.default.yaml @@ -981,6 +981,7 @@ plotting: BEV charger: '#baf238' V2G: '#e5ffa8' land transport EV: '#baf238' + land transport demand: '#38baf2' Li ion: '#baf238' # hot water storage water tanks: '#e69487' From d4bdf489c9ef8e729012f6ed2a70135ec7f2df8a Mon Sep 17 00:00:00 2001 From: lisazeyen Date: Mon, 4 Mar 2024 12:08:45 +0100 Subject: [PATCH 09/34] add cyclic shift for EVs --- scripts/prepare_sector_network.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index e7bd105b..b7bb4ebe 100755 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -1573,11 +1573,18 @@ def add_EVs( options["EV_upper_degree_factor"], ) suffix = " land transport EV" - + + p_shifted = (p_set + cycling_shift(p_set, 1) + cycling_shift(p_set, 2)) / 3 + + cyclic_eff = p_set.div(p_shifted) + + efficiency *= cyclic_eff + p_nom = electric_share * p_set.div(efficiency).max() - profile = p_set.div(efficiency) / p_set.div(efficiency).max() - + profile = p_shifted.div(efficiency) / p_shifted.div(efficiency).max() + + n.madd( "Link", nodes, From c14f782f51790efee01bb66e6e175cb360f4cb4f Mon Sep 17 00:00:00 2001 From: lisazeyen Date: Mon, 4 Mar 2024 12:11:19 +0100 Subject: [PATCH 10/34] add lifetime to links --- scripts/prepare_sector_network.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index b7bb4ebe..38c1e737 100755 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -1597,6 +1597,7 @@ def add_EVs( p_max_pu=profile, p_nom=p_nom, p_nom_extendable=False, + lifetime=1, ) p_nom = number_cars * options.get("bev_charge_rate", 0.011) * electric_share @@ -1679,6 +1680,7 @@ def add_fuel_cell_cars(n, nodes, p_set, fuel_cell_share, temperature): p_nom=p_nom, p_min_pu=profile, p_max_pu=profile, + lifetime=1, ) @@ -1713,6 +1715,7 @@ def add_ice_cars(n, nodes, p_set, ice_share, temperature): efficiency2=costs.at["oil", "CO2 intensity"], p_nom_extendable=False, p_nom=p_nom, + lifetime=1, ) From fa935be5cb2561d0c63673619a6c89b0c187df9f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 4 Mar 2024 11:11:43 +0000 Subject: [PATCH 11/34] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- scripts/prepare_sector_network.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index 38c1e737..c32a5d14 100755 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -1573,18 +1573,17 @@ def add_EVs( options["EV_upper_degree_factor"], ) suffix = " land transport EV" - + p_shifted = (p_set + cycling_shift(p_set, 1) + cycling_shift(p_set, 2)) / 3 - + cyclic_eff = p_set.div(p_shifted) - + efficiency *= cyclic_eff - + p_nom = electric_share * p_set.div(efficiency).max() profile = p_shifted.div(efficiency) / p_shifted.div(efficiency).max() - - + n.madd( "Link", nodes, From 66257e1e843039852fd586e99c7c3de3a08b645c Mon Sep 17 00:00:00 2001 From: lisazeyen Date: Mon, 4 Mar 2024 15:41:57 +0100 Subject: [PATCH 12/34] fix bug with cyclic shift --- scripts/prepare_sector_network.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index c32a5d14..c4554b87 100755 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -1573,17 +1573,18 @@ def add_EVs( options["EV_upper_degree_factor"], ) suffix = " land transport EV" - + p_shifted = (p_set + cycling_shift(p_set, 1) + cycling_shift(p_set, 2)) / 3 - + cyclic_eff = p_set.div(p_shifted) - + efficiency *= cyclic_eff - + p_nom = electric_share * p_set.div(efficiency).max() - profile = p_shifted.div(efficiency) / p_shifted.div(efficiency).max() - + profile = p_set.div(efficiency) / p_set.div(efficiency).max() + + n.madd( "Link", nodes, From fb08d16be95094c0c16a53edbaee495cee53e60e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 4 Mar 2024 14:49:17 +0000 Subject: [PATCH 13/34] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- scripts/prepare_sector_network.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index c4554b87..0affa679 100755 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -1573,18 +1573,17 @@ def add_EVs( options["EV_upper_degree_factor"], ) suffix = " land transport EV" - + p_shifted = (p_set + cycling_shift(p_set, 1) + cycling_shift(p_set, 2)) / 3 - + cyclic_eff = p_set.div(p_shifted) - + efficiency *= cyclic_eff - + p_nom = electric_share * p_set.div(efficiency).max() profile = p_set.div(efficiency) / p_set.div(efficiency).max() - - + n.madd( "Link", nodes, From 6712e48aa0a6cb88b79d8afdec7f6048c1ee0fe1 Mon Sep 17 00:00:00 2001 From: lisazeyen Date: Mon, 4 Mar 2024 15:50:50 +0100 Subject: [PATCH 14/34] fix spelling mistake --- scripts/build_transport_demand.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/build_transport_demand.py b/scripts/build_transport_demand.py index a13c8df9..26f90ca7 100644 --- a/scripts/build_transport_demand.py +++ b/scripts/build_transport_demand.py @@ -19,7 +19,7 @@ logger = logging.getLogger(__name__) def build_nodal_transport_data(fn, pop_layout): - # get numbers of car and fuel efficieny per country + # get numbers of car and fuel efficiency per country transport_data = pd.read_csv(fn, index_col=0) # break number of cars down to nodal level based on population density From fe51fd7022aa973c4a03785f5dce7f87024aa75e Mon Sep 17 00:00:00 2001 From: lisazeyen Date: Mon, 4 Mar 2024 17:00:57 +0100 Subject: [PATCH 15/34] adjust for temporal aggregation --- scripts/prepare_sector_network.py | 34 +++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index 0affa679..a82778d4 100755 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -1701,7 +1701,9 @@ def add_ice_cars(n, nodes, p_set, ice_share, temperature): suffix = " land transport ICE" p_nom = ice_share * p_set.div(efficiency).max() - + + profile = p_set.div(efficiency) / p_set.div(efficiency).max() + n.madd( "Link", nodes, @@ -1714,6 +1716,8 @@ def add_ice_cars(n, nodes, p_set, ice_share, temperature): efficiency2=costs.at["oil", "CO2 intensity"], p_nom_extendable=False, p_nom=p_nom, + p_min_pu=profile, + p_max_pu=profile, lifetime=1, ) @@ -3686,6 +3690,30 @@ def lossy_bidirectional_links(n, carrier, efficiencies={}): ) + +def adjust_transport_temporal_agg(n): + + engine_types = {"fuel_cell":'land transport fuel cell', + "electric": 'land transport EV', + "ice": 'land transport oil'} + + p_set = n.loads_t.p_set.loc[:, n.loads.carrier=="land transport demand"] + + for engine, carrier in engine_types.items(): + share = get(options[f"land_transport_{engine}_share"], investment_year) + + if share==0: continue + links_i = n.links[n.links.carrier==carrier].index + efficiency = n.links_t.efficiency.loc[:, links_i] + p_set.columns = efficiency.columns + p_nom = share * p_set.div(efficiency).max() + profile = p_set.div(efficiency) / p_set.div(efficiency).max() + + n.links.loc[links_i, "p_nom"] = p_nom + n.links_t.p_max_pu[links_i] = profile + n.links_t.p_min_pu[links_i] = profile + + # %% if __name__ == "__main__": if "snakemake" not in globals(): @@ -3699,7 +3727,7 @@ if __name__ == "__main__": clusters="37", ll="v1.0", sector_opts="730H-T-H-B-I-A-dist1", - planning_horizons="2030", + planning_horizons="2050", ) configure_logging(snakemake) @@ -3784,6 +3812,8 @@ if __name__ == "__main__": solver_name = snakemake.config["solving"]["solver"]["name"] resolution = snakemake.params.time_resolution n = set_temporal_aggregation(n, resolution, solver_name) + + adjust_transport_temporal_agg(n) co2_budget = snakemake.params.co2_budget if isinstance(co2_budget, str) and co2_budget.startswith("cb"): From ff0093c8fed0b3b778c8a175c7a9d61cb80c75c6 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 4 Mar 2024 16:01:29 +0000 Subject: [PATCH 16/34] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- scripts/prepare_sector_network.py | 36 ++++++++++++++++--------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index a82778d4..a5635235 100755 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -1701,9 +1701,9 @@ def add_ice_cars(n, nodes, p_set, ice_share, temperature): suffix = " land transport ICE" p_nom = ice_share * p_set.div(efficiency).max() - + profile = p_set.div(efficiency) / p_set.div(efficiency).max() - + n.madd( "Link", nodes, @@ -3690,30 +3690,32 @@ def lossy_bidirectional_links(n, carrier, efficiencies={}): ) - def adjust_transport_temporal_agg(n): - - engine_types = {"fuel_cell":'land transport fuel cell', - "electric": 'land transport EV', - "ice": 'land transport oil'} - - p_set = n.loads_t.p_set.loc[:, n.loads.carrier=="land transport demand"] - + + engine_types = { + "fuel_cell": "land transport fuel cell", + "electric": "land transport EV", + "ice": "land transport oil", + } + + p_set = n.loads_t.p_set.loc[:, n.loads.carrier == "land transport demand"] + for engine, carrier in engine_types.items(): share = get(options[f"land_transport_{engine}_share"], investment_year) - - if share==0: continue - links_i = n.links[n.links.carrier==carrier].index + + if share == 0: + continue + links_i = n.links[n.links.carrier == carrier].index efficiency = n.links_t.efficiency.loc[:, links_i] p_set.columns = efficiency.columns p_nom = share * p_set.div(efficiency).max() profile = p_set.div(efficiency) / p_set.div(efficiency).max() - + n.links.loc[links_i, "p_nom"] = p_nom n.links_t.p_max_pu[links_i] = profile n.links_t.p_min_pu[links_i] = profile - - + + # %% if __name__ == "__main__": if "snakemake" not in globals(): @@ -3812,7 +3814,7 @@ if __name__ == "__main__": solver_name = snakemake.config["solving"]["solver"]["name"] resolution = snakemake.params.time_resolution n = set_temporal_aggregation(n, resolution, solver_name) - + adjust_transport_temporal_agg(n) co2_budget = snakemake.params.co2_budget From 1b6e5dc0c2b4c04c51f158896067ef14d8d76a8e Mon Sep 17 00:00:00 2001 From: lisazeyen Date: Mon, 4 Mar 2024 17:22:52 +0100 Subject: [PATCH 17/34] adjust for perfect foresight --- scripts/prepare_perfect_foresight.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/prepare_perfect_foresight.py b/scripts/prepare_perfect_foresight.py index f7e8495e..fbc17a52 100644 --- a/scripts/prepare_perfect_foresight.py +++ b/scripts/prepare_perfect_foresight.py @@ -21,6 +21,7 @@ from add_existing_baseyear import add_build_year_to_new_assets from pypsa.descriptors import expand_series from pypsa.io import import_components_from_dataframe from six import iterkeys +from prepare_sector_network import adjust_transport_temporal_agg logger = logging.getLogger(__name__) @@ -520,7 +521,8 @@ if __name__ == "__main__": segments = snakemake.params.time_resolution if isinstance(segments, (int, float)): n = apply_time_segmentation_perfect(n, segments, solver_name=solver_name) - + adjust_transport_temporal_agg(n) + # adjust global constraints lv limit if the same for all years n = adjust_lvlimit(n) # adjust global constraints CO2 limit From 3549e6843d1362374aa45997502735b8b6df00d6 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 4 Mar 2024 16:24:49 +0000 Subject: [PATCH 18/34] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- scripts/prepare_perfect_foresight.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/prepare_perfect_foresight.py b/scripts/prepare_perfect_foresight.py index fbc17a52..8df05cee 100644 --- a/scripts/prepare_perfect_foresight.py +++ b/scripts/prepare_perfect_foresight.py @@ -18,10 +18,10 @@ from _helpers import ( update_config_from_wildcards, ) from add_existing_baseyear import add_build_year_to_new_assets +from prepare_sector_network import adjust_transport_temporal_agg from pypsa.descriptors import expand_series from pypsa.io import import_components_from_dataframe from six import iterkeys -from prepare_sector_network import adjust_transport_temporal_agg logger = logging.getLogger(__name__) @@ -522,7 +522,7 @@ if __name__ == "__main__": if isinstance(segments, (int, float)): n = apply_time_segmentation_perfect(n, segments, solver_name=solver_name) adjust_transport_temporal_agg(n) - + # adjust global constraints lv limit if the same for all years n = adjust_lvlimit(n) # adjust global constraints CO2 limit From 9c93917c31138c7fbc8b601761d10b76727e4284 Mon Sep 17 00:00:00 2001 From: lisazeyen Date: Mon, 4 Mar 2024 17:29:25 +0100 Subject: [PATCH 19/34] add release notes --- doc/release_notes.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/doc/release_notes.rst b/doc/release_notes.rst index 8167f6dd..9d6e3c26 100644 --- a/doc/release_notes.rst +++ b/doc/release_notes.rst @@ -9,6 +9,11 @@ Release Notes Upcoming Release ================ +* fix bug in land transport for temperature correction of ICEs and fuel cell cars + +* restructure land transport, demand is now attached to one single node, the +different car types (ICE, EV, fuel cell) are modelled as links + * Upgrade default techno-economic assumptions to ``technology-data`` v0.8.1. * Linearly interpolate missing investment periods in year-dependent From a805f5cfb3f6873144b6f5cfc20250535d102b29 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 4 Mar 2024 16:38:36 +0000 Subject: [PATCH 20/34] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- doc/release_notes.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/release_notes.rst b/doc/release_notes.rst index 9d6e3c26..c3da4147 100644 --- a/doc/release_notes.rst +++ b/doc/release_notes.rst @@ -11,7 +11,7 @@ Upcoming Release ================ * fix bug in land transport for temperature correction of ICEs and fuel cell cars -* restructure land transport, demand is now attached to one single node, the +* restructure land transport, demand is now attached to one single node, the different car types (ICE, EV, fuel cell) are modelled as links * Upgrade default techno-economic assumptions to ``technology-data`` v0.8.1. From 74504a0ff3bba7d8d69e7e6f7c50d8f4a0b9c5d0 Mon Sep 17 00:00:00 2001 From: lisazeyen <35347358+lisazeyen@users.noreply.github.com> Date: Wed, 6 Mar 2024 09:04:30 +0100 Subject: [PATCH 21/34] add lifetime to BEV --- scripts/prepare_sector_network.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index a5635235..bb4b71a8 100755 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -1610,6 +1610,7 @@ def add_EVs( p_nom=p_nom, carrier="BEV charger", p_max_pu=avail_profile[nodes], + lifetime=1, efficiency=options.get("bev_charge_efficiency", 0.9), ) From 5046289c5ff77f671945db6c87abbcacbd601f65 Mon Sep 17 00:00:00 2001 From: lisazeyen <35347358+lisazeyen@users.noreply.github.com> Date: Wed, 6 Mar 2024 14:00:38 +0100 Subject: [PATCH 22/34] add lifetime V2G --- scripts/prepare_sector_network.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index 71772e81..712cb9fa 100755 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -1618,6 +1618,7 @@ def add_EVs( p_nom=p_nom, carrier="V2G", p_max_pu=avail_profile[nodes], + lifetime=1, efficiency=options.get("bev_charge_efficiency", 0.9), ) From 435350e07b626e98601f015de9c0ba8d45c7a65c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 26 Mar 2024 15:25:46 +0000 Subject: [PATCH 23/34] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- scripts/build_transport_demand.py | 2 -- scripts/prepare_sector_network.py | 4 +--- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/scripts/build_transport_demand.py b/scripts/build_transport_demand.py index a9ac9e22..2954a999 100644 --- a/scripts/build_transport_demand.py +++ b/scripts/build_transport_demand.py @@ -23,13 +23,11 @@ from _helpers import ( logger = logging.getLogger(__name__) - def build_nodal_transport_data(fn, pop_layout, year): # get numbers of car and fuel efficiency per country transport_data = pd.read_csv(fn, index_col=[0, 1]) transport_data = transport_data.xs(min(2015, year), level="year") - # break number of cars down to nodal level based on population density nodal_transport_data = transport_data.loc[pop_layout.ct].fillna(0.0) nodal_transport_data.index = pop_layout.index diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index 970d6e9d..53355b27 100755 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -23,15 +23,13 @@ from _helpers import ( update_config_from_wildcards, ) from add_electricity import calculate_annuity, sanitize_carriers, sanitize_locations - -from build_transport_demand import transport_degree_factor - from build_energy_totals import ( build_co2_totals, build_eea_co2, build_eurostat, build_eurostat_co2, ) +from build_transport_demand import transport_degree_factor from networkx.algorithms import complement from networkx.algorithms.connectivity.edge_augmentation import k_edge_augmentation from prepare_network import maybe_adjust_costs_and_potentials From 4f8cc6346143504661e434d6e112913ebb28c659 Mon Sep 17 00:00:00 2001 From: lisazeyen Date: Fri, 12 Apr 2024 13:58:01 +0200 Subject: [PATCH 24/34] clarify units --- scripts/build_energy_totals.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/scripts/build_energy_totals.py b/scripts/build_energy_totals.py index b56d3294..61e845d8 100644 --- a/scripts/build_energy_totals.py +++ b/scripts/build_energy_totals.py @@ -394,14 +394,13 @@ def build_idees(countries): keys=countries, names=["country", "year"], ) - + + # efficiency kgoe/100km -> ktoe/100km + totals.loc[:, "passenger car efficiency"] *= 1e3 # convert ktoe to TWh exclude = totals.columns.str.fullmatch("passenger cars") totals.loc[:, ~exclude] *= 11.63 / 1e3 - # convert TWh/100km to kWh/km - totals.loc[:, "passenger car efficiency"] *= 10 - return totals From ed6fa8f32c8a7715fc6604211735bad0ed1aed45 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 12 Apr 2024 11:59:15 +0000 Subject: [PATCH 25/34] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- scripts/build_energy_totals.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/build_energy_totals.py b/scripts/build_energy_totals.py index 61e845d8..0f053d40 100644 --- a/scripts/build_energy_totals.py +++ b/scripts/build_energy_totals.py @@ -394,7 +394,7 @@ def build_idees(countries): keys=countries, names=["country", "year"], ) - + # efficiency kgoe/100km -> ktoe/100km totals.loc[:, "passenger car efficiency"] *= 1e3 # convert ktoe to TWh From 53a6b91b06c3d4338738563d2b709e179ce4cf68 Mon Sep 17 00:00:00 2001 From: lisazeyen Date: Fri, 12 Apr 2024 14:28:03 +0200 Subject: [PATCH 26/34] clear up units --- config/config.default.yaml | 4 ++-- scripts/build_transport_demand.py | 9 ++++++--- scripts/prepare_sector_network.py | 6 +++--- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/config/config.default.yaml b/config/config.default.yaml index 32a8865e..b1d5a9a6 100644 --- a/config/config.default.yaml +++ b/config/config.default.yaml @@ -430,8 +430,8 @@ sector: 2040: 0.3 2045: 0.15 2050: 0 - transport_fuel_cell_efficiency: 0.5 - transport_internal_combustion_efficiency: 0.3 + transport_fuel_cell_efficiency: 30.003 # MWh_H2 per 100 km + transport_internal_combustion_efficiency: 16.0712 # MWh_oil per 100 km agriculture_machinery_electric_share: 0 agriculture_machinery_oil_share: 1 agriculture_machinery_fuel_efficiency: 0.7 diff --git a/scripts/build_transport_demand.py b/scripts/build_transport_demand.py index 2954a999..eedaecb1 100644 --- a/scripts/build_transport_demand.py +++ b/scripts/build_transport_demand.py @@ -45,7 +45,7 @@ def build_nodal_transport_data(fn, pop_layout, year): def build_transport_demand(traffic_fn, airtemp_fn, nodes, nodal_transport_data): """ - Returns transport demand per bus in unit kinetic energy. + Returns transport demand per bus in unit km driven [100 km]. """ # averaged weekly counts from the year 2010-2015 traffic = pd.read_csv(traffic_fn, skiprows=2, usecols=["count"]).squeeze("columns") @@ -78,9 +78,12 @@ def build_transport_demand(traffic_fn, airtemp_fn, nodes, nodal_transport_data): + pop_weighted_energy_totals["total rail"] - pop_weighted_energy_totals["electricity rail"] ) - + + # convert average fuel efficiency from kW/100 km -> MW/100km + eff = nodal_transport_data["average fuel efficiency"] * 1e3 + return (transport_shape.multiply(energy_totals_transport) * 1e6 * nyears).divide( - nodal_transport_data["average fuel efficiency"] * ice_correction + eff * ice_correction ) diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index 53355b27..b7e5cfb5 100755 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -1562,7 +1562,7 @@ def add_EVs( unit="MWh_el", ) - car_efficiency = costs.at["Battery electric (passenger cars)", "efficiency"] + car_efficiency = options["transport_electric_efficiency"] # temperature corrected efficiency efficiency = get_temp_efficency( @@ -1730,7 +1730,7 @@ def add_land_transport(n, costs): logger.info("Add land transport") - # read in transport demand in units kinetic energy + # read in transport demand in units driven km [100 km] transport = pd.read_csv( snakemake.input.transport_demand, index_col=0, parse_dates=True ) @@ -1764,7 +1764,7 @@ def add_land_transport(n, costs): location=nodes, suffix=" land transport", carrier="land transport demand", - unit="MWh_kinetic", + unit="100 km", ) p_set = transport[nodes] From ba93a404efecbb01b72692d796a4b30170d8e330 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 12 Apr 2024 12:29:35 +0000 Subject: [PATCH 27/34] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- scripts/build_transport_demand.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/build_transport_demand.py b/scripts/build_transport_demand.py index eedaecb1..f6ff7b3e 100644 --- a/scripts/build_transport_demand.py +++ b/scripts/build_transport_demand.py @@ -78,10 +78,10 @@ def build_transport_demand(traffic_fn, airtemp_fn, nodes, nodal_transport_data): + pop_weighted_energy_totals["total rail"] - pop_weighted_energy_totals["electricity rail"] ) - + # convert average fuel efficiency from kW/100 km -> MW/100km eff = nodal_transport_data["average fuel efficiency"] * 1e3 - + return (transport_shape.multiply(energy_totals_transport) * 1e6 * nyears).divide( eff * ice_correction ) From a424bd2e40da7e5bf7fb4cb15f578f018ee15247 Mon Sep 17 00:00:00 2001 From: lisazeyen Date: Fri, 12 Apr 2024 14:36:42 +0200 Subject: [PATCH 28/34] rename efficiencies in config.yaml --- config/config.default.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/config/config.default.yaml b/config/config.default.yaml index b1d5a9a6..eab701e2 100644 --- a/config/config.default.yaml +++ b/config/config.default.yaml @@ -430,8 +430,9 @@ sector: 2040: 0.3 2045: 0.15 2050: 0 - transport_fuel_cell_efficiency: 30.003 # MWh_H2 per 100 km - transport_internal_combustion_efficiency: 16.0712 # MWh_oil per 100 km + transport_electric_efficiency: 53.19 # 1 MWh_el = 53.19*100 km + transport_fuel_cell_efficiency: 30.003 # 1 MWh_H2 = 30.003*100 km + transport_ice_efficiency: 16.0712 # 1 MWh_oil = 16.0712 * 100 km agriculture_machinery_electric_share: 0 agriculture_machinery_oil_share: 1 agriculture_machinery_fuel_efficiency: 0.7 From 245ba5c20bb7c3e9c4488c4b11ee5e3b03289b07 Mon Sep 17 00:00:00 2001 From: lisazeyen Date: Fri, 12 Apr 2024 14:38:56 +0200 Subject: [PATCH 29/34] rename efficiencies in script --- scripts/prepare_sector_network.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index b7e5cfb5..0e5a08fb 100755 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -1690,7 +1690,7 @@ def add_ice_cars(n, nodes, p_set, ice_share, temperature): add_carrier_buses(n, "oil") - car_efficiency = options["transport_internal_combustion_efficiency"] + car_efficiency = options["transport_ice_efficiency"] # temperature corrected efficiency efficiency = get_temp_efficency( From 1a5141455013e5f442e5a0ae2b80c228893cad2e Mon Sep 17 00:00:00 2001 From: lisazeyen Date: Mon, 15 Apr 2024 08:43:12 +0200 Subject: [PATCH 30/34] correct conversion kW->MW --- scripts/build_transport_demand.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/build_transport_demand.py b/scripts/build_transport_demand.py index f6ff7b3e..a052581b 100644 --- a/scripts/build_transport_demand.py +++ b/scripts/build_transport_demand.py @@ -80,7 +80,7 @@ def build_transport_demand(traffic_fn, airtemp_fn, nodes, nodal_transport_data): ) # convert average fuel efficiency from kW/100 km -> MW/100km - eff = nodal_transport_data["average fuel efficiency"] * 1e3 + eff = nodal_transport_data["average fuel efficiency"] / 1e3 return (transport_shape.multiply(energy_totals_transport) * 1e6 * nyears).divide( eff * ice_correction From f3e3113b89a5b10d692140a320a43b75acbf771d Mon Sep 17 00:00:00 2001 From: lisazeyen Date: Mon, 15 Apr 2024 08:45:51 +0200 Subject: [PATCH 31/34] Reset file to the version in master branch --- scripts/solve_network.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/scripts/solve_network.py b/scripts/solve_network.py index b2a71206..0f6725c7 100644 --- a/scripts/solve_network.py +++ b/scripts/solve_network.py @@ -155,7 +155,7 @@ def _add_land_use_constraint(n): def _add_land_use_constraint_m(n, planning_horizons, config): # if generators clustering is lower than network clustering, land_use accounting is at generators clusters - grouping_years = config["existing_capacities"]["grouping_years"] + grouping_years = config["existing_capacities"]["grouping_years_power"] current_horizon = snakemake.wildcards.planning_horizons for carrier in ["solar", "onwind", "offwind-ac", "offwind-dc"]: @@ -923,19 +923,18 @@ def solve_network(n, config, solving, **kwargs): return n -# %% if __name__ == "__main__": if "snakemake" not in globals(): from _helpers import mock_snakemake snakemake = mock_snakemake( "solve_sector_network", - # configfiles="../config/test/config.perfect.yaml", + configfiles="../config/test/config.perfect.yaml", simpl="", opts="", clusters="37", ll="v1.0", - sector_opts="730H-T-H-B-I-A-dist1", + sector_opts="CO2L0-1H-T-H-B-I-A-dist1", planning_horizons="2030", ) configure_logging(snakemake) From 5b5be713416bdfa1a83cff4f4c250cabebc099ec Mon Sep 17 00:00:00 2001 From: Fabian Neumann Date: Wed, 15 May 2024 16:41:49 +0200 Subject: [PATCH 32/34] use loads instead of links where possible --- .gitignore | 2 + config/config.default.yaml | 1 - doc/configtables/sector.csv | 6 +- scripts/prepare_sector_network.py | 144 ++++++++++++------------------ 4 files changed, 60 insertions(+), 93 deletions(-) diff --git a/.gitignore b/.gitignore index 95ba5524..21062dd3 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,8 @@ # # SPDX-License-Identifier: CC0-1.0 +master + .snakemake* .ipynb_checkpoints __pycache__ diff --git a/config/config.default.yaml b/config/config.default.yaml index 497971dc..e1fce90f 100644 --- a/config/config.default.yaml +++ b/config/config.default.yaml @@ -417,7 +417,6 @@ sector: bev_availability: 0.5 bev_energy: 0.05 bev_charge_efficiency: 0.9 - bev_plug_to_wheel_efficiency: 0.2 bev_charge_rate: 0.011 bev_avail_max: 0.95 bev_avail_mean: 0.8 diff --git a/doc/configtables/sector.csv b/doc/configtables/sector.csv index 58ccd9bf..703652b5 100644 --- a/doc/configtables/sector.csv +++ b/doc/configtables/sector.csv @@ -24,7 +24,6 @@ bev_dsm,--,"{true, false}",Add the option for battery electric vehicles (BEV) to bev_availability,--,float,The share for battery electric vehicles (BEV) that are able to do demand side management (DSM) bev_energy,--,float,The average size of battery electric vehicles (BEV) in MWh bev_charge_efficiency,--,float,Battery electric vehicles (BEV) charge and discharge efficiency -bev_plug_to_wheel _efficiency,km/kWh,float,The distance battery electric vehicles (BEV) can travel in km per kWh of energy charge in battery. Base value comes from `Tesla Model S `_ bev_charge_rate,MWh,float,The power consumption for one electric vehicle (EV) in MWh. Value derived from 3-phase charger with 11 kW. bev_avail_max,--,float,The maximum share plugged-in availability for passenger electric vehicles. bev_avail_mean,--,float,The average share plugged-in availability for passenger electric vehicles. @@ -32,8 +31,9 @@ v2g,--,"{true, false}",Allows feed-in to grid from EV battery land_transport_fuel_cell _share,--,Dictionary with planning horizons as keys.,The share of vehicles that uses fuel cells in a given year land_transport_electric _share,--,Dictionary with planning horizons as keys.,The share of vehicles that uses electric vehicles (EV) in a given year land_transport_ice _share,--,Dictionary with planning horizons as keys.,The share of vehicles that uses internal combustion engines (ICE) in a given year. What is not EV or FCEV is oil-fuelled ICE. -transport_fuel_cell _efficiency,--,float,The H2 conversion efficiencies of fuel cells in transport -transport_internal _combustion_efficiency,--,float,The oil conversion efficiencies of internal combustion engine (ICE) in transport +transport_electric_efficiency,MWh/100km,float,The conversion efficiencies of electric vehicles in transport +transport_fuel_cell_efficiency,MWh/100km,float,The H2 conversion efficiencies of fuel cells in transport +transport_ice_efficiency,MWh/100km,float,The oil conversion efficiencies of internal combustion engine (ICE) in transport agriculture_machinery _electric_share,--,float,The share for agricultural machinery that uses electricity agriculture_machinery _oil_share,--,float,The share for agricultural machinery that uses oil agriculture_machinery _fuel_efficiency,--,float,The efficiency of electric-powered machinery in the conversion of electricity to meet agricultural needs. diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index 583de9e3..0ef99f62 100755 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -1540,7 +1540,6 @@ def get_temp_efficency( def add_EVs( n, - nodes, avail_profile, dsm_profile, p_set, @@ -1553,9 +1552,9 @@ def add_EVs( n.madd( "Bus", - nodes, + spatial.nodes, suffix=" EV battery", - location=nodes, + location=spatial.nodes, carrier="Li ion", unit="MWh_el", ) @@ -1571,7 +1570,6 @@ def add_EVs( options["EV_lower_degree_factor"], options["EV_upper_degree_factor"], ) - suffix = " land transport EV" p_shifted = (p_set + cycling_shift(p_set, 1) + cycling_shift(p_set, 2)) / 3 @@ -1579,36 +1577,28 @@ def add_EVs( efficiency *= cyclic_eff - p_nom = electric_share * p_set.div(efficiency).max() - - profile = p_set.div(efficiency) / p_set.div(efficiency).max() + profile = electric_share * p_set.div(efficiency) n.madd( - "Link", - nodes, - suffix=suffix, - bus0=nodes + " EV battery", - bus1=nodes + " land transport", + "Load", + spatial.nodes, + suffix=" land transport EV", + bus=spatial.nodes + " EV battery", carrier="land transport EV", - efficiency=efficiency, - p_min_pu=profile, - p_max_pu=profile, - p_nom=p_nom, - p_nom_extendable=False, - lifetime=1, + p_set=profile, ) p_nom = number_cars * options.get("bev_charge_rate", 0.011) * electric_share n.madd( "Link", - nodes, + spatial.nodes, suffix=" BEV charger", - bus0=nodes, - bus1=nodes + " EV battery", + bus0=spatial.nodes, + bus1=spatial.nodes + " EV battery", p_nom=p_nom, carrier="BEV charger", - p_max_pu=avail_profile[nodes], + p_max_pu=avail_profile[spatial.nodes], lifetime=1, efficiency=options.get("bev_charge_efficiency", 0.9), ) @@ -1616,13 +1606,13 @@ def add_EVs( if options["v2g"]: n.madd( "Link", - nodes, + spatial.nodes, suffix=" V2G", - bus1=nodes, - bus0=nodes + " EV battery", + bus1=spatial.nodes, + bus0=spatial.nodes + " EV battery", p_nom=p_nom, carrier="V2G", - p_max_pu=avail_profile[nodes], + p_max_pu=avail_profile[spatial.nodes], lifetime=1, efficiency=options.get("bev_charge_efficiency", 0.9), ) @@ -1637,18 +1627,18 @@ def add_EVs( n.madd( "Store", - nodes, + spatial.nodes, suffix=" battery storage", - bus=nodes + " EV battery", + bus=spatial.nodes + " EV battery", carrier="battery storage", e_cyclic=True, e_nom=e_nom, e_max_pu=1, - e_min_pu=dsm_profile[nodes], + e_min_pu=dsm_profile[spatial.nodes], ) -def add_fuel_cell_cars(n, nodes, p_set, fuel_cell_share, temperature): +def add_fuel_cell_cars(n, p_set, fuel_cell_share, temperature): car_efficiency = options["transport_fuel_cell_efficiency"] @@ -1662,29 +1652,19 @@ def add_fuel_cell_cars(n, nodes, p_set, fuel_cell_share, temperature): options["ICE_upper_degree_factor"], ) - suffix = " land transport fuel cell" - - p_nom = fuel_cell_share * p_set.div(efficiency).max() - - profile = p_set.div(efficiency) / p_set.div(efficiency).max() + profile = fuel_cell_share * p_set.div(efficiency) n.madd( - "Link", - nodes, - suffix=suffix, - bus0=spatial.h2.nodes, - bus1=nodes + " land transport", + "Load", + spatial.nodes, + suffix=" land transport fuel cell", + bus=spatial.h2.nodes, carrier="land transport fuel cell", - efficiency=efficiency, - p_nom_extendable=False, - p_nom=p_nom, - p_min_pu=profile, - p_max_pu=profile, - lifetime=1, + p_set=profile, ) -def add_ice_cars(n, nodes, p_set, ice_share, temperature): +def add_ice_cars(n, p_set, ice_share, temperature): add_carrier_buses(n, "oil") @@ -1699,32 +1679,43 @@ def add_ice_cars(n, nodes, p_set, ice_share, temperature): options["ICE_lower_degree_factor"], options["ICE_upper_degree_factor"], ) - suffix = " land transport ICE" - p_nom = ice_share * p_set.div(efficiency).max() + profile = ice_share * p_set.div(efficiency).rename( + columns=lambda x: x + " land transport oil" + ) - profile = p_set.div(efficiency) / p_set.div(efficiency).max() + if not options["regional_oil_demand"]: + profile = profile.sum(axis=1).to_frame(name="EU land transport oil") + + n.madd( + "Bus", + spatial.oil.land_transport, + location=spatial.oil.demand_locations, + carrier="land transport oil", + unit="land transport", + ) + + n.madd( + "Load", + spatial.oil.land_transport, + bus=spatial.oil.land_transport, + carrier="land transport oil", + p_set=profile, + ) n.madd( "Link", - nodes, - suffix=suffix, + spatial.oil.land_transport, bus0=spatial.oil.nodes, - bus1=nodes + " land transport", - bus2=["co2 atmosphere"], + bus1=spatial.oil.land_transport, + bus2="co2 atmosphere", carrier="land transport oil", - efficiency=efficiency, efficiency2=costs.at["oil", "CO2 intensity"], - p_nom_extendable=False, - p_nom=p_nom, - p_min_pu=profile, - p_max_pu=profile, - lifetime=1, + p_nom_extendable=True, ) def add_land_transport(n, costs): - # TODO options? logger.info("Add land transport") @@ -1751,31 +1742,7 @@ def add_land_transport(n, costs): check_land_transport_shares(shares) - nodes = spatial.nodes - - # Add load for transport demand - n.add("Carrier", "land transport demand") - - n.madd( - "Bus", - nodes, - location=nodes, - suffix=" land transport", - carrier="land transport demand", - unit="100 km", - ) - - p_set = transport[nodes] - - # add demand - n.madd( - "Load", - nodes, - suffix=" land transport", - bus=nodes + " land transport", - carrier="land transport demand", - p_set=p_set, - ) + p_set = transport[spatial.nodes] # temperature for correction factor for heating/cooling temperature = xr.open_dataarray(snakemake.input.temp_air_total).to_pandas() @@ -1783,7 +1750,6 @@ def add_land_transport(n, costs): if shares["electric"] > 0: add_EVs( n, - nodes, avail_profile, dsm_profile, p_set, @@ -1793,10 +1759,10 @@ def add_land_transport(n, costs): ) if shares["fuel_cell"] > 0: - add_fuel_cell_cars(n, nodes, p_set, shares["fuel_cell"], temperature) + add_fuel_cell_cars(n, p_set, shares["fuel_cell"], temperature) if shares["ice"] > 0: - add_ice_cars(n, nodes, p_set, shares["ice"], temperature) + add_ice_cars(n, p_set, shares["ice"], temperature) def build_heat_demand(n): From 1aa4f12c5e21546a850d11de9c4c4eb466b9853b Mon Sep 17 00:00:00 2001 From: Fabian Neumann Date: Wed, 15 May 2024 16:49:26 +0200 Subject: [PATCH 33/34] remove adjust_transport_temporal_agg --- scripts/prepare_perfect_foresight.py | 2 -- scripts/prepare_sector_network.py | 28 ---------------------------- 2 files changed, 30 deletions(-) diff --git a/scripts/prepare_perfect_foresight.py b/scripts/prepare_perfect_foresight.py index b4b7ddec..fea0cef4 100644 --- a/scripts/prepare_perfect_foresight.py +++ b/scripts/prepare_perfect_foresight.py @@ -18,7 +18,6 @@ from _helpers import ( update_config_from_wildcards, ) from add_existing_baseyear import add_build_year_to_new_assets -from prepare_sector_network import adjust_transport_temporal_agg from pypsa.descriptors import expand_series from pypsa.io import import_components_from_dataframe from six import iterkeys @@ -521,7 +520,6 @@ if __name__ == "__main__": segments = snakemake.params.time_resolution if isinstance(segments, (int, float)): n = apply_time_segmentation_perfect(n, segments, solver_name=solver_name) - adjust_transport_temporal_agg(n) # adjust global constraints lv limit if the same for all years n = adjust_lvlimit(n) diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index 0ef99f62..af9491b0 100755 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -3751,32 +3751,6 @@ def lossy_bidirectional_links(n, carrier, efficiencies={}): ) -def adjust_transport_temporal_agg(n): - - engine_types = { - "fuel_cell": "land transport fuel cell", - "electric": "land transport EV", - "ice": "land transport oil", - } - - p_set = n.loads_t.p_set.loc[:, n.loads.carrier == "land transport demand"] - - for engine, carrier in engine_types.items(): - share = get(options[f"land_transport_{engine}_share"], investment_year) - - if share == 0: - continue - links_i = n.links[n.links.carrier == carrier].index - efficiency = n.links_t.efficiency.loc[:, links_i] - p_set.columns = efficiency.columns - p_nom = share * p_set.div(efficiency).max() - profile = p_set.div(efficiency) / p_set.div(efficiency).max() - - n.links.loc[links_i, "p_nom"] = p_nom - n.links_t.p_max_pu[links_i] = profile - n.links_t.p_min_pu[links_i] = profile - - # %% if __name__ == "__main__": if "snakemake" not in globals(): @@ -3881,8 +3855,6 @@ if __name__ == "__main__": resolution = snakemake.params.time_resolution n = set_temporal_aggregation(n, resolution, solver_name) - adjust_transport_temporal_agg(n) - co2_budget = snakemake.params.co2_budget if isinstance(co2_budget, str) and co2_budget.startswith("cb"): fn = "results/" + snakemake.params.RDIR + "/csvs/carbon_budget_distribution.csv" From 438f651ea0ec2faef70a163e839009413ac84cb9 Mon Sep 17 00:00:00 2001 From: Fabian Neumann Date: Wed, 15 May 2024 17:13:37 +0200 Subject: [PATCH 34/34] amend release note [no ci] --- doc/release_notes.rst | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/doc/release_notes.rst b/doc/release_notes.rst index 5b830d2c..fefaf0e6 100644 --- a/doc/release_notes.rst +++ b/doc/release_notes.rst @@ -78,12 +78,8 @@ Upcoming Release * bugfix: convert Strings to pathlib.Path objects as input to ConfigSettings - - -* fix bug in land transport for temperature correction of ICEs and fuel cell cars - -* restructure land transport, demand is now attached to one single node, the -different car types (ICE, EV, fuel cell) are modelled as links +* bugfix: fix distinction of temperature-dependent correction factors for the + energy demand of electric vehicles, ICES fuel cell cars. * Allow the use of more solvers in clustering (Xpress, COPT, Gurobi, CPLEX, SCIP, MOSEK).