diff --git a/config.default.yaml b/config.default.yaml index 156454543..cd8850723 100644 --- a/config.default.yaml +++ b/config.default.yaml @@ -594,6 +594,13 @@ sector: efficiency_heat_biomass_to_elec: 0.9 efficiency_heat_gas_to_elec: 0.9 + electricity_distribution_grid: true # adds low voltage buses and shifts AC loads, BEVs, heat pumps, and resistive heaters, micro CHPs to low voltage buses if technologies are present + solar_rooftop: true # adds distribution side customer rooftop PV (only work if electricity_distribution_grid: true) + home_battery: true # adds home batteries to low voltage buses ((only work if electricity_distribution_grid: true) + transmission_efficiency: + electricity distribution grid: + efficiency_static: 0.97 # efficiency of distribution grid (i.e. 3% loses) + dynamic_transport: enable: false # If "True", then the BEV and FCEV shares are obtained depending on the "Co2L"-wildcard (e.g. "Co2L0.70: 0.10"). If "False", then the shares are obtained depending on the "demand" wildcard and "planning_horizons" wildcard as listed below (e.g. "DF_2050: 0.08") land_transport_electric_share: diff --git a/doc/release_notes.rst b/doc/release_notes.rst index eb4437cfa..68c30fee8 100644 --- a/doc/release_notes.rst +++ b/doc/release_notes.rst @@ -39,6 +39,8 @@ E.g. if a new rule becomes available describe how to use it `make test` and in o * Drop vrestil depenedncy `PR #1220 `__ +* Add electricity distribution grid with solar rooftop and home battery technologies `PR #1221 `__ + **Minor Changes and bug-fixing** * The default configuration for `electricity:estimate_renewable_capacities:year` was updated from 2020 to 2023. `PR #1106 `__ diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index 01ab70a7b..df8e7b48b 100644 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -2629,6 +2629,141 @@ def add_residential(n, costs): ) +def add_electricity_distribution_grid(n, costs): + logger.info("Adding electricity distribution network") + nodes = pop_layout.index + + n.madd( + "Bus", + nodes + " low voltage", + location=nodes, + carrier="low voltage", + unit="MWh_el", + ) + + n.madd( + "Link", + nodes + " electricity distribution grid", + bus0=nodes, + bus1=nodes + " low voltage", + p_nom_extendable=True, + p_min_pu=-1, + carrier="electricity distribution grid", + efficiency=1, + lifetime=costs.at["electricity distribution grid", "lifetime"], + capital_cost=costs.at["electricity distribution grid", "fixed"], + ) + + # deduct distribution losses from electricity demand as these are included in total load + # https://nbviewer.org/github/Open-Power-System-Data/datapackage_timeseries/blob/2020-10-06/main.ipynb + if ( + efficiency := options["transmission_efficiency"] + .get("electricity distribution grid", {}) + .get("efficiency_static") + ): + logger.info( + f"Deducting distribution losses from electricity demand: {np.around(100*(1-efficiency), decimals=2)}%" + ) + n.loads_t.p_set.loc[:, n.loads.carrier == "AC"] *= efficiency + + # move AC loads to low voltage buses + ac_loads = n.loads.index[n.loads.carrier == "AC"] + n.loads.loc[ac_loads, "bus"] += " low voltage" + + # move industry, rail transport, agriculture and services electricity to low voltage + loads = n.loads.index[n.loads.carrier.str.contains("electricity")] + n.loads.loc[loads, "bus"] += " low voltage" + + bevs = n.links.index[n.links.carrier == "BEV charger"] + n.links.loc[bevs, "bus0"] += " low voltage" + + v2gs = n.links.index[n.links.carrier == "V2G"] + n.links.loc[v2gs, "bus1"] += " low voltage" + + hps = n.links.index[n.links.carrier.str.contains("heat pump")] + n.links.loc[hps, "bus0"] += " low voltage" + + rh = n.links.index[n.links.carrier.str.contains("resistive heater")] + n.links.loc[rh, "bus0"] += " low voltage" + + mchp = n.links.index[n.links.carrier.str.contains("micro gas")] + n.links.loc[mchp, "bus1"] += " low voltage" + + if options.get("solar_rooftop", False): + logger.info("Adding solar rooftop technology") + # set existing solar to cost of utility cost rather the 50-50 rooftop-utility + solar = n.generators.index[n.generators.carrier == "solar"] + n.generators.loc[solar, "capital_cost"] = costs.at["solar-utility", "fixed"] + pop_solar = pop_layout.total.rename(index=lambda x: x + " solar") + + # add max solar rooftop potential assuming 0.1 kW/m2 and 20 m2/person, + # i.e. 2 kW/person (population data is in thousands of people) so we get MW + potential = 0.1 * 20 * pop_solar + + n.madd( + "Generator", + solar, + suffix=" rooftop", + bus=n.generators.loc[solar, "bus"] + " low voltage", + carrier="solar rooftop", + p_nom_extendable=True, + p_nom_max=potential.loc[solar], + marginal_cost=n.generators.loc[solar, "marginal_cost"], + capital_cost=costs.at["solar-rooftop", "fixed"], + efficiency=n.generators.loc[solar, "efficiency"], + p_max_pu=n.generators_t.p_max_pu[solar], + lifetime=costs.at["solar-rooftop", "lifetime"], + ) + + if options.get("home_battery", False): + logger.info("Adding home battery technology") + n.add("Carrier", "home battery") + + n.madd( + "Bus", + nodes + " home battery", + location=nodes, + carrier="home battery", + unit="MWh_el", + ) + + n.madd( + "Store", + nodes + " home battery", + bus=nodes + " home battery", + location=nodes, + e_cyclic=True, + e_nom_extendable=True, + carrier="home battery", + capital_cost=costs.at["home battery storage", "fixed"], + lifetime=costs.at["battery storage", "lifetime"], + ) + + n.madd( + "Link", + nodes + " home battery charger", + bus0=nodes + " low voltage", + bus1=nodes + " home battery", + carrier="home battery charger", + efficiency=costs.at["battery inverter", "efficiency"] ** 0.5, + capital_cost=costs.at["home battery inverter", "fixed"], + p_nom_extendable=True, + lifetime=costs.at["battery inverter", "lifetime"], + ) + + n.madd( + "Link", + nodes + " home battery discharger", + bus0=nodes + " home battery", + bus1=nodes + " low voltage", + carrier="home battery discharger", + efficiency=costs.at["battery inverter", "efficiency"] ** 0.5, + marginal_cost=options["marginal_cost_storage"], + p_nom_extendable=True, + lifetime=costs.at["battery inverter", "lifetime"], + ) + + # def add_co2limit(n, Nyears=1.0, limit=0.0): # print("Adding CO2 budget limit as per unit of 1990 levels of", limit) @@ -2950,6 +3085,9 @@ def remove_carrier_related_components(n, carriers_to_drop): add_residential(n, costs) add_services(n, costs) + if options.get("electricity_distribution_grid", False): + add_electricity_distribution_grid(n, costs) + sopts = snakemake.wildcards.sopts.split("-") for o in sopts: