streamline code for year-dependent technologies (turbines/panels)

This commit is contained in:
Fabian Neumann 2024-02-05 12:10:35 +01:00
parent bb4eb123e5
commit a834ff222a
12 changed files with 90 additions and 122 deletions

View File

@ -164,14 +164,11 @@ atlite:
# docs in https://pypsa-eur.readthedocs.io/en/latest/configuration.html#renewable # docs in https://pypsa-eur.readthedocs.io/en/latest/configuration.html#renewable
renewable: renewable:
year: 2020
onwind: onwind:
cutout: europe-2013-era5 cutout: europe-2013-era5
resource: resource:
method: wind method: wind
turbine: turbine: Vestas_V112_3MW
2020: Vestas_V112_3MW
2030: NREL_ReferenceTurbine_2020ATB_5.5MW
add_cutout_windspeed: true add_cutout_windspeed: true
capacity_per_sqkm: 3 capacity_per_sqkm: 3
# correction_factor: 0.93 # correction_factor: 0.93
@ -190,9 +187,7 @@ renewable:
cutout: europe-2013-era5 cutout: europe-2013-era5
resource: resource:
method: wind method: wind
turbine: turbine: NREL_ReferenceTurbine_5MW_offshore
2020: NREL_ReferenceTurbine_5MW_offshore.yaml
2030: NREL_ReferenceTurbine_2020ATB_15MW_offshore
add_cutout_windspeed: true add_cutout_windspeed: true
capacity_per_sqkm: 2 capacity_per_sqkm: 2
correction_factor: 0.8855 correction_factor: 0.8855
@ -208,10 +203,7 @@ renewable:
cutout: europe-2013-era5 cutout: europe-2013-era5
resource: resource:
method: wind method: wind
turbine: turbine: Vestas_V164_7MW_offshore
2020: Vestas_V164_7MW_offshore
2025: NREL_ReferenceTurbine_2020ATB_15MW_offshore
2030: NREL_ReferenceTurbine_2020ATB_18MW_offshore
add_cutout_windspeed: true add_cutout_windspeed: true
capacity_per_sqkm: 2 capacity_per_sqkm: 2
correction_factor: 0.8855 correction_factor: 0.8855
@ -227,8 +219,7 @@ renewable:
cutout: europe-2013-sarah cutout: europe-2013-sarah
resource: resource:
method: pv method: pv
panel: panel: CSi
2020: CSi
orientation: orientation:
slope: 35. slope: 35.
azimuth: 180. azimuth: 180.

View File

@ -2,7 +2,7 @@
cutout,--,"Should be a folder listed in the configuration ``atlite: cutouts:`` (e.g. 'europe-2013-era5') or reference an existing folder in the directory ``cutouts``. Source module must be ERA5.","Specifies the directory where the relevant weather data ist stored." cutout,--,"Should be a folder listed in the configuration ``atlite: cutouts:`` (e.g. 'europe-2013-era5') or reference an existing folder in the directory ``cutouts``. Source module must be ERA5.","Specifies the directory where the relevant weather data ist stored."
resource,,, resource,,,
-- method,--,"Must be 'wind'","A superordinate technology type." -- method,--,"Must be 'wind'","A superordinate technology type."
-- turbine,--,"One of turbine types included in `atlite <https://github.com/PyPSA/atlite/tree/master/atlite/resources/windturbine>`_","Specifies the turbine type and its characteristic power curve." -- turbine,--,"One of turbine types included in `atlite <https://github.com/PyPSA/atlite/tree/master/atlite/resources/windturbine>`_. Can be a string or a dictionary with years as keys which denote the year another turbine model becomes available.","Specifies the turbine type and its characteristic power curve."
capacity_per_sqkm,:math:`MW/km^2`,float,"Allowable density of wind turbine placement." capacity_per_sqkm,:math:`MW/km^2`,float,"Allowable density of wind turbine placement."
correction_factor,--,float,"Correction factor for capacity factor time series." correction_factor,--,float,"Correction factor for capacity factor time series."
excluder_resolution,m,float,"Resolution on which to perform geographical elibility analysis." excluder_resolution,m,float,"Resolution on which to perform geographical elibility analysis."

1 Unit Values Description
2 cutout -- Should be a folder listed in the configuration ``atlite: cutouts:`` (e.g. 'europe-2013-era5') or reference an existing folder in the directory ``cutouts``. Source module must be ERA5. Specifies the directory where the relevant weather data ist stored.
3 resource
4 -- method -- Must be 'wind' A superordinate technology type.
5 -- turbine -- One of turbine types included in `atlite <https://github.com/PyPSA/atlite/tree/master/atlite/resources/windturbine>`_ One of turbine types included in `atlite <https://github.com/PyPSA/atlite/tree/master/atlite/resources/windturbine>`_. Can be a string or a dictionary with years as keys which denote the year another turbine model becomes available. Specifies the turbine type and its characteristic power curve.
6 capacity_per_sqkm :math:`MW/km^2` float Allowable density of wind turbine placement.
7 correction_factor -- float Correction factor for capacity factor time series.
8 excluder_resolution m float Resolution on which to perform geographical elibility analysis.

View File

@ -2,7 +2,7 @@
cutout,--,"Should be a folder listed in the configuration ``atlite: cutouts:`` (e.g. 'europe-2013-era5') or reference an existing folder in the directory ``cutouts``. Source module must be ERA5.","Specifies the directory where the relevant weather data ist stored." cutout,--,"Should be a folder listed in the configuration ``atlite: cutouts:`` (e.g. 'europe-2013-era5') or reference an existing folder in the directory ``cutouts``. Source module must be ERA5.","Specifies the directory where the relevant weather data ist stored."
resource,,, resource,,,
-- method,--,"Must be 'wind'","A superordinate technology type." -- method,--,"Must be 'wind'","A superordinate technology type."
-- turbine,--,"One of turbine types included in `atlite <https://github.com/PyPSA/atlite/tree/master/atlite/resources/windturbine>`__","Specifies the turbine type and its characteristic power curve." -- turbine,--,"One of turbine types included in `atlite <https://github.com/PyPSA/atlite/tree/master/atlite/resources/windturbine>`_. Can be a string or a dictionary with years as keys which denote the year another turbine model becomes available.","Specifies the turbine type and its characteristic power curve."
capacity_per_sqkm,:math:`MW/km^2`,float,"Allowable density of wind turbine placement." capacity_per_sqkm,:math:`MW/km^2`,float,"Allowable density of wind turbine placement."
correction_factor,--,float,"Correction factor for capacity factor time series." correction_factor,--,float,"Correction factor for capacity factor time series."
excluder_resolution,m,float,"Resolution on which to perform geographical elibility analysis." excluder_resolution,m,float,"Resolution on which to perform geographical elibility analysis."

1 Unit Values Description
2 cutout -- Should be a folder listed in the configuration ``atlite: cutouts:`` (e.g. 'europe-2013-era5') or reference an existing folder in the directory ``cutouts``. Source module must be ERA5. Specifies the directory where the relevant weather data ist stored.
3 resource
4 -- method -- Must be 'wind' A superordinate technology type.
5 -- turbine -- One of turbine types included in `atlite <https://github.com/PyPSA/atlite/tree/master/atlite/resources/windturbine>`__ One of turbine types included in `atlite <https://github.com/PyPSA/atlite/tree/master/atlite/resources/windturbine>`_. Can be a string or a dictionary with years as keys which denote the year another turbine model becomes available. Specifies the turbine type and its characteristic power curve.
6 capacity_per_sqkm :math:`MW/km^2` float Allowable density of wind turbine placement.
7 correction_factor -- float Correction factor for capacity factor time series.
8 excluder_resolution m float Resolution on which to perform geographical elibility analysis.

View File

@ -2,7 +2,7 @@
cutout,--,"Should be a folder listed in the configuration ``atlite: cutouts:`` (e.g. 'europe-2013-era5') or reference an existing folder in the directory ``cutouts``. Source module must be ERA5.","Specifies the directory where the relevant weather data ist stored." cutout,--,"Should be a folder listed in the configuration ``atlite: cutouts:`` (e.g. 'europe-2013-era5') or reference an existing folder in the directory ``cutouts``. Source module must be ERA5.","Specifies the directory where the relevant weather data ist stored."
resource,,, resource,,,
-- method,--,"Must be 'wind'","A superordinate technology type." -- method,--,"Must be 'wind'","A superordinate technology type."
-- turbine,--,"One of turbine types included in `atlite <https://github.com/PyPSA/atlite/tree/master/atlite/resources/windturbine>`__","Specifies the turbine type and its characteristic power curve." -- turbine,--,"One of turbine types included in `atlite <https://github.com/PyPSA/atlite/tree/master/atlite/resources/windturbine>`_. Can be a string or a dictionary with years as keys which denote the year another turbine model becomes available.","Specifies the turbine type and its characteristic power curve."
capacity_per_sqkm,:math:`MW/km^2`,float,"Allowable density of wind turbine placement." capacity_per_sqkm,:math:`MW/km^2`,float,"Allowable density of wind turbine placement."
corine,,, corine,,,
-- grid_codes,--,"Any subset of the `CORINE Land Cover code list <http://www.eea.europa.eu/data-and-maps/data/corine-land-cover-2006-raster-1/corine-land-cover-classes-and/clc_legend.csv/at_download/file>`_","Specifies areas according to CORINE Land Cover codes which are generally eligible for wind turbine placement." -- grid_codes,--,"Any subset of the `CORINE Land Cover code list <http://www.eea.europa.eu/data-and-maps/data/corine-land-cover-2006-raster-1/corine-land-cover-classes-and/clc_legend.csv/at_download/file>`_","Specifies areas according to CORINE Land Cover codes which are generally eligible for wind turbine placement."

1 Unit Values Description
2 cutout -- Should be a folder listed in the configuration ``atlite: cutouts:`` (e.g. 'europe-2013-era5') or reference an existing folder in the directory ``cutouts``. Source module must be ERA5. Specifies the directory where the relevant weather data ist stored.
3 resource
4 -- method -- Must be 'wind' A superordinate technology type.
5 -- turbine -- One of turbine types included in `atlite <https://github.com/PyPSA/atlite/tree/master/atlite/resources/windturbine>`__ One of turbine types included in `atlite <https://github.com/PyPSA/atlite/tree/master/atlite/resources/windturbine>`_. Can be a string or a dictionary with years as keys which denote the year another turbine model becomes available. Specifies the turbine type and its characteristic power curve.
6 capacity_per_sqkm :math:`MW/km^2` float Allowable density of wind turbine placement.
7 corine
8 -- grid_codes -- Any subset of the `CORINE Land Cover code list <http://www.eea.europa.eu/data-and-maps/data/corine-land-cover-2006-raster-1/corine-land-cover-classes-and/clc_legend.csv/at_download/file>`_ Specifies areas according to CORINE Land Cover codes which are generally eligible for wind turbine placement.

View File

@ -2,7 +2,7 @@
cutout,--,"Should be a folder listed in the configuration ``atlite: cutouts:`` (e.g. 'europe-2013-era5') or reference an existing folder in the directory ``cutouts``. Source module can be ERA5 or SARAH-2.","Specifies the directory where the relevant weather data ist stored that is specified at ``atlite/cutouts`` configuration. Both ``sarah`` and ``era5`` work." cutout,--,"Should be a folder listed in the configuration ``atlite: cutouts:`` (e.g. 'europe-2013-era5') or reference an existing folder in the directory ``cutouts``. Source module can be ERA5 or SARAH-2.","Specifies the directory where the relevant weather data ist stored that is specified at ``atlite/cutouts`` configuration. Both ``sarah`` and ``era5`` work."
resource,,, resource,,,
-- method,--,"Must be 'pv'","A superordinate technology type." -- method,--,"Must be 'pv'","A superordinate technology type."
-- panel,--,"One of {'Csi', 'CdTe', 'KANENA'} as defined in `atlite <https://github.com/PyPSA/atlite/tree/master/atlite/resources/solarpanel>`__","Specifies the solar panel technology and its characteristic attributes." -- panel,--,"One of {'Csi', 'CdTe', 'KANENA'} as defined in `atlite <https://github.com/PyPSA/atlite/tree/master/atlite/resources/solarpanel>`_ . Can be a string or a dictionary with years as keys which denote the year another turbine model becomes available.","Specifies the solar panel technology and its characteristic attributes."
-- orientation,,, -- orientation,,,
-- -- slope,°,"Realistically any angle in [0., 90.]","Specifies the tilt angle (or slope) of the solar panel. A slope of zero corresponds to the face of the panel aiming directly overhead. A positive tilt angle steers the panel towards the equator." -- -- slope,°,"Realistically any angle in [0., 90.]","Specifies the tilt angle (or slope) of the solar panel. A slope of zero corresponds to the face of the panel aiming directly overhead. A positive tilt angle steers the panel towards the equator."
-- -- azimuth,°,"Any angle in [0., 360.]","Specifies the `azimuth <https://en.wikipedia.org/wiki/Azimuth>`_ orientation of the solar panel. South corresponds to 180.°." -- -- azimuth,°,"Any angle in [0., 360.]","Specifies the `azimuth <https://en.wikipedia.org/wiki/Azimuth>`_ orientation of the solar panel. South corresponds to 180.°."

1 Unit Values Description
2 cutout -- Should be a folder listed in the configuration ``atlite: cutouts:`` (e.g. 'europe-2013-era5') or reference an existing folder in the directory ``cutouts``. Source module can be ERA5 or SARAH-2. Specifies the directory where the relevant weather data ist stored that is specified at ``atlite/cutouts`` configuration. Both ``sarah`` and ``era5`` work.
3 resource
4 -- method -- Must be 'pv' A superordinate technology type.
5 -- panel -- One of {'Csi', 'CdTe', 'KANENA'} as defined in `atlite <https://github.com/PyPSA/atlite/tree/master/atlite/resources/solarpanel>`__ One of {'Csi', 'CdTe', 'KANENA'} as defined in `atlite <https://github.com/PyPSA/atlite/tree/master/atlite/resources/solarpanel>`_ . Can be a string or a dictionary with years as keys which denote the year another turbine model becomes available. Specifies the solar panel technology and its characteristic attributes.
6 -- orientation
7 -- -- slope ° Realistically any angle in [0., 90.] Specifies the tilt angle (or slope) of the solar panel. A slope of zero corresponds to the face of the panel aiming directly overhead. A positive tilt angle steers the panel towards the equator.
8 -- -- azimuth ° Any angle in [0., 360.] Specifies the `azimuth <https://en.wikipedia.org/wiki/Azimuth>`_ orientation of the solar panel. South corresponds to 180.°.

View File

@ -71,6 +71,12 @@ Upcoming Release
Energiewende (2021) Energiewende (2021)
<https://static.agora-energiewende.de/fileadmin/Projekte/2021/2021_02_EU_CEAP/A-EW_254_Mobilising-circular-economy_study_WEB.pdf>`_. <https://static.agora-energiewende.de/fileadmin/Projekte/2021/2021_02_EU_CEAP/A-EW_254_Mobilising-circular-economy_study_WEB.pdf>`_.
* Added option to specify turbine and solar panel models for specific years as a
dictionary (e.g. ``renewable: onwind: resource: turbine:``). The years will be
interpreted as years from when the the corresponding turbine model substitutes
the previous model for new installations. This will only have an effect on
workflows with foresight "myopic" and still needs to be added foresight option
"perfect".
PyPSA-Eur 0.9.0 (5th January 2024) PyPSA-Eur 0.9.0 (5th January 2024)
================================== ==================================

View File

@ -261,7 +261,6 @@ rule build_renewable_profiles:
params: params:
snapshots={k: config["snapshots"][k] for k in ["start", "end", "inclusive"]}, snapshots={k: config["snapshots"][k] for k in ["start", "end", "inclusive"]},
renewable=config["renewable"], renewable=config["renewable"],
foresight=config["foresight"],
input: input:
**opt, **opt,
base_network=RESOURCES + "networks/base.nc", base_network=RESOURCES + "networks/base.nc",

View File

@ -85,10 +85,13 @@ rule add_brownfield:
H2_retrofit=config["sector"]["H2_retrofit"], H2_retrofit=config["sector"]["H2_retrofit"],
H2_retrofit_capacity_per_CH4=config["sector"]["H2_retrofit_capacity_per_CH4"], H2_retrofit_capacity_per_CH4=config["sector"]["H2_retrofit_capacity_per_CH4"],
threshold_capacity=config["existing_capacities"]["threshold_capacity"], threshold_capacity=config["existing_capacities"]["threshold_capacity"],
snapshots={k: config["snapshots"][k] for k in ["start", "end", "inclusive"]},
carriers=config["electricity"]["renewable_carriers"],
input: input:
**{ **{
f"profile_{tech}": RESOURCES + f"profile_{tech}.nc" f"profile_{tech}": RESOURCES + f"profile_{tech}.nc"
for tech in config["electricity"]["renewable_carriers"] for tech in config["electricity"]["renewable_carriers"]
if tech != "hydro"
}, },
simplify_busmap=RESOURCES + "busmap_elec_s{simpl}.csv", simplify_busmap=RESOURCES + "busmap_elec_s{simpl}.csv",
cluster_busmap=RESOURCES + "busmap_elec_s{simpl}_{clusters}.csv", cluster_busmap=RESOURCES + "busmap_elec_s{simpl}_{clusters}.csv",

View File

@ -145,78 +145,53 @@ def disable_grid_expansion_if_LV_limit_hit(n):
n.global_constraints.drop("lv_limit", inplace=True) n.global_constraints.drop("lv_limit", inplace=True)
def adjust_renewable_profiles(n, input_profiles, config, year): def adjust_renewable_profiles(n, input_profiles, params, year):
""" """
Adjusts renewable profiles according to the renewable technology specified. Adjusts renewable profiles according to the renewable technology specified,
using the latest year below or equal to the selected year.
If the planning horizon is not available, the closest year is used
instead.
""" """
# spatial clustering
cluster_busmap = pd.read_csv(snakemake.input.cluster_busmap, index_col=0).squeeze() cluster_busmap = pd.read_csv(snakemake.input.cluster_busmap, index_col=0).squeeze()
simplify_busmap = pd.read_csv( simplify_busmap = pd.read_csv(
snakemake.input.simplify_busmap, index_col=0 snakemake.input.simplify_busmap, index_col=0
).squeeze() ).squeeze()
clustermaps = simplify_busmap.map(cluster_busmap) clustermaps = simplify_busmap.map(cluster_busmap)
clustermaps.index = clustermaps.index.astype(str) clustermaps.index = clustermaps.index.astype(str)
dr = pd.date_range(**config["snapshots"], freq="H")
# temporal clustering
dr = pd.date_range(**params["snapshots"], freq="h")
snapshotmaps = ( snapshotmaps = (
pd.Series(dr, index=dr).where(lambda x: x.isin(n.snapshots), pd.NA).ffill() pd.Series(dr, index=dr).where(lambda x: x.isin(n.snapshots), pd.NA).ffill()
) )
for carrier in config["electricity"]["renewable_carriers"]: for carrier in params["carriers"]:
if carrier == "hydro":
continue
clustermaps.index = clustermaps.index.astype(str)
dr = pd.date_range(**config["snapshots"], freq="H")
snapshotmaps = (
pd.Series(dr, index=dr).where(lambda x: x.isin(n.snapshots), pd.NA).ffill()
)
for carrier in config["electricity"]["renewable_carriers"]:
if carrier == "hydro": if carrier == "hydro":
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
if year in ds.indexes["year"]:
p_max_pu = ( closest_year = max(
ds["year_profiles"] (y for y in ds.year.values if y <= year), default=min(ds.year.values)
.sel(year=year) )
.transpose("time", "bus")
.to_pandas() p_max_pu = (
) ds["profile"]
else: .sel(year=closest_year)
available_previous_years = [ .transpose("time", "bus")
available_year .to_pandas()
for available_year in ds.indexes["year"] )
if available_year < year
]
available_following_years = [
available_year
for available_year in ds.indexes["year"]
if available_year > year
]
if available_previous_years:
closest_year = max(available_previous_years)
if available_following_years:
closest_year = min(available_following_years)
logging.warning(
f"Planning horizon {year} not in {carrier} profiles. Using closest year {closest_year} instead."
)
p_max_pu = (
ds["year_profiles"]
.sel(year=closest_year)
.transpose("time", "bus")
.to_pandas()
)
# spatial clustering # spatial clustering
weight = ds["weight"].to_pandas() weight = ds["weight"].sel(year=closest_year).to_pandas()
weight = weight.groupby(clustermaps).transform(normed_or_uniform) weight = weight.groupby(clustermaps).transform(normed_or_uniform)
p_max_pu = (p_max_pu * weight).T.groupby(clustermaps).sum().T p_max_pu = (p_max_pu * weight).T.groupby(clustermaps).sum().T
p_max_pu.columns = p_max_pu.columns + f" {carrier}" p_max_pu.columns = p_max_pu.columns + f" {carrier}"
# temporal_clustering # temporal_clustering
p_max_pu = p_max_pu.groupby(snapshotmaps).mean() p_max_pu = p_max_pu.groupby(snapshotmaps).mean()
# replace renewable time series # replace renewable time series
n.generators_t.p_max_pu.loc[:, p_max_pu.columns] = p_max_pu n.generators_t.p_max_pu.loc[:, p_max_pu.columns] = p_max_pu
@ -245,7 +220,7 @@ if __name__ == "__main__":
n = pypsa.Network(snakemake.input.network) n = pypsa.Network(snakemake.input.network)
adjust_renewable_profiles(n, snakemake.input, snakemake.config, year) adjust_renewable_profiles(n, snakemake.input, snakemake.params, year)
add_build_year_to_new_assets(n, year) add_build_year_to_new_assets(n, year)

View File

@ -374,6 +374,10 @@ def attach_wind_and_solar(
if ds.indexes["bus"].empty: if ds.indexes["bus"].empty:
continue continue
# if-statement for compatibility with old profiles
if "year" in ds.indexes:
ds = ds.sel(year=ds.year.min(), drop=True)
supcar = car.split("-", 2)[0] supcar = car.split("-", 2)[0]
if supcar == "offwind": if supcar == "offwind":
underwater_fraction = ds["underwater_fraction"].to_pandas() underwater_fraction = ds["underwater_fraction"].to_pandas()

View File

@ -200,24 +200,20 @@ if __name__ == "__main__":
if "snakemake" not in globals(): if "snakemake" not in globals():
from _helpers import mock_snakemake from _helpers import mock_snakemake
snakemake = mock_snakemake("build_renewable_profiles", technology="onwind") snakemake = mock_snakemake("build_renewable_profiles", technology="offwind-dc")
configure_logging(snakemake) configure_logging(snakemake)
nprocesses = int(snakemake.threads) nprocesses = int(snakemake.threads)
noprogress = snakemake.config["run"].get("disable_progressbar", True) noprogress = snakemake.config["run"].get("disable_progressbar", True)
noprogress = noprogress or not snakemake.config["atlite"]["show_progress"] noprogress = noprogress or not snakemake.config["atlite"]["show_progress"]
year = snakemake.params.renewable["year"]
foresight = snakemake.params.foresight
params = snakemake.params.renewable[snakemake.wildcards.technology] params = snakemake.params.renewable[snakemake.wildcards.technology]
resource = params["resource"] # pv panel params / wind turbine params resource = params["resource"] # pv panel params / wind turbine params
year_dependent_techs = { tech = next(t for t in ["panel", "turbine"] if t in resource)
k: resource.get(k) models = resource[tech]
for k in ["panel", "turbine"] if not isinstance(models, dict):
if isinstance(resource.get(k), dict) models = {0: models}
} resource[tech] = models[next(iter(models))]
for key, techs in year_dependent_techs.items():
resource[key] = resource[key][year]
correction_factor = params.get("correction_factor", 1.0) correction_factor = params.get("correction_factor", 1.0)
capacity_per_sqkm = params["capacity_per_sqkm"] capacity_per_sqkm = params["capacity_per_sqkm"]
@ -334,45 +330,40 @@ if __name__ == "__main__":
duration = time.time() - start duration = time.time() - start
logger.info(f"Completed average capacity factor calculation ({duration:2.2f}s)") logger.info(f"Completed average capacity factor calculation ({duration:2.2f}s)")
logger.info("Calculate weighted capacity factor time series...") profiles = []
start = time.time() capacities = []
for year, model in models.items():
profile, capacities = func( logger.info(
matrix=availability.stack(spatial=["y", "x"]), f"Calculate weighted capacity factor time series for model {model}..."
layout=layout, )
index=buses, start = time.time()
per_unit=True,
return_capacity=True,
**resource,
)
if year_dependent_techs and foresight != "overnight": resource[tech] = model
for key, techs in year_dependent_techs.items():
year_profiles = list()
tech_profiles = dict()
tech_profiles[resource[key]] = profile
for year, tech in techs.items():
resource[key] = tech
if tech not in tech_profiles:
tech_profiles[tech] = func(
matrix=availability.stack(spatial=["y", "x"]),
layout=layout,
index=buses,
per_unit=True,
return_capacity=False,
**resource,
)
year_profile = tech_profiles[tech]
year_profile = year_profile.expand_dims({"year": [year]}).rename(
"year_profiles"
)
year_profiles.append(year_profile)
year_profiles = xr.merge(year_profiles)
duration = time.time() - start profile, capacity = func(
logger.info( matrix=availability.stack(spatial=["y", "x"]),
f"Completed weighted capacity factor time series calculation ({duration:2.2f}s)" layout=layout,
) index=buses,
per_unit=True,
return_capacity=True,
**resource,
)
dim = {"year": [year]}
profile = profile.expand_dims(dim)
capacity = capacity.expand_dims(dim)
profiles.append(profile.rename("profile"))
capacities.append(capacity.rename("weight"))
duration = time.time() - start
logger.info(
f"Completed weighted capacity factor time series calculation for model {model} ({duration:2.2f}s)"
)
profiles = xr.merge(profiles)
capacities = xr.merge(capacities)
logger.info("Calculating maximal capacity per bus") logger.info("Calculating maximal capacity per bus")
p_nom_max = capacity_per_sqkm * availability @ area p_nom_max = capacity_per_sqkm * availability @ area
@ -399,17 +390,14 @@ if __name__ == "__main__":
ds = xr.merge( ds = xr.merge(
[ [
(correction_factor * profile).rename("profile"), correction_factor * profiles,
capacities.rename("weight"), capacities,
p_nom_max.rename("p_nom_max"), p_nom_max.rename("p_nom_max"),
potential.rename("potential"), potential.rename("potential"),
average_distance.rename("average_distance"), average_distance.rename("average_distance"),
] ]
) )
if year_dependent_techs:
ds = xr.merge([ds, year_profiles * correction_factor])
if snakemake.wildcards.technology.startswith("offwind"): if snakemake.wildcards.technology.startswith("offwind"):
logger.info("Calculate underwater fraction of connections.") logger.info("Calculate underwater fraction of connections.")
offshore_shape = gpd.read_file(snakemake.input["offshore_shapes"]).unary_union offshore_shape = gpd.read_file(snakemake.input["offshore_shapes"]).unary_union
@ -425,7 +413,7 @@ if __name__ == "__main__":
# select only buses with some capacity and minimal capacity factor # select only buses with some capacity and minimal capacity factor
ds = ds.sel( ds = ds.sel(
bus=( bus=(
(ds["profile"].mean("time") > params.get("min_p_max_pu", 0.0)) (ds["profile"].mean("time").max("year") > params.get("min_p_max_pu", 0.0))
& (ds["p_nom_max"] > params.get("min_p_nom_max", 0.0)) & (ds["p_nom_max"] > params.get("min_p_nom_max", 0.0))
) )
) )
@ -433,9 +421,6 @@ if __name__ == "__main__":
if "clip_p_max_pu" in params: if "clip_p_max_pu" in params:
min_p_max_pu = params["clip_p_max_pu"] min_p_max_pu = params["clip_p_max_pu"]
ds["profile"] = ds["profile"].where(ds["profile"] >= min_p_max_pu, 0) ds["profile"] = ds["profile"].where(ds["profile"] >= min_p_max_pu, 0)
ds["year_profiles"] = ds["year_profiles"].where(
ds["year_profiles"] >= min_p_max_pu, 0
)
ds.to_netcdf(snakemake.output.profile) ds.to_netcdf(snakemake.output.profile)

View File

@ -421,6 +421,11 @@ def update_wind_solar_costs(n, costs):
tech = "offwind-" + connection tech = "offwind-" + connection
profile = snakemake.input["profile_offwind_" + connection] profile = snakemake.input["profile_offwind_" + connection]
with xr.open_dataset(profile) as ds: with xr.open_dataset(profile) as ds:
# if-statement for compatibility with old profiles
if "year" in ds.indexes:
ds = ds.sel(year=ds.year.min(), drop=True)
underwater_fraction = ds["underwater_fraction"].to_pandas() underwater_fraction = ds["underwater_fraction"].to_pandas()
connection_cost = ( connection_cost = (
snakemake.params.length_factor snakemake.params.length_factor