Merge pull request #1066 from PyPSA/adding_solar_tracking_single_axis
Adding solar tracking single axis
This commit is contained in:
commit
be28996a68
@ -107,7 +107,7 @@ electricity:
|
|||||||
H2: 168
|
H2: 168
|
||||||
|
|
||||||
extendable_carriers:
|
extendable_carriers:
|
||||||
Generator: [solar, onwind, offwind-ac, offwind-dc, offwind-float, OCGT]
|
Generator: [solar, solar-hsat, onwind, offwind-ac, offwind-dc, offwind-float, OCGT]
|
||||||
StorageUnit: [] # battery, H2
|
StorageUnit: [] # battery, H2
|
||||||
Store: [battery, H2]
|
Store: [battery, H2]
|
||||||
Link: [] # H2 pipeline
|
Link: [] # H2 pipeline
|
||||||
@ -117,7 +117,7 @@ electricity:
|
|||||||
everywhere_powerplants: [nuclear, oil, OCGT, CCGT, coal, lignite, geothermal, biomass]
|
everywhere_powerplants: [nuclear, oil, OCGT, CCGT, coal, lignite, geothermal, biomass]
|
||||||
|
|
||||||
conventional_carriers: [nuclear, oil, OCGT, CCGT, coal, lignite, geothermal, biomass]
|
conventional_carriers: [nuclear, oil, OCGT, CCGT, coal, lignite, geothermal, biomass]
|
||||||
renewable_carriers: [solar, onwind, offwind-ac, offwind-dc, offwind-float, hydro]
|
renewable_carriers: [solar, solar-hsat, onwind, offwind-ac, offwind-dc, hydro]
|
||||||
|
|
||||||
estimate_renewable_capacities:
|
estimate_renewable_capacities:
|
||||||
enable: true
|
enable: true
|
||||||
@ -246,6 +246,21 @@ renewable:
|
|||||||
natura: true
|
natura: true
|
||||||
excluder_resolution: 100
|
excluder_resolution: 100
|
||||||
clip_p_max_pu: 1.e-2
|
clip_p_max_pu: 1.e-2
|
||||||
|
solar-hsat:
|
||||||
|
cutout: europe-2013-sarah
|
||||||
|
resource:
|
||||||
|
method: pv
|
||||||
|
panel: CSi
|
||||||
|
orientation:
|
||||||
|
slope: 35.
|
||||||
|
azimuth: 180.
|
||||||
|
tracking: horizontal
|
||||||
|
capacity_per_sqkm: 4.43 # 15% higher land usage acc. to NREL
|
||||||
|
corine: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 26, 31, 32]
|
||||||
|
luisa: false # [1111, 1121, 1122, 1123, 1130, 1210, 1221, 1222, 1230, 1241, 1242, 1310, 1320, 1330, 1410, 1421, 1422, 2110, 2120, 2130, 2210, 2220, 2230, 2310, 2410, 2420, 3210, 3320, 3330]
|
||||||
|
natura: true
|
||||||
|
excluder_resolution: 100
|
||||||
|
clip_p_max_pu: 1.e-2
|
||||||
hydro:
|
hydro:
|
||||||
cutout: europe-2013-era5
|
cutout: europe-2013-era5
|
||||||
carriers: [ror, PHS, hydro]
|
carriers: [ror, PHS, hydro]
|
||||||
@ -324,7 +339,6 @@ pypsa_eur:
|
|||||||
- onwind
|
- onwind
|
||||||
- offwind-ac
|
- offwind-ac
|
||||||
- offwind-dc
|
- offwind-dc
|
||||||
- offwind-float
|
|
||||||
- solar
|
- solar
|
||||||
- ror
|
- ror
|
||||||
- nuclear
|
- nuclear
|
||||||
@ -588,6 +602,7 @@ sector:
|
|||||||
biogas_upgrading_cc: false
|
biogas_upgrading_cc: false
|
||||||
conventional_generation:
|
conventional_generation:
|
||||||
OCGT: gas
|
OCGT: gas
|
||||||
|
solar_utility_horizontal_axis_tracking: true
|
||||||
biomass_to_liquid: false
|
biomass_to_liquid: false
|
||||||
biosng: false
|
biosng: false
|
||||||
limit_max_growth:
|
limit_max_growth:
|
||||||
@ -924,6 +939,7 @@ plotting:
|
|||||||
# solar
|
# solar
|
||||||
solar: "#f9d002"
|
solar: "#f9d002"
|
||||||
solar PV: "#f9d002"
|
solar PV: "#f9d002"
|
||||||
|
solar-hsat: "#fdb915"
|
||||||
solar thermal: '#ffbf2b'
|
solar thermal: '#ffbf2b'
|
||||||
residential rural solar thermal: '#f1c069'
|
residential rural solar thermal: '#f1c069'
|
||||||
services rural solar thermal: '#eabf61'
|
services rural solar thermal: '#eabf61'
|
||||||
@ -1168,6 +1184,4 @@ plotting:
|
|||||||
DC-DC: "#8a1caf"
|
DC-DC: "#8a1caf"
|
||||||
DC link: "#8a1caf"
|
DC link: "#8a1caf"
|
||||||
load: "#dd2e23"
|
load: "#dd2e23"
|
||||||
waste CHP: '#e3d37d'
|
|
||||||
waste CHP CC: '#e3d3ff'
|
|
||||||
HVC to air: 'k'
|
HVC to air: 'k'
|
||||||
|
@ -34,7 +34,7 @@ electricity:
|
|||||||
Store: [H2]
|
Store: [H2]
|
||||||
Link: [H2 pipeline]
|
Link: [H2 pipeline]
|
||||||
|
|
||||||
renewable_carriers: [solar, onwind, offwind-ac, offwind-dc, offwind-float]
|
renewable_carriers: [solar, solar-hsat, onwind, offwind-ac, offwind-dc, offwind-float]
|
||||||
|
|
||||||
|
|
||||||
atlite:
|
atlite:
|
||||||
@ -61,6 +61,8 @@ renewable:
|
|||||||
min_depth: false
|
min_depth: false
|
||||||
solar:
|
solar:
|
||||||
cutout: be-03-2013-era5
|
cutout: be-03-2013-era5
|
||||||
|
solar-hsat:
|
||||||
|
cutout: be-03-2013-era5
|
||||||
|
|
||||||
|
|
||||||
clustering:
|
clustering:
|
||||||
|
@ -42,7 +42,7 @@ electricity:
|
|||||||
Store: [H2]
|
Store: [H2]
|
||||||
Link: [H2 pipeline]
|
Link: [H2 pipeline]
|
||||||
|
|
||||||
renewable_carriers: [solar, onwind, offwind-ac, offwind-dc, offwind-float]
|
renewable_carriers: [solar, solar-hsat, onwind, offwind-ac, offwind-dc, offwind-float]
|
||||||
|
|
||||||
atlite:
|
atlite:
|
||||||
default_cutout: be-03-2013-era5
|
default_cutout: be-03-2013-era5
|
||||||
@ -68,6 +68,8 @@ renewable:
|
|||||||
min_depth: false
|
min_depth: false
|
||||||
solar:
|
solar:
|
||||||
cutout: be-03-2013-era5
|
cutout: be-03-2013-era5
|
||||||
|
solar-hsat:
|
||||||
|
cutout: be-03-2013-era5
|
||||||
|
|
||||||
clustering:
|
clustering:
|
||||||
temporal:
|
temporal:
|
||||||
|
@ -36,7 +36,7 @@ electricity:
|
|||||||
Store: [H2]
|
Store: [H2]
|
||||||
Link: [H2 pipeline]
|
Link: [H2 pipeline]
|
||||||
|
|
||||||
renewable_carriers: [solar, onwind, offwind-ac, offwind-dc, offwind-float]
|
renewable_carriers: [solar, solar-hsat, onwind, offwind-ac, offwind-dc, offwind-float]
|
||||||
|
|
||||||
atlite:
|
atlite:
|
||||||
default_cutout: be-03-2013-era5
|
default_cutout: be-03-2013-era5
|
||||||
@ -62,6 +62,8 @@ renewable:
|
|||||||
min_depth: false
|
min_depth: false
|
||||||
solar:
|
solar:
|
||||||
cutout: be-03-2013-era5
|
cutout: be-03-2013-era5
|
||||||
|
solar-hsat:
|
||||||
|
cutout: be-03-2013-era5
|
||||||
|
|
||||||
clustering:
|
clustering:
|
||||||
temporal:
|
temporal:
|
||||||
|
@ -39,7 +39,7 @@ electricity:
|
|||||||
Store: [H2]
|
Store: [H2]
|
||||||
Link: [H2 pipeline]
|
Link: [H2 pipeline]
|
||||||
|
|
||||||
renewable_carriers: [solar, onwind, offwind-ac, offwind-dc, offwind-float]
|
renewable_carriers: [solar, solar-hsat, onwind, offwind-ac, offwind-dc, offwind-float]
|
||||||
|
|
||||||
sector:
|
sector:
|
||||||
min_part_load_fischer_tropsch: 0
|
min_part_load_fischer_tropsch: 0
|
||||||
@ -69,6 +69,8 @@ renewable:
|
|||||||
min_depth: false
|
min_depth: false
|
||||||
solar:
|
solar:
|
||||||
cutout: be-03-2013-era5
|
cutout: be-03-2013-era5
|
||||||
|
solar-hsat:
|
||||||
|
cutout: be-03-2013-era5
|
||||||
|
|
||||||
clustering:
|
clustering:
|
||||||
temporal:
|
temporal:
|
||||||
|
@ -9,6 +9,9 @@ Release Notes
|
|||||||
|
|
||||||
Upcoming Release
|
Upcoming Release
|
||||||
================
|
================
|
||||||
|
* New technology, solar PV with single-axis horizontal tracking (on a N-S axis),
|
||||||
|
with a carrier called ``solar-hsat`` to the networks. The default option for adding
|
||||||
|
this technology is set to ``true`` in the ``config.yaml``.
|
||||||
|
|
||||||
* The technology-data version was updated to v0.9.0.
|
* The technology-data version was updated to v0.9.0.
|
||||||
|
|
||||||
|
@ -193,7 +193,7 @@ rule determine_availability_matrix_MD_UA:
|
|||||||
offshore_shapes=resources("offshore_shapes.geojson"),
|
offshore_shapes=resources("offshore_shapes.geojson"),
|
||||||
regions=lambda w: (
|
regions=lambda w: (
|
||||||
resources("regions_onshore.geojson")
|
resources("regions_onshore.geojson")
|
||||||
if w.technology in ("onwind", "solar")
|
if w.technology in ("onwind", "solar", "solar-hsat")
|
||||||
else resources("regions_offshore.geojson")
|
else resources("regions_offshore.geojson")
|
||||||
),
|
),
|
||||||
cutout=lambda w: "cutouts/"
|
cutout=lambda w: "cutouts/"
|
||||||
@ -264,7 +264,7 @@ rule build_renewable_profiles:
|
|||||||
offshore_shapes=resources("offshore_shapes.geojson"),
|
offshore_shapes=resources("offshore_shapes.geojson"),
|
||||||
regions=lambda w: (
|
regions=lambda w: (
|
||||||
resources("regions_onshore.geojson")
|
resources("regions_onshore.geojson")
|
||||||
if w.technology in ("onwind", "solar")
|
if w.technology in ("onwind", "solar", "solar-hsat")
|
||||||
else resources("regions_offshore.geojson")
|
else resources("regions_offshore.geojson")
|
||||||
),
|
),
|
||||||
cutout=lambda w: "cutouts/"
|
cutout=lambda w: "cutouts/"
|
||||||
|
@ -195,8 +195,12 @@ def adjust_renewable_profiles(n, input_profiles, params, year):
|
|||||||
)
|
)
|
||||||
|
|
||||||
for carrier in params["carriers"]:
|
for carrier in params["carriers"]:
|
||||||
if carrier == "hydro":
|
if carrier == "hydro" or (
|
||||||
|
carrier == "solar-hsat"
|
||||||
|
and not snakemake.config["sector"]["solar_utility_horizontal_axis_tracking"]
|
||||||
|
):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
with xr.open_dataset(getattr(input_profiles, "profile_" + carrier)) as ds:
|
with xr.open_dataset(getattr(input_profiles, "profile_" + carrier)) as ds:
|
||||||
if ds.indexes["bus"].empty or "year" not in ds.indexes:
|
if ds.indexes["bus"].empty or "year" not in ds.indexes:
|
||||||
continue
|
continue
|
||||||
|
@ -235,6 +235,8 @@ def load_costs(tech_costs, config, max_hours, Nyears=1.0):
|
|||||||
+ (1 - config["rooftop_share"]) * costs.at["solar-utility", "capital_cost"]
|
+ (1 - config["rooftop_share"]) * costs.at["solar-utility", "capital_cost"]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
costs = costs.rename({"solar-utility single-axis tracking": "solar-hsat"})
|
||||||
|
|
||||||
def costs_for_storage(store, link1, link2=None, max_hours=1.0):
|
def costs_for_storage(store, link1, link2=None, max_hours=1.0):
|
||||||
capital_cost = link1["capital_cost"] + max_hours * store["capital_cost"]
|
capital_cost = link1["capital_cost"] + max_hours * store["capital_cost"]
|
||||||
if link2 is not None:
|
if link2 is not None:
|
||||||
|
@ -1113,7 +1113,7 @@ def insert_gas_distribution_costs(n, costs):
|
|||||||
|
|
||||||
|
|
||||||
def add_electricity_grid_connection(n, costs):
|
def add_electricity_grid_connection(n, costs):
|
||||||
carriers = ["onwind", "solar"]
|
carriers = ["onwind", "solar", "solar-hsat"]
|
||||||
|
|
||||||
gens = n.generators.index[n.generators.carrier.isin(carriers)]
|
gens = n.generators.index[n.generators.carrier.isin(carriers)]
|
||||||
|
|
||||||
@ -3412,6 +3412,13 @@ def remove_h2_network(n):
|
|||||||
n.stores.drop("EU H2 Store", inplace=True)
|
n.stores.drop("EU H2 Store", inplace=True)
|
||||||
|
|
||||||
|
|
||||||
|
def remove_solar_tracking(n):
|
||||||
|
|
||||||
|
for tech in ["solar-hsat"]:
|
||||||
|
logger.info("removing " + tech)
|
||||||
|
n.mremove("Generator", n.generators.index[n.generators.carrier == tech])
|
||||||
|
|
||||||
|
|
||||||
def limit_individual_line_extension(n, maxext):
|
def limit_individual_line_extension(n, maxext):
|
||||||
logger.info(f"Limiting new HVAC and HVDC extensions to {maxext} MW")
|
logger.info(f"Limiting new HVAC and HVDC extensions to {maxext} MW")
|
||||||
n.lines["s_nom_max"] = n.lines["s_nom"] + maxext
|
n.lines["s_nom_max"] = n.lines["s_nom"] + maxext
|
||||||
@ -3789,6 +3796,9 @@ if __name__ == "__main__":
|
|||||||
if options["electricity_distribution_grid"]:
|
if options["electricity_distribution_grid"]:
|
||||||
insert_electricity_distribution_grid(n, costs)
|
insert_electricity_distribution_grid(n, costs)
|
||||||
|
|
||||||
|
if not options["solar_utility_horizontal_axis_tracking"]:
|
||||||
|
remove_solar_tracking(n)
|
||||||
|
|
||||||
maybe_adjust_costs_and_potentials(n, snakemake.params["adjustments"])
|
maybe_adjust_costs_and_potentials(n, snakemake.params["adjustments"])
|
||||||
|
|
||||||
if options["gas_distribution_grid"]:
|
if options["gas_distribution_grid"]:
|
||||||
|
@ -31,6 +31,7 @@ import logging
|
|||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
|
from functools import reduce
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
@ -123,7 +124,15 @@ def add_land_use_constraint_perfect(n):
|
|||||||
def _add_land_use_constraint(n):
|
def _add_land_use_constraint(n):
|
||||||
# warning: this will miss existing offwind which is not classed AC-DC and has carrier 'offwind'
|
# warning: this will miss existing offwind which is not classed AC-DC and has carrier 'offwind'
|
||||||
|
|
||||||
for carrier in ["solar", "onwind", "offwind-ac", "offwind-dc", "offwind-float"]:
|
for carrier in [
|
||||||
|
"solar",
|
||||||
|
"solar rooftop",
|
||||||
|
"solar-hsat",
|
||||||
|
"onwind",
|
||||||
|
"offwind-ac",
|
||||||
|
"offwind-dc",
|
||||||
|
"offwind-float",
|
||||||
|
]:
|
||||||
extendable_i = (n.generators.carrier == carrier) & n.generators.p_nom_extendable
|
extendable_i = (n.generators.carrier == carrier) & n.generators.p_nom_extendable
|
||||||
n.generators.loc[extendable_i, "p_nom_min"] = 0
|
n.generators.loc[extendable_i, "p_nom_min"] = 0
|
||||||
|
|
||||||
@ -158,7 +167,14 @@ def _add_land_use_constraint_m(n, planning_horizons, config):
|
|||||||
grouping_years = config["existing_capacities"]["grouping_years_power"]
|
grouping_years = config["existing_capacities"]["grouping_years_power"]
|
||||||
current_horizon = snakemake.wildcards.planning_horizons
|
current_horizon = snakemake.wildcards.planning_horizons
|
||||||
|
|
||||||
for carrier in ["solar", "onwind", "offwind-ac", "offwind-dc", "offwind-float"]:
|
for carrier in [
|
||||||
|
"solar",
|
||||||
|
"solar rooftop",
|
||||||
|
"solar-hsat",
|
||||||
|
"onwind",
|
||||||
|
"offwind-ac",
|
||||||
|
"offwind-dc",
|
||||||
|
]:
|
||||||
extendable_i = (n.generators.carrier == carrier) & n.generators.p_nom_extendable
|
extendable_i = (n.generators.carrier == carrier) & n.generators.p_nom_extendable
|
||||||
n.generators.loc[extendable_i, "p_nom_min"] = 0
|
n.generators.loc[extendable_i, "p_nom_min"] = 0
|
||||||
|
|
||||||
@ -199,6 +215,83 @@ def _add_land_use_constraint_m(n, planning_horizons, config):
|
|||||||
n.generators.p_nom_max.clip(lower=0, inplace=True)
|
n.generators.p_nom_max.clip(lower=0, inplace=True)
|
||||||
|
|
||||||
|
|
||||||
|
def add_solar_potential_constraints(n, config):
|
||||||
|
"""
|
||||||
|
Add constraint to make sure the sum capacity of all solar technologies (fixed, tracking, ets. ) is below the region potential.
|
||||||
|
Example:
|
||||||
|
ES1 0: total solar potential is 10 GW, meaning:
|
||||||
|
solar potential : 10 GW
|
||||||
|
solar-hsat potential : 8 GW (solar with single axis tracking is assumed to have higher land use)
|
||||||
|
The constraint ensures that:
|
||||||
|
solar_p_nom + solar_hsat_p_nom * 1.13 <= 10 GW
|
||||||
|
"""
|
||||||
|
land_use_factors = {
|
||||||
|
"solar-hsat": config["renewable"]["solar"]["capacity_per_sqkm"]
|
||||||
|
/ config["renewable"]["solar-hsat"]["capacity_per_sqkm"],
|
||||||
|
}
|
||||||
|
rename = {"Generator-ext": "Generator"}
|
||||||
|
|
||||||
|
solar_carriers = ["solar", "solar-hsat"]
|
||||||
|
solar = n.generators[
|
||||||
|
n.generators.carrier.isin(solar_carriers) & n.generators.p_nom_extendable
|
||||||
|
].index
|
||||||
|
|
||||||
|
solar_today = n.generators[
|
||||||
|
(n.generators.carrier == "solar") & (n.generators.p_nom_extendable)
|
||||||
|
].index
|
||||||
|
solar_hsat = n.generators[(n.generators.carrier == "solar-hsat")].index
|
||||||
|
|
||||||
|
if solar.empty:
|
||||||
|
return
|
||||||
|
|
||||||
|
land_use = pd.DataFrame(1, index=solar, columns=["land_use_factor"])
|
||||||
|
for carrier, factor in land_use_factors.items():
|
||||||
|
land_use = land_use.apply(
|
||||||
|
lambda x: (x * factor) if carrier in x.name else x, axis=1
|
||||||
|
)
|
||||||
|
|
||||||
|
if "m" in snakemake.wildcards.clusters:
|
||||||
|
location = pd.Series(
|
||||||
|
[" ".join(i.split(" ")[:2]) for i in n.generators.index],
|
||||||
|
index=n.generators.index,
|
||||||
|
)
|
||||||
|
ggrouper = pd.Series(
|
||||||
|
n.generators.loc[solar].index.rename("bus").map(location),
|
||||||
|
index=n.generators.loc[solar].index,
|
||||||
|
).to_xarray()
|
||||||
|
rhs = (
|
||||||
|
n.generators.loc[solar_today, "p_nom_max"]
|
||||||
|
.groupby(n.generators.loc[solar_today].index.rename("bus").map(location))
|
||||||
|
.sum()
|
||||||
|
- n.generators.loc[solar_hsat, "p_nom_opt"]
|
||||||
|
.groupby(n.generators.loc[solar_hsat].index.rename("bus").map(location))
|
||||||
|
.sum()
|
||||||
|
* land_use_factors["solar-hsat"]
|
||||||
|
).clip(lower=0)
|
||||||
|
|
||||||
|
else:
|
||||||
|
location = pd.Series(n.buses.index, index=n.buses.index)
|
||||||
|
ggrouper = n.generators.loc[solar].bus
|
||||||
|
rhs = (
|
||||||
|
n.generators.loc[solar_today, "p_nom_max"]
|
||||||
|
.groupby(n.generators.loc[solar_today].bus.map(location))
|
||||||
|
.sum()
|
||||||
|
- n.generators.loc[solar_hsat, "p_nom_opt"]
|
||||||
|
.groupby(n.generators.loc[solar_hsat].bus.map(location))
|
||||||
|
.sum()
|
||||||
|
* land_use_factors["solar-hsat"]
|
||||||
|
).clip(lower=0)
|
||||||
|
|
||||||
|
lhs = (
|
||||||
|
(n.model["Generator-p_nom"].rename(rename).loc[solar] * land_use.squeeze())
|
||||||
|
.groupby(ggrouper)
|
||||||
|
.sum()
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.info("Adding solar potential constraint.")
|
||||||
|
n.model.add_constraints(lhs <= rhs, name="solar_potential")
|
||||||
|
|
||||||
|
|
||||||
def add_co2_sequestration_limit(n, limit=200):
|
def add_co2_sequestration_limit(n, limit=200):
|
||||||
"""
|
"""
|
||||||
Add a global constraint on the amount of Mt CO2 that can be sequestered.
|
Add a global constraint on the amount of Mt CO2 that can be sequestered.
|
||||||
@ -862,6 +955,9 @@ def extra_functionality(n, snapshots):
|
|||||||
if EQ_o := constraints["EQ"]:
|
if EQ_o := constraints["EQ"]:
|
||||||
add_EQ_constraints(n, EQ_o.replace("EQ", ""))
|
add_EQ_constraints(n, EQ_o.replace("EQ", ""))
|
||||||
|
|
||||||
|
if config["sector"]["solar_utility_horizontal_axis_tracking"]:
|
||||||
|
add_solar_potential_constraints(n, config)
|
||||||
|
|
||||||
add_battery_constraints(n)
|
add_battery_constraints(n)
|
||||||
add_lossy_bidirectional_link_constraints(n)
|
add_lossy_bidirectional_link_constraints(n)
|
||||||
add_pipe_retrofit_constraint(n)
|
add_pipe_retrofit_constraint(n)
|
||||||
|
Loading…
Reference in New Issue
Block a user