diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e57e453d9..1eea4e2db 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -28,7 +28,7 @@ repos: # Find common spelling mistakes in comments and docstrings - repo: https://github.com/codespell-project/codespell - rev: v2.2.6 + rev: v2.3.0 hooks: - id: codespell args: ['--ignore-regex="(\b[A-Z]+\b)"', '--ignore-words-list=fom,appartment,bage,ore,setis,tabacco,berfore,fo,FO'] @@ -72,7 +72,7 @@ repos: # Format Snakemake rule / workflow files - repo: https://github.com/snakemake/snakefmt - rev: v0.10.1 + rev: v0.10.2 hooks: - id: snakefmt diff --git a/Snakefile b/Snakefile index b04a3e382..93514a25f 100644 --- a/Snakefile +++ b/Snakefile @@ -461,7 +461,7 @@ rule build_renewable_profiles: powerplants="resources/" + RDIR + "powerplants.csv", regions=lambda w: ( "resources/" + RDIR + "bus_regions/regions_onshore.geojson" - if w.technology in ("onwind", "solar", "hydro") + if w.technology in ("onwind", "solar", "hydro", "csp") else "resources/" + RDIR + "bus_regions/regions_offshore.geojson" ), cutout=lambda w: "cutouts/" diff --git a/config.default.yaml b/config.default.yaml index b4182a5d5..2a09a18eb 100644 --- a/config.default.yaml +++ b/config.default.yaml @@ -155,7 +155,7 @@ electricity: custom_powerplants: false # "false" use only powerplantmatching (ppm) data, "merge" combines ppm and custom powerplants, "replace" use only custom powerplants conventional_carriers: [nuclear, oil, OCGT, CCGT, coal, lignite, geothermal, biomass] - renewable_carriers: [solar, onwind, offwind-ac, offwind-dc, hydro] + renewable_carriers: [solar, csp, onwind, offwind-ac, offwind-dc, hydro] estimate_renewable_capacities: stats: "irena" # False, = greenfield expansion, 'irena' uses IRENA stats to add expansion limits @@ -313,6 +313,26 @@ renewable: method: hydro_capacities # 'hydro_capacities' to rescale country hydro production by using hydro_capacities, 'eia' to rescale by eia data, false for no rescaling year: 2013 # (optional) year of statistics used to rescale the runoff time series. When not provided, the weather year of the snapshots is used multiplier: 1.1 # multiplier applied after the normalization of the hydro production; default 1.0 + csp: + cutout: cutout-2013-era5 + resource: + method: csp + installation: SAM_solar_tower + capacity_per_sqkm: 2.392 # From 1.7 to 4.6 addresses issue #361 + # Determined by comparing uncorrected area-weighted full-load hours to those + # published in Supplementary Data to + # Pietzcker, Robert Carl, et al. "Using the sun to decarbonize the power + # sector: The economic potential of photovoltaics and concentrating solar + # power." Applied Energy 135 (2014): 704-720. + copernicus: + grid_codes: [20, 30, 40, 60, 90] + distancing_codes: [50] + distance_to_codes: 3000 + natura: true + potential: simple # or conservative + clip_p_max_pu: 1.e-2 + extendable: true + csp_model: advanced # simple or advanced # TODO: Needs to be adjusted for Africa. # Costs Configuration (Do not remove, needed for Sphynx documentation). @@ -467,7 +487,8 @@ plotting: "HVDC links": "#8a1caf" "DC-DC": "#8a1caf" "DC link": "#8a1caf" - "load": "#FF0000" + "load": "#ff0000" + "csp": "#fdd404" nice_names: OCGT: "Open-Cycle Gas" CCGT: "Combined-Cycle Gas" diff --git a/config.tutorial.yaml b/config.tutorial.yaml index 8b8d6705e..7919ed175 100644 --- a/config.tutorial.yaml +++ b/config.tutorial.yaml @@ -169,7 +169,7 @@ electricity: custom_powerplants: false # "false" use only powerplantmatching (ppm) data, "merge" combines ppm and custom powerplants, "replace" use only custom powerplants conventional_carriers: [nuclear, oil, OCGT, CCGT, coal, lignite, geothermal, biomass] - renewable_carriers: [solar, onwind, offwind-ac, offwind-dc, hydro] + renewable_carriers: [solar, csp, onwind, offwind-ac, offwind-dc, hydro] estimate_renewable_capacities: stats: "irena" # False, = greenfield expansion, 'irena' uses IRENA stats to add expansion limits @@ -310,6 +310,26 @@ renewable: method: hydro_capacities # 'hydro_capacities' to rescale country hydro production by using hydro_capacities, 'eia' to rescale by eia data, false for no rescaling year: 2013 # (optional) year of statistics used to rescale the runoff time series. When not provided, the cutout weather year is used multiplier: 1.1 # multiplier applied after the normalization of the hydro production; default 1.0 + csp: + cutout: cutout-2013-era5-tutorial + resource: + method: csp + installation: SAM_solar_tower + capacity_per_sqkm: 2.392 # From 1.7 to 4.6 addresses issue #361 + # Determined by comparing uncorrected area-weighted full-load hours to those + # published in Supplementary Data to + # Pietzcker, Robert Carl, et al. "Using the sun to decarbonize the power + # sector: The economic potential of photovoltaics and concentrating solar + # power." Applied Energy 135 (2014): 704-720. + copernicus: + grid_codes: [20, 30, 40, 60, 90] + distancing_codes: [50] + distance_to_codes: 3000 + natura: true + potential: simple # or conservative + clip_p_max_pu: 1.e-2 + extendable: true + csp_model: advanced # simple or advanced # TODO: Needs to be adjusted for Africa costs: @@ -453,7 +473,8 @@ plotting: "HVDC links": "#8a1caf" "DC-DC": "#8a1caf" "DC link": "#8a1caf" - "load": "#FF0000" + "load": "#ff0000" + "csp": "#fdd404" nice_names: OCGT: "Open-Cycle Gas" CCGT: "Combined-Cycle Gas" diff --git a/data/costs.csv b/data/costs.csv index a5c93ec09..178bbcf88 100644 --- a/data/costs.csv +++ b/data/costs.csv @@ -193,3 +193,12 @@ HVDC submarine,2030,FOM,2,%/year,Hagspiel HVDC inverter pair,2030,investment,150000,EUR/MW,Hagspiel HVDC inverter pair,2030,lifetime,40,years,Hagspiel HVDC inverter pair,2030,FOM,2,%/year,Hagspiel +csp-tower,2030,FOM,1.1,%/year,ATB CSP data (https://atb.nrel.gov/electricity/2021/concentrating_solar_power) +csp-tower,2030,investment,108.37,"EUR/kW_th,dp",ATB CSP data (https://atb.nrel.gov/electricity/2021/concentrating_solar_power) and NREL SAM v2021.12.2 (https://sam.nrel.gov/). +csp-tower,2030,lifetime,30.0,years,ATB CSP data (https://atb.nrel.gov/electricity/2021/concentrating_solar_power) +csp-tower TES,2030,FOM,1.1,%/year,see solar-tower. +csp-tower TES,2030,investment,14.52,EUR/kWh_th,ATB CSP data (https://atb.nrel.gov/electricity/2021/concentrating_solar_power) and NREL SAM v2021.12.2 (https://sam.nrel.gov/). +csp-tower TES,2030,lifetime,30.0,years,see solar-tower. +csp-tower power block,2030,FOM,1.1,%/year,see solar-tower. +csp-tower power block,2030,investment,759.17,EUR/kW_e,ATB CSP data (https://atb.nrel.gov/electricity/2021/concentrating_solar_power) and NREL SAM v2021.12.2 (https://sam.nrel.gov/). +csp-tower power block,2030,lifetime,30.0,years,see solar-tower. diff --git a/doc/configtables/csp.csv b/doc/configtables/csp.csv new file mode 100644 index 000000000..4da3bd79f --- /dev/null +++ b/doc/configtables/csp.csv @@ -0,0 +1,15 @@ +,Unit,Values,Description +cutout,--,Should be a file name 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 is stored that is specified at ``atlite/cutouts`` configuration. Both ``sarah`` and ``era5`` work. +resource,,, +-- method,--,Must be 'csp', +-- installation,--,"Should be 'SAM_solar_tower' as defined in `atlite `__",Specifies the csp technology and its characteristic attributes. +capacity_per_sqkm,:math:`MW/km^2`,float,Allowable density of csp tower placement. Value relates to socio-technical acceptable density. +copernicus,,, +-- grid_codes,--,Any subset of the `Copernicus Land Cover code list `_,Specifies areas based on CLC which generally eligible for csp tower placement. +-- distance,m,"int","(Optional) Distance to reserve as uneligible area around 'distance_grid_codes' for the renewable technology." +-- distance_grid_codes,--,"(Optional with 'distance') Any subset of the `Copernicus Land Cover code list `_","Specifies from which a distance of 'distance' metres is unavailable as a buffer area." +natura,bool,"{true, false}",Switch to exclude `Natura 2000 `_ natural protection areas. Area is excluded if ``true``. +potential,--,"One of {'simple', 'conservative'}",Method to compute the maximal installable potential for a node; confer :ref:`renewableprofiles` +clip_p_max_pu,p.u.,float,To avoid too small values in the renewables` per-unit availability time series values below this threshold are set to zero. +extendable, bool, "{True, False}", "True: In nodes where there is no csp generation, adds a zero-capacity csp generator so that csp is considered for capacity expansion. It is done in the ``add_electricity`` rule." +csp_model,--, One of {'advanced' or 'simple'}, Specifies the CSP model to be used. The advanced model attach stores and links to the csp buses while the simple has no stores and links. diff --git a/doc/configtables/hydro.csv b/doc/configtables/hydro.csv index b4a563922..f7b58911c 100644 --- a/doc/configtables/hydro.csv +++ b/doc/configtables/hydro.csv @@ -1,5 +1,5 @@ ,Unit,Values,Description -cutout,--,"Must be 'europe-2013-era5'","Specifies the directory where the relevant weather data ist stored." +cutout,--,"Must be 'europe-2013-era5'","Specifies the directory where the relevant weather data is stored." resource,,, -- method,,, "Specifies the Atlite method to calculate renewable potential." -- hydrobasin,,, "Specifies the file location for hydrobasins. They are used to make the runoff calibration, defining a polygon to compute the available water surface using a surface integral." diff --git a/doc/configtables/offwind-ac.csv b/doc/configtables/offwind-ac.csv index 870bf3198..ea03dfb5c 100644 --- a/doc/configtables/offwind-ac.csv +++ b/doc/configtables/offwind-ac.csv @@ -1,5 +1,5 @@ ,Unit,Values,Description -cutout,--,"Should be a file name 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 file name 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 is stored." resource,,, -- method,--,"Must be 'wind'","A superordinate technology type." -- turbine,--,"One of turbine types included in `atlite `_","Specifies the turbine type and its characteristic power curve." diff --git a/doc/configtables/offwind-dc.csv b/doc/configtables/offwind-dc.csv index b406b07f9..2603ff635 100644 --- a/doc/configtables/offwind-dc.csv +++ b/doc/configtables/offwind-dc.csv @@ -1,5 +1,5 @@ ,Unit,Values,Description -cutout,--,"Should be a file name 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 file name 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 is stored." resource,,, -- method,--,"Must be 'wind'","A superordinate technology type." -- turbine,--,"One of turbine types included in `atlite `__","Specifies the turbine type and its characteristic power curve." diff --git a/doc/configtables/onwind.csv b/doc/configtables/onwind.csv index f0d847d16..1db97a7b8 100644 --- a/doc/configtables/onwind.csv +++ b/doc/configtables/onwind.csv @@ -1,5 +1,5 @@ ,Unit,Values,Description -cutout,--,"Should be a file name 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 file name 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 is stored." resource,,, -- method,--,"Must be 'wind'","A superordinate technology type." -- turbine,--,"One of turbine types included in `atlite `__","Specifies the turbine type and its characteristic power curve." diff --git a/doc/configtables/solar.csv b/doc/configtables/solar.csv index e28e7368d..f1eb887eb 100644 --- a/doc/configtables/solar.csv +++ b/doc/configtables/solar.csv @@ -1,5 +1,5 @@ ,Unit,Values,Description -cutout,--,Should be a file name 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 file name 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 is stored that is specified at ``atlite/cutouts`` configuration. Both ``sarah`` and ``era5`` work. resource,,, -- method,--,Must be 'pv',A superordinate technology type. -- panel,--,"One of {'Csi', 'CdTe', 'KANENA'} as defined in `atlite `__",Specifies the solar panel technology and its characteristic attributes. diff --git a/doc/configuration.rst b/doc/configuration.rst index d6a1c6480..1bfb3d30c 100644 --- a/doc/configuration.rst +++ b/doc/configuration.rst @@ -227,7 +227,7 @@ Specifies the minimum voltage magnitude in the base network and the offshore sub ``load_options`` ============================= -Specifies the options to estimate future electricity demand (load). Different years might be considered for weather and the socio-economic pathway (GDP and population growth), to enhance modelling capabilities. +Specifies the options to estimate future electricity demand (load). Different years might be considered for weather and the socioeconomic pathway (GDP and population growth), to enhance modelling capabilities. .. literalinclude:: ../config.default.yaml :language: yaml @@ -402,6 +402,19 @@ Specifies the options to obtain renewable potentials in every cutout. These are :widths: 25,7,22,30 :file: configtables/hydro.csv +``csp`` +--------------- + +.. literalinclude:: ../config.default.yaml + :language: yaml + :start-at: csp: + :end-at: csp_model: + +.. csv-table:: + :header-rows: 1 + :widths: 25,7,22,30 + :file: configtables/csp.csv + .. _costs_cf: ``costs`` diff --git a/doc/release_notes.rst b/doc/release_notes.rst index f7f351314..f7e8232b7 100644 --- a/doc/release_notes.rst +++ b/doc/release_notes.rst @@ -46,6 +46,8 @@ E.g. if a new rule becomes available describe how to use it `snakemake -j1 run_t * Resolve pandas deprecation warning. `PR #1023 `__ +* Create files where the code outputs the value of the objective function. `PR #1033 `__ + PyPSA-Earth 0.3.0 ================= diff --git a/scripts/add_electricity.py b/scripts/add_electricity.py index 7e9307e00..0911588f7 100755 --- a/scripts/add_electricity.py +++ b/scripts/add_electricity.py @@ -127,6 +127,8 @@ def _add_missing_carriers_from_costs(n, costs, carriers): costs.columns.to_series().loc[lambda s: s.str.endswith("_emissions")].values ) suptechs = missing_carriers.str.split("-").str[0] + if "csp" in suptechs: + suptechs = suptechs.str.replace("csp", "csp-tower") emissions = costs.loc[suptechs, emissions_cols].fillna(0.0) emissions.index = missing_carriers n.import_components_from_dataframe(emissions, "Carrier") @@ -357,7 +359,9 @@ def attach_wind_and_solar( ) ) else: - capital_cost = costs.at[tech, "capital_cost"] + capital_cost = costs.at[ + "csp-tower" if tech == "csp" else tech, "capital_cost" + ] if not df.query("carrier == @tech").empty: buses = n.buses.loc[ds.indexes["bus"]] @@ -379,9 +383,13 @@ def attach_wind_and_solar( p_nom_max=ds["p_nom_max"].to_pandas(), p_max_pu=ds["profile"].transpose("time", "bus").to_pandas(), weight=ds["weight"].to_pandas(), - marginal_cost=costs.at[suptech, "marginal_cost"], + marginal_cost=costs.at[ + "csp-tower" if suptech == "csp" else suptech, "marginal_cost" + ], capital_cost=capital_cost, - efficiency=costs.at[suptech, "efficiency"], + efficiency=costs.at[ + "csp-tower" if suptech == "csp" else suptech, "efficiency" + ], ) diff --git a/scripts/add_extra_components.py b/scripts/add_extra_components.py index f1f4034f4..29c57e60c 100644 --- a/scripts/add_extra_components.py +++ b/scripts/add_extra_components.py @@ -184,6 +184,43 @@ def attach_stores(n, costs, config): marginal_cost=costs.at["battery inverter", "marginal_cost"], ) + if ("csp" in config["renewable"].keys()) and ( + config["renewable"]["csp"]["csp_model"] == "advanced" + ): + # add buses for csp + n.madd("Bus", buses_i + " csp", carrier="csp", **bus_sub_dict) + + csp_buses_i = n.buses.index[n.buses.index.str.contains("csp")] + + # change bus of existing csp generators + old_csp_bus_vector = buses_i + " csp" + n.generators.loc[old_csp_bus_vector, "bus"] = csp_buses_i + + # add stores for csp + n.madd( + "Store", + csp_buses_i, + bus=csp_buses_i, + carrier="csp", + e_cyclic=True, + e_nom_extendable=True, + capital_cost=costs.at["csp-tower TES", "capital_cost"], + marginal_cost=costs.at["csp-tower TES", "marginal_cost"], + ) + + # add links for csp + n.madd( + "Link", + csp_buses_i, + bus0=csp_buses_i, + bus1=buses_i, + carrier="csp", + efficiency=costs.at["csp-tower", "efficiency"], + capital_cost=costs.at["csp-tower", "capital_cost"], + p_nom_extendable=True, + marginal_cost=costs.at["csp-tower", "marginal_cost"], + ) + def attach_hydrogen_pipelines(n, costs, config): elec_opts = config["electricity"] diff --git a/scripts/simplify_network.py b/scripts/simplify_network.py index 503819352..30d60e32f 100644 --- a/scripts/simplify_network.py +++ b/scripts/simplify_network.py @@ -319,7 +319,7 @@ def simplify_links( exclude_carriers=[], aggregation_strategies=dict(), ): - ## Complex multi-node links are folded into end-points + # Complex multi-node links are folded into end-points logger.info("Simplifying connected link components") if n.links.empty: diff --git a/scripts/solve_network.py b/scripts/solve_network.py index 5ed5c5260..f83b47478 100755 --- a/scripts/solve_network.py +++ b/scripts/solve_network.py @@ -576,3 +576,5 @@ def solve_network(n, config, opts="", **kwargs): ) n.meta = dict(snakemake.config, **dict(wildcards=dict(snakemake.wildcards))) n.export_to_netcdf(snakemake.output[0]) + logger.info(f"Objective function: {n.objective}") + logger.info(f"Objective constant: {n.objective_constant}") diff --git a/test/config.landlock.yaml b/test/config.landlock.yaml index 4fd4d96e7..c56bdd968 100644 --- a/test/config.landlock.yaml +++ b/test/config.landlock.yaml @@ -9,7 +9,7 @@ retrieve_databundle: # required to be "false" for nice CI test output countries: ["BW"] electricity: - renewable_carriers: [solar, onwind, hydro] + renewable_carriers: [solar, csp, onwind, hydro] build_osm_network: force_ac: true # When true, it forces all components (lines and substation) to be AC-only