diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 7aabf0e6..bad6039f 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -19,7 +19,7 @@ on: - cron: "0 5 * * TUE" env: - DATA_CACHE_NUMBER: 1 + DATA_CACHE_NUMBER: 2 jobs: build: @@ -31,7 +31,7 @@ jobs: os: - ubuntu-latest - macos-latest -# - windows-latest + - windows-latest inhouse: - stable - master diff --git a/.gitignore b/.gitignore index 3336fca7..21062dd3 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,8 @@ # # SPDX-License-Identifier: CC0-1.0 +master + .snakemake* .ipynb_checkpoints __pycache__ @@ -37,18 +39,16 @@ dconf /data/links_p_nom.csv /data/*totals.csv /data/biomass* -/data/bundle-sector/emobility/ -/data/bundle-sector/eea* -/data/bundle-sector/jrc* +/data/bundle/emobility/ +/data/bundle/eea* +/data/bundle/jrc* /data/heating/ -/data/bundle-sector/eurostat* +/data/bundle/eurostat* /data/odyssee/ /data/transport_data.csv -/data/bundle-sector/switzerland* /data/.nfs* -/data/bundle-sector/Industrial_Database.csv -/data/retro/tabula-calculator-calcsetbuilding.csv -/data/bundle-sector/nuts* +/data/retro/* +/data/bundle/nuts* data/gas_network/scigrid-gas/ data/costs_*.csv diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2fb51c54..7acbef9b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -5,7 +5,7 @@ exclude: "^LICENSES" repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.5.0 + rev: v4.6.0 hooks: - id: check-merge-conflict - id: end-of-file-fixer @@ -51,7 +51,7 @@ repos: # Formatting with "black" coding style - repo: https://github.com/psf/black-pre-commit-mirror - rev: 24.3.0 + rev: 24.4.2 hooks: # Format Python files - id: black @@ -67,14 +67,14 @@ repos: # Do YAML formatting (before the linter checks it for misses) - repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks - rev: v2.12.0 + rev: v2.13.0 hooks: - id: pretty-format-yaml args: [--autofix, --indent, "2", --preserve-quotes] # Format Snakemake rule / workflow files - repo: https://github.com/snakemake/snakefmt - rev: v0.10.0 + rev: v0.10.2 hooks: - id: snakefmt @@ -87,6 +87,6 @@ repos: # Check for FSFE REUSE compliance (licensing) - repo: https://github.com/fsfe/reuse-tool - rev: v3.0.1 + rev: v3.0.2 hooks: - id: reuse diff --git a/.reuse/dep5 b/.reuse/dep5 index 27edd808..a40d090a 100644 --- a/.reuse/dep5 +++ b/.reuse/dep5 @@ -30,7 +30,3 @@ License: CC0-1.0 Files: borg-it Copyright: 2017-2024 The PyPSA-Eur Authors License: CC0-1.0 - -Files: graphics/* -Copyright: 2017-2024 The PyPSA-Eur Authors -License: CC-BY-4.0 diff --git a/CITATION.cff b/CITATION.cff index af26fd53..c921a773 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -6,7 +6,7 @@ cff-version: 1.1.0 message: "If you use this package, please cite it in the following way." title: "PyPSA-Eur: An open sector-coupled optimisation model of the European energy system" repository: https://github.com/pypsa/pypsa-eur -version: 0.10.0 +version: 0.11.0 license: MIT authors: - family-names: Brown diff --git a/README.md b/README.md index b4c03574..d5a72b77 100644 --- a/README.md +++ b/README.md @@ -80,7 +80,7 @@ all greenhouse gas emitters except waste management and land use. This diagram gives an overview of the sectors and the links between them: -![sector diagram](graphics/multisector_figure.png) +![sector diagram](doc/img/multisector_figure.png) Each of these sectors is built up on the transmission network nodes from [PyPSA-Eur](https://github.com/PyPSA/pypsa-eur): diff --git a/Snakefile b/Snakefile index ba93a869..412d520d 100644 --- a/Snakefile +++ b/Snakefile @@ -8,7 +8,7 @@ from os.path import normpath, exists from shutil import copyfile, move, rmtree from snakemake.utils import min_version -min_version("8.5") +min_version("8.11") from scripts._helpers import path_provider, copy_default_files, get_scenarios, get_rdir @@ -24,9 +24,11 @@ run = config["run"] scenarios = get_scenarios(run) RDIR = get_rdir(run) -logs = path_provider("logs/", RDIR, run["shared_resources"]) -benchmarks = path_provider("benchmarks/", RDIR, run["shared_resources"]) -resources = path_provider("resources/", RDIR, run["shared_resources"]) +shared_resources = run["shared_resources"]["policy"] +exclude_from_shared = run["shared_resources"]["exclude"] +logs = path_provider("logs/", RDIR, shared_resources, exclude_from_shared) +benchmarks = path_provider("benchmarks/", RDIR, shared_resources, exclude_from_shared) +resources = path_provider("resources/", RDIR, shared_resources, exclude_from_shared) CDIR = "" if run["shared_cutouts"] else RDIR RESULTS = "results/" + RDIR diff --git a/config/config.default.yaml b/config/config.default.yaml index cf6e0bc3..a0b44cb4 100644 --- a/config/config.default.yaml +++ b/config/config.default.yaml @@ -3,7 +3,7 @@ # SPDX-License-Identifier: CC0-1.0 # docs in https://pypsa-eur.readthedocs.io/en/latest/configuration.html#top-level-configuration -version: 0.10.0 +version: 0.11.0 tutorial: false logging: @@ -20,12 +20,15 @@ remote: # docs in https://pypsa-eur.readthedocs.io/en/latest/configuration.html#run run: + prefix: "" name: "" scenarios: enable: false file: config/scenarios.yaml disable_progressbar: false - shared_resources: false + shared_resources: + policy: false + exclude: [] shared_cutouts: true # docs in https://pypsa-eur.readthedocs.io/en/latest/configuration.html#foresight @@ -37,17 +40,15 @@ scenario: simpl: - '' ll: - - v1.5 + - vopt clusters: - 37 - 128 - 256 - - 512 - - 1024 opts: - '' sector_opts: - - Co2L0-3H-T-H-B-I-A-dist1 + - '' planning_horizons: # - 2020 # - 2030 @@ -68,13 +69,9 @@ enable: retrieve: auto prepare_links_p_nom: false retrieve_databundle: true - retrieve_sector_databundle: true retrieve_cost_data: true build_cutout: false - retrieve_irena: false retrieve_cutout: true - build_natura_raster: false - retrieve_natura_raster: true custom_busmap: false drop_leap_day: true @@ -97,7 +94,6 @@ electricity: co2limit_enable: false co2limit: 7.75e+7 co2base: 1.487e+9 - agg_p_nom_limits: data/agg_p_nom_minmax.csv operational_reserve: activate: false @@ -110,17 +106,17 @@ electricity: H2: 168 extendable_carriers: - Generator: [solar, onwind, offwind-ac, offwind-dc, OCGT] + Generator: [solar, solar-hsat, onwind, offwind-ac, offwind-dc, offwind-float, OCGT, CCGT] StorageUnit: [] # battery, H2 Store: [battery, H2] Link: [] # H2 pipeline powerplants_filter: (DateOut >= 2023 or DateOut != DateOut) and not (Country == 'Germany' and Fueltype == 'Nuclear') custom_powerplants: false - everywhere_powerplants: [nuclear, oil, OCGT, CCGT, coal, lignite, geothermal, biomass] + everywhere_powerplants: [] conventional_carriers: [nuclear, oil, OCGT, CCGT, coal, lignite, geothermal, biomass] - renewable_carriers: [solar, onwind, offwind-ac, offwind-dc, hydro] + renewable_carriers: [solar, solar-hsat, onwind, offwind-ac, offwind-dc, offwind-float, hydro] estimate_renewable_capacities: enable: true @@ -128,7 +124,7 @@ electricity: year: 2020 expansion_limit: false technology_mapping: - Offshore: [offwind-ac, offwind-dc] + Offshore: [offwind-ac, offwind-dc, offwind-float] Onshore: [onwind] PV: [solar] @@ -196,7 +192,7 @@ renewable: luisa: false # [0, 5230] natura: true ship_threshold: 400 - max_depth: 50 + max_depth: 60 max_shore_distance: 30000 excluder_resolution: 200 clip_p_max_pu: 1.e-2 @@ -212,10 +208,28 @@ renewable: luisa: false # [0, 5230] natura: true ship_threshold: 400 - max_depth: 50 + max_depth: 60 min_shore_distance: 30000 excluder_resolution: 200 clip_p_max_pu: 1.e-2 + offwind-float: + cutout: europe-2013-era5 + resource: + method: wind + turbine: NREL_ReferenceTurbine_5MW_offshore + # ScholzPhd Tab 4.3.1: 10MW/km^2 + capacity_per_sqkm: 2 + correction_factor: 0.8855 + # proxy for wake losses + # from 10.1016/j.energy.2018.08.153 + # until done more rigorously in #153 + corine: [44, 255] + natura: true + ship_threshold: 400 + excluder_resolution: 200 + min_depth: 60 + max_depth: 1000 + clip_p_max_pu: 1.e-2 solar: cutout: europe-2013-sarah resource: @@ -231,6 +245,21 @@ renewable: natura: true excluder_resolution: 100 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: cutout: europe-2013-era5 carriers: [ror, PHS, hydro] @@ -263,7 +292,7 @@ lines: max_extension: 20000 #MW length_factor: 1.25 reconnect_crimea: true - under_construction: 'zero' # 'zero': set capacity to zero, 'remove': remove, 'keep': with full capacity + under_construction: 'keep' # 'zero': set capacity to zero, 'remove': remove, 'keep': with full capacity dynamic_line_rating: activate: false cutout: europe-2013-era5 @@ -309,6 +338,8 @@ pypsa_eur: - onwind - offwind-ac - offwind-dc + - offwind-float + - solar-hsat - solar - ror - nuclear @@ -359,8 +390,8 @@ solar_thermal: # docs in https://pypsa-eur.readthedocs.io/en/latest/configuration.html#existing-capacities existing_capacities: - grouping_years_power: [1960, 1965, 1970, 1975, 1980, 1985, 1990, 1995, 2000, 2005, 2010, 2015, 2020, 2025, 2030] - grouping_years_heat: [1980, 1985, 1990, 1995, 2000, 2005, 2010, 2015, 2019] # these should not extend 2020 + grouping_years_power: [1895, 1920, 1950, 1955, 1960, 1965, 1970, 1975, 1980, 1985, 1990, 1995, 2000, 2005, 2010, 2015, 2020, 2025, 2030] + grouping_years_heat: [1980, 1985, 1990, 1995, 2000, 2005, 2010, 2015, 2020] # heat grouping years >= baseyear will be ignored threshold_capacity: 10 default_heating_lifetime: 20 conventional_carriers: @@ -401,7 +432,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 @@ -430,8 +460,9 @@ sector: 2040: 0.3 2045: 0.15 2050: 0 - transport_fuel_cell_efficiency: 0.5 - transport_internal_combustion_efficiency: 0.3 + 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 @@ -515,7 +546,11 @@ sector: regional_coal_demand: false regional_co2_sequestration_potential: enable: false - attribute: 'conservative estimate Mt' + attribute: + - conservative estimate Mt + - conservative estimate GAS Mt + - conservative estimate OIL Mt + - conservative estimate aquifer Mt include_onshore: false min_size: 3 max_size: 25 @@ -533,7 +568,7 @@ sector: - nearshore # within 50 km of sea # - offshore ammonia: false - min_part_load_fischer_tropsch: 0.7 + min_part_load_fischer_tropsch: 0.5 min_part_load_methanolisation: 0.3 min_part_load_methanation: 0.3 use_fischer_tropsch_waste_heat: true @@ -556,6 +591,8 @@ sector: gas pipeline: efficiency_per_1000km: 1 #0.977 compression_per_1000km: 0.01 + electricity distribution grid: + efficiency_static: 0.97 H2_network: true gas_network: false H2_retrofit: false @@ -656,6 +693,9 @@ industry: 2040: 0.12 2045: 0.16 2050: 0.20 + HVC_environment_sequestration_fraction: 0. + waste_to_energy: false + waste_to_energy_cc: false sector_ratios_fraction_future: 2020: 0.0 2025: 0.1 @@ -674,6 +714,7 @@ industry: methanol_production_today: 1.5 MWh_elec_per_tMeOH: 0.167 MWh_CH4_per_tMeOH: 10.25 + MWh_MeOH_per_tMeOH: 5.528 hotmaps_locate_missing: false reference_year: 2015 @@ -681,8 +722,7 @@ industry: # docs in https://pypsa-eur.readthedocs.io/en/latest/configuration.html#costs costs: year: 2030 - version: v0.8.1 - rooftop_share: 0.14 # based on the potentials, assuming (0.1 kW/m2 and 10 m2/person) + version: v0.9.0 social_discountrate: 0.02 fill_values: FOM: 0 @@ -754,11 +794,28 @@ solving: # io_api: "direct" # Increases performance but only supported for the highs and gurobi solvers # options that go into the optimize function track_iterations: false - min_iterations: 4 - max_iterations: 6 + min_iterations: 2 + max_iterations: 3 transmission_losses: 2 linearized_unit_commitment: true horizon: 365 + post_discretization: + enable: false + line_unit_size: 1700 + line_threshold: 0.3 + link_unit_size: + DC: 2000 + H2 pipeline: 1200 + gas pipeline: 1500 + link_threshold: + DC: 0.3 + H2 pipeline: 0.3 + gas pipeline: 0.3 + + agg_p_nom_limits: + agg_offwind: false + include_existing: false + file: data/agg_p_nom_minmax.csv constraints: CCL: false @@ -772,7 +829,7 @@ solving: solver_options: highs-default: - # refer to https://ergo-code.github.io/HiGHS/options/definitions.html#solver + # refer to https://ergo-code.github.io/HiGHS/dev/options/definitions/ threads: 4 solver: "ipm" run_crossover: "off" @@ -793,7 +850,6 @@ solving: PreDual: 0 GURO_PAR_BARDENSETHRESH: 200 gurobi-numeric-focus: - name: gurobi NumericFocus: 3 # Favour numeric stability over speed method: 2 # barrier crossover: 0 # do not use crossover @@ -805,7 +861,6 @@ solving: threads: 8 Seed: 123 gurobi-fallback: # Use gurobi defaults - name: gurobi crossover: 0 method: 2 # barrier BarHomogeneous: 1 # Use homogeneous barrier if standard does not converge @@ -859,6 +914,7 @@ plotting: CCGT: "Combined-Cycle Gas" offwind-ac: "Offshore Wind (AC)" offwind-dc: "Offshore Wind (DC)" + offwind-float: "Offshore Wind (Floating)" onwind: "Onshore Wind" solar: "Solar" PHS: "Pumped Hydro Storage" @@ -883,6 +939,9 @@ plotting: offwind-dc: "#74c6f2" offshore wind (DC): "#74c6f2" offshore wind dc: "#74c6f2" + offwind-float: "#b5e2fa" + offshore wind (Float): "#b5e2fa" + offshore wind float: "#b5e2fa" # water hydro: '#298c81' hydro reservoir: '#298c81' @@ -894,6 +953,7 @@ plotting: # solar solar: "#f9d002" solar PV: "#f9d002" + solar-hsat: "#fdb915" solar thermal: '#ffbf2b' residential rural solar thermal: '#f1c069' services rural solar thermal: '#eabf61' @@ -995,6 +1055,7 @@ plotting: BEV charger: '#baf238' V2G: '#e5ffa8' land transport EV: '#baf238' + land transport demand: '#38baf2' Li ion: '#baf238' # hot water storage water tanks: '#e69487' @@ -1099,6 +1160,7 @@ plotting: methanolisation: '#83d6d5' methanol: '#468c8b' shipping methanol: '#468c8b' + industry methanol: '#468c8b' # co2 CC: '#f29dae' CCS: '#f29dae' @@ -1140,3 +1202,6 @@ plotting: DC-DC: "#8a1caf" DC link: "#8a1caf" load: "#dd2e23" + waste CHP: '#e3d37d' + waste CHP CC: '#e3d3ff' + HVC to air: 'k' diff --git a/config/config.entsoe-all.yaml b/config/examples/config.entsoe-all.yaml similarity index 92% rename from config/config.entsoe-all.yaml rename to config/examples/config.entsoe-all.yaml index 40e3c0a5..4e7edd03 100644 --- a/config/config.entsoe-all.yaml +++ b/config/examples/config.entsoe-all.yaml @@ -5,7 +5,8 @@ run: name: "entsoe-all" disable_progressbar: true - shared_resources: false + shared_resources: + policy: false shared_cutouts: true scenario: @@ -38,6 +39,5 @@ lines: enable: retrieve: true retrieve_databundle: true - retrieve_sector_databundle: false retrieve_cost_data: true retrieve_cutout: true diff --git a/config/config.perfect.yaml b/config/examples/config.perfect.yaml similarity index 100% rename from config/config.perfect.yaml rename to config/examples/config.perfect.yaml diff --git a/config/config.validation.yaml b/config/examples/config.validation.yaml similarity index 100% rename from config/config.validation.yaml rename to config/examples/config.validation.yaml diff --git a/config/test/config.electricity.yaml b/config/test/config.electricity.yaml index 57964415..38fa31ab 100644 --- a/config/test/config.electricity.yaml +++ b/config/test/config.electricity.yaml @@ -8,14 +8,15 @@ tutorial: true run: name: "test-elec" # use this to keep track of runs with different settings disable_progressbar: true - shared_resources: "test" + shared_resources: + policy: "test" shared_cutouts: true scenario: clusters: - 5 opts: - - Co2L-24h + - '' countries: ['BE'] @@ -24,6 +25,7 @@ snapshots: end: "2013-03-08" electricity: + co2limit_enable: true co2limit: 100.e+6 extendable_carriers: @@ -32,7 +34,7 @@ electricity: Store: [H2] Link: [H2 pipeline] - renewable_carriers: [solar, onwind, offwind-ac, offwind-dc] + renewable_carriers: [solar, solar-hsat, onwind, offwind-ac, offwind-dc, offwind-float] atlite: @@ -53,12 +55,20 @@ renewable: offwind-dc: cutout: be-03-2013-era5 max_depth: false + offwind-float: + cutout: be-03-2013-era5 + max_depth: false + min_depth: false solar: cutout: be-03-2013-era5 + solar-hsat: + cutout: be-03-2013-era5 clustering: exclude_carriers: ["OCGT", "offwind-ac", "coal"] + temporal: + resolution_elec: 24h lines: dynamic_line_rating: diff --git a/config/test/config.myopic.yaml b/config/test/config.myopic.yaml index 5abae36d..0ede9aa7 100644 --- a/config/test/config.myopic.yaml +++ b/config/test/config.myopic.yaml @@ -7,7 +7,8 @@ tutorial: true run: name: "test-sector-myopic" disable_progressbar: true - shared_resources: "test" + shared_resources: + policy: "test" shared_cutouts: true foresight: myopic @@ -18,7 +19,7 @@ scenario: clusters: - 5 sector_opts: - - 24h-T-H-B-I-A-dist1 + - '' planning_horizons: - 2030 - 2040 @@ -34,7 +35,6 @@ sector: central_heat_vent: true electricity: - co2limit: 100.e+6 extendable_carriers: Generator: [OCGT] @@ -42,7 +42,10 @@ electricity: Store: [H2] Link: [H2 pipeline] - renewable_carriers: [solar, onwind, offwind-ac, offwind-dc] + renewable_carriers: [solar, solar-hsat, onwind, offwind-ac, offwind-dc, offwind-float] + + estimate_renewable_capacities: + enable: false atlite: default_cutout: be-03-2013-era5 @@ -62,8 +65,18 @@ renewable: offwind-dc: cutout: be-03-2013-era5 max_depth: false + offwind-float: + cutout: be-03-2013-era5 + max_depth: false + min_depth: false solar: cutout: be-03-2013-era5 + solar-hsat: + cutout: be-03-2013-era5 + +clustering: + temporal: + resolution_sector: 24h industry: St_primary_fraction: diff --git a/config/test/config.overnight.yaml b/config/test/config.overnight.yaml index 7fb53e42..92379ae2 100644 --- a/config/test/config.overnight.yaml +++ b/config/test/config.overnight.yaml @@ -7,7 +7,8 @@ tutorial: true run: name: "test-sector-overnight" disable_progressbar: true - shared_resources: "test" + shared_resources: + policy: "test" shared_cutouts: true @@ -17,7 +18,7 @@ scenario: clusters: - 5 sector_opts: - - CO2L0-24h-T-H-B-I-A-dist1 + - '' planning_horizons: - 2030 @@ -28,7 +29,6 @@ snapshots: end: "2013-03-08" electricity: - co2limit: 100.e+6 extendable_carriers: Generator: [OCGT] @@ -36,7 +36,7 @@ electricity: Store: [H2] Link: [H2 pipeline] - renewable_carriers: [solar, onwind, offwind-ac, offwind-dc] + renewable_carriers: [solar, solar-hsat, onwind, offwind-ac, offwind-dc, offwind-float] atlite: default_cutout: be-03-2013-era5 @@ -56,13 +56,28 @@ renewable: offwind-dc: cutout: be-03-2013-era5 max_depth: false + offwind-float: + cutout: be-03-2013-era5 + max_depth: false + min_depth: false solar: cutout: be-03-2013-era5 + solar-hsat: + cutout: be-03-2013-era5 + +clustering: + temporal: + resolution_sector: 24h sector: gas_network: true H2_retrofit: true +industry: + HVC_environment_sequestration_fraction: 0.5 + waste_to_energy: true + waste_to_energy_cc: true + solving: solver: name: glpk diff --git a/config/test/config.perfect.yaml b/config/test/config.perfect.yaml index 5d77c9c5..781b3fd4 100644 --- a/config/test/config.perfect.yaml +++ b/config/test/config.perfect.yaml @@ -7,7 +7,8 @@ tutorial: true run: name: "test-sector-perfect" disable_progressbar: true - shared_resources: "test" + shared_resources: + policy: "test" shared_cutouts: true foresight: perfect @@ -18,7 +19,7 @@ scenario: clusters: - 5 sector_opts: - - 8760h-T-H-B-I-A-dist1 + - '' planning_horizons: - 2030 - 2040 @@ -31,7 +32,6 @@ snapshots: end: "2013-03-08" electricity: - co2limit: 100.e+6 extendable_carriers: Generator: [OCGT] @@ -39,7 +39,7 @@ electricity: Store: [H2] Link: [H2 pipeline] - renewable_carriers: [solar, onwind, offwind-ac, offwind-dc] + renewable_carriers: [solar, solar-hsat, onwind, offwind-ac, offwind-dc, offwind-float] sector: min_part_load_fischer_tropsch: 0 @@ -63,8 +63,18 @@ renewable: offwind-dc: cutout: be-03-2013-era5 max_depth: false + offwind-float: + cutout: be-03-2013-era5 + max_depth: false + min_depth: false solar: cutout: be-03-2013-era5 + solar-hsat: + cutout: be-03-2013-era5 + +clustering: + temporal: + resolution_sector: 8760h industry: St_primary_fraction: diff --git a/config/test/config.scenarios.yaml b/config/test/config.scenarios.yaml index 8ecbb91b..a9a826fd 100644 --- a/config/test/config.scenarios.yaml +++ b/config/test/config.scenarios.yaml @@ -12,14 +12,15 @@ run: enable: true file: "config/test/scenarios.yaml" disable_progressbar: true - shared_resources: base + shared_resources: + policy: base shared_cutouts: true scenario: clusters: - 5 opts: - - Co2L-24H + - '' countries: ['BE'] diff --git a/data/agg_p_nom_minmax.csv b/data/agg_p_nom_minmax.csv index 111215bc..a3a10dc2 100644 --- a/data/agg_p_nom_minmax.csv +++ b/data/agg_p_nom_minmax.csv @@ -1,31 +1,33 @@ -country,carrier,min,max -DE,onwind,0.1, -DE,offwind-ac,0.1, -DE,offwind-dc,0.1, -DE,solar,0.2, -LU,onwind,, -LU,solar,, -NL,onwind,, -NL,offwind-ac,, -NL,offwind-dc,, -NL,solar,, -GB,onwind,, -GB,offwind-ac,, -GB,offwind-dc,, -GB,solar,, -IE,onwind,, -IE,offwind-ac,, -IE,offwind-dc,, -IE,solar,, -FR,onwind,, -FR,offwind-ac,, -FR,offwind-dc,, -FR,solar,, -DK,onwind,, -DK,offwind-ac,, -DK,offwind-dc,, -DK,solar,, -BE,onwind,, -BE,offwind-ac,, -BE,offwind-dc,, -BE,solar,, +,,2030,2030,2040,2040,2050,2050 +,,min,max,min,max,min,max +country,carrier,,,,,, +DE,onwind,0.1,,0.1,,0.1, +DE,offwind-ac,0.1,,0.1,,0.1, +DE,offwind-dc,0.1,,0.1,,0.1, +DE,solar,0.2,,0.2,,0.2, +LU,onwind,,,,,, +LU,solar,,,,,, +NL,onwind,,,,,, +NL,offwind-ac,,,,,, +NL,offwind-dc,,,,,, +NL,solar,,,,,, +GB,onwind,,,,,, +GB,offwind-ac,,,,,, +GB,offwind-dc,,,,,, +GB,solar,,,,,, +IE,onwind,,,,,, +IE,offwind-ac,,,,,, +IE,offwind-dc,,,,,, +IE,solar,,,,,, +FR,onwind,,,,,, +FR,offwind-ac,,,,,, +FR,offwind-dc,,,,,, +FR,solar,,,,,, +DK,onwind,,,,,, +DK,offwind-ac,,,,,, +DK,offwind-dc,,,,,, +DK,solar,,,,,, +BE,onwind,,,,,, +BE,offwind-ac,,,,,, +BE,offwind-dc,,,,,, +BE,solar,,,,,, diff --git a/data/ch_cantons.csv b/data/ch_cantons.csv new file mode 100644 index 00000000..22711274 --- /dev/null +++ b/data/ch_cantons.csv @@ -0,0 +1,27 @@ +Canton,HASC,NUTS +Aargau,CH.AG,CH033 +Appenzell Inner Rhodes,CH.AI,CH054 +Appenzell Outer Rhodes,CH.AR,CH053 +Basel-Landschaft,CH.BL,CH032 +Basel-Stadt,CH.BS,CH031 +Bern,CH.BE,CH021 +Fribourg,CH.FR,CH022 +Geneva,CH.GE,CH013 +Glarus,CH.GL,CH051 +Graubünden,CH.GR,CH056 +Jura,CH.JU,CH025 +Lucerne,CH.LU,CH061 +Neuchâtel,CH.NE,CH024 +Nidwalden,CH.NW,CH065 +Obwalden,CH.OW,CH064 +Sankt Gallen,CH.SG,CH055 +Schaffhausen,CH.SH,CH052 +Schwyz,CH.SZ,CH063 +Solothurn,CH.SO,CH023 +Thurgau,CH.TG,CH057 +Ticino,CH.TI,CH07 +Uri,CH.UR,CH062 +Valais,CH.VS,CH012 +Vaud,CH.VD,CH011 +Zug,CH.ZG,CH066 +Zurich,CH.ZH,CH04 diff --git a/data/existing_infrastructure/offwind_capacity_IRENA.csv b/data/existing_infrastructure/offwind_capacity_IRENA.csv deleted file mode 100644 index d2a3f0f1..00000000 --- a/data/existing_infrastructure/offwind_capacity_IRENA.csv +++ /dev/null @@ -1,34 +0,0 @@ -Country/area,2000,2001,2002,2003,2004,2005,2006,2007,2008,2009,2010,2011,2012,2013,2014,2015,2016,2017,2018,2019,2020,2021,2022 -Albania,,,,,,,,,,,,,,,,,,,,,,, -Austria,,,,,,,,,,,,,,,,,,,,,,, -Belgium,,,,,,,,,,31.5,196.5,196.5,381.0,707.7,707.7,712.0,712.2,877.2,1185.9,1555.5,2261.8,2261.8,2261.8 -Bosnia Herzg,,,,,,,,,,,,,,,,,,,,,,, -Bulgaria,,,,,,,,,,,,,,,,,,,,,,, -Croatia,,,,,,,,,,,,,,,,,,,,,,, -Czechia,,,,,,,,,,,,,,,,,,,,,,, -Denmark,49.95,49.95,213.95,423.35,423.35,423.35,423.35,423.35,423.35,660.85,867.85,871.45,921.85,1271.05,1271.05,1271.05,1271.05,1263.8,1700.8,1700.8,1700.8,2305.6,2305.6 -Estonia,,,,,,,,,,,,,,,,,,,,,,, -Finland,,,,,,,,,24.0,24.0,26.3,26.3,26.3,26.3,26.3,32.0,32.0,72.7,72.7,73.0,73.0,73.0,73.0 -France,,,,,,,,,,,,,,,,,,2.0,2.0,2.0,2.0,2.0,482.0 -Germany,,,,,,,,,,35.0,80.0,188.0,268.0,508.0,994.0,3283.0,4132.0,5406.0,6393.0,7555.0,7787.0,7787.0,8129.0 -Greece,,,,,,,,,,,,,,,,,,,,,,, -Hungary,,,,,,,,,,,,,,,,,,,,,,, -Ireland,,,,,25.2,25.2,25.2,25.2,25.2,25.2,25.2,25.2,25.2,25.2,25.2,25.2,25.2,25.2,25.2,25.2,25.2,25.2,25.2 -Italy,,,,,,,,,,,,,,,,,,,,,,,30.0 -Latvia,,,,,,,,,,,,,,,,,,,,,,, -Lithuania,,,,,,,,,,,,,,,,,,,,,,, -Luxembourg,,,,,,,,,,,,,,,,,,,,,,, -Montenegro,,,,,,,,,,,,,,,,,,,,,,, -Netherlands,,,,,,,108.0,108.0,228.0,228.0,228.0,228.0,228.0,228.0,228.0,357.0,957.0,957.0,957.0,957.0,2459.5,2459.5,2571.0 -North Macedonia,,,,,,,,,,,,,,,,,,,,,,, -Norway,,,,,,,,,,2.3,2.3,2.3,2.3,2.3,2.3,2.3,2.3,2.3,2.3,2.3,2.3,6.3,66.3 -Poland,,,,,,,,,,,,,,,,,,,,,,, -Portugal,,,,,,,,,,,,1.86,2.0,2.0,2.0,2.0,,,,,25.0,25.0,25.0 -Romania,,,,,,,,,,,,,,,,,,,,,,, -Serbia,,,,,,,,,,,,,,,,,,,,,,, -Slovakia,,,,,,,,,,,,,,,,,,,,,,, -Slovenia,,,,,,,,,,,,,,,,,,,,,,, -Spain,,,,,,,,,,,,,,5.0,5.0,5.0,5.0,5.0,5.0,5.0,5.0,5.0,5.0 -Sweden,13.0,22.0,22.0,22.0,22.0,22.0,22.0,131.0,133.0,163.0,163.0,163.0,163.0,212.0,213.0,213.0,203.0,203.0,203.0,203.0,203.0,193.0,193.0 -Switzerland,,,,,,,,,,,,,,,,,,,,,,, -UK,4.0,4.0,4.0,64.0,124.0,214.0,304.0,394.0,596.2,951.0,1341.0,1838.0,2995.0,3696.0,4501.0,5093.0,5293.0,6988.0,8181.0,9888.0,10383.0,11255.0,13928.0 diff --git a/data/existing_infrastructure/onwind_capacity_IRENA.csv b/data/existing_infrastructure/onwind_capacity_IRENA.csv deleted file mode 100644 index cd5ac19c..00000000 --- a/data/existing_infrastructure/onwind_capacity_IRENA.csv +++ /dev/null @@ -1,34 +0,0 @@ -Country/area,2000,2001,2002,2003,2004,2005,2006,2007,2008,2009,2010,2011,2012,2013,2014,2015,2016,2017,2018,2019,2020,2021,2022 -Albania,,,,,,,,,,,,,,,,,,,,,,, -Austria,50.0,67.0,109.0,322.0,581.0,825.22,968.27,991.16,991.97,1000.99,1015.83,1105.97,1337.15,1674.54,2110.28,2488.73,2730.0,2886.7,3132.71,3224.12,3225.98,3407.81,3735.81 -Belgium,14.0,26.0,31.0,67.0,96.0,167.0,212.0,276.0,324.0,576.5,715.5,872.5,985.9,1061.3,1225.0,1469.3,1621.6,1902.2,2119.0,2308.0,2410.9,2686.6,2989.6 -Bosnia Herzg,,,,,,,,,,,,0.3,0.3,0.3,0.3,0.3,0.3,0.3,51.0,87.0,87.0,135.0,135.0 -Bulgaria,,,,,1.0,8.0,27.0,30.0,114.0,333.0,488.0,541.0,677.0,683.0,699.0,699.0,699.0,698.39,698.92,703.12,702.8,704.38,704.38 -Croatia,,,,,6.0,6.0,17.0,17.0,17.0,70.0,79.0,130.0,180.0,254.0,339.0,418.0,483.0,576.1,586.3,646.3,801.3,986.9,1042.9 -Czechia,2.0,,6.4,10.6,16.5,22.0,43.5,113.8,150.0,193.0,213.0,213.0,258.0,262.0,278.0,281.0,282.0,308.21,316.2,339.41,339.42,339.41,339.41 -Denmark,2340.07,2447.2,2680.58,2696.57,2700.36,2704.49,2712.35,2700.86,2739.52,2821.24,2933.98,3080.53,3240.09,3547.87,3615.35,3805.92,3974.09,4225.15,4421.86,4409.74,4566.23,4715.24,4782.24 -Estonia,,,1.0,3.0,7.0,31.0,31.0,50.0,77.0,104.0,108.0,180.0,266.0,248.0,275.0,300.0,310.0,311.8,310.0,316.0,317.0,315.0,315.0 -Finland,38.0,39.0,43.0,52.0,82.0,82.0,86.0,110.0,119.0,123.0,170.7,172.7,230.7,420.7,600.7,973.0,1533.0,1971.3,1968.3,2211.0,2513.0,3184.0,5541.0 -France,38.0,66.0,138.0,218.0,358.0,690.0,1412.0,2223.0,3403.0,4582.0,5912.0,6758.02,7607.5,8155.96,9201.42,10298.18,11566.56,13497.35,14898.14,16424.85,17512.0,18737.98,20637.98 -Germany,6095.0,8754.0,12001.0,14381.0,16419.0,18248.0,20474.0,22116.0,22794.0,25697.0,26823.0,28524.0,30711.0,32969.0,37620.0,41297.0,45303.0,50174.0,52328.0,53187.0,54414.0,56046.0,58165.0 -Greece,226.0,270.0,287.0,371.0,470.0,491.0,749.0,846.0,1022.0,1171.0,1298.0,1640.0,1753.0,1809.0,1978.0,2091.0,2370.0,2624.0,2877.5,3589.0,4119.25,4649.13,4879.13 -Hungary,,1.0,1.0,3.0,3.0,17.0,33.0,61.0,134.0,203.0,293.0,331.0,325.0,329.0,329.0,329.0,329.0,329.0,329.0,323.0,323.0,324.0,324.0 -Ireland,116.5,122.9,134.8,210.3,311.2,468.1,651.3,715.3,917.1,1226.1,1365.2,1559.4,1679.15,1898.1,2258.05,2425.95,2776.45,3293.95,3648.65,4101.25,4281.5,4313.84,4593.84 -Italy,363.0,664.0,780.0,874.0,1127.0,1635.0,1902.0,2702.0,3525.0,4879.0,5794.0,6918.0,8102.0,8542.0,8683.0,9137.0,9384.0,9736.58,10230.25,10679.46,10870.62,11253.73,11749.73 -Latvia,2.0,2.0,22.0,26.0,26.0,26.0,26.0,26.0,28.0,29.0,30.0,36.0,59.0,65.89,68.92,68.17,69.91,77.11,78.17,78.07,78.07,77.13,136.13 -Lithuania,,,,,1.0,1.0,31.0,47.0,54.0,98.0,133.0,202.0,275.0,279.0,288.0,436.0,509.0,518.0,533.0,534.0,540.0,671.0,814.0 -Luxembourg,14.0,13.9,13.9,20.5,34.9,34.9,34.9,34.9,42.92,42.93,43.73,44.53,58.33,58.33,58.34,63.79,119.69,119.69,122.89,135.79,152.74,136.44,165.44 -Montenegro,,,,,,,,,,,,,,,,,,72.0,72.0,118.0,118.0,118.0,118.0 -Netherlands,447.0,486.0,672.0,905.0,1075.0,1224.0,1453.0,1641.0,1921.0,1994.0,2009.0,2088.0,2205.0,2485.0,2637.0,3033.84,3300.12,3245.0,3436.11,3527.16,4188.38,5309.87,6176.0 -North Macedonia,,,,,,,,,,,,,,,37.0,37.0,37.0,37.0,37.0,37.0,37.0,37.0,37.0 -Norway,13.0,13.0,97.0,97.0,152.0,265.0,284.0,348.0,395.0,420.7,422.7,509.7,702.7,815.7,856.7,864.7,880.7,1204.7,1707.7,2911.7,4027.7,5042.7,5067.7 -Poland,4.0,19.0,32.0,35.0,40.0,121.0,172.0,306.0,526.0,709.0,1108.0,1800.0,2564.0,3429.0,3836.0,4886.0,5747.0,5759.36,5766.08,5837.76,6298.25,6967.34,7987.34 -Portugal,83.0,125.0,190.0,268.0,553.0,1064.0,1681.0,2201.0,2857.0,3326.0,3796.0,4254.35,4409.55,4607.95,4854.56,4934.84,5124.1,5124.1,5172.36,5222.75,5097.26,5402.33,5430.33 -Romania,,,,,,1.0,1.0,3.0,5.0,15.0,389.0,988.0,1822.0,2773.0,3244.0,3130.0,3025.0,3029.8,3032.26,3037.52,3012.53,3014.96,3014.96 -Serbia,,,,,,,,,,,,,0.5,0.5,0.5,10.4,17.0,25.0,227.0,398.0,398.0,398.0,398.0 -Slovakia,,,,3.0,3.0,5.0,5.0,5.0,5.0,3.0,3.0,3.0,3.0,5.0,3.0,3.0,3.0,4.0,3.0,4.0,4.0,4.0,4.0 -Slovenia,,,,,,,,,,,,,2.0,2.0,3.0,3.0,3.0,3.3,3.3,3.3,3.3,3.33,3.33 -Spain,2206.0,3397.0,4891.0,5945.0,8317.0,9918.0,11722.0,14820.0,16555.0,19176.0,20693.0,21529.0,22789.0,22953.0,22920.0,22938.0,22985.0,23119.48,23400.06,25585.08,26814.19,27902.65,29302.84 -Sweden,196.0,273.0,335.0,395.0,453.0,500.0,563.0,692.0,956.0,1312.0,1854.0,2601.0,3443.0,3982.0,4875.0,5606.0,6232.0,6408.0,7097.0,8478.0,9773.0,11923.0,14364.0 -Switzerland,3.0,5.0,5.0,5.0,9.0,12.0,12.0,12.0,14.0,18.0,42.0,46.0,49.0,60.0,60.0,60.0,75.0,75.0,75.0,75.0,87.0,87.0,87.0 -UK,431.0,490.0,531.0,678.0,809.0,1351.0,1651.0,2083.0,2849.8,3468.0,4080.0,4758.0,6035.0,7586.0,8573.0,9212.0,10833.0,12597.0,13425.0,13999.0,14075.0,14492.0,14832.0 diff --git a/data/existing_infrastructure/solar_capacity_IRENA.csv b/data/existing_infrastructure/solar_capacity_IRENA.csv deleted file mode 100644 index 01683f8d..00000000 --- a/data/existing_infrastructure/solar_capacity_IRENA.csv +++ /dev/null @@ -1,34 +0,0 @@ -Country/area,2000,2001,2002,2003,2004,2005,2006,2007,2008,2009,2010,2011,2012,2013,2014,2015,2016,2017,2018,2019,2020,2021,2022 -Albania,,0.1,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.3,0.4,0.56,0.68,0.76,0.87,1.05,1.0,1.0,1.0,14.0,21.0,23.0,28.6 -Austria,5.0,7.0,9.0,23.0,27.0,18.49,19.61,21.42,27.0,45.56,85.27,169.88,333.09,620.78,779.76,931.56,1089.53,1262.01,1447.94,1694.4,2034.74,2773.91,3538.91 -Belgium,,,1.0,1.0,1.0,2.0,2.0,20.0,62.0,386.0,1006.6,1978.6,2646.6,2901.6,3015.0,3131.6,3328.8,3620.6,4000.0,4636.6,5572.8,6012.4,6898.4 -Bosnia Herzg,,,,0.1,0.2,0.3,0.3,0.3,0.3,0.3,0.3,0.3,0.35,1.34,7.17,8.17,14.12,16.0,18.15,22.35,34.89,56.51,107.47 -Bulgaria,,,,,,,,0.03,0.1,2.0,25.0,154.0,921.99,1038.54,1028.92,1027.89,1029.89,1030.7,1033.06,1044.39,1100.21,1274.71,1948.36 -Croatia,,,,,,,,,,0.3,0.3,0.3,4.0,19.0,33.0,47.8,55.8,60.0,67.7,84.8,108.5,138.3,182.3 -Czechia,0.1,0.1,0.2,0.3,0.4,0.59,0.84,3.96,39.5,464.6,1727.0,1913.0,2022.0,2063.5,2067.4,2074.9,2067.9,2075.44,2081.05,2110.67,2171.96,2246.09,2627.09 -Denmark,1.0,1.0,2.0,2.0,2.0,3.0,3.0,3.0,3.0,5.0,7.0,17.0,402.0,571.0,607.0,782.11,850.95,906.35,998.0,1080.0,1304.29,1704.04,3122.04 -Estonia,,,,,,,,,,0.1,0.1,0.2,0.38,1.5,3.34,6.5,10.0,15.0,31.9,120.6,207.67,394.77,534.77 -Finland,2.0,3.0,3.0,3.0,4.0,4.0,5.0,5.0,6.0,6.0,7.0,7.0,8.0,9.0,11.0,17.0,39.0,82.0,140.0,222.0,318.0,425.0,590.6 -France,7.0,7.0,8.0,9.0,11.0,13.0,15.0,26.0,80.0,277.0,1044.0,3003.57,4358.75,5277.29,6034.42,7137.52,7702.08,8610.44,9638.88,10738.39,11812.2,14436.97,17036.97 -Germany,114.0,195.0,260.0,435.0,1105.0,2056.0,2899.0,4170.0,6120.0,10564.0,18004.0,25914.0,34075.0,36708.0,37898.0,39222.0,40677.0,42291.0,45156.0,48912.0,53669.0,59371.0,66662.0 -Greece,,1.0,1.0,1.0,1.0,1.0,5.0,9.0,12.0,46.0,202.0,612.0,1536.0,2579.0,2596.0,2604.0,2604.0,2605.53,2651.57,2833.79,3287.72,4277.42,5557.42 -Hungary,,,,,,,,0.4,1.0,1.0,2.0,4.0,12.0,35.0,89.0,172.0,235.0,344.0,728.0,1400.0,2131.0,2968.0,2988.0 -Ireland,,,,,,,,,,,,,,,,,,,,,,, -Italy,19.0,20.0,22.0,26.0,31.0,34.0,45.0,110.0,483.0,1264.0,3592.0,13131.0,16785.0,18185.0,18594.0,18901.0,19283.0,19682.29,20107.59,20865.28,21650.04,22594.26,25076.56 -Latvia,,,,,,,,,,,,,,,,,0.69,0.69,1.96,3.3,5.1,7.16,56.16 -Lithuania,,,,,,,,,0.1,0.1,0.1,0.3,7.0,68.0,69.0,69.0,70.0,70.08,72.0,73.0,80.0,84.0,397.0 -Luxembourg,,0.16,1.59,14.17,23.56,23.58,23.7,23.93,24.56,26.36,29.45,40.67,74.65,95.02,109.93,116.27,121.9,128.1,130.62,159.74,186.64,277.16,319.16 -Montenegro,,,,,,,,,,,,,,,,,,,,,2.57,2.57,22.2 -Netherlands,13.0,21.0,26.0,46.0,50.0,51.0,53.0,54.0,59.0,69.0,90.0,149.0,287.0,650.0,1007.0,1526.26,2135.02,2910.89,4608.0,7226.0,11108.43,14910.69,18848.69 -North Macedonia,,,,,,,,,,,,2.0,4.0,7.0,15.0,17.0,16.7,16.7,16.7,16.71,84.93,84.93,84.93 -Norway,6.0,6.0,6.0,7.0,7.0,7.0,8.0,8.0,8.3,8.7,9.1,9.5,10.0,11.0,13.0,15.0,26.7,44.9,53.11,102.53,141.53,186.53,302.53 -Poland,,,,,,,,,,,,1.11,1.3,2.39,27.15,107.78,187.25,287.09,561.98,1539.26,3954.96,7415.52,11166.52 -Portugal,1.0,1.0,1.0,2.0,2.0,2.0,3.0,24.0,59.0,115.0,134.0,169.6,235.6,293.6,412.6,441.75,493.05,539.42,617.85,832.74,1010.07,1474.78,2364.78 -Romania,,,,,,,,,0.1,0.1,0.1,1.0,41.0,761.0,1293.0,1326.0,1372.0,1374.13,1385.82,1397.71,1382.54,1393.92,1413.92 -Serbia,,,,,,0.1,0.2,0.4,0.9,1.2,1.3,1.5,3.1,4.7,6.0,9.0,11.0,10.0,11.0,11.0,11.5,11.94,11.94 -Slovakia,,,,,,,,,,,19.0,496.0,513.0,533.0,533.0,533.0,533.0,528.0,472.0,590.0,535.0,537.0,537.0 -Slovenia,1.0,1.0,,,,0.05,0.19,0.59,1.0,4.0,12.0,57.0,142.0,187.0,223.0,238.0,233.0,246.8,246.8,277.88,369.78,461.16,632.16 -Spain,1.0,3.0,6.0,10.0,19.0,37.0,113.0,476.0,3365.0,3403.0,3851.0,4260.0,4545.0,4665.0,4672.0,4677.0,4687.0,4696.0,4730.7,8772.02,10100.42,13678.4,18176.73 -Sweden,3.0,3.0,3.0,4.0,4.0,4.0,5.0,6.0,8.0,9.0,11.0,12.0,24.0,43.0,60.0,104.0,153.0,231.0,411.0,698.0,1090.0,1587.0,2587.0 -Switzerland,16.0,18.0,20.0,22.0,24.0,28.0,30.0,37.0,49.0,79.0,125.0,223.0,437.0,756.0,1061.0,1394.0,1664.0,1906.0,2173.0,2498.0,2973.0,3655.0,4339.92 -UK,2.0,3.0,4.0,6.0,8.0,11.0,14.0,18.0,23.0,27.0,95.0,1000.0,1753.0,2937.0,5528.0,9601.0,11914.0,12760.0,13059.0,13345.0,13579.0,13965.0,14660.0 diff --git a/data/heat_load_profile_DK_AdamJensen.csv b/data/heat_load_profile_DK_AdamJensen.csv deleted file mode 100644 index cb417bde..00000000 --- a/data/heat_load_profile_DK_AdamJensen.csv +++ /dev/null @@ -1,25 +0,0 @@ -hour,weekday,weekend -0,0.9181438689,0.9421512708 -1,0.9172359071,0.9400891069 -2,0.9269464481,0.9461062015 -3,0.9415047932,0.9535084941 -4,0.9656299507,0.9651094993 -5,1.0221166443,0.9834676747 -6,1.1553090493,1.0124171051 -7,1.2093411031,1.0446615927 -8,1.1470295942,1.088203419 -9,1.0877191341,1.1110334576 -10,1.0418327372,1.0926752822 -11,1.0062977133,1.055488209 -12,0.9837030359,1.0251266112 -13,0.9667570278,0.9990015154 -14,0.9548320932,0.9782897278 -15,0.9509232061,0.9698167237 -16,0.9636973319,0.974288587 -17,0.9799372563,0.9886456216 -18,1.0046501848,1.0084159643 -19,1.0079452419,1.0171243296 -20,0.9860566481,0.9994722379 -21,0.9705228074,0.982761591 -22,0.9586485819,0.9698167237 -23,0.9335023778,0.9515079292 diff --git a/data/hydro_capacities.csv b/data/hydro_capacities.csv new file mode 100644 index 00000000..1b39f731 --- /dev/null +++ b/data/hydro_capacities.csv @@ -0,0 +1,31 @@ +Country,p_nom_discharge[GW],p_nom_store[GW],E_store[TWh],InflowHourlyAvg[GWh] +AT,13.08,3.8,3.2,4.02 +BE,1.42,1.31,0,0.04 +BA,2.05,0.62,2.5,0.71 +BG,3.13,0.86,4,0.53 +HR,2,0.61,2.8,0.57 +CZ,2.21,0.68,1.5,0.24 +DK,0.01,0,0,0 +EE,0.01,0,0,0 +FI,3.2,0,5.5,1.59 +FR,25.37,6.99,9.8,7.82 +DE,11.26,6.8,0.3,1.93 +GB,4.43,2.74,0,0.46 +GR,3.24,0.7,2.3,0.26 +HU,0.06,0,0.1,0.02 +IE,0.53,0.29,0,0.08 +IT,21.88,7.55,7.9,5.19 +LV,1.58,0,1.8,0.3 +LT,0.88,0.76,0.2,0.05 +LU,1.13,1.29,0,0 +NL,0.04,0,0,0.01 +NO,30.51,1.35,84.4,14 +PL,2.35,1.4,1.6,0.23 +PT,5.72,1.03,2.6,1.37 +RO,6.55,0.09,12.1,1.95 +RS,2.14,0.61,0,1.18 +SK,2.52,0.92,2.2,0.49 +SI,1.25,0.18,2.2,0.36 +ES,18.55,2.75,18.4,2.61 +SE,16.41,0.1,33.8,7.8 +CH,13.3,4.03,8.4,4.29 diff --git a/data/hydrogen_salt_cavern_potentials.csv b/data/hydrogen_salt_cavern_potentials.csv deleted file mode 100644 index c1168266..00000000 --- a/data/hydrogen_salt_cavern_potentials.csv +++ /dev/null @@ -1,31 +0,0 @@ -ct,TWh -AT, -BA, -BE, -BG, -CH, -CZ, -DE,4500 -DK,700 -EE, -ES,350 -FI, -FR, -GB,1050 -GR,120 -HR, -HU, -IE, -IT, -LT, -LU, -LV, -NL,150 -NO, -PL,120 -PT,400 -RO, -RS, -SE, -SI, -SK, diff --git a/doc/conf.py b/doc/conf.py index afa01d3a..5c4b3b89 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -34,10 +34,10 @@ sys.path.insert(0, os.path.abspath("../scripts")) # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ - #'sphinx.ext.autodoc', + "sphinx.ext.autodoc", #'sphinx.ext.autosummary', "myst_parser", - "sphinx.ext.autosectionlabel", + # "sphinx.ext.autosectionlabel", "sphinx.ext.intersphinx", "sphinx.ext.todo", "sphinx.ext.mathjax", @@ -50,6 +50,19 @@ extensions = [ "sphinx.ext.imgconverter", # for SVG conversion ] +autodoc_mock_imports = [ + "atlite", + "snakemake", + "pycountry", + "rioxarray", + "country_converter", + "tabula", + "memory_profiler", + "powerplantmatching", + "rasterio", + "dask.distributed", +] + autodoc_default_flags = ["members"] autosummary_generate = True @@ -80,9 +93,9 @@ author = "Tom Brown (KIT, TUB, FIAS), Jonas Hoersch (KIT, FIAS), Fabian Hofmann # built documents. # # The short X.Y version. -version = "0.10" +version = "0.11" # The full version, including alpha/beta/rc tags. -release = "0.10.0" +release = "0.11.0" # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/doc/configtables/co2_budget.csv b/doc/configtables/co2_budget.csv index 21b42f05..8f11f9c6 100644 --- a/doc/configtables/co2_budget.csv +++ b/doc/configtables/co2_budget.csv @@ -1,2 +1,2 @@ ,Unit,Values,Description -co2_budget,--,Dictionary with planning horizons as keys.,CO2 budget as a fraction of 1990 emissions. Overwritten if ``CO2Lx`` or ``cb`` are set in ``{sector_opts}`` wildcard"doc/configtables/othertoplevel.csv +co2_budget,--,Dictionary with planning horizons as keys.,CO2 budget as a fraction of 1990 emissions. Overwritten if ``Co2Lx`` or ``cb`` are set in ``{sector_opts}`` wildcard"doc/configtables/othertoplevel.csv diff --git a/doc/configtables/costs.csv b/doc/configtables/costs.csv index 03933c18..38079e34 100644 --- a/doc/configtables/costs.csv +++ b/doc/configtables/costs.csv @@ -1,7 +1,6 @@ ,Unit,Values,Description year,--,YYYY; e.g. '2030',Year for which to retrieve cost assumptions of ``resources/costs.csv``. version,--,vX.X.X or //vX.X.X; e.g. 'v0.5.0',Version of ``technology-data`` repository to use. If this string is of the form // then costs are instead retrieved from ``github.com//`` at the tag. -rooftop_share,--,float,Share of rooftop PV when calculating capital cost of solar (joint rooftop and utility-scale PV). social_discountrate,p.u.,float,Social discount rate to compare costs in different investment periods. 0.02 corresponds to a social discount rate of 2%. fill_values,--,float,Default values if not specified for a technology in ``resources/costs.csv``. capital_cost,EUR/MW,Keys should be in the 'technology' column of ``resources/costs.csv``. Values can be any float.,"For the given technologies, assumptions about their capital investment costs are set to the corresponding value. Optional; overwrites cost assumptions from ``resources/costs.csv``." diff --git a/doc/configtables/electricity.csv b/doc/configtables/electricity.csv index 22a22d57..ee733660 100644 --- a/doc/configtables/electricity.csv +++ b/doc/configtables/electricity.csv @@ -2,10 +2,9 @@ voltages,kV,"Any subset of {220., 300., 380.}",Voltage levels to consider gaslimit_enable,bool,true or false,Add an overall absolute gas limit configured in ``electricity: gaslimit``. gaslimit,MWhth,float or false,Global gas usage limit -co2limit_enable,bool,true or false,Add an overall absolute carbon-dioxide emissions limit configured in ``electricity: co2limit``. +co2limit_enable,bool,true or false,Add an overall absolute carbon-dioxide emissions limit configured in ``electricity: co2limit`` in :mod:`prepare_network`. **Warning:** This option should currently only be used with electricity-only networks, not for sector-coupled networks.. co2limit,:math:`t_{CO_2-eq}/a`,float,Cap on total annual system carbon dioxide emissions co2base,:math:`t_{CO_2-eq}/a`,float,Reference value of total annual system carbon dioxide emissions if relative emission reduction target is specified in ``{opts}`` wildcard. -agg_p_nom_limits,file,path,Reference to ``.csv`` file specifying per carrier generator nominal capacity constraints for individual countries if ``'CCL'`` is in ``{opts}`` wildcard. Defaults to ``data/agg_p_nom_minmax.csv``. operational_reserve,,,Settings for reserve requirements following `GenX `_ ,,, -- activate,bool,true or false,Whether to take operational reserve requirements into account during optimisation @@ -28,14 +27,14 @@ everywhere_powerplants,--,"Any subset of {nuclear, oil, OCGT, CCGT, coal, lignit ,,, conventional_carriers,--,"Any subset of {nuclear, oil, OCGT, CCGT, coal, lignite, geothermal, biomass}","List of conventional power plants to include in the model from ``resources/powerplants.csv``. If an included carrier is also listed in ``extendable_carriers``, the capacity is taken as a lower bound." ,,, -renewable_carriers,--,"Any subset of {solar, onwind, offwind-ac, offwind-dc, hydro}",List of renewable generators to include in the model. +renewable_carriers,--,"Any subset of {solar, onwind, offwind-ac, offwind-dc, offwind-float, hydro}",List of renewable generators to include in the model. estimate_renewable_capacities,,, --- enable,,bool,Activate routine to estimate renewable capacities +-- enable,,bool,Activate routine to estimate renewable capacities in rule :mod:`add_electricity`. This option should not be used in combination with pathway planning ``foresight: myopic`` or ``foresight: perfect`` as renewable capacities are added differently in :mod:`add_existing_baseyear`. -- from_opsd,--,bool,Add renewable capacities from `OPSD database `_. The value is depreciated but still can be used. -- year,--,bool,Renewable capacities are based on existing capacities reported by IRENA (IRENASTAT) for the specified year -- expansion_limit,--,float or false,"Artificially limit maximum IRENA capacities to a factor. For example, an ``expansion_limit: 1.1`` means 110% of capacities . If false are chosen, the estimated renewable potentials determine by the workflow are used." -- technology_mapping,,,Mapping between PyPSA-Eur and powerplantmatching technology names --- -- Offshore,--,"Any subset of {offwind-ac, offwind-dc}","List of PyPSA-Eur carriers that is considered as (IRENA, OPSD) onshore technology." +-- -- Offshore,--,"Any subset of {offwind-ac, offwind-dc, offwind-float}","List of PyPSA-Eur carriers that is considered as (IRENA, OPSD) onshore technology." -- -- Offshore,--,{onwind},"List of PyPSA-Eur carriers that is considered as (IRENA, OPSD) offshore technology." -- -- PV,--,{solar},"List of PyPSA-Eur carriers that is considered as (IRENA, OPSD) PV technology." autarky,,, diff --git a/doc/configtables/enable.csv b/doc/configtables/enable.csv index 1e5571b3..c74d0eff 100644 --- a/doc/configtables/enable.csv +++ b/doc/configtables/enable.csv @@ -2,12 +2,8 @@ enable,str or bool,"{auto, true, false}","Switch to include (true) or exclude (false) the retrieve_* rules of snakemake into the workflow; 'auto' sets true|false based on availability of an internet connection to prevent issues with snakemake failing due to lack of internet connection." prepare_links_p_nom,bool,"{true, false}","Switch to retrieve current HVDC projects from `Wikipedia `_" retrieve_databundle,bool,"{true, false}","Switch to retrieve databundle from zenodo via the rule :mod:`retrieve_databundle` or whether to keep a custom databundle located in the corresponding folder." -retrieve_sector_databundle,bool,"{true, false}","Switch to retrieve sector databundle from zenodo via the rule :mod:`retrieve_sector_databundle` or whether to keep a custom databundle located in the corresponding folder." retrieve_cost_data,bool,"{true, false}","Switch to retrieve technology cost data from `technology-data repository `_." build_cutout,bool,"{true, false}","Switch to enable the building of cutouts via the rule :mod:`build_cutout`." -retrieve_irena,bool,"{true, false}",Switch to enable the retrieval of ``existing_capacities`` from IRENASTAT with :mod:`retrieve_irena`. retrieve_cutout,bool,"{true, false}","Switch to enable the retrieval of cutouts from zenodo with :mod:`retrieve_cutout`." -build_natura_raster,bool,"{true, false}","Switch to enable the creation of the raster ``natura.tiff`` via the rule :mod:`build_natura_raster`." -retrieve_natura_raster,bool,"{true, false}","Switch to enable the retrieval of ``natura.tiff`` from zenodo with :mod:`retrieve_natura_raster`." custom_busmap,bool,"{true, false}","Switch to enable the use of custom busmaps in rule :mod:`cluster_network`. If activated the rule looks for provided busmaps at ``data/custom_busmap_elec_s{simpl}_{clusters}.csv`` which should have the same format as ``resources/busmap_elec_s{simpl}_{clusters}.csv``, i.e. the index should contain the buses of ``networks/elec_s{simpl}.nc``." drop_leap_day,bool,"{true, false}","Switch to drop February 29 from all time-dependent data in leap years" diff --git a/doc/configtables/industry.csv b/doc/configtables/industry.csv index d1b560ed..4187e118 100644 --- a/doc/configtables/industry.csv +++ b/doc/configtables/industry.csv @@ -16,6 +16,9 @@ petrochemical_process _emissions,MtCO2/a,float,The emission of petrochemical pro HVC_primary_fraction,--,float,The fraction of high value chemicals (HVC) produced via primary route HVC_mechanical_recycling _fraction,--,float,The fraction of high value chemicals (HVC) produced using mechanical recycling HVC_chemical_recycling _fraction,--,float,The fraction of high value chemicals (HVC) produced using chemical recycling +HVC_environment_sequestration_fraction,--,float,The fraction of high value chemicals (HVC) put into landfill resulting in additional carbon sequestration. The default value is 0. +waste_to_energy,--,bool,Switch to enable expansion of waste to energy CHPs for conversion of plastics. Default is false. +waste_to_energy_cc,--,bool,Switch to enable expansion of waste to energy CHPs for conversion of plastics with carbon capture. Default is false. ,,, sector_ratios_fraction_future,--,Dictionary with planning horizons as keys.,The fraction of total progress in fuel and process switching achieved in the industry sector. basic_chemicals_without_NH3_production_today,Mt/a,float,"The amount of basic chemicals produced without ammonia (= 86 Mtethylene-equiv - 17 MtNH3)." @@ -29,5 +32,6 @@ MWh_H2_per_tCl,MWhH2/tCl,float,"The energy amount of hydrogen needed to produce methanol_production _today,MtMeOH/a,float,"The amount of methanol produced. From `DECHEMA (2017) `_, page 62" MWh_elec_per_tMeOH,MWh/tMeOH,float,"The energy amount of electricity needed to produce a ton of methanol. From `DECHEMA (2017) `_, Table 14, page 65" MWh_CH4_per_tMeOH,MWhCH4/tMeOH,float,"The energy amount of methane needed to produce a ton of methanol. From `DECHEMA (2017) `_, Table 14, page 65" +MWh_MeOH_per_tMeOH,LHV,float,"The energy amount per ton of methanol. From `DECHEMA (2017) `_, page 74." hotmaps_locate_missing,--,"{true,false}",Locate industrial sites without valid locations based on city and countries. reference_year,year,YYYY,The year used as the baseline for industrial energy demand and production. Data extracted from `JRC-IDEES 2015 `_ diff --git a/doc/configtables/licenses-sector.csv b/doc/configtables/licenses-sector.csv index 7f20b5a6..a44e0a5d 100644 --- a/doc/configtables/licenses-sector.csv +++ b/doc/configtables/licenses-sector.csv @@ -1,14 +1,12 @@ description,file/folder,licence,source JRC IDEES database,jrc-idees-2015/,CC BY 4.0,https://ec.europa.eu/jrc/en/potencia/jrc-idees urban/rural fraction,urban_percent.csv,unknown,unknown -JRC biomass potentials,biomass/,unknown,https://doi.org/10.2790/39014 JRC ENSPRESO biomass potentials,remote,CC BY 4.0,https://data.jrc.ec.europa.eu/dataset/74ed5a04-7d74-4807-9eab-b94774309d9f EEA emission statistics,eea/UNFCCC_v23.csv,EEA standard re-use policy,https://www.eea.europa.eu/data-and-maps/data/national-emissions-reported-to-the-unfccc-and-to-the-eu-greenhouse-gas-monitoring-mechanism-16 Eurostat Energy Balances,eurostat-energy_balances-*/,Eurostat,https://ec.europa.eu/eurostat/web/energy/data/energy-balances Swiss energy statistics from Swiss Federal Office of Energy,switzerland-sfoe/,unknown,http://www.bfe.admin.ch/themen/00526/00541/00542/02167/index.html?dossier_id=02169 BASt emobility statistics,emobility/,unknown,http://www.bast.de/DE/Verkehrstechnik/Fachthemen/v2-verkehrszaehlung/Stundenwerte.html?nn=626916 BDEW heating profile,heat_load_profile_BDEW.csv,unknown,https://github.com/oemof/demandlib -heating profiles for Aarhus,heat_load_profile_DK_AdamJensen.csv,unknown,Adam Jensen MA thesis at Aarhus University co2 budgets,co2_budget.csv,CC BY 4.0,https://arxiv.org/abs/2004.11009 existing heating potentials,existing_infrastructure/existing_heating_raw.csv,unknown,https://energy.ec.europa.eu/publications/mapping-and-analyses-current-and-future-2020-2030-heatingcooling-fuel-deployment-fossilrenewables-1_en IRENA existing VRE capacities,existing_infrastructure/{solar|onwind|offwind}_capcity_IRENA.csv,unknown,https://www.irena.org/Statistics/Download-Data diff --git a/doc/configtables/licenses.csv b/doc/configtables/licenses.csv index 37f46cd0..d1fa4aa8 100644 --- a/doc/configtables/licenses.csv +++ b/doc/configtables/licenses.csv @@ -5,10 +5,8 @@ "naturalearth/*",,,,,http://www.naturalearthdata.com/about/terms-of-use/ "NUTS_2013 _60M_SH/*","x","x",,"x",https://ec.europa.eu/eurostat/web/gisco/geodata/reference-data/administrative-units-statistical-units "cantons.csv","x",,"x",,https://en.wikipedia.org/wiki/Data_codes_for_Switzerland -"eia_hydro_annual_generation.csv","x",,,,https://www.eia.gov/about/copyrights_reuse.php -"GEBCO_2014_2D.nc","x",,,,https://www.gebco.net/data_and_products/gridded_bathymetry_data/documents/gebco_2014_historic.pdf +"gebco/GEBCO_2014_2D.nc","x",,,,https://www.gebco.net/data_and_products/gridded_bathymetry_data/documents/gebco_2014_historic.pdf "hydro_capacities.csv","x",,,, "je-e-21.03.02.xls","x","x",,,https://www.bfs.admin.ch/bfs/en/home/fso/swiss-federal-statistical-office/terms-of-use.html "nama_10r_3 gdp.tsv.gz","x",,,"x",https://ec.europa.eu/eurostat/about/policies/copyright "nama_10r_3 popgdp.tsv.gz","x",,,"x",https://ec.europa.eu/eurostat/about/policies/copyright -"time_series_60min _singleindex_filtered.csv","x",,,,https://data.open-power-system-data.org/time_series/2019-06-05/README.md diff --git a/doc/configtables/load.csv b/doc/configtables/load.csv index 34d73dc5..9ebfea32 100644 --- a/doc/configtables/load.csv +++ b/doc/configtables/load.csv @@ -4,4 +4,4 @@ time_shift_for_large_gaps,string,string,"Periods which are used for copying time manual_adjustments,bool,"{true, false}","Whether to adjust the load data manually according to the function in :func:`manual_adjustment`." scaling_factor,--,float,"Global correction factor for the load time series." fixed_year,--,Year or False,"To specify a fixed year for the load time series that deviates from the snapshots' year" -supplement_synthetic,bool,"{true, false}","Whether to supplement missing data for selected time period should be supplemented by synthetic data from https://zenodo.org/record/10820928." +supplement_synthetic,bool,"{true, false}","Whether to supplement missing data for selected time period should be supplemented by synthetic data from https://zenodo.org/records/10820928." diff --git a/doc/configtables/run.csv b/doc/configtables/run.csv index 2835a324..f11a8d96 100644 --- a/doc/configtables/run.csv +++ b/doc/configtables/run.csv @@ -1,8 +1,11 @@ ,Unit,Values,Description name,--,str/list,"Specify a name for your run. Results will be stored under this name. If ``scenario: enable:`` is set to ``true``, the name must contain a subset of scenario names defined in ``scenario: file:``. If the name is 'all', all defined scenarios will be run." +prefix,--,str,"Prefix for the run name which is used as a top-layer directory name in the results and resources folders." scenarios,,, -- enable,bool,"{true, false}","Switch to select whether workflow should generate scenarios based on ``file``." -- file,str,,"Path to the scenario yaml file. The scenario file contains config overrides for each scenario. In order to be taken account, ``run: scenarios`` has to be set to ``true`` and ``run: name`` has to be a subset of top level keys given in the scenario file. In order to automatically create a `scenario.yaml` file based on a combination of settings, alter and use the ``config/create_scenarios.py`` script in the ``config`` directory." disable_progressbar,bool,"{true, false}","Switch to select whether progressbar should be disabled." -shared_resources,bool/str,,"Switch to select whether resources should be shared across runs. If a string is passed, this is used as a subdirectory name for shared resources. If set to 'base', only resources before creating the elec.nc file are shared." +shared_resources,,, +-- policy,bool/str,,"Boolean switch to select whether resources should be shared across runs. If a string is passed, this is used as a subdirectory name for shared resources. If set to 'base', only resources before creating the elec.nc file are shared." +-- exclude,str,"For the case shared_resources=base, specify additional files that should not be shared across runs." shared_cutouts,bool,"{true, false}","Switch to select whether cutouts should be shared across runs." diff --git a/doc/configtables/sector-opts.csv b/doc/configtables/sector-opts.csv index fc9e8c10..afe631fa 100644 --- a/doc/configtables/sector-opts.csv +++ b/doc/configtables/sector-opts.csv @@ -1,6 +1,6 @@ Trigger, Description, Definition, Status -``nH``, i.e. ``2H``-``6H``, "Resample the time-resolution by averaging over every ``n`` snapshots, ``prepare_network``: `average_every_nhours() `_ and its `caller `__)", In active use -``Co2L``, Add an overall absolute carbon-dioxide emissions limit configured in ``electricity: co2limit``. If a float is appended an overall emission limit relative to the emission level given in ``electricity: co2base`` is added (e.g. ``Co2L0.05`` limits emissisions to 5% of what is given in ``electricity: co2base``), ``prepare_network``: `add_co2limit() `_ and its `caller `__, In active use +``nH``, i.e. ``2h``-``6h``, "Resample the time-resolution by averaging over every ``n`` snapshots, ``prepare_network``: `average_every_nhours() `_ and its `caller `__)", In active use +``Co2L`` + ``n``, Add an overall absolute carbon-dioxide emissions limit of ``n`` times of the 1990 base emissions (e.g. ``Co2L0.05`` limits emissisions to 5% of what is calculated in the rule :mod:``prepare_sector_network`` in the function ``co2_emissions_year()``),:mod:``prepare_sector_network`` in the function ``co2_emissions_year()`` , In active use ``carrier+{c|p|m}factor``,"Alter the capital cost (``c``), installable potential (``p``) or marginal costs (``m``) of a carrier by a factor. Example: ``solar+c0.5`` reduces the capital cost of solar to 50\% of original values.", ``prepare_network``, In active use ``T``,Add land transport sector,,In active use ``H``,Add heating sector,,In active use diff --git a/doc/configtables/sector.csv b/doc/configtables/sector.csv index 69b3375b..059c4233 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,14 +31,15 @@ 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. agriculture_machinery _electric_efficiency,--,float,The efficiency of oil-powered machinery in the conversion of oil to meet agricultural needs. Mwh_MeOH_per_MWh_H2,LHV,float,"The energy amount of the produced methanol per energy amount of hydrogen. From `DECHEMA (2017) `_, page 64." -MWh_MeOH_per_tCO2,LHV,float,"The energy amount of the produced methanol per ton of CO2. From `DECHEMA (2017) `_, page 64." +MWh_MeOH_per_tCO2,LHV,float,"The energy amount of the produced methanol per ton of CO2. From `DECHEMA (2017) `_, page 66." MWh_MeOH_per_MWh_e,LHV,float,"The energy amount of the produced methanol per energy amount of electricity. From `DECHEMA (2017) `_, page 64." shipping_hydrogen _liquefaction,--,"{true, false}",Whether to include liquefaction costs for hydrogen demand in shipping. ,,, @@ -90,7 +90,7 @@ regional_methanol_demand,--,"{true, false}",Spatially resolve methanol demand. S regional_oil_demand,--,"{true, false}",Spatially resolve oil demand. Set to true if regional CO2 constraints needed. regional_co2 _sequestration_potential,,, -- enable,--,"{true, false}",Add option for regionally-resolved geological carbon dioxide sequestration potentials based on `CO2StoP `_. --- attribute,--,string,Name of the attribute for the sequestration potential +-- attribute,--,string or list,Name (or list of names) of the attribute(s) for the sequestration potential -- include_onshore,--,"{true, false}",Add options for including onshore sequestration potentials -- min_size,Gt ,float,Any sites with lower potential than this value will be excluded -- max_size,Gt ,float,The maximum sequestration potential for any one site. @@ -143,7 +143,7 @@ limit_max_growth,,, -- factor,p.u.,float,The maximum growth factor of a carrier (e.g. 1.3 allows 30% larger than max historic growth) -- max_growth,,, -- -- {carrier},GW,float,The historic maximum growth of a carrier --- max_relative_growth, +-- max_relative_growth,,, -- -- {carrier},p.u.,float,The historic maximum relative growth of a carrier ,,, enhanced_geothermal,,, diff --git a/doc/configtables/solving.csv b/doc/configtables/solving.csv index 7189399b..4cfb9065 100644 --- a/doc/configtables/solving.csv +++ b/doc/configtables/solving.csv @@ -4,7 +4,7 @@ options,,, -- load_shedding,bool/float,"{'true','false', float}","Add generators with very high marginal cost to simulate load shedding and avoid problem infeasibilities. If load shedding is a float, it denotes the marginal cost in EUR/kWh." -- noisy_costs,bool,"{'true','false'}","Add random noise to marginal cost of generators by :math:`\mathcal{U}(0.009,0,011)` and capital cost of lines and links by :math:`\mathcal{U}(0.09,0,11)`." -- skip_iterations,bool,"{'true','false'}","Skip iterating, do not update impedances of branches. Defaults to true." --- rolling_horizon,bool,"{'true','false'}","Whether to optimize the network in a rolling horizon manner, where the snapshot range is split into slices of size `horizon` which are solved consecutively." +-- rolling_horizon,bool,"{'true','false'}","Switch for rule :mod:`solve_operations_network` whether to optimize the network in a rolling horizon manner, where the snapshot range is split into slices of size `horizon` which are solved consecutively. This setting has currently no effect on sector-coupled networks." -- seed,--,int,Random seed for increased deterministic behaviour. -- custom_extra_functionality,--,str,Path to a Python file with custom extra functionality code to be injected into the solving rules of the workflow relative to ``rules`` directory. -- io_api,string,"{'lp','mps','direct'}",Passed to linopy and determines the API used to communicate with the solver. With the ``'lp'`` and ``'mps'`` options linopy passes a file to the solver; with the ``'direct'`` option (only supported for HIGHS and Gurobi) linopy uses an in-memory python API resulting in better performance. @@ -14,13 +14,25 @@ options,,, -- transmission_losses,int,[0-9],"Add piecewise linear approximation of transmission losses based on n tangents. Defaults to 0, which means losses are ignored." -- linearized_unit_commitment,bool,"{'true','false'}",Whether to optimise using the linearized unit commitment formulation. -- horizon,--,int,Number of snapshots to consider in each iteration. Defaults to 100. +-- post_discretization,,, +-- -- enable,bool,"{'true','false'}",Switch to enable post-discretization of the network. Disabled by default. +-- -- line_unit_size,MW,float,Discrete unit size of lines in MW. +-- -- line_threshold,,float,The threshold relative to the discrete line unit size beyond which to round up to the next unit. +-- -- link_unit_size,MW,float,Discrete unit size of links in MW by carrier (given in dictionary style). +-- -- -- {carrier},,, +-- -- link_threshold,,float,The threshold relative to the discrete link unit size beyond which to round up to the next unit by carrier (given in dictionary style). +-- -- -- {carrier},,, +agg_p_nom_limits,,,Configure per carrier generator nominal capacity constraints for individual countries if ``'CCL'`` is in ``{opts}`` wildcard. +-- agg_offwind,bool,"{'true','false'}",Aggregate together all the types of offwind when writing the constraint. Default is false. +-- include_existing,bool,"{'true','false'}",Take existing capacities into account when writing the constraint. Default is false. +-- file,file,path,Reference to ``.csv`` file specifying per carrier generator nominal capacity constraints for individual countries and planning horizons. Defaults to ``data/agg_p_nom_minmax.csv``. constraints ,,, -- CCL,bool,"{'true','false'}",Add minimum and maximum levels of generator nominal capacity per carrier for individual countries. These can be specified in the file linked at ``electricity: agg_p_nom_limits`` in the configuration. File defaults to ``data/agg_p_nom_minmax.csv``. -- EQ,bool/string,"{'false',`n(c| )``; i.e. ``0.5``-``0.7c``}",Require each country or node to on average produce a minimal share of its total consumption itself. Example: ``EQ0.5c`` demands each country to produce on average at least 50% of its consumption; ``EQ0.5`` demands each node to produce on average at least 50% of its consumption. -- BAU,bool,"{'true','false'}",Add a per-``carrier`` minimal overall capacity; i.e. at least ``40GW`` of ``OCGT`` in Europe; configured in ``electricity: BAU_mincapacities`` -- SAFE,bool,"{'true','false'}",Add a capacity reserve margin of a certain fraction above the peak demand to which renewable generators and storage do *not* contribute. Ignores network. solver,,, --- name,--,"One of {'gurobi', 'cplex', 'cbc', 'glpk', 'ipopt'}; potentially more possible",Solver to use for optimisation problems in the workflow; e.g. clustering and linear optimal power flow. +-- name,--,"One of {'gurobi', 'cplex', 'highs', 'cbc', 'glpk'}; potentially more possible",Solver to use for optimisation problems in the workflow; e.g. clustering and linear optimal power flow. -- options,--,Key listed under ``solver_options``.,Link to specific parameter settings. solver_options,,dict,Dictionaries with solver-specific parameter settings. mem,MB,int,Estimated maximum memory requirement for solving networks. diff --git a/doc/configuration.rst b/doc/configuration.rst index dae91380..8695d4bb 100644 --- a/doc/configuration.rst +++ b/doc/configuration.rst @@ -31,7 +31,7 @@ Top-level configuration .. _run_cf: ``run`` -======= +============= It is common conduct to analyse energy system optimisation models for **multiple scenarios** for a variety of reasons, e.g. assessing their sensitivity towards changing the temporal and/or geographical resolution or investigating how @@ -90,9 +90,9 @@ For each wildcard, a **list of values** is provided. The rule ``results/networks/elec_s{simpl}_{clusters}_ec_l{ll}_{opts}.nc`` for **all combinations** of the provided wildcard values as defined by Python's `itertools.product(...) -`_ function +`__ function that snakemake's `expand(...) function -`_ +`__ uses. An exemplary dependency graph (starting from the simplification rules) then looks like this: @@ -129,7 +129,7 @@ An exemplary dependency graph (starting from the simplification rules) then look ``snapshots`` ============= -Specifies the temporal range to build an energy system model for as arguments to `pandas.date_range `_ +Specifies the temporal range to build an energy system model for as arguments to `pandas.date_range `__ .. literalinclude:: ../config/config.default.yaml :language: yaml @@ -174,7 +174,7 @@ Switches for some rules and optional features. :file: configtables/co2_budget.csv .. note:: - this parameter is over-ridden if ``CO2Lx`` or ``cb`` is set in + this parameter is over-ridden if ``Co2Lx`` or ``cb`` is set in sector_opts. .. _electricity_cf: @@ -197,7 +197,7 @@ Switches for some rules and optional features. ``atlite`` ========== -Define and specify the ``atlite.Cutout`` used for calculating renewable potentials and time-series. All options except for ``features`` are directly used as `cutout parameters `_. +Define and specify the ``atlite.Cutout`` used for calculating renewable potentials and time-series. All options except for ``features`` are directly used as `cutout parameters `__. .. literalinclude:: ../config/config.default.yaml :language: yaml @@ -265,7 +265,7 @@ Define and specify the ``atlite.Cutout`` used for calculating renewable potentia .. literalinclude:: ../config/config.default.yaml :language: yaml :start-at: offwind-dc: - :end-before: solar: + :end-before: offwind-float: .. csv-table:: :header-rows: 1 @@ -273,9 +273,25 @@ Define and specify the ``atlite.Cutout`` used for calculating renewable potentia :file: configtables/offwind-dc.csv .. note:: - both ``offwind-ac`` and ``offwind-dc`` have the same assumption on + Both ``offwind-ac`` and ``offwind-dc`` have the same assumption on ``capacity_per_sqkm`` and ``correction_factor``. +``offwind-float`` +--------------- + +.. literalinclude:: ../config/config.default.yaml + :language: yaml + :start-at: offwind-float: + :end-before: solar: + +.. csv-table:: + :header-rows: 1 + :widths: 22,7,22,33 + :file: configtables/offwind-float.csv + +.. note:: + ``offwind-ac``, ``offwind-dc`` , ``offwind-float`` have the same assumption on + ``capacity_per_sqkm`` and ``correction_factor``. ``solar`` --------------- @@ -427,7 +443,7 @@ overwrite the existing values. :widths: 22,7,22,33 :file: configtables/biomass.csv -The list of available biomass is given by the category in `ENSPRESO_BIOMASS `_, namely: +The list of available biomass is given by the category in `ENSPRESO_BIOMASS `__, namely: - Agricultural waste - Manure solid, liquid @@ -534,9 +550,6 @@ The list of available biomass is given by the category in `ENSPRESO_BIOMASS `_. To ask and answer general usage questions, join the `PyPSA mailing list `_. + +Contributing to the documentation +==================================== + +We strive to keep documentation useful and up to date for all PyPSA users. If you encounter an area where documentation is not available or insufficient, we very much welcome your contribution. Here is How To: + +#. Install the conda environment for documentation from the `PyPSA repository `_. + (Here is `how to install a conda environment `_.) +#. Make your changes in the corresponding .rst file under ``pypsa-eur/doc``. +#. Compile your changes by running the following command in your terminal in the ``doc`` folder: ``make html`` + You may encounter some warnings, but end up with a message such as ``build succeeded, XX warnings.``. html files to review your changes can then be found under ``doc/_build/html``. +#. Contribute your documentation in a pull request (`here is a guide `_). diff --git a/doc/costs.rst b/doc/costs.rst index 12242df4..2a946b00 100644 --- a/doc/costs.rst +++ b/doc/costs.rst @@ -8,7 +8,7 @@ Techno-Economic Assumptions ############################ The database of cost assumptions is retrieved from the repository -`PyPSA/technology-data `_ and then +`PyPSA/technology-data `__ and then saved to a file ``resources/costs_{year}.csv``. The ``config/config.yaml`` provides options to choose a reference year and use a specific version of the repository. @@ -30,7 +30,7 @@ years compiled from various sources, namely for - carbon-dioxide intensity. Many values are taken from a database published by the Danish Energy Agency (`DEA -`_). +`__). The given overnight capital costs are annualised to net present costs diff --git a/doc/foresight.rst b/doc/foresight.rst index c749c84c..400f67ce 100644 --- a/doc/foresight.rst +++ b/doc/foresight.rst @@ -166,13 +166,13 @@ Options The total carbon budget for the entire transition path can be indicated in the `sector_opts -`_ +`__ in ``config/config.yaml``. The carbon budget can be split among the ``planning_horizons`` following an exponential or beta decay. E.g. ``'cb40ex0'`` splits a carbon budget equal to 40 Gt :math:`_{CO_2}` following an exponential decay whose initial linear growth rate r is zero. They can also follow some user-specified path, if defined `here -`_. +`__. The paper `Speed of technological transformations required in Europe to achieve different climate goals (2022) `__ defines CO_2 budgets corresponding to global temperature increases (1.5C – 2C) diff --git a/graphics/Heat_and_el_demand_timeseries.png b/doc/img/Heat_and_el_demand_timeseries.png similarity index 100% rename from graphics/Heat_and_el_demand_timeseries.png rename to doc/img/Heat_and_el_demand_timeseries.png diff --git a/graphics/demand-map-heat.png b/doc/img/demand-map-heat.png similarity index 100% rename from graphics/demand-map-heat.png rename to doc/img/demand-map-heat.png diff --git a/graphics/fec_industry_today_tomorrow.png b/doc/img/fec_industry_today_tomorrow.png similarity index 100% rename from graphics/fec_industry_today_tomorrow.png rename to doc/img/fec_industry_today_tomorrow.png diff --git a/graphics/gas_pipeline_figure.png b/doc/img/gas_pipeline_figure.png similarity index 100% rename from graphics/gas_pipeline_figure.png rename to doc/img/gas_pipeline_figure.png diff --git a/graphics/hotmaps.png b/doc/img/hotmaps.png similarity index 100% rename from graphics/hotmaps.png rename to doc/img/hotmaps.png diff --git a/doc/img/intro-workflow.png b/doc/img/intro-workflow.png index 27b5a389..4009fbcd 100644 Binary files a/doc/img/intro-workflow.png and b/doc/img/intro-workflow.png differ diff --git a/graphics/multisector_figure.pdf b/doc/img/multisector_figure.pdf similarity index 100% rename from graphics/multisector_figure.pdf rename to doc/img/multisector_figure.pdf diff --git a/graphics/multisector_figure.png b/doc/img/multisector_figure.png similarity index 100% rename from graphics/multisector_figure.png rename to doc/img/multisector_figure.png diff --git a/graphics/multisector_figure.svg b/doc/img/multisector_figure.svg similarity index 100% rename from graphics/multisector_figure.svg rename to doc/img/multisector_figure.svg diff --git a/graphics/process-emissions.png b/doc/img/process-emissions.png similarity index 100% rename from graphics/process-emissions.png rename to doc/img/process-emissions.png diff --git a/graphics/validation_production_bar_elec_s_37_ec_lv1.0_Ept.png b/doc/img/validation_production_bar_elec_s_37_ec_lv1.0_Ept.png similarity index 100% rename from graphics/validation_production_bar_elec_s_37_ec_lv1.0_Ept.png rename to doc/img/validation_production_bar_elec_s_37_ec_lv1.0_Ept.png diff --git a/graphics/validation_seasonal_operation_area_elec_s_37_ec_lv1.0_Ept.png b/doc/img/validation_seasonal_operation_area_elec_s_37_ec_lv1.0_Ept.png similarity index 100% rename from graphics/validation_seasonal_operation_area_elec_s_37_ec_lv1.0_Ept.png rename to doc/img/validation_seasonal_operation_area_elec_s_37_ec_lv1.0_Ept.png diff --git a/doc/img/workflow.png b/doc/img/workflow.png new file mode 100644 index 00000000..0b274af8 Binary files /dev/null and b/doc/img/workflow.png differ diff --git a/doc/index.rst b/doc/index.rst index acff820b..e23744e1 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -74,23 +74,23 @@ greenhouse gas emitters except waste management, agriculture, forestry and land use. The diagram below gives an overview of the sectors and the links between them: -.. image:: ../graphics/multisector_figure.png +.. image:: img/multisector_figure.png :width: 70% :align: center .. note:: You can find showcases of the model's capabilities in the Supplementary Materials of the Joule paper `The potential role of a hydrogen network in Europe - `_, the Supplementary Materials of another `paper in Joule with a + `__, the Supplementary Materials of another `paper in Joule with a description of the industry sector - `_, or in `a 2021 presentation - at EMP-E `_. + `__, or in `a 2021 presentation + at EMP-E `__. The sector-coupled extension of PyPSA-Eur was initially described in the paper `Synergies of sector coupling and transmission reinforcement in a cost-optimised, highly renewable European energy system - `_ (2018) but it differs by being based on the + `__ (2018) but it differs by being based on the higher resolution electricity transmission model `PyPSA-Eur - `_ rather than a one-node-per-country model, + `__ rather than a one-node-per-country model, and by including biomass, industry, industrial feedstocks, aviation, shipping, better carbon management, carbon capture and usage/sequestration, and gas networks. @@ -99,8 +99,8 @@ About ===== PyPSA-Eur is designed to be imported into the open energy system modelling -framework `PyPSA `_ for which `documentation -`_ is available as well. However, since the +framework `PyPSA `__ for which `documentation +`__ is available as well. However, since the workflow is modular, it should be easy to adapt the data workflow to other modelling frameworks. @@ -114,28 +114,28 @@ of the individual parts. PyPSA-Eur is under active development and has several :doc:`limitations` which you should understand before using the model. The Github repository - `issues `_ collect known + `issues `__ collect known topics we are working on. Please feel free to help or make suggestions. This project is currently maintained by the `Department of Digital -Transformation in Energy Systems `_ at the -`Technische Universität Berlin `_. Previous versions were -developed within the `IAI `_ at the `Karlsruhe Institute -of Technology (KIT) `_ which was funded by -the `Helmholtz Association `_, and by the +Transformation in Energy Systems `__ at the +`Technische Universität Berlin `__. Previous versions were +developed within the `IAI `__ at the `Karlsruhe Institute +of Technology (KIT) `__ which was funded by +the `Helmholtz Association `__, and by the `Renewable Energy Group -`_ -at `FIAS `_ to carry out simulations for the -`CoNDyNet project `_, financed by the `German Federal -Ministry for Education and Research (BMBF) `_ +`__ +at `FIAS `__ to carry out simulations for the +`CoNDyNet project `__, financed by the `German Federal +Ministry for Education and Research (BMBF) `__ as part of the `Stromnetze Research Initiative -`_. +`__. Workflow ======== -.. image:: ../graphics/workflow.png +.. image:: img/workflow.png :class: full-width :align: center @@ -153,10 +153,10 @@ to reading this documentation. - Documentation of `PyPSA `__, the package for modelling energy systems which PyPSA-Eur uses under the hood. -- Course on `Energy Systems `_ given at - Technical University of Berlin by `Prof. Dr. Tom Brown `_. -- Course on `Data Science for Energy System Modelling `_ - given at Technical University of Berlin by `Dr. Fabian Neumann `_. +- Course on `Energy Systems `__ given at + Technical University of Berlin by `Prof. Dr. Tom Brown `__. +- Course on `Data Science for Energy System Modelling `__ + given at Technical University of Berlin by `Dr. Fabian Neumann `__. Citing PyPSA-Eur diff --git a/doc/installation.rst b/doc/installation.rst index fbabfd15..45404e1f 100644 --- a/doc/installation.rst +++ b/doc/installation.rst @@ -15,7 +15,7 @@ directory in which the commands following the ``%`` should be entered. Clone the Repository ==================== -First of all, clone the `PyPSA-Eur repository `_ using the version control system ``git`` in the command line. +First of all, clone the `PyPSA-Eur repository `__ using the version control system ``git`` in the command line. .. code:: bash @@ -30,11 +30,11 @@ Install Python Dependencies =============================== PyPSA-Eur relies on a set of other Python packages to function. -We recommend using the package manager `mamba `_ to install them and manage your environments. -For instructions for your operating system follow the ``mamba`` `installation guide `_. +We recommend using the package manager `mamba `__ to install them and manage your environments. +For instructions for your operating system follow the ``mamba`` `installation guide `__. You can also use ``conda`` equivalently. -The package requirements are curated in the `envs/environment.yaml `_ file. +The package requirements are curated in the `envs/environment.yaml `__ file. The environment can be installed and activated using .. code:: bash @@ -59,16 +59,16 @@ Install a Solver PyPSA passes the PyPSA-Eur network model to an external solver for performing the optimisation. PyPSA is known to work with the free software -- `HiGHS `_ -- `Cbc `_ -- `GLPK `_ (`WinGLKP `_) -- `Ipopt `_ +- `HiGHS `__ +- `Cbc `__ +- `GLPK `__ (`WinGLKP `__) +- `SCIP `__ and the non-free, commercial software (for some of which free academic licenses are available) -- `Gurobi `_ -- `CPLEX `_ -- `FICO Xpress Solver `_ +- `Gurobi `__ +- `CPLEX `__ +- `FICO Xpress Solver `__ For installation instructions of these solvers for your operating system, follow the links above. Commercial solvers such as Gurobi and CPLEX currently significantly outperform open-source solvers for large-scale problems, and @@ -76,11 +76,11 @@ it might be the case that you can only retrieve solutions by using a commercial Nevertheless, you can still use open-source solvers for smaller problems. .. seealso:: - `Instructions how to install a solver in the documentation of PyPSA `_ + `Instructions how to install a solver in the documentation of PyPSA `__ .. note:: The rules :mod:`cluster_network` and :mod:`simplify_network` solve a mixed-integer quadratic optimisation problem for clustering. - The open-source solvers HiGHS, Cbc and GlPK cannot handle this. A fallback to SCIP is implemented in this case. + The open-source solvers HiGHS, Cbc and GlPK cannot handle this. A fallback to SCIP is implemented in this case, which is included in the standard environment specifications. For an open-source solver setup install in your ``conda`` environment on OSX/Linux. To install the default solver Gurobi, run .. code:: bash @@ -88,7 +88,7 @@ Nevertheless, you can still use open-source solvers for smaller problems. mamba activate pypsa-eur mamba install -c gurobi gurobi - Additionally, you need to setup your `Gurobi license `_. + Additionally, you need to setup your `Gurobi license `__. .. _defaultconfig: diff --git a/doc/introduction.rst b/doc/introduction.rst index d271391c..d0f7386d 100644 --- a/doc/introduction.rst +++ b/doc/introduction.rst @@ -14,7 +14,7 @@ .. note:: - Find the introductory slides `here `_. + Find the introductory slides `here `__. .. warning:: The video only introduces the electricity-only part of PyPSA-Eur. @@ -23,7 +23,7 @@ Workflow ========= The generation of the model is controlled by the open workflow management system -`Snakemake `_. In a nutshell, the ``Snakefile`` +`Snakemake `__. In a nutshell, the ``Snakefile`` declares for each script in the ``scripts`` directory a rule which describes which files the scripts consume and produce (their corresponding input and output files). The ``snakemake`` tool then runs the scripts in the correct order @@ -35,7 +35,7 @@ For instance, an invocation to .. code:: bash - .../pypsa-eur % snakemake -call results/networks/elec_s_128_ec_lvopt_Co2L-3H.nc + .../pypsa-eur % snakemake -call results/networks/elec_s_128_ec_lvopt_.nc follows this dependency graph @@ -50,13 +50,13 @@ preceding rules which another rule takes as input data. .. note:: The dependency graph was generated using - ``snakemake --dag results/networks/elec_s_128_ec_lvopt_Co2L-3H.nc -F | sed -n "/digraph/,/}/p" | dot -Tpng -o doc/img/intro-workflow.png`` + ``snakemake --dag results/networks/elec_s_128_ec_lvopt_.nc -F | sed -n "/digraph/,/}/p" | dot -Tpng -o doc/img/intro-workflow.png`` For the use of ``snakemake``, it makes sense to familiarize yourself quickly with the `basic tutorial -`_ and then +`__ and then read carefully through the documentation of the `command line interface -`_, noting the +`__, noting the arguments ``-j``, ``-c``, ``-f``, ``-F``, ``-n``, ``-r``, ``--dag`` and ``-t`` in particular. @@ -64,10 +64,10 @@ Scenarios, Configuration and Modification ========================================= It is easy to run PyPSA-Eur for multiple scenarios using the `wildcards feature -`_ +`__ of ``snakemake``. Wildcards allow to generalise a rule to produce all files that follow a `regular expression -`_ pattern, which defines +`__ pattern, which defines a particular scenario. One can think of a wildcard as a parameter that shows up in the input/output file names and thereby determines which rules to run, what data to retrieve and what files to produce. Details are explained in @@ -97,5 +97,5 @@ System Requirements Building the model with the scripts in this repository runs on a regular computer. But optimising for investment and operation decisions across many scenarios requires a strong interior-point solver -like `Gurobi `_ or `CPLEX `_ with more memory. +like `Gurobi `__ or `CPLEX `__ with more memory. Open-source solvers like `HiGHS ` can also be used for smaller problems. diff --git a/doc/licenses.rst b/doc/licenses.rst index 74640ea5..5ab65ca9 100644 --- a/doc/licenses.rst +++ b/doc/licenses.rst @@ -10,12 +10,12 @@ Licenses PyPSA-Eur is released under multiple licenses: -* All original source code is licensed as free software under `MIT `_. -* The documentation is licensed under `CC-BY-4.0 `_. -* Configuration files are mostly licensed under `CC0-1.0 `_. -* Data files are licensed under `CC-BY-4.0 `_. +* All original source code is licensed as free software under `MIT `__. +* The documentation is licensed under `CC-BY-4.0 `__. +* Configuration files are mostly licensed under `CC0-1.0 `__. +* Data files are licensed under `CC-BY-4.0 `__. -See the individual files and the `dep5 <.reuse/dep5>`_ file for license details. +See the individual files and the `dep5 <.reuse/dep5>`__ file for license details. Additionally, different licenses and terms of use also apply to the various input data for both electricity-only and sector-coupled modelling exercises, @@ -26,18 +26,15 @@ Electricity Systems Databundle .. note:: More details are included in `the description of the - data bundles on zenodo `_. - -.. csv-table:: - :header-rows: 1 - :file: configtables/licenses.csv + data bundles on zenodo `__. * BY: Attribute Source * NC: Non-Commercial Use Only * SA: Share Alike -Sector-Coupled Systems Databundle -================================= +.. csv-table:: + :header-rows: 1 + :file: configtables/licenses.csv .. csv-table:: :header-rows: 1 diff --git a/doc/limitations.rst b/doc/limitations.rst index 2aa8ecfe..aeec5da6 100644 --- a/doc/limitations.rst +++ b/doc/limitations.rst @@ -19,7 +19,7 @@ improving the approximations. This list of limitations is incomplete and will be added to over time. .. seealso:: - See also the `GitHub repository issues `_. + See also the `GitHub repository issues `__. - **Electricity transmission network topology:** The grid data is based on a map of the ENTSO-E area that is known diff --git a/doc/preparation.rst b/doc/preparation.rst index bb55ba6b..06e8b19b 100644 --- a/doc/preparation.rst +++ b/doc/preparation.rst @@ -15,19 +15,17 @@ Instead we provide separate data bundles which can be obtained using the ``retrieve*`` rules (:ref:`data`). Having downloaded the necessary data, -- :mod:`build_shapes` generates GeoJSON files with shapes of the countries, exclusive economic zones and `NUTS3 `_ areas. -- :mod:`build_cutout` prepares smaller weather data portions from `ERA5 `_ for cutout ``europe-2013-era5`` and SARAH for cutout ``europe-2013-sarah``. +- :mod:`build_shapes` generates GeoJSON files with shapes of the countries, exclusive economic zones and `NUTS3 `__ areas. +- :mod:`build_cutout` prepares smaller weather data portions from `ERA5 `__ for cutout ``europe-2013-era5`` and SARAH for cutout ``europe-2013-sarah``. With these and the externally extracted ENTSO-E online map topology (``data/entsoegridkit``), it can build a base PyPSA network with the following rules: -- :mod:`base_network` builds and stores the base network with all buses, HVAC lines and HVDC links, while -- :mod:`build_bus_regions` determines `Voronoi cells `_ for all substations. +- :mod:`base_network` builds and stores the base network with all buses, HVAC lines and HVDC links, and determines `Voronoi cells `__ for all substations. Then the process continues by calculating conventional power plant capacities, potentials, and per-unit availability time series for variable renewable energy carriers and hydro power plants with the following rules: -- :mod:`build_powerplants` for today's thermal power plant capacities using `powerplantmatching `_ allocating these to the closest substation for each powerplant, -- :mod:`build_natura_raster` for rasterising NATURA2000 natural protection areas, +- :mod:`build_powerplants` for today's thermal power plant capacities using `powerplantmatching `__ allocating these to the closest substation for each powerplant, - :mod:`build_ship_raster` for building shipping traffic density, - :mod:`build_renewable_profiles` for the hourly capacity factors and installation potentials constrained by land-use in each substation's Voronoi cell for PV, onshore and offshore wind, and - :mod:`build_hydro_profile` for the hourly per-unit hydro power availability time series. @@ -35,13 +33,6 @@ Then the process continues by calculating conventional power plant capacities, p The central rule :mod:`add_electricity` then ties all the different data inputs together into a detailed PyPSA network stored in ``networks/elec.nc``. -.. _busregions: - -Rule ``build_bus_regions`` -============================= - -.. automodule:: build_bus_regions - .. _cutout: Rule ``build_cutout`` @@ -55,14 +46,6 @@ Rule ``prepare_links_p_nom`` .. automodule:: prepare_links_p_nom -.. _natura: - -Rule ``build_natura_raster`` -=============================== - -.. automodule:: build_natura_raster - - .. _base: Rule ``base_network`` diff --git a/doc/release_notes.rst b/doc/release_notes.rst index e08981d3..2e5487ac 100644 --- a/doc/release_notes.rst +++ b/doc/release_notes.rst @@ -7,166 +7,365 @@ Release Notes ########################################## -Upcoming Release -================ +.. Upcoming Release +.. ================ +PyPSA-Eur 0.11.0 (25th May 2024) +===================================== -* bugfix: convert Strings to pathlib.Path objects as input to ConfigSettings +**New Features** -* Allow the use of more solvers in clustering (Xpress, COPT, Gurobi, CPLEX, SCIP, MOSEK). +* Introduced scenario management to support the simultaneous execution of + multiple scenarios with a single ``snakemake`` call. A ``scenarios.yaml`` file + allows customizable scenario names with configuration overrides. To enable, + set ``run: scenarios: true`` and define the list of scenario names under + ``run: name:`` in the configuration file. The scenario file's top-level keys + must match the defined scenario names. + (https://github.com/PyPSA/pypsa-eur/pull/724, + https://github.com/PyPSA/pypsa-eur/pull/975, + https://github.com/PyPSA/pypsa-eur/pull/989, + https://github.com/PyPSA/pypsa-eur/pull/993, + https://github.com/PyPSA/pypsa-eur/pull/1011) -* Enhanced support for choosing different weather years - (https://github.com/PyPSA/pypsa-eur/pull/204): + - A scenarios template file ``config/scenarios.template.yaml`` is included and + copied to ``config/scenarios.yaml`` on first use. + - The scenario file can be changed via ``run: scenarios: file:``. + - Activating scenario management with ``run: scenarios: enable: true`` + introduces a new wildcard ``{run}``. Configuration settings may now depend + on this wildcard. A new ``config_provider()`` function is used in the + ``Snakefile`` and ``.smk`` files to handle wildcard values. + - Scenario files can be programmatically created using + ``config/create_scenarios.py``. This script can be run with ``snakemake -j1 + create_scenarios``. + - The setting ``run: name: all`` will run all scenarios in + ``config/scenarios.yaml``. Otherwise, only the scenarios listed under ``run: + name:`` will run. + - The setting ``run: shared_resources:`` indicates whether resources should be + encapsulated by ``run: name:``. The special setting ``run: shared_resources: + base`` shares resources until ``add_electricity`` that do not contain + wildcards other than ``{"technology", "year", "scope"}``. + - Added new configuration options for all ``{opts}`` and ``{sector_opts}`` + wildcard values to create a unique configuration file (``config.yaml``) per + PyPSA network file using ``update_config_from_wildcards()``. This function + updates the ``snakemake.config`` object with settings from wildcards. + - The cost data was moved from ``data/costs_{year}.csv`` to + ``resources/costs_{year}.csv``. The ``retrieve_cost_data`` rule now calls a + Python script. + - Time clustering settings moved to ``clustering: temporal:`` from + ``snapshots:``, simplifying scenario management. + - Collection rules have a new wildcard ``run=config["run"]["name"]`` to + collect outputs across scenarios. + - Scenarios can be encapsulated in a directory using ``run: prefix:``. + - The ``{sector_opts}`` wildcard is no longer used by default. All scenario + definitions are now in ``config.yaml``. + - **Warning:** Scenario management with myopic or perfect foresight pathway + optimization requires the first investment period to be shared across all + scenarios. The ``wildcard_constraints`` for the ``add_existing_baseyear`` + rule do not accept wildcard-aware input functions. - - Processed energy statistics from eurostat (1990-2021) and IDEES (2000-2015) - are now initially stored for all available years and filtered by the year - given in ``energy: energy_totals_year:``. +* Enhanced support for choosing different weather years. + (https://github.com/PyPSA/pypsa-eur/pull/204) + - Processed energy statistics from Eurostat (1990-2021) and IDEES (2000-2015) + are stored for all available years and filtered by the year in ``energy: + energy_totals_year:``. - Added option to supplement electricity load data with synthetic time series - for years not contained in OPSD (from https://zenodo.org/records/10820928, - ``load: supplement_synthetic:``). - - - The total annual heat demand for years not contained in the energy - statistics by eurostat (1990-2021) or IDEES (2000-2015) are scaled based on - a regression between the total number of heating degree days and the total - annual heat demand between the years 2007-2021, assuming a similar building - stock. - + for years not in OPSD (from https://zenodo.org/records/10820928, ``load: + supplement_synthetic:``). + - Total annual heat demand for years not in Eurostat (1990-2021) or IDEES + (2000-2015) is scaled based on a regression between heating degree days and + heat demand for 2007-2021, assuming a similar building stock. - Added option to scale annual hydro-electricity generation data for years not - contained in the in EIA (1980-2021) based on a regression between annual - generation and total runoff per country for the years 1980-2021 - (``renewable: hydro: eia_approximate_missing:``) - - - Added option to normalize annual hydro generation data by the associated - installed capacity reported by EIA (1980-2021) in order to eliminate changes - in generation due to newly built capacity (``renewable: hydro: - eia_approximate_missing: eia_correct_by_capacity:``). - + in EIA (1980-2021) based on a regression between annual generation and total + runoff per country for 1980-2021 (``renewable: hydro: + eia_approximate_missing:``). + - Added option to normalize annual hydro generation data by the installed + capacity reported by EIA (1980-2021) to eliminate changes due to newly built + capacity (``renewable: hydro: eia_approximate_missing: + eia_correct_by_capacity:``). - Added option to make hydro generation data independent of weather year (``renewable: hydro: eia_approximate_missing: eia_norm_year:``). - - Added option to drop leap days (``enable: drop_leap_day:``). - - - Added option to make electric load data independent of weather year - (``load: fixed_year:``). - - - Include time series of Swiss number of passenger vehicles from the `Swiss - Federal Statistical Office - `_. - + - Added option to make electric load data independent of weather year (``load: + fixed_year:``). + - Include time series of Swiss passenger vehicles from the Swiss Federal + Statistical Office. - Updated hydro-electricity generation and capacity data from EIA. + - The easiest way to use multiple weather years is with the new scenario + management. An example `create_scenarios.py` script is available in this + `Github gist + `__. - - The easiest way to sweep over multiple weather years is to use the new - scenario management. An example for the necessary `create_scenarios.py` - script can be found in this `Github gist - `_. +* New renewable technologies: + + - Solar PV with single-axis horizontal tracking (N-S axis), carrier: + ``solar-hsat``. (https://github.com/PyPSA/pypsa-eur/pull/1066) + - Floating offshore wind technology for water depths below 60m, carrier: + ``offwind-float``. (https://github.com/PyPSA/pypsa-eur/pull/773) + +* Added default values for power distribution losses, assuming uniform 3% losses + on distribution grid links. These are deducted from national load time series + to avoid double counting. Extensions for country-specific loss factors and + planning horizon developments are planned. + +* Added ``industry: HVC_environment_sequestration_fraction:`` to specify the + fraction of carbon in plastics that is permanently sequestered in landfills. + The default assumption is that all carbon in plastics is eventually released + to the atmosphere. (https://github.com/PyPSA/pypsa-eur/pull/1060) + +* Added options for building waste-to-energy plants with and without carbon + capture to consume non-recycled and non-sequestered plastics. Config settings: + ``industry: waste_to_energy:`` and ``industry: waste_to_energy_cc``. This + excludes municipal solid waste. (https://github.com/PyPSA/pypsa-eur/pull/1060) + +* Added option to post-discretize line and link capacities based on unit sizes + and rounding thresholds in the configuration under ``solving: options: + post_discretization:``. This is disabled by default. + (https://github.com/PyPSA/pypsa-eur/pull/1064) + +* Time aggregation for sector-coupled networks is now its own rule + :mod:`time_aggregation`. Time aggregation is constant over planning horizons + of the same network when using time step segmentation. + (https://github.com/PyPSA/pypsa-eur/pull/1065, + https://github.com/PyPSA/pypsa-eur/pull/1075) + +* Added config ``run: shared_resources: exclude:`` to specify files excluded + from shared resources with ``run: shared_resources: base``. The function + ``_helpers/get_run_path()`` now takes an additional keyword argument + ``exclude_from_shared`` with a list of files that should not be shared. + (https://github.com/PyPSA/pypsa-eur/pull/1050) + +* Added existing biomass boilers in :mod:`add_existing_baseyear`. + (https://github.com/PyPSA/pypsa-eur/pull/951) + +* Added new HVDC transmission projects from `TYNDP 2024 draft projects + `__. + (https://github.com/PyPSA/pypsa-eur/pull/982) + +* Linearly interpolated missing investment periods in year-dependent + configuration options. (https://github.com/PyPSA/pypsa-eur/pull/943) + +* Added shapes to the ``netCDF`` files for different stages of the network + object in `base_network`, `simplify_network`, and `cluster_network`. The + `build_bus_regions` rule is now integrated into the `base_network` rule. + (https://github.com/PyPSA/pypsa-eur/pull/1013, + https://github.com/PyPSA/pypsa-eur/pull/1051) + +* Added config ``land_transport_demand_factor`` to model growth in land + transport demand for different time horizons. + +* Allowed dictionary for ``aviation_demand_factor`` to specify changes in + aviation demand by investment period. + +* Allowed more solvers in clustering (Xpress, COPT, Gurobi, CPLEX, SCIP, MOSEK). + (https://github.com/PyPSA/pypsa-eur/pull/949) + +* Added option to download cost data from custom fork of ``technology-data``. + (https://github.com/PyPSA/pypsa-eur/pull/970) + +* Added ``nodal_supply_energy`` to :mod:`make_summary`. + (https://github.com/PyPSA/pypsa-eur/pull/1046) + +**Breaking Changes** + +* Upgraded to Snakemake v8.5+. This version is the new minimum requirement. To + upgrade an existing environment, run ``conda install -c bioconda + snakemake-minimal">=8.5"`` and ``pip install snakemake-storage-plugin-http``. + (https://github.com/PyPSA/pypsa-eur/pull/825) + +* Removed exogenously set share of rooftop PV (``costs: rooftop_share:``). + Rooftop and utility-scale PV are now separate technologies with endogenous + shares. * Removed rule ``copy_config``. Instead, a config file is created for each network output of the ``solve_*`` rules, with the same content as ``n.meta``. + (https://github.com/PyPSA/pypsa-eur/pull/965) -* Added new HVDC transmission projects from `TYNDP 2024 draft projects - `_. +* Moved switch ``run: shared_resources:`` to ``run: shared_resources: policy:``. -* Upgrade to Snakemake v8.5+. This version is the new minimum version required. - To upgrade an existing environment, run ``conda install -c bioconda - snakemake-minimal">=8.5"`` and ``pip install snakemake-storage-plugin-http`` - (https://github.com/PyPSA/pypsa-eur/pull/825). +**Changes** -* Corrected a bug leading to power plants operating after their DateOut - (https://github.com/PyPSA/pypsa-eur/pull/958). Added additional grouping years - before 1980. +* Updated, merged, and reduced data bundle: + (https://github.com/PyPSA/pypsa-eur/pull/1020, + https://github.com/PyPSA/pypsa-eur/pull/1027) -* The Eurostat data was updated to the 2023 version in :mod:`build_energy_totals`. + - Merged electricity-only and sector-coupled data bundles into one bundle. + This removed the ``retrieve_sector_databundle`` rule. + - Included rasterised ``natura.tiff`` in the data bundle and removed the + ``retrieve_natura_raster`` rule. + - Removed the ``build_natura_raster`` rule due to its infrequent use and + significant data bundle size increase. + - Removed outdated files from the data bundle (e.g., Eurostat energy + balances). + - Reduced the spatial scope of GEBCO bathymetry data to Europe to save space. + - Removed a separate data bundle for tutorials. + - Directly downloaded the `Hotmaps Industrial Database + `__ + from the source, removing ``Industrial_Database.csv`` from the data bundle. -* The latest `Swiss energy totals - `_ - have been updated to the 2023 version. +* Updated energy statistics: (https://github.com/PyPSA/pypsa-eur/pull/947, + https://github.com/PyPSA/pypsa-eur/pull/973, + https://github.com/PyPSA/pypsa-eur/pull/990, + https://github.com/PyPSA/pypsa-eur/pull/1025, + https://github.com/PyPSA/pypsa-eur/pull/1074) -* The JRC-IDEES data is only available until 2015. For energy totals years (``energy: energy_totals_year``) after - 2015, the data scaled using the ratio of Eurostat data reported for the energy - totals year and 2015. + - Updated Eurostat data to the 2023 version in :mod:`build_energy_totals`. + - Updated the latest Swiss energy totals to the 2023 version. + - Scaled JRC-IDEES data using the ratio of Eurostat data for energy totals + years after 2015 and 2015. + - Updated default energy totals year to 2019. + - Updated energy balances for residential demands (space, water, cooking) in + JRC-IDEES data with newer Eurostat values. -* The default energy totals year (``energy: energy_totals_year``) was updated to 2019. +* Improved documentation: (https://github.com/PyPSA/pypsa-eur/pull/1017, + https://github.com/PyPSA/pypsa-eur/pull/1014) -* Upgrade default techno-economic assumptions to ``technology-data`` v0.8.1. + - Clarified that ``solving: rolling_horizon:`` only works for + :mod:`solve_operations_network`, not for networks with sector-coupling or + investment variables. + - Clarified suffix usage in `add_existing_baseyear`. + - Added documentation section for contributing documentation. -* Add possibility to download cost data from custom fork of ``technology-data``. +* Included gas and oil fields and saline aquifers for estimating carbon + sequestration potentials. (https://github.com/PyPSA/pypsa-eur/pull/1010, + https://github.com/PyPSA/pypsa-eur/pull/983) -* Linearly interpolate missing investment periods in year-dependent - configuration options. +* Doubled solar rooftop potentials to roughly 1 TW for Europe based on recent + European Commission reports. -* Added new scenario management that supports the simultaneous execution of - multiple scenarios with a single ``snakemake`` call. For this purpose, a - ``scenarios.yaml`` file is introduced which contains customizable scenario - names with configuration overrides. To enable it, set the ``run: scenarios: - true`` and define the list of scenario names to run under ``run: name:`` in - the configuration file. The latter must be a subset of toplevel keys in the - scenario file. +* Consistently sourced data on existing renewable capacities from + ``powerplantmatching``. Removed ``retrieve_irena`` rule. Updated the dataset + to include 2023 values. (https://github.com/PyPSA/pypsa-eur/pull/1018) - - To get started, a scenarios template file ``config/scenarios.template.yaml`` - is included in the repository, which is copied to ``config/scenarios.yaml`` - on first use. +* Added methanol consumption in industry as reported in the DECHEMA report + directly as methanol demand. (https://github.com/PyPSA/pypsa-eur/pull/1068) - - The scenario file can be changed via ``run: scenarios: file:``. +* Adapted disabling of transmission expansion in myopic foresight optimizations + when the limit is reached to handle cost limits. + (https://github.com/PyPSA/pypsa-eur/pull/952, + https://github.com/PyPSA/pypsa-eur/pull/1076) - - If scenario management is activated with ``run: scenarios: enable: true``, a - new wildcard ``{run}`` is introduced. This means that the configuration - settings may depend on the new ``{run}`` wildcard. Therefore, a new - ``config_provider()`` function is used in the ``Snakefile`` and ``.smk`` - files, which takes wildcard values into account. The calls to the ``config`` - object have been reduced in ``.smk`` files since there is no awareness of - wildcard values outside rule definitions. +* Improved the behavior of ``agg_p_nom_limits``: Moved configuration to + ``solving``; added the ability to aggregate all ``offwind`` types; added + option to consider existing capacities; added option to distinguish by + planning horizon. (https://github.com/PyPSA/pypsa-eur/pull/1023) - - The scenario files can also be programmatically created using the template - script ``config/create_scenarios.py``. This script can be run with - ``snakemake -j1 create_scenarios`` and creates the scenarios file referenced - under ``run: scenarios: file:``. +* Disabled ``electricity: everywhere_powerplants``` by default to save memory in + :mod:`simplify_network`. - - The setting ``run: name: all`` will run all scenarios in - ``config/scenarios.yaml``. Otherwise, it will run those passed as list in - ``run: name:`` as long as ``run: scenarios: enable: true``. +* Moved non-essential example configuration files to ``config/examples``. - - The setting ``run: shared_resources:`` indicates via a boolean whether the - resources should be encapsulated by the ``run: name:``. The special setting - ``run: shared_resources: base`` shares resources until ``add_electricity`` - that do not contain wildcards other than ``{"technology", "year", - "scope"}``. +* Outputs of the retrieve rules are no longer marked as ``protected()``. - - Added new configuration options for all ``{opts}`` and ``{sector_opts}`` - wildcard values to create a unique configuration file (``config.yaml``) per - PyPSA network file. This is done with the help of a new function - ``update_config_from_wildcards()`` which parses configuration settings from - wildcards and updates the ``snakemake.config`` object. These updated - configuration settings are used in the scripts rather than directly parsed - values from ``snakemake.wildcards``. +* Improved carbon budget distribution plot. + (https://github.com/PyPSA/pypsa-eur/pull/1070) - - The cost data was moved from ``data/costs_{year}.csv`` to - ``resources/costs_{year}.csv`` since it depends on configuration settings. - The ``retrieve_cost_data`` rule was changed to calling a Python script. +* Moved all graphics to ``doc/img``. + (https://github.com/PyPSA/pypsa-eur/pull/1052) - - Moved time clustering settings to ``clustering: temporal:`` from - ``snapshots:`` so that the latter is only used to define the - ``pandas.DatetimeIndex`` which simplifies the scenario management. +* Connection costs calculated in :mod:`simplify_network` are no longer written + to file. (https://github.com/PyPSA/pypsa-eur/pull/1031) - - Collection rules get a new wildcard ``run=config["run"]["name"]`` so they - can collect outputs across different scenarios. +**Bugs and Compatibility** - - **Warning:** One caveat remains for the scenario management with myopic or - perfect foresight pathway optimisation. The first investment period must be - shared across all scenarios. The reason is that the ``wildcard_constraints`` - defined for the rule ``add_existing_baseyear`` do not accept wildcard-aware - input functions (cf. - `https://github.com/snakemake/snakemake/issues/2703`_). +* Updated ``technology-data`` to version v0.9.0. -* The outputs of the rule ``retrieve_gas_infrastructure_data`` no longer - marked as ``protected()`` as the download size is small. +* Bumped minimum ``powerplantmatching`` version to v0.5.15. + (https://github.com/PyPSA/pypsa-eur/pull/1057) -* Bugfix: allow modelling sector-coupled landlocked regions. (Fixed handling of offshore wind.) +* Bugfix: The configuration setting ``electricity: + estimate_renewable_capacities: enable:`` for rule :mod:`add_electricity` is + not compatible with ``foresight: myopic``. The logic now skips adding existing + renewable capacities in :mod:`add_electricity` if the foresight mode is + ``myopic``. (https://github.com/PyPSA/pypsa-eur/pull/1080) -* Adapt the disabling of transmission expansion in myopic foresight optimisations when limit is already reached to also handle cost limits. +* Bugfix: Ensure gas-fired power plants are correctly added as OCGT or CCGT in + :mod:`add_electricity`. Previously, they were always added as OCGT. -* Fix duplicated years in `add_land_use_constraint_m`. +* Bugfix: Fix distinction of temperature-dependent correction factors for the + energy demand of electric vehicles and ICEs fuel cell cars. + (https://github.com/PyPSA/pypsa-eur/pull/957) -* Fix type error with `m` option in `cluster_network`. +* Bugfix: Ensure all industry coal demands are considered when using + ``sector_ratios_fraction_future``. + (https://github.com/PyPSA/pypsa-eur/pull/1047) + +* Bugfix: Add existing heat pumps to low-voltage level. + (https://github.com/PyPSA/pypsa-eur/pull/948) + +* Fixed gas network retrofitting to hydrogen in :mod:`add_brownfield` for myopic + pathway studies. (https://github.com/PyPSA/pypsa-eur/pull/1036) + +* Bugfix: Consider decommissioning of existing renewable assets in + :mod:`add_existing_baseyear`. (https://github.com/PyPSA/pypsa-eur/pull/1001, + https://github.com/PyPSA/pypsa-eur/pull/959) + +* Bugfix: Adjust build year groups of existing capacities for consistency with + optimized capacities per planning horizon. The previous setup neglected some + existing heating capacities. (https://github.com/PyPSA/pypsa-eur/pull/1019) + +* Bugfix: Corrected a bug causing power plants to operate after their + ``DateOut``. Added additional grouping years before 1980. + (https://github.com/PyPSA/pypsa-eur/pull/958) + +* Bugfix: Allow modeling sector-coupled landlocked regions by handling the + absence of offshore wind. (https://github.com/PyPSA/pypsa-eur/pull/944) + +* Bugfix: Correct approximation of hydropower generation if Portugal or Spain + are not included. (https://github.com/PyPSA/pypsa-eur/pull/1054) + +* Bugfix: In :mod:`build_electricity_demand`, ensure load data is only added if + the country is included in the configuration. + (https://github.com/PyPSA/pypsa-eur/pull/1054) + +* Bugfix: Skip heat bus for CHPs in areas without central heating. + (https://github.com/PyPSA/pypsa-eur/pull/1021) + +* Bugfix: Avoid duplicated offshore regions. + +* Fixed type error with ``m`` option in :mod:`cluster_network`. + (https://github.com/PyPSA/pypsa-eur/pull/986) + +* Fixed error with ``symbol`` column of buses in :mod:`simplify_network`. + (https://github.com/PyPSA/pypsa-eur/pull/987) + +* Fixed index of existing capacities in + ``add_power_capacities_installed_before_baseyear`` with ``m`` option. + (https://github.com/PyPSA/pypsa-eur/pull/1002) + +* Fixed reading in custom busmaps in :mod:`cluster_network`. + (https://github.com/PyPSA/pypsa-eur/pull/1008) + +* Fixed ``p_nom_min`` of renewables generators for myopic approach and added + check of existing capacities in ``add_land_use_constraint_m``. + (https://github.com/PyPSA/pypsa-eur/pull/1022, + https://github.com/PyPSA/pypsa-eur/pull/1029) + +* Fixed duplicated years and grouping years reference in + ``add_land_use_constraint_m``. (https://github.com/PyPSA/pypsa-eur/pull/991, + https://github.com/PyPSA/pypsa-eur/pull/968) + +* Fixed filling of missing data in + ``build_industry_sector_ratios_intermediate``. + (https://github.com/PyPSA/pypsa-eur/pull/1004) + +* Fixed file name encoding in optional rule :mod:`build_biomass_transport_costs` + depending on the operating system. + (https://github.com/PyPSA/pypsa-eur/pull/769) + +* Technical fix for constraint function ``add_operational_reserve_margin``. + (https://github.com/PyPSA/pypsa-eur/pull/1071) + +* Technical fix for constraint function ``add_BAU_constraints``. + (https://github.com/PyPSA/pypsa-eur/pull/1024) + +* Fixed network clustering and simplification issues caused by adding TYNDP + links. (https://github.com/PyPSA/pypsa-eur/pull/1067) + +* Bugfix: Ensure correct indexing of weights in :mod:`cluster_network`. + (https://github.com/PyPSA/pypsa-eur/pull/988) + +* Bugfix: Only sanitize locations when there are buses with a location. + (https://github.com/PyPSA/pypsa-eur/pull/971) PyPSA-Eur 0.10.0 (19th February 2024) ===================================== @@ -259,10 +458,10 @@ PyPSA-Eur 0.10.0 (19th February 2024) * Default settings for recycling rates and primary product shares of high-value chemicals have been set in accordance with the values used in `Neumann et al. - (2023) `_ linearly interpolated + (2023) `__ linearly interpolated between 2020 and 2050. The recycling rates are based on data from `Agora Energiewende (2021) - `_. + `__. * Air-sourced heat pumps can now also be built in rural areas. Previously, only ground-sourced heat pumps were considered for this category @@ -380,7 +579,7 @@ PyPSA-Eur 0.10.0 (19th February 2024) (https://github.com/PyPSA/pypsa-eur/pull/861). Special thanks for this release to Koen van Greevenbroek (`@koen-vg -`_) for various new features, bugfixes and taking +`__) for various new features, bugfixes and taking care of deprecations. @@ -417,18 +616,18 @@ PyPSA-Eur 0.9.0 (5th January 2024) * Add locations, capacities and costs of existing gas storage using Global Energy Monitor's `Europe Gas Tracker - `_ + `__ (https://github.com/PyPSA/pypsa-eur/pull/835). * Add option to use `LUISA Base Map - `_ 50m land + `__ 50m land coverage dataset for land eligibility analysis in :mod:`build_renewable_profiles`. Settings are analogous to the CORINE dataset but with the key ``luisa:`` in the configuration file. To leverage the dataset's full advantages, set the excluder resolution to 50m (``excluder_resolution: 50``). For land category codes, see `Annex 1 of the technical documentation - `_ + `__ (https://github.com/PyPSA/pypsa-eur/pull/842). * Add option to capture CO2 contained in biogas when upgrading (``sector: @@ -671,7 +870,7 @@ PyPSA-Eur 0.8.1 (27th July 2023) * Add option to consider dynamic line rating based on wind speeds and temperature according to `Glaum and Hofmann (2022) - `_. See configuration section ``lines: + `__. See configuration section ``lines: dynamic_line_rating:`` for more details. (https://github.com/PyPSA/pypsa-eur/pull/675) * Add option to include a piecewise linear approximation of transmission losses, @@ -690,7 +889,7 @@ PyPSA-Eur 0.8.1 (27th July 2023) * A ``param:`` section in the snakemake rule definitions was added to track changed settings in ``config.yaml``. The goal is to automatically re-execute rules where parameters have changed. See `Non-file parameters for rules - `_ + `__ in the snakemake documentation. (https://github.com/PyPSA/pypsa-eur/pull/663) * A new function named ``sanitize_carrier`` ensures that all unique carrier @@ -799,7 +998,7 @@ PyPSA-Eur 0.8.0 (18th March 2023) * The :mod:`solve_network` script now uses the ``linopy`` backend of PyPSA and is applied for both electricity-only and sector-coupled models. This requires an adjustment of custom ``extra_functionality``. - See the `migration guide `_ in the PyPSA documentation. + See the `migration guide `__ in the PyPSA documentation. * The configuration file ``config.default.yaml`` now also includes settings for sector-coupled models, which will be ignored when the user runs @@ -904,7 +1103,7 @@ PyPSA-Eur 0.7.0 (16th February 2023) inclusive:`` to address the upstream deprecation with ``pandas=1.4``. The previous setting ``None`` is no longer supported and replaced by ``both``, see the `pandas documentation - `_. + `__. Minimum version is now ``pandas>=1.4``. * The configuration setting ``summary_dir`` was removed. @@ -958,7 +1157,7 @@ PyPSA-Eur 0.6.1 (20th September 2022) * Individual commits are now tested against pre-commit hooks. This includes black style formatting, sorting of package imports, Snakefile formatting and others. Installation instructions can for the pre-commit can be found `here - `_. + `__. * Pre-commit CI is now part of the repository's CI. @@ -981,7 +1180,7 @@ PyPSA-Eur 0.6.0 (10th September 2022) * Functionality to consider shipping routes when calculating the available area for offshore technologies were added. Data for the shipping density comes from the `Global Shipping Traffic Density dataset - `_. + `__. * When transforming all transmission lines to a unified voltage level of 380kV, the workflow now preserves the transmission capacity rather than electrical @@ -1018,7 +1217,7 @@ PyPSA-Eur 0.5.0 (27th July 2022) ``from_opsd`` to ``True``. * Add operational reserve margin constraint analogous to `GenX implementation - `_. Can be activated + `__. Can be activated with config setting ``electricity: operational_reserve:``. * Implement country-specific Energy Availability Factors (EAFs) for nuclear @@ -1044,12 +1243,12 @@ PyPSA-Eur 0.5.0 (27th July 2022) * Techno-economic parameters of technologies (e.g. costs and efficiencies) will now be retrieved from a separate repository `PyPSA/technology-data - `_ that collects assumptions from a + `__ that collects assumptions from a variety of sources. It is activated by default with ``enable: retrieve_cost_data: true`` and controlled with ``costs: year:`` and ``costs: version:``. The location of this data changed from ``data/costs.csv`` to ``resources/costs.csv`` [`#184 - `_]. + `__]. * A new section ``conventional`` was added to the config file. This section contains configurations for conventional carriers. @@ -1064,18 +1263,18 @@ PyPSA-Eur 0.5.0 (27th July 2022) * Add an efficiency factor of 88.55% to offshore wind capacity factors as a proxy for wake losses. More rigorous modelling is `planned - `_ [`#277 - `_]. + `__ [`#277 + `__]. * Following discussion in `#285 - `_ we have disabled the + `__ we have disabled the correction factor for solar PV capacity factors by default while satellite data is used. A correction factor of 0.854337 is recommended if reanalysis data like ERA5 is used. * The default deployment density of AC- and DC-connected offshore wind capacity is reduced from 3 MW/sqkm to a more conservative estimate of 2 MW/sqkm [`#280 - `_]. + `__]. * The inclusion of renewable carriers is now specified in the config entry ``renewable_carriers``. Before this was done by commenting/uncommenting @@ -1104,12 +1303,12 @@ PyPSA-Eur 0.5.0 (27th July 2022) * Resource definitions for memory usage now follow `Snakemake standard resource definition - `_ + `__ ``mem_mb`` rather than ``mem``. * The powerplants that have been shut down by 2021 are filtered out. -* Updated historical `EIA hydro generation data `_. +* Updated historical `EIA hydro generation data `__. * Network building is made deterministic by supplying a fixed random state to network clustering routines. @@ -1158,24 +1357,24 @@ Synchronisation Release - Ukraine and Moldova (17th March 2022) --------------------------------------------------------------- On March 16, 2022, the transmission networks of Ukraine and Moldova have -successfully been `synchronised with the continental European grid `_. We have taken +successfully been `synchronised with the continental European grid `__. We have taken this as an opportunity to add the power systems of Ukraine and Moldova to PyPSA-Eur. This includes: .. image:: img/synchronisation.png :width: 500 -* the transmission network topology from the `ENTSO-E interactive map `_. +* the transmission network topology from the `ENTSO-E interactive map `__. -* existing power plants (incl. nuclear, coal, gas and hydro) from the `powerplantmatching `_ tool +* existing power plants (incl. nuclear, coal, gas and hydro) from the `powerplantmatching `__ tool -* country-level load time series from ENTSO-E through the `OPSD platform `_, which are then distributed heuristically to substations by GDP and population density. +* country-level load time series from ENTSO-E through the `OPSD platform `__, which are then distributed heuristically to substations by GDP and population density. * wind and solar profiles based on ERA5 and SARAH-2 weather data -* hydro profiles based on historical `EIA generation data `_ +* hydro profiles based on historical `EIA generation data `__ -* a simplified calculation of wind and solar potentials based on the `Copernicus Land Cover dataset `_. +* a simplified calculation of wind and solar potentials based on the `Copernicus Land Cover dataset `__. * electrical characteristics of 750 kV transmission lines @@ -1196,18 +1395,18 @@ PyPSA-Eur 0.4.0 (22th September 2021) * With this release, we change the license from copyleft GPLv3 to the more liberal MIT license with the consent of all contributors - [`#276 `_]. + [`#276 `__]. * Switch to the new major ``atlite`` release v0.2. The version upgrade comes along with significant speed up for the rule ``build_renewable_profiles.py`` (~factor 2). A lot of the code which calculated the land-use availability is now outsourced and does not rely on ``glaes``, ``geokit`` anymore. This facilitates the environment building and version compatibility of ``gdal``, ``libgdal`` with - other packages [`#224 `_]. + other packages [`#224 `__]. * Implemented changes to ``n.snapshot_weightings`` in new PyPSA version v0.18 - (cf. `PyPSA/PyPSA/#227 `_) - [`#259 `_]. + (cf. `PyPSA/PyPSA/#227 `__) + [`#259 `__]. * Add option to pre-aggregate nodes without power injections (positive or negative, i.e. generation or demand) to electrically closest nodes or neighbors @@ -1216,18 +1415,18 @@ PyPSA-Eur 0.4.0 (22th September 2021) * In :mod:`simplify_network`, bus columns with no longer correct entries are removed (symbol, tags, under_construction, substation_lv, substation_off) - [`#219 `_] + [`#219 `__] * Add option to include marginal costs of links representing fuel cells, electrolysis, and battery inverters - [`#232 `_]. + [`#232 `__]. * The rule and script ``build_country_flh`` are removed as they are no longer used or maintained. * The connection cost of generators in :mod:`simplify_network` are now reported in ``resources/connection_costs_s{simpl}.csv`` - [`#261 `_]. + [`#261 `__]. * The tutorial cutout was renamed from ``cutouts/europe-2013-era5.nc`` to ``cutouts/be-03-2013-era5.nc`` to accommodate tutorial and productive @@ -1237,72 +1436,72 @@ PyPSA-Eur 0.4.0 (22th September 2021) potentials was deprecated and now defaults to ``True``. * Update dependencies in ``envs/environment.yaml`` - [`#257 `_] + [`#257 `__] * Continuous integration testing switches to Github Actions from Travis CI - [`#252 `_]. + [`#252 `__]. * Documentation on readthedocs.io is now built with ``pip`` only and no longer - requires ``conda`` [`#267 `_]. + requires ``conda`` [`#267 `__]. -* Use ``Citation.cff`` [`#273 `_]. +* Use ``Citation.cff`` [`#273 `__]. **Bugs and Compatibility** -* Support for PyPSA v0.18 [`#268 `_]. +* Support for PyPSA v0.18 [`#268 `__]. * Minimum Python version set to ``3.8``. -* Removed ``six`` dependency [`#245 `_]. +* Removed ``six`` dependency [`#245 `__]. * Update :mod:`plot_network` and :mod:`make_summary` rules to latest PyPSA - versions [`#270 `_]. + versions [`#270 `__]. * Keep converter links to store components when using the ``ATK`` - wildcard and only remove DC links [`#214 `_]. + wildcard and only remove DC links [`#214 `__]. * Value for ``co2base`` in ``config.yaml`` adjusted to 1.487e9 t CO2-eq (from 3.1e9 t CO2-eq). The new value represents emissions related to the electricity sector for EU+UK+Balkan. The old value was too high and used when the emissions wildcard in ``{opts}`` was used - [`#233 `_]. + [`#233 `__]. * Add escape in :mod:`base_network` if all TYNDP links are already contained in the network - [`#246 `_]. + [`#246 `__]. * In :mod:`solve_operations_network` the optimised capacities are now fixed for all extendable links, not only HVDC links - [`#244 `_]. + [`#244 `__]. * The ``focus_weights`` are now also considered when pre-clustering in the :mod:`simplify_network` rule - [`#241 `_]. + [`#241 `__]. * in :mod:`build_renewable_profile` where offshore wind profiles could - no longer be created [`#249 `_]. + no longer be created [`#249 `__]. * Lower expansion limit of extendable carriers is now set to the existing capacity, i.e. ``p_nom_min = p_nom`` (0 before). Simultaneously, the upper limit (``p_nom_max``) is now the maximum of the installed capacity (``p_nom``) and the previous estimate based on land availability (``p_nom_max``) - [`#260 `_]. + [`#260 `__]. * Solving an operations network now includes optimized store capacities as well. Before only lines, links, generators and storage units were considered - [`#269 `_]. + [`#269 `__]. * With ``load_shedding: true`` in the solving options of ``config.yaml`` load shedding generators are only added at the AC buses, excluding buses for H2 - and battery stores [`#269 `_]. + and battery stores [`#269 `__]. * Delete duplicated capital costs at battery discharge link - [`#240 `_]. + [`#240 `__]. * Propagate the solver log file name to the solver. Previously, the PyPSA network solving functions were not told about the solver logfile specified - in the Snakemake file [`#247 `_] + in the Snakemake file [`#247 `__] PyPSA-Eur 0.3.0 (7th December 2020) ----------------------------------- @@ -1315,29 +1514,29 @@ Using the ``{opts}`` wildcard for scenario: For example ``EQ0.5c`` set in the ``{opts}`` wildcard requires each country to produce on average at least 50% of its consumption. Additionally, the option ``ATK`` requires autarky at each node and removes all means of power transmission through lines and links. ``ATKc`` only removes cross-border transfer capacities. - [`#166 `_]. + [`#166 `__]. * Added an option to alter the capital cost (``c``) or installable potentials (``p``) of carriers by a factor via ``carrier+{c,p}factor`` in the ``{opts}`` wildcard. This can be useful for exploring uncertain cost parameters. Example: ``solar+c0.5`` reduces the capital cost of solar to 50% of original values - [`#167 `_, `#207 `_]. + [`#167 `__, `#207 `__]. * Added an option to the ``{opts}`` wildcard that applies a time series segmentation algorithm based on renewables, hydro inflow and load time series to produce a given total number of adjacent snapshots of varying lengths. This feature is an alternative to downsampling the temporal resolution by simply averaging and - uses the `tsam `_ package - [`#186 `_]. + uses the `tsam `__ package + [`#186 `__]. More OPSD integration: -* Add renewable power plants from `OPSD `_ to the network for specified technologies. +* Add renewable power plants from `OPSD `__ to the network for specified technologies. This will overwrite the capacities calculated from the heuristic approach in :func:`estimate_renewable_capacities()` - [`#212 `_]. + [`#212 `__]. -* Electricity consumption data is now retrieved directly from the `OPSD website `_ using the rule :mod:`build_electricity_demand`. +* Electricity consumption data is now retrieved directly from the `OPSD website `__ using the rule :mod:`build_electricity_demand`. The user can decide whether to take the ENTSO-E power statistics data (default) or the ENTSO-E transparency data - [`#211 `_]. + [`#211 `__]. Other: @@ -1345,93 +1544,93 @@ Other: Then, the rule looks for custom busmaps at ``data/custom_busmap_elec_s{simpl}_{clusters}.csv``, which should have the same format as ``resources/busmap_elec_s{simpl}_{clusters}.csv``. i.e. the index should contain the buses of ``networks/elec_s{simpl}.nc`` - [`#193 `_]. + [`#193 `__]. * Line and link capacities can be capped in the ``config.yaml`` at ``lines: s_nom_max:`` and ``links: p_nom_max``: - [`#166 `_]. + [`#166 `__]. * Added Google Cloud Platform tutorial (for Windows users) - [`#177 `_]. + [`#177 `__]. **Changes** * Don't remove capital costs from lines and links, when imposing a line volume limit (``lv``) or a line cost limit (``lc``). Previously, these were removed to move the expansion in direction of the limit - [`#183 `_]. + [`#183 `__]. * The mappings for clustered lines and buses produced by the :mod:`simplify_network` and :mod:`cluster_network` rules changed from Hierarchical Data Format (``.h5``) to Comma-Separated Values format (``.csv``) for ease of use. - [`#198 `_] + [`#198 `__] * The N-1 security margin for transmission lines is now fixed to a provided value in ``config.yaml``, removing an undocumented linear interpolation between 0.5 and 0.7 in the range between 37 and 200 nodes. - [`#199 `_]. + [`#199 `__]. * Modelling hydrogen and battery storage with Store and Link components is now the default, rather than using StorageUnit components with fixed power-to-energy ratio - [`#205 `_]. + [`#205 `__]. * Use ``mamba`` (https://github.com/mamba-org/mamba) for faster Travis CI builds - [`#196 `_]. + [`#196 `__]. * Multiple smaller changes: Removed unused ``{network}`` wildcard, moved environment files to dedicated ``envs`` folder, removed sector-coupling components from configuration files, updated documentation colors, minor refactoring and code cleaning - [`#190 `_]. + [`#190 `__]. **Bugs and Compatibility** * Add compatibility for pyomo 5.7.0 in :mod:`cluster_network` and :mod:`simplify_network` - [`#172 `_]. + [`#172 `__]. * Fixed a bug for storage units such that individual store and dispatch efficiencies are correctly taken account of rather than only their round-trip efficiencies. In the cost database (``data/costs.csv``) the efficiency of battery inverters should be stated as per discharge/charge rather than per roundtrip - [`#202 `_]. + [`#202 `__]. * Corrected exogenous emission price setting (in ``config: cost: emission price:``), which now correctly accounts for the efficiency and effective emission of the generators - [`#171 `_]. + [`#171 `__]. * Corrected HVDC link connections (a) between Norway and Denmark and (b) mainland Italy, Corsica (FR) and Sardinia (IT) as well as for East-Western and Anglo-Scottish interconnectors - [`#181 `_, `#206 `_]. + [`#181 `__, `#206 `__]. * Fix bug of clustering ``offwind-{ac,dc}`` generators in the option of high-resolution generators for renewables. Now, there are more sites for ``offwind-{ac,dc}`` available than network nodes. Before, they were clustered to the resolution of the network (``elec_s1024_37m.nc``: 37 network nodes, 1024 generators) - [`#191 `_]. + [`#191 `__]. * Raise a warning if ``tech_colors`` in the config are not defined for all carriers - [`#178 `_]. + [`#178 `__]. PyPSA-Eur 0.2.0 (8th June 2020) ------------------------------- -* The optimization is now performed using the ``pyomo=False`` setting in the :func:`pypsa.lopf.network_lopf`. This speeds up the solving process significantly and consumes much less memory. The inclusion of additional constraints were adjusted to the new implementation. They are all passed to the :func:`network_lopf` function via the ``extra_functionality`` argument. The rule ``trace_solve_network`` was integrated into the rule :mod:`solve_network` and can be activated via configuration with ``solving: options: track_iterations: true``. The charging and discharging capacities of batteries modelled as store-link combination are now coupled [`#116 `_]. +* The optimization is now performed using the ``pyomo=False`` setting in the :func:`pypsa.lopf.network_lopf`. This speeds up the solving process significantly and consumes much less memory. The inclusion of additional constraints were adjusted to the new implementation. They are all passed to the :func:`network_lopf` function via the ``extra_functionality`` argument. The rule ``trace_solve_network`` was integrated into the rule :mod:`solve_network` and can be activated via configuration with ``solving: options: track_iterations: true``. The charging and discharging capacities of batteries modelled as store-link combination are now coupled [`#116 `__]. -* An updated extract of the `ENTSO-E Transmission System Map `_ (including Malta) was added to the repository using the `GridKit `_ tool. This tool has been updated to retrieve up-to-date map extracts using a single `script `_. The update extract features 5322 buses, 6574 lines, 46 links. [`#118 `_]. +* An updated extract of the `ENTSO-E Transmission System Map `__ (including Malta) was added to the repository using the `GridKit `__ tool. This tool has been updated to retrieve up-to-date map extracts using a single `script `__. The update extract features 5322 buses, 6574 lines, 46 links. [`#118 `__]. -* Added `FSFE REUSE `_ compliant license information. Documentation now licensed under CC-BY-4.0 [`#160 `_]. +* Added `FSFE REUSE `__ compliant license information. Documentation now licensed under CC-BY-4.0 [`#160 `__]. -* Added a 30 minute `video introduction `_ and a 20 minute `video tutorial `_ +* Added a 30 minute `video introduction `__ and a 20 minute `video tutorial `__ * Networks now store a color and a nicely formatted name for each carrier, accessible via ``n.carrier['color']`` and ``n.carrier['nice_name'] ``(networks after ``elec.nc``). * Added an option to skip iterative solving usually performed to update the line impedances of expanded lines at ``solving: options: skip_iterations:``. -* ``snakemake`` rules for retrieving cutouts and the natura raster can now be disabled independently from their respective rules to build them; via ``config.*yaml`` [`#136 `_]. +* ``snakemake`` rules for retrieving cutouts and the natura raster can now be disabled independently from their respective rules to build them; via ``config.*yaml`` [`#136 `__]. -* Removed the ``id`` column for custom power plants in ``data/custom_powerplants.csv`` to avoid custom power plants with conflicting ids getting attached to the wrong bus [`#131 `_]. +* Removed the ``id`` column for custom power plants in ``data/custom_powerplants.csv`` to avoid custom power plants with conflicting ids getting attached to the wrong bus [`#131 `__]. -* Add option ``renewables: {carrier}: keep_all_available_areas:`` to use all available weather cells for renewable profile and potential generation. The default ignores weather cells where only less than 1 MW can be installed [`#150 `_]. +* Add option ``renewables: {carrier}: keep_all_available_areas:`` to use all available weather cells for renewable profile and potential generation. The default ignores weather cells where only less than 1 MW can be installed [`#150 `__]. -* Added a function ``_helpers.load_network()`` which loads a network with overridden components specified in ``snakemake.config['override_components']`` [`#128 `_]. +* Added a function ``_helpers.load_network()`` which loads a network with overridden components specified in ``snakemake.config['override_components']`` [`#128 `__]. -* Bugfix in :mod:`base_network` which now finds all closest links, not only the first entry [`#143 `_]. +* Bugfix in :mod:`base_network` which now finds all closest links, not only the first entry [`#143 `__]. -* Bugfix in :mod:`cluster_network` which now skips recalculation of link parameters if there are no links [`#149 `_]. +* Bugfix in :mod:`cluster_network` which now skips recalculation of link parameters if there are no links [`#149 `__]. -* Added information on pull requests to contribution guidelines [`#151 `_]. +* Added information on pull requests to contribution guidelines [`#151 `__]. * Improved documentation on open-source solver setup and added usage warnings. @@ -1442,31 +1641,31 @@ PyPSA-Eur 0.1.0 (9th January 2020) This is the first release of PyPSA-Eur, a model of the European power system at the transmission network level. Recent changes include: -* Documentation on installation, workflows and configuration settings is now available online at `pypsa-eur.readthedocs.io `_ [`#65 `_]. +* Documentation on installation, workflows and configuration settings is now available online at `pypsa-eur.readthedocs.io `__ [`#65 `__]. -* The ``conda`` environment files were updated and extended [`#81 `_]. +* The ``conda`` environment files were updated and extended [`#81 `__]. -* The power plant database was updated with extensive filtering options via ``pandas.query`` functionality [`#84 `_ and `#94 `_]. +* The power plant database was updated with extensive filtering options via ``pandas.query`` functionality [`#84 `__ and `#94 `__]. -* Continuous integration testing with `Travis CI `_ is now included for Linux, Mac and Windows [`#82 `_]. +* Continuous integration testing with `Travis CI `__ is now included for Linux, Mac and Windows [`#82 `__]. -* Data dependencies were moved to `zenodo `_ and are now versioned [`#60 `_]. +* Data dependencies were moved to `zenodo `__ and are now versioned [`#60 `__]. -* Data dependencies are now retrieved directly from within the snakemake workflow [`#86 `_]. +* Data dependencies are now retrieved directly from within the snakemake workflow [`#86 `__]. -* Emission prices can be added to marginal costs of generators through the keywords ``Ep`` in the ``{opts}`` wildcard [`#100 `_]. +* Emission prices can be added to marginal costs of generators through the keywords ``Ep`` in the ``{opts}`` wildcard [`#100 `__]. -* An option is introduced to add extendable nuclear power plants to the network [`#98 `_]. +* An option is introduced to add extendable nuclear power plants to the network [`#98 `__]. -* Focus weights can now be specified for particular countries for the network clustering, which allows to set a proportion of the total number of clusters for particular countries [`#87 `_]. +* Focus weights can now be specified for particular countries for the network clustering, which allows to set a proportion of the total number of clusters for particular countries [`#87 `__]. -* A new rule :mod:`add_extra_components` allows to add additional components to the network only after clustering. It is thereby possible to model storage units (e.g. battery and hydrogen) in more detail via a combination of ``Store``, ``Link`` and ``Bus`` elements [`#97 `_]. +* A new rule :mod:`add_extra_components` allows to add additional components to the network only after clustering. It is thereby possible to model storage units (e.g. battery and hydrogen) in more detail via a combination of ``Store``, ``Link`` and ``Bus`` elements [`#97 `__]. -* Hydrogen pipelines (including cost assumptions) can now be added alongside clustered network connections in the rule :mod:`add_extra_components` . Set ``electricity: extendable_carriers: Link: [H2 pipeline]`` and ensure hydrogen storage is modelled as a ``Store``. This is a first simplified stage [`#108 `_]. +* Hydrogen pipelines (including cost assumptions) can now be added alongside clustered network connections in the rule :mod:`add_extra_components` . Set ``electricity: extendable_carriers: Link: [H2 pipeline]`` and ensure hydrogen storage is modelled as a ``Store``. This is a first simplified stage [`#108 `__]. -* Logfiles for all rules of the ``snakemake`` workflow are now written in the folder ``log/`` [`#102 `_]. +* Logfiles for all rules of the ``snakemake`` workflow are now written in the folder ``log/`` [`#102 `__]. -* The new function ``_helpers.mock_snakemake`` creates a ``snakemake`` object which mimics the actual ``snakemake`` object produced by workflow by parsing the ``Snakefile`` and setting all paths for inputs, outputs, and logs. This allows running all scripts within a (I)python terminal (or just by calling ``python ``) and thereby facilitates developing and debugging scripts significantly [`#107 `_]. +* The new function ``_helpers.mock_snakemake`` creates a ``snakemake`` object which mimics the actual ``snakemake`` object produced by workflow by parsing the ``Snakefile`` and setting all paths for inputs, outputs, and logs. This allows running all scripts within a (I)python terminal (or just by calling ``python ``) and thereby facilitates developing and debugging scripts significantly [`#107 `__]. PyPSA-Eur-Sec Releases (pre-merge) @@ -1483,13 +1682,13 @@ biomass, and explicit modelling of methanol and ammonia as separate energy carriers. This release is known to work with `PyPSA-Eur -`_ Version 0.7.0 and `Technology Data -`_ Version 0.5.0. +`__ Version 0.7.0 and `Technology Data +`__ Version 0.5.0. **Gas Transmission Network** * New rule ``retrieve_gas_infrastructure_data`` that downloads and extracts the - SciGRID_gas `IGGIELGN `_ dataset from + SciGRID_gas `IGGIELGN `__ dataset from zenodo. It includes data on the transmission routes, pipe diameters, capacities, pressure, and whether the pipeline is bidirectional and carries H-Gas or L-Gas. @@ -1500,7 +1699,7 @@ This release is known to work with `PyPSA-Eur * New rule ``build_gas_input_locations`` compiles the LNG import capacities (from the Global Energy Monitor's `Europe Gas Tracker - `_, pipeline + `__, pipeline entry capacities and local production capacities for each region of the model. These are the regions where fossil gas can eventually enter the model. @@ -1513,7 +1712,7 @@ This release is known to work with `PyPSA-Eur * With the option ``sector: gas_network:``, the existing gas network is added with a lossless transport model. A length-weighted `k-edge augmentation algorithm - `_ + `__ can be run to add new candidate gas pipelines such that all regions of the model can be connected to the gas network. The number of candidates can be controlled via the setting ``sector: gas_network_connectivity_upgrade:``. When @@ -1544,7 +1743,7 @@ This release is known to work with `PyPSA-Eur * Add option for regionally-resolved geological carbon dioxide sequestration potentials through new rule ``build_sequestration_potentials`` based on - `CO2StoP `_. This + `CO2StoP `__. This can be controlled in the section ``regional_co2_sequestration_potential`` of the ``config.yaml``. It includes options to select the level of conservatism, whether onshore potentials should be included, the respective upper and lower @@ -1556,7 +1755,7 @@ This release is known to work with `PyPSA-Eur ``seq200`` in the ``{sector_opts}`` wildcard (for limit of 200 Mt CO2). * Add option to include `Allam cycle gas power plants - `_ (``allam_cycle``). + `__ (``allam_cycle``). * Add option for planning a new carbon dioxide network (``co2network``). @@ -1575,7 +1774,7 @@ This release is known to work with `PyPSA-Eur * Add regionalised hydrogen salt cavern storage potentials from `Technical Potential of Salt Caverns for Hydrogen Storage in Europe - `_. This data is compiled in + `__. This data is compiled in a new rule ``build_salt_cavern_potentials``. * Add option to resolve ammonia as separate energy carrier with Haber-Bosch @@ -1592,11 +1791,11 @@ This release is known to work with `PyPSA-Eur * Demand for liquid hydrogen in international shipping is now geographically distributed by port trade volumes in a new rule ``build_shipping_demand`` using data from the `World Bank Data Catalogue - `_. + `__. Domestic shipping remains distributed by population. * Add option to aggregate network temporally using representative snapshots or - segments (with `tsam `_). + segments (with `tsam `__). * Add option for minimum part load for Fischer-Tropsch plants (default: 90%) and methanolisation plants (default: 50%). @@ -1649,7 +1848,7 @@ This release is known to work with `PyPSA-Eur PyPSA network. * Updated `data bundle - `_ + `__ that includes the hydrogan salt cavern storage potentials. * Updated and extended documentation in @@ -1677,7 +1876,7 @@ This release is known to work with `PyPSA-Eur expansion of previous iteration as minimum capacity for next iteration. * Further rather minor bugfixes for myopic optimisation code (see `#256 - `_). + `__). Many thanks to all who contributed to this release! @@ -1696,9 +1895,9 @@ more options in setting exogenous transition paths, besides many performance improvements. This release is known to work with `PyPSA-Eur -`_ Version 0.4.0, `Technology Data -`_ Version 0.3.0 and -`PyPSA `_ Version 0.18.0. +`__ Version 0.4.0, `Technology Data +`__ Version 0.3.0 and +`PyPSA `__ Version 0.18.0. Please note that the data bundle has also been updated. @@ -1715,11 +1914,11 @@ Please note that the data bundle has also been updated. battery costs. * Separate basic chemicals into HVC (high-value chemicals), chlorine, methanol and ammonia - [`#166 `_]. + [`#166 `__]. * Add option to specify reuse, primary production, and mechanical and chemical recycling fraction of platics - [`#166 `_]. + [`#166 `__]. * Include energy demands and CO2 emissions for the agriculture, forestry and fishing sector. It is included by default through the option ``A`` in the ``sector_opts`` wildcard. @@ -1733,11 +1932,11 @@ Please note that the data bundle has also been updated. Heat demand is assigned at "services rural heat" buses. Electricity demands are added to low-voltage buses. Time series for demands are constant and distributed inside countries by population - [`#147 `_]. + [`#147 `__]. * Include today's district heating shares in myopic optimisation and add option to specify exogenous path for district heating share increase under ``sector: - district_heating:`` [`#149 `_]. + district_heating:`` [`#149 `__]. * Added option for hydrogen liquefaction costs for hydrogen demand in shipping. This introduces a new ``H2 liquid`` bus at each location. It is activated via @@ -1757,16 +1956,16 @@ Please note that the data bundle has also been updated. factor 2. In this example, ``e_nom_max`` represents the CO2 sequestration potential in Europe. -* Use `JRC ENSPRESO database `_ to +* Use `JRC ENSPRESO database `__ to spatially disaggregate biomass potentials to PyPSA-Eur regions based on overlaps with NUTS2 regions from ENSPRESO (proportional to area) (`#151 - `_). + `__). * Add option to regionally disaggregate biomass potential to individual nodes (previously given per country, then distributed by population density within) and allow the transport of solid biomass. The transport costs are determined based on the `JRC-EU-Times Bioenergy report - `_ in the new optional rule + `__ in the new optional rule ``build_biomass_transport_costs``. Biomass transport can be activated with the setting ``sector: biomass_transport: true``. @@ -1784,7 +1983,7 @@ Please note that the data bundle has also been updated. * The myopic option can now be used together with different clustering for the generators and the network. The existing renewable capacities are split evenly - among the regions in every country [`#144 `_]. + among the regions in every country [`#144 `__]. * Add optional function to use ``geopy`` to locate entries of the Hotmaps database of industrial sites with missing location based on city and country, @@ -1850,7 +2049,7 @@ Please note that the data bundle has also been updated. * Consistent use of ``__main__`` block and further unspecific code cleaning. -* Updated data bundle and moved data bundle to zenodo.org (`10.5281/zenodo.5546517 `_). +* Updated data bundle and moved data bundle to zenodo.org (`10.5281/zenodo.5546517 `__). **Bugfixes and Compatibility** @@ -1874,7 +2073,7 @@ PyPSA-Eur-Sec 0.5.0 (21st May 2021) This release includes improvements to the cost database for building retrofits, carbon budget management and wildcard settings, as well as an important bugfix for the emissions from land transport. -This release is known to work with `PyPSA-Eur `_ Version 0.3.0 and `Technology Data `_ Version 0.2.0. +This release is known to work with `PyPSA-Eur `__ Version 0.3.0 and `Technology Data `__ Version 0.2.0. Please note that the data bundle has also been updated. @@ -1895,15 +2094,15 @@ PyPSA-Eur-Sec 0.4.0 (11th December 2020) This release includes a more accurate nodal disaggregation of industry demand within each country, fixes to CHP and CCS representations, as well as changes to some configuration settings. -It has been released to coincide with `PyPSA-Eur `_ Version 0.3.0 and `Technology Data `_ Version 0.2.0, and is known to work with these releases. +It has been released to coincide with `PyPSA-Eur `__ Version 0.3.0 and `Technology Data `__ Version 0.2.0, and is known to work with these releases. New features: -* The `Hotmaps Industrial Database `_ is used to disaggregate the industrial demand spatially to the nodes inside each country (previously it was distributed by population density). +* The `Hotmaps Industrial Database `__ is used to disaggregate the industrial demand spatially to the nodes inside each country (previously it was distributed by population density). * Electricity demand from industry is now separated from the regular electricity demand and distributed according to the industry demand. Only the remaining regular electricity demand for households and services is distributed according to GDP and population. -* A cost database for the retrofitting of the thermal envelope of residential and services buildings has been integrated, as well as endogenous optimisation of the level of retrofitting. This is described in the paper `Mitigating heat demand peaks in buildings in a highly renewable European energy system `_. Retrofitting can be activated both exogenously and endogenously from the ``config.yaml``. -* The biomass and gas combined heat and power (CHP) parameters ``c_v`` and ``c_b`` were read in assuming they were extraction plants rather than back pressure plants. The data is now corrected in `Technology Data `_ Version 0.2.0 to the correct DEA back pressure assumptions and they are now implemented as single links with a fixed ratio of electricity to heat output (even as extraction plants, they were always sitting on the backpressure line in simulations, so there was no point in modelling the full heat-electricity feasibility polygon). The old assumptions underestimated the heat output. -* The Danish Energy Agency released `new assumptions for carbon capture `_ in October 2020, which have now been incorporated in PyPSA-Eur-Sec, including direct air capture (DAC) and post-combustion capture on CHPs, cement kilns and other industrial facilities. The electricity and heat demand for DAC is modelled for each node (with heat coming from district heating), but currently the electricity and heat demand for industrial capture is not modelled very cleanly (for process heat, 10% of the energy is assumed to go to carbon capture) - a new issue will be opened on this. +* A cost database for the retrofitting of the thermal envelope of residential and services buildings has been integrated, as well as endogenous optimisation of the level of retrofitting. This is described in the paper `Mitigating heat demand peaks in buildings in a highly renewable European energy system `__. Retrofitting can be activated both exogenously and endogenously from the ``config.yaml``. +* The biomass and gas combined heat and power (CHP) parameters ``c_v`` and ``c_b`` were read in assuming they were extraction plants rather than back pressure plants. The data is now corrected in `Technology Data `__ Version 0.2.0 to the correct DEA back pressure assumptions and they are now implemented as single links with a fixed ratio of electricity to heat output (even as extraction plants, they were always sitting on the backpressure line in simulations, so there was no point in modelling the full heat-electricity feasibility polygon). The old assumptions underestimated the heat output. +* The Danish Energy Agency released `new assumptions for carbon capture `__ in October 2020, which have now been incorporated in PyPSA-Eur-Sec, including direct air capture (DAC) and post-combustion capture on CHPs, cement kilns and other industrial facilities. The electricity and heat demand for DAC is modelled for each node (with heat coming from district heating), but currently the electricity and heat demand for industrial capture is not modelled very cleanly (for process heat, 10% of the energy is assumed to go to carbon capture) - a new issue will be opened on this. * Land transport is separated by energy carrier (fossil, hydrogen fuel cell electric vehicle, and electric vehicle), but still needs to be separated into heavy and light vehicles (the data is there, just not the code yet). * For assumptions that change with the investment year, there is a new time-dependent format in the ``config.yaml`` using a dictionary with keys for each year. Implemented examples include the CO2 budget, exogenous retrofitting share and land transport energy carrier; more parameters will be dynamised like this in future. * Some assumptions have been moved out of the code and into the ``config.yaml``, including the carbon sequestration potential and cost, the heat pump sink temperature, reductions in demand for high value chemicals, and some BEV DSM parameters and transport efficiencies. @@ -1926,7 +2125,7 @@ New features: * The script ``build_industrial_production_per_country_tomorrow.py`` determines the future industrial production of materials based on today's levels as well as assumed recycling and demand change measures. * The energy demand for each industry sector and each location in 2015 is also calculated, so that it can be later incorporated in the pathway optimization. * Ammonia production data is taken from the USGS and deducted from JRC-IDEES's "basic chemicals" so that it ammonia can be handled separately from the others (olefins, aromatics and chlorine). -* Solid biomass is no longer allowed to be used for process heat in cement and basic chemicals, since the wastes and residues cannot be guaranteed to reach the high temperatures required. Instead, solid biomass is used in the paper and pulp as well as food, beverages and tobacco industries, where required temperatures are lower (see `DOI:10.1002/er.3436 `_ and `DOI:10.1007/s12053-017-9571-y `_). +* Solid biomass is no longer allowed to be used for process heat in cement and basic chemicals, since the wastes and residues cannot be guaranteed to reach the high temperatures required. Instead, solid biomass is used in the paper and pulp as well as food, beverages and tobacco industries, where required temperatures are lower (see `DOI:10.1002/er.3436 `__ and `DOI:10.1007/s12053-017-9571-y `__). * National installable potentials for salt caverns are now applied. * When electricity distribution grids are activated, new industry electricity demand, resistive heaters and micro-CHPs are now connected to the lower voltage levels. * Gas distribution grid costs are included for gas boilers and micro-CHPs. @@ -1938,15 +2137,15 @@ New features: PyPSA-Eur-Sec 0.2.0 (21st August 2020) -------------------------------------- -This release introduces pathway optimization over many years (e.g. 2020, 2030, 2040, 2050) with myopic foresight, as well as outsourcing the technology assumptions to the `technology-data `_ repository. +This release introduces pathway optimization over many years (e.g. 2020, 2030, 2040, 2050) with myopic foresight, as well as outsourcing the technology assumptions to the `technology-data `__ repository. It is known to work with PyPSA-Eur v0.1.0 (commit bb3477cd69), PyPSA v0.17.1 and technology-data v0.1.0. New features: -* Option for pathway optimization with myopic foresight, based on the paper `Early decarbonisation of the European Energy system pays off (2020) `_. Investments are optimized sequentially for multiple years (e.g. 2020, 2030, 2040, 2050) taking account of existing assets built in previous years and their lifetimes. The script uses data on the existing assets for electricity and building heating technologies, but there are no assumptions yet for existing transport and industry (if you include these, the model will greenfield them). There are also some `outstanding issues `_ on e.g. the distribution of existing wind, solar and heating technologies within each country. To use myopic foresight, set ``foresight : 'myopic'`` in the ``config.yaml`` instead of the default ``foresight : 'overnight'``. An example configuration can be found in ``config.myopic.yaml``. More details on the implementation can be found in :doc:`myopic`. +* Option for pathway optimization with myopic foresight, based on the paper `Early decarbonisation of the European Energy system pays off (2020) `__. Investments are optimized sequentially for multiple years (e.g. 2020, 2030, 2040, 2050) taking account of existing assets built in previous years and their lifetimes. The script uses data on the existing assets for electricity and building heating technologies, but there are no assumptions yet for existing transport and industry (if you include these, the model will greenfield them). There are also some `outstanding issues `__ on e.g. the distribution of existing wind, solar and heating technologies within each country. To use myopic foresight, set ``foresight : 'myopic'`` in the ``config.yaml`` instead of the default ``foresight : 'overnight'``. An example configuration can be found in ``config.myopic.yaml``. More details on the implementation can be found in :doc:`myopic`. -* Technology assumptions (costs, efficiencies, etc.) are no longer stored in the repository. Instead, you have to install the `technology-data `_ database in a parallel directory. These assumptions are largely based on the `Danish Energy Agency Technology Data `_. More details on the installation can be found in :doc:`installation`. +* Technology assumptions (costs, efficiencies, etc.) are no longer stored in the repository. Instead, you have to install the `technology-data `__ database in a parallel directory. These assumptions are largely based on the `Danish Energy Agency Technology Data `__. More details on the installation can be found in :doc:`installation`. * Logs and benchmarks are now stored with the other model outputs in ``results/run-name/``. @@ -1969,7 +2168,7 @@ It is known to work with PyPSA-Eur v0.1.0 (commit bb3477cd69) and PyPSA v0.17.0. We are making this release since in version 0.2.0 we will introduce changes to allow myopic investment planning that will require minor changes for users of the overnight investment planning. PyPSA-Eur-Sec builds on the electricity generation and transmission -model `PyPSA-Eur `_ to add demand +model `PyPSA-Eur `__ to add demand and supply for the following sectors: transport, space and water heating, biomass, industry and industrial feedstocks. This completes the energy system and includes all greenhouse gas emitters except @@ -1978,17 +2177,17 @@ waste management, agriculture, forestry and land use. PyPSA-Eur-Sec was initially based on the model PyPSA-Eur-Sec-30 (Version 0.0.1 below) described in the paper `Synergies of sector coupling and transmission reinforcement in a cost-optimised, highly renewable European energy -system `_ (2018) but it differs by +system `__ (2018) but it differs by being based on the higher resolution electricity transmission model -`PyPSA-Eur `_ rather than a +`PyPSA-Eur `__ rather than a one-node-per-country model, and by including biomass, industry, industrial feedstocks, aviation, shipping, better carbon management, carbon capture and usage/sequestration, and gas networks. PyPSA-Eur-Sec includes PyPSA-Eur as a -`snakemake `_ -`subworkflow `_. PyPSA-Eur-Sec +`snakemake `__ +`subworkflow `__. PyPSA-Eur-Sec uses PyPSA-Eur to build the clustered transmission model along with wind, solar PV and hydroelectricity potentials and time series. Then PyPSA-Eur-Sec adds other conventional generators, storage units and @@ -2003,13 +2202,13 @@ PyPSA-Eur-Sec 0.0.2 (4th September 2020) This version, also called PyPSA-Eur-Sec-30-Path, built on PyPSA-Eur-Sec 0.0.1 (also called PyPSA-Eur-Sec-30) to include myopic pathway optimisation for the paper `Early decarbonisation of the -European energy system pays off `_ +European energy system pays off `__ (2020). The myopic pathway optimisation was then merged into the main PyPSA-Eur-Sec codebase in Version 0.2.0 above. This model has `its own github repository -`_ and is `archived -on Zenodo `_. +`__ and is `archived +on Zenodo `__. @@ -2020,12 +2219,12 @@ This is the first published version of PyPSA-Eur-Sec, also called PyPSA-Eur-Sec-30. It was first used in the research paper `Synergies of sector coupling and transmission reinforcement in a cost-optimised, highly renewable European energy system -`_ (2018). The model covers 30 +`__ (2018). The model covers 30 European countries with one node per country. It includes demand and supply for electricity, space and water heating in buildings, and land transport. -It is `archived on Zenodo `_. +It is `archived on Zenodo `__. Release Process @@ -2048,6 +2247,6 @@ Release Process * Tag a release on Github via ``git tag v0.x.x``, ``git push``, ``git push --tags``. Include release notes in the tag message. -* Make a `GitHub release `_, which automatically triggers archiving to the `zenodo code repository `_ with `MIT license `_. +* Make a `GitHub release `__, which automatically triggers archiving to the `zenodo code repository `__ with `MIT license `__. -* Send announcement on the `PyPSA mailing list `_. +* Send announcement on the `PyPSA mailing list `__. diff --git a/doc/retrieve.rst b/doc/retrieve.rst index f9d6e2a7..6b339355 100644 --- a/doc/retrieve.rst +++ b/doc/retrieve.rst @@ -25,12 +25,12 @@ Rule ``retrieve_cutout`` .. image:: https://zenodo.org/badge/DOI/10.5281/zenodo.6382570.svg :target: https://doi.org/10.5281/zenodo.6382570 -Cutouts are spatio-temporal subsets of the European weather data from the `ECMWF ERA5 `_ reanalysis dataset and the `CMSAF SARAH-2 `_ solar surface radiation dataset for the year 2013. -They have been prepared by and are for use with the `atlite `_ tool. You can either generate them yourself using the ``build_cutouts`` rule or retrieve them directly from `zenodo `__ through the rule ``retrieve_cutout``. +Cutouts are spatio-temporal subsets of the European weather data from the `ECMWF ERA5 `__ reanalysis dataset and the `CMSAF SARAH-2 `__ solar surface radiation dataset for the year 2013. +They have been prepared by and are for use with the `atlite `__ tool. You can either generate them yourself using the ``build_cutouts`` rule or retrieve them directly from `zenodo `__ through the rule ``retrieve_cutout``. The :ref:`tutorial` uses a smaller cutout than required for the full model (30 MB), which is also automatically downloaded. .. note:: - To download cutouts yourself from the `ECMWF ERA5 `_ you need to `set up the CDS API `_. + To download cutouts yourself from the `ECMWF ERA5 `__ you need to `set up the CDS API `__. **Relevant Settings** @@ -47,43 +47,17 @@ The :ref:`tutorial` uses a smaller cutout than required for the full model (30 M **Outputs** -- ``cutouts/{cutout}``: weather data from either the `ERA5 `_ reanalysis weather dataset or `SARAH-2 `_ satellite-based historic weather data. +- ``cutouts/{cutout}``: weather data from either the `ERA5 `__ reanalysis weather dataset or `SARAH-2 `__ satellite-based historic weather data. .. seealso:: - For details see :mod:`build_cutout` and read the `atlite documentation `_. + For details see :mod:`build_cutout` and read the `atlite documentation `__. -Rule ``retrieve_natura_raster`` -================================ - -.. image:: https://zenodo.org/badge/DOI/10.5281/zenodo.4706686.svg - :target: https://doi.org/10.5281/zenodo.4706686 - -This rule, as a substitute for :mod:`build_natura_raster`, downloads an already rasterized version (`natura.tiff `_) of `Natura 2000 `_ natural protection areas to reduce computation times. The file is placed into the ``resources`` sub-directory. - -**Relevant Settings** - -.. code:: yaml - - enable: - build_natura_raster: - -.. seealso:: - Documentation of the configuration file ``config/config.yaml`` at - :ref:`toplevel_cf` - -**Outputs** - -- ``resources/natura.tiff``: Rasterized version of `Natura 2000 `_ natural protection areas to reduce computation times. - -.. seealso:: - For details see :mod:`build_natura_raster`. - Rule ``retrieve_electricity_demand`` ==================================== -This rule downloads hourly electric load data for each country from the `OPSD platform `_. +This rule downloads hourly electric load data for each country from the `OPSD platform `__. **Relevant Settings** @@ -97,7 +71,7 @@ None. Rule ``retrieve_cost_data`` ================================ -This rule downloads techno-economic assumptions from the `technology-data repository `_. +This rule downloads techno-economic assumptions from the `technology-data repository `__. **Relevant Settings** @@ -118,15 +92,10 @@ This rule downloads techno-economic assumptions from the `technology-data reposi - ``resources/costs.csv`` -Rule ``retrieve_irena`` -================================ - -.. automodule:: retrieve_irena - Rule ``retrieve_ship_raster`` ================================ -This rule downloads data on global shipping traffic density from the `World Bank Data Catalogue `_. +This rule downloads data on global shipping traffic density from the `World Bank Data Catalogue `__. **Relevant Settings** @@ -135,14 +104,3 @@ None. **Outputs** - ``data/shipdensity_global.zip`` - - -Rule ``retrieve_sector_databundle`` -==================================== - -.. image:: https://zenodo.org/badge/DOI/10.5281/zenodo.5546516.svg - :target: https://doi.org/10.5281/zenodo.5546516 - -In addition to the databundle required for electricity-only studies, -another databundle is required for modelling sector-coupled systems. -The size of this data bundle is around 640 MB. diff --git a/doc/sector.rst b/doc/sector.rst index ba3aa3e6..e186ccf7 100644 --- a/doc/sector.rst +++ b/doc/sector.rst @@ -7,8 +7,15 @@ Building Sector-Coupled Networks ########################################## -.. warning:: - This part of the documentation is under development. +The preparation process of the sector-coupled version of the PyPSA-Eur energy system model consists of a group of ``snakemake`` rules which are briefly outlined and explained in detail in the sections below. + +Not all data dependencies are shipped with the git repository. +Instead we provide separate data bundles which can be obtained +using the ``retrieve*`` rules (:ref:`data`). +Having downloaded the necessary data, + +- :mod:`add_brownfield` builds and stores the base network with all buses, HVAC lines and HVDC links, while + Rule ``add_brownfield`` ============================================================================== @@ -176,6 +183,11 @@ Rule ``cluster_gas_network`` .. automodule:: cluster_gas_network +Rule ``time_aggregation`` +============================================================================== + +.. automodule:: time_aggregation + Rule ``prepare_sector_network`` ============================================================================== diff --git a/doc/simplification.rst b/doc/simplification.rst index 2272505d..cb048461 100644 --- a/doc/simplification.rst +++ b/doc/simplification.rst @@ -12,11 +12,11 @@ Simplifying Electricity Networks The simplification ``snakemake`` rules prepare **approximations** of the full model, for which it is computationally viable to co-optimize generation, storage and transmission capacities. - :mod:`simplify_network` transforms the transmission grid to a 380 kV only equivalent network, while -- :mod:`cluster_network` uses a `k-means `_ based clustering technique to partition the network into a given number of zones and then reduce the network to a representation with one bus per zone. +- :mod:`cluster_network` uses a `k-means `__ based clustering technique to partition the network into a given number of zones and then reduce the network to a representation with one bus per zone. The simplification and clustering steps are described in detail in the paper -- Jonas Hörsch and Tom Brown. `The role of spatial scale in joint optimisations of generation and transmission for European highly renewable scenarios `_), *14th International Conference on the European Energy Market*, 2017. `arXiv:1705.07617 `_, `doi:10.1109/EEM.2017.7982024 `_. +- Jonas Hörsch and Tom Brown. `The role of spatial scale in joint optimisations of generation and transmission for European highly renewable scenarios `__), *14th International Conference on the European Energy Market*, 2017. `arXiv:1705.07617 `__, `doi:10.1109/EEM.2017.7982024 `__. After simplification and clustering of the network, additional components may be appended in the rule :mod:`add_extra_components` and the network is prepared for solving in :mod:`prepare_network`. diff --git a/doc/spatial_resolution.rst b/doc/spatial_resolution.rst index a408f464..76228ad8 100644 --- a/doc/spatial_resolution.rst +++ b/doc/spatial_resolution.rst @@ -9,19 +9,19 @@ Spatial resolution ########################################## -The default nodal resolution of the model follows the electricity generation and transmission model `PyPSA-Eur `_, which clusters down the electricity transmission substations in each European country based on the k-means algorithm (See `cluster_network `_ for a complete explanation). This gives nodes which correspond to major load and generation centres (typically cities). +The default nodal resolution of the model follows the electricity generation and transmission model `PyPSA-Eur `__, which clusters down the electricity transmission substations in each European country based on the k-means algorithm (See `cluster_network `__ for a complete explanation). This gives nodes which correspond to major load and generation centres (typically cities). The total number of nodes for Europe is set in the ``config/config.yaml`` file under ``clusters``. The number of nodes can vary between 37, the number of independent countries / synchronous areas, and several hundred. With 200-300 nodes the model needs 100-150 GB RAM to solve with a commercial solver like Gurobi. Exemplary unsolved network clustered to 512 nodes: -.. image:: ../graphics/elec_s_512.png +.. image:: img/elec_s_512.png Exemplary unsolved network clustered to 37 nodes: -.. image:: ../graphics/elec_s_37.png +.. image:: img/elec_s_37.png -The total number of nodes for Europe is set in the ``config/config.yaml`` file under `clusters `_. The number of nodes can vary between 37, the number of independent countries/synchronous areas, and several hundred. With 200-300 nodes, the model needs 100-150 GB RAM to solve with a commercial solver like Gurobi. +The total number of nodes for Europe is set in the ``config/config.yaml`` file under `clusters `__. The number of nodes can vary between 37, the number of independent countries/synchronous areas, and several hundred. With 200-300 nodes, the model needs 100-150 GB RAM to solve with a commercial solver like Gurobi. Not all of the sectors are at the full nodal resolution, and some demand for some sectors is distributed to nodes using heuristics that need to be corrected. Some networks are copper-plated to reduce computational times. Here are some examples of how spatial resolution is set for different sectors in PyPSA-Eur-Sec: @@ -37,18 +37,18 @@ Here are some examples of how spatial resolution is set for different sectors in • Electricity demand in industry: Modeled as nodal, based on the location of industrial facilities from HotMaps database. • Industry demand (heat, chemicals, etc.) : Modeled as nodal, distributed in each country based on locations of industry from HotMaps database. -• Hydrogen network: Modeled as nodal (if activated in the `config `_ file). +• Hydrogen network: Modeled as nodal (if activated in the `config `__ file). -• Methane network: It can be modeled as a single node for Europe or it can be nodally resolved if activated in the `config `_. One node can be considered reasonable since future demand is expected to be low and no bottlenecks are expected. Also, the nodally resolved methane grid is based on SciGRID_gas data. +• Methane network: It can be modeled as a single node for Europe or it can be nodally resolved if activated in the `config `__. One node can be considered reasonable since future demand is expected to be low and no bottlenecks are expected. Also, the nodally resolved methane grid is based on SciGRID_gas data. -• Solid biomass: It can be modeled as a single node for Europe or it can be nodally resolved if activated in the `config `_. Nodal modeling includes modeling biomass potential per country (given per country, then distributed by population density within) and the transport of solid biomass between countries. +• Solid biomass: It can be modeled as a single node for Europe or it can be nodally resolved if activated in the `config `__. Nodal modeling includes modeling biomass potential per country (given per country, then distributed by population density within) and the transport of solid biomass between countries. -• CO2: It can be modeled as a single node for Europe or it can be nodally resolved with CO2 transport pipelines if activated in the `config `_. It should mentioned that in single node mode a transport and storage cost is added for sequestered CO2, the cost of which can be adjusted in the `config `_. +• CO2: It can be modeled as a single node for Europe or it can be nodally resolved with CO2 transport pipelines if activated in the `config `__. It should mentioned that in single node mode a transport and storage cost is added for sequestered CO2, the cost of which can be adjusted in the `config `__. • Carbonaceous fuels: Modeled as a single node for Europe by default, since transport costs for liquids are low and no bottlenecks are expected. Can be regionally resolved in configuration. **Electricity distribution network** -Contrary to the transmission grid, the grid topology at the distribution level (at and below 110 kV) is not included due to the very high computational burden. However, a link per node can be used (if activated in the `Config `_ file) to represent energy transferred between distribution and transmission levels at every node. In essence, the total energy capacity connecting the transmission grid and the low-voltage level is optimized. The cost assumptions for this link can be adjusted in Config file `options `_ , and is currently assumed to be 500 Eur/kW. +Contrary to the transmission grid, the grid topology at the distribution level (at and below 110 kV) is not included due to the very high computational burden. However, a link per node can be used (if activated in the `Config `__ file) to represent energy transferred between distribution and transmission levels at every node. In essence, the total energy capacity connecting the transmission grid and the low-voltage level is optimized. The cost assumptions for this link can be adjusted in Config file `options `__ , and is currently assumed to be 500 Eur/kW. Rooftop PV, heat pumps, resistive heater, home batteries chargers for passenger EVs, as well as individual heating technologies (heat pumps and resistive heaters) are connected to low-voltage level. All the remaining generation and storage technologies are connected to the transmission grid. In practice, this means that the distribution grid capacity is only extended if it is necessary to balance the mismatch between local generation and demand. diff --git a/doc/supply_demand.rst b/doc/supply_demand.rst index 8f6edcad..e5646fcb 100644 --- a/doc/supply_demand.rst +++ b/doc/supply_demand.rst @@ -11,14 +11,14 @@ An initial orientation to the supply and demand options in the model PyPSA-Eur-Sec can be found in the description of the model PyPSA-Eur-Sec-30 in the paper `Synergies of sector coupling and transmission reinforcement in a cost-optimised, highly renewable -European energy system `_ (2018). +European energy system `__ (2018). The latest version of PyPSA-Eur-Sec differs by including biomass, industry, industrial feedstocks, aviation, shipping, better carbon management, carbon capture and usage/sequestration, and gas networks. The basic supply (left column) and demand (right column) options in the model are described in this figure: -.. image:: ../graphics/multisector_figure.png +.. image:: img/multisector_figure.png .. _Electricity supply and demand: @@ -26,13 +26,13 @@ Electricity supply and demand ============================= Electricity supply and demand follows the electricity generation and -transmission model `PyPSA-Eur `_, +transmission model `PyPSA-Eur `__, except that hydrogen storage is integrated into the hydrogen supply, demand and network, and PyPSA-Eur-Sec includes CHPs. Unlike PyPSA-Eur, PyPSA-Eur-Sec does not distribution electricity demand for industry according to population and GDP, but uses the geographical data from the `Hotmaps Industrial Database -`_. +`__. Also unlike PyPSA-Eur, PyPSA-Eur-Sec subtracts existing electrified heating from the existing electricity demand, so that power-to-heat can be optimised separately. @@ -44,7 +44,7 @@ Heat demand =========== Building heating in residential and services sectors is resolved regionally, both for individual buildings and district heating systems, which include different supply options (see :ref:`heat-supply`.) -Annual heat demands per country are retrieved from `JRC-IDEES `_ and split into space and water heating. For space heating, the annual demands are converted to daily values based on the population-weighted Heating Degree Day (HDD) using the `atlite tool `_, where space heat demand is proportional to the difference between the daily average ambient temperature (read from `ERA5 `_) and a threshold temperature above which space heat demand is zero. A threshold temperature of 15 °C is assumed by default. The daily space heat demand is distributed to the hours of the day following heat demand profiles from `BDEW `_. These differ for weekdays and weekends/holidays and between residential and services demand. +Annual heat demands per country are retrieved from `JRC-IDEES `__ and split into space and water heating. For space heating, the annual demands are converted to daily values based on the population-weighted Heating Degree Day (HDD) using the `atlite tool `__, where space heat demand is proportional to the difference between the daily average ambient temperature (read from `ERA5 `__) and a threshold temperature above which space heat demand is zero. A threshold temperature of 15 °C is assumed by default. The daily space heat demand is distributed to the hours of the day following heat demand profiles from `BDEW `__. These differ for weekdays and weekends/holidays and between residential and services demand. *Space heating* @@ -54,11 +54,11 @@ The space heating demand can be exogenously reduced by retrofitting measures tha :language: yaml :lines: 205 -Co-optimsing of building renovation is also possible, if it is activated in the `config file `_. +Co-optimsing of building renovation is also possible, if it is activated in the `config file `__. Renovation of the thermal envelope reduces the space heating demand and is optimised at each node for every heat bus. Renovation measures through additional insulation material and replacement of energy inefficient windows are considered. -In a first step, costs per energy savings are estimated in `build_retro_cost.py `_. They depend on the insulation condition of the building stock and costs for renovation of the building elements. In a second step, for those cost per energy savings two possible renovation strengths are determined: a moderate renovation with lower costs, a lower maximum possible space heat savings, and an ambitious renovation with associated higher costs and higher efficiency gains. They are added by step-wise linearisation in form of two additional generations in `prepare_sector_network.py `_. +In a first step, costs per energy savings are estimated in `build_retro_cost.py `__. They depend on the insulation condition of the building stock and costs for renovation of the building elements. In a second step, for those cost per energy savings two possible renovation strengths are determined: a moderate renovation with lower costs, a lower maximum possible space heat savings, and an ambitious renovation with associated higher costs and higher efficiency gains. They are added by step-wise linearisation in form of two additional generations in `prepare_sector_network.py `__. Further information are given in the publication : - `Mitigating heat demand peaks in buildings in a highly renewable European energy system, (2021) `_. +`Mitigating heat demand peaks in buildings in a highly renewable European energy system, (2021) `__. *Water heating* @@ -66,17 +66,17 @@ Hot water demand is assumed to be constant throughout the year. *Urban and rural heating* -For every country, heat demand is split between low and high population density areas. These country-level totals are then distributed to each region in proportion to their rural and urban populations respectively. Urban areas with dense heat demand can be supplied with large-scale district heating systems. The percentage of urban heat demand that can be supplied by district heating networks as well as lump-sum losses in district heating systems is exogenously determined in the `config file `_. +For every country, heat demand is split between low and high population density areas. These country-level totals are then distributed to each region in proportion to their rural and urban populations respectively. Urban areas with dense heat demand can be supplied with large-scale district heating systems. The percentage of urban heat demand that can be supplied by district heating networks as well as lump-sum losses in district heating systems is exogenously determined in the `config file `__. *Cooling demand* Cooling is electrified and is included in the electricity demand. Cooling demand is assumed to remain at current levels. An example of regional distribution of the total heat demand for network 181 regions is depicted below. -.. image:: ../graphics/demand-map-heat.png +.. image:: img/demand-map-heat.png As below figure shows, the current total heat demand in Europe is similar to the total electricity demand but features much more pronounced seasonal variations. The current total building heating demand in Europe adds up to 3084 TWh/a of which 78% occurs in urban areas. -.. image:: ../graphics/Heat_and_el_demand_timeseries.png +.. image:: img/Heat_and_el_demand_timeseries.png In practice, in PyPSA-Eur-Sec, there are heat demand buses to which the corresponding heat demands are added. @@ -96,41 +96,41 @@ Different supply options are available depending on whether demand is met centra **Urban central heat** -For large-scale district heating systems the following options are available: combined heat and power (CHP) plants consuming gas or biomass from waste and residues with and without carbon capture (CC), large-scale air-sourced heat pumps, gas and oil boilers, resistive heaters, and fuel cell CHPs. Additionally, waste heat from the `Fischer-Tropsch `_ and `Sabatier `_ processes for the production of synthetic hydrocarbons can supply district heating systems. For more detailed explanation of these processes, see :ref:`Oil-based products supply` and :ref:`Methane supply`. +For large-scale district heating systems the following options are available: combined heat and power (CHP) plants consuming gas or biomass from waste and residues with and without carbon capture (CC), large-scale air-sourced heat pumps, gas and oil boilers, resistive heaters, and fuel cell CHPs. Additionally, waste heat from the `Fischer-Tropsch `__ and `Sabatier `__ processes for the production of synthetic hydrocarbons can supply district heating systems. For more detailed explanation of these processes, see :ref:`Oil-based products supply` and :ref:`Methane supply`. **Residential and Urban decentral heat** Supply options in individual buildings include gas and oil boilers, air- and ground-sourced heat pumps, resistive heaters, and solar thermal collectors. -Ground-source heat pumps are only allowed in rural areas because of space constraints. Thus, only air- source heat pumps are allowed in urban areas. This is a conservative assumption, since there are many possible sources of low-temperature heat that could be tapped in cities (e.g. waste water, ground water, or natural bodies of water). Costs, lifetimes and efficiencies for these technologies are retrieved from the `technology-data repository `_. +Ground-source heat pumps are only allowed in rural areas because of space constraints. Thus, only air- source heat pumps are allowed in urban areas. This is a conservative assumption, since there are many possible sources of low-temperature heat that could be tapped in cities (e.g. waste water, ground water, or natural bodies of water). Costs, lifetimes and efficiencies for these technologies are retrieved from the `technology-data repository `__. -Below are more detailed explanations for each heating supply component, all of which are modelled as `links `_ in PyPSA-Eur-Sec. +Below are more detailed explanations for each heating supply component, all of which are modelled as `links `__ in PyPSA-Eur-Sec. .. _Large-scale CHP: **Large-scale CHP** -Large Combined Heat and Power plants are included in the model if it is specified in the `config file `_. +Large Combined Heat and Power plants are included in the model if it is specified in the `config file `__. -CHPs are based on back pressure plants operating with a fixed ratio of electricity to heat output. The efficiencies of each are given on the back pressure line, where the back pressure coefficient cb is the electricity output divided by the heat output. (For a more complete explanation of the operation of CHPs refer to the study by Dahl et al. : `Cost sensitivity of optimal sector-coupled district heating production systems `_. +CHPs are based on back pressure plants operating with a fixed ratio of electricity to heat output. The efficiencies of each are given on the back pressure line, where the back pressure coefficient cb is the electricity output divided by the heat output. (For a more complete explanation of the operation of CHPs refer to the study by Dahl et al. : `Cost sensitivity of optimal sector-coupled district heating production systems `__. PyPSA-Eur-Sec includes CHP plants fueled by methane and solid biomass from waste and residues. Hydrogen fuel cells also produce both electricity and heat. -The methane CHP is modeled on the Danish Energy Agency (DEA) “Gas turbine simple cycle (large)” while the solid biomass CHP is based on the DEA’s “09b Wood Pellets Medium”. For biomass CHP, cb = `0.46 `_ , whereas for gas CHP, cb = `1 `_. +The methane CHP is modeled on the Danish Energy Agency (DEA) “Gas turbine simple cycle (large)” while the solid biomass CHP is based on the DEA’s “09b Wood Pellets Medium”. For biomass CHP, cb = `0.46 `__ , whereas for gas CHP, cb = `1 `__. -NB: The old PyPSA-Eur-Sec-30 model assumed an extraction plant (like the DEA coal CHP) for gas which has flexible production of heat and electricity within the feasibility diagram of Figure 4 in the study by `Brown et al. `_ We have switched to the DEA back pressure plants since these are more common for smaller plants for biomass, and because the extraction plants were on the back pressure line for 99.5% of the time anyway. The plants were all changed to back pressure in PyPSA-Eur-Sec v0.4.0. +NB: The old PyPSA-Eur-Sec-30 model assumed an extraction plant (like the DEA coal CHP) for gas which has flexible production of heat and electricity within the feasibility diagram of Figure 4 in the study by `Brown et al. `__ We have switched to the DEA back pressure plants since these are more common for smaller plants for biomass, and because the extraction plants were on the back pressure line for 99.5% of the time anyway. The plants were all changed to back pressure in PyPSA-Eur-Sec v0.4.0. **Micro-CHP** -PyPSA-Eur-Sec allows individual buildings to make use of `micro gas CHPs `_ that are assumed to be installed at the distribution grid level. +PyPSA-Eur-Sec allows individual buildings to make use of `micro gas CHPs `__ that are assumed to be installed at the distribution grid level. **Heat pumps** -The coefficient of performance (COP) of air- and ground-sourced heat pumps depends on the ambient or soil temperature respectively. Hence, the COP is a time-varying parameter (refer to `Config `_ file). Generally, the COP will be lower during winter when temperatures are low. Because the ambient temperature is more volatile than the soil temperature, the COP of ground-sourced heat pumps is less variable. Moreover, the COP depends on the difference between the source and sink temperatures: +The coefficient of performance (COP) of air- and ground-sourced heat pumps depends on the ambient or soil temperature respectively. Hence, the COP is a time-varying parameter (refer to `Config `__ file). Generally, the COP will be lower during winter when temperatures are low. Because the ambient temperature is more volatile than the soil temperature, the COP of ground-sourced heat pumps is less variable. Moreover, the COP depends on the difference between the source and sink temperatures: .. math:: \Delta T = T_{sink} − T_{source} -For the sink water temperature Tsink we assume 55 °C [`Config `_ file]. For the time- and location-dependent source temperatures Tsource, we rely on the `ERA5 `_ reanalysis weather data. The temperature differences are converted into COP time series using results from a regression analysis performed in the study by `Stafell et al. `_. For air-sourced heat pumps (ASHP), we use the function: +For the sink water temperature Tsink we assume 55 °C [`Config `__ file]. For the time- and location-dependent source temperatures Tsource, we rely on the `ERA5 `__ reanalysis weather data. The temperature differences are converted into COP time series using results from a regression analysis performed in the study by `Stafell et al. `__. For air-sourced heat pumps (ASHP), we use the function: .. math:: COP (\Delta T) = 6.81 - 0.121\Delta T + 0.000630\Delta T^2 @@ -142,44 +142,44 @@ for ground-sourced heat pumps (GSHP), we use the function: **Resistive heaters** -Can be activated in Config from the `boilers `_ option. -Resistive heaters produce heat with a fixed conversion efficiency (refer to `Technology-data repository `_ ). +Can be activated in Config from the `boilers `__ option. +Resistive heaters produce heat with a fixed conversion efficiency (refer to `Technology-data repository `__ ). **Gas, oil, and biomass boilers** -Can be activated in Config from the `boilers `_ , `oil boilers `_ , and `biomass boiler `_ option. +Can be activated in Config from the `boilers `__ , `oil boilers `__ , and `biomass boiler `__ option. Similar to resistive heaters, boilers have a fixed efficiency and produce heat using gas, oil or biomass. **Solar thermal collectors** -Can be activated in the config file from the `solar_thermal `_ option. -Solar thermal profiles are built based on weather data and also have the `options `_ for setting the sky model and the orientation of the panel in the config file, which are then used by the atlite tool to calculate the solar resource time series. +Can be activated in the config file from the `solar_thermal `__ option. +Solar thermal profiles are built based on weather data and also have the `options `__ for setting the sky model and the orientation of the panel in the config file, which are then used by the atlite tool to calculate the solar resource time series. **Waste heat from Fuel Cells, Methanation and Fischer-Tropsch plants** -Waste heat from `fuel cells `_ in addition to processes like `Fischer-Tropsch `_, methanation, and Direct Air Capture (DAC) is dumped into district heating networks. +Waste heat from `fuel cells `__ in addition to processes like `Fischer-Tropsch `__, methanation, and Direct Air Capture (DAC) is dumped into district heating networks. **Existing heating capacities and decommissioning** -For the myopic transition paths, capacities already existing for technologies supplying heat are retrieved from `“Mapping and analyses of the current and future (2020 - 2030)” `_ . For the sake of simplicity, coal, oil and gas boiler capacities are assimilated to gas boilers. Besides that, existing capacities for heat resistors, air-sourced and ground-sourced heat pumps are included in the model. For heating capacities, 25% of existing capacities in 2015 are assumed to be decommissioned in every 5-year time step after 2020. +For the myopic transition paths, capacities already existing for technologies supplying heat are retrieved from `“Mapping and analyses of the current and future (2020 - 2030)” `__ . For the sake of simplicity, coal, oil and gas boiler capacities are assimilated to gas boilers. Besides that, existing capacities for heat resistors, air-sourced and ground-sourced heat pumps are included in the model. For heating capacities, 25% of existing capacities in 2015 are assumed to be decommissioned in every 5-year time step after 2020. **Thermal Energy Storage** -Activated in Config from the `tes `_ option. +Activated in Config from the `tes `__ option. -Thermal energy can be stored in large water pits associated with district heating systems and individual thermal energy storage (TES), i.e., small water tanks. Water tanks are modelled as `stores `_. -A thermal energy density of 46.8 kWh :math:`_{th}`/m3 is assumed, corresponding to a temperature difference of 40 K. The decay of thermal energy in the stores: 1- :math:`e^{-1/24τ}` is assumed to have a time constant of  τ=180 days for central TES and  τ=3 days for individual TES, both modifiable through `tes_tau `_ in config file. Charging and discharging efficiencies are 90% due to pipe losses. +Thermal energy can be stored in large water pits associated with district heating systems and individual thermal energy storage (TES), i.e., small water tanks. Water tanks are modelled as `stores `__. +A thermal energy density of 46.8 kWh :math:`_{th}`/m3 is assumed, corresponding to a temperature difference of 40 K. The decay of thermal energy in the stores: 1- :math:`e^{-1/24τ}` is assumed to have a time constant of  τ=180 days for central TES and  τ=3 days for individual TES, both modifiable through `tes_tau `__ in config file. Charging and discharging efficiencies are 90% due to pipe losses. **Retrofitting of the thermal envelope of buildings** -Co-optimising building renovation is only enabled if in the `config `_ file. To reduce the computational burden, +Co-optimising building renovation is only enabled if in the `config `__ file. To reduce the computational burden, default setting is set as false. Renovation of the thermal envelope reduces the space heating demand and is optimised at each node for every heat bus. Renovation measures through additional insulation material and replacement of energy inefficient windows are considered. -In a first step, costs per energy savings are estimated in the `build_retro_cost.py `_ script. +In a first step, costs per energy savings are estimated in the `build_retro_cost.py `__ script. They depend on the insulation condition of the building stock and costs for renovation of the building elements. In a second step, for those cost per energy savings two possible renovation @@ -187,12 +187,12 @@ strengths are determined: a moderate renovation with lower costs and lower maximum possible space heat savings, and an ambitious renovation with associated higher costs and higher efficiency gains. They are added by step-wise linearisation in form of two additional generations in -the `prepare_sector_network.py `_ script. +the `prepare_sector_network.py `__ script. Settings in the ``config/config.yaml`` concerning the endogenously optimisation of building -renovation include `cost factor `_, `interest rate `_, `annualised cost `_, `tax weighting `_, and `construction index `_. +renovation include `cost factor `__, `interest rate `__, `annualised cost `__, `tax weighting `__, and `construction index `__. -Further information are given in the study by Zeyen et al. : `Mitigating heat demand peaks in buildings in a highly renewable European energy system, (2021) `_. +Further information are given in the study by Zeyen et al. : `Mitigating heat demand peaks in buildings in a highly renewable European energy system, (2021) `__. .. _Hydrogen demand: @@ -200,7 +200,7 @@ Hydrogen demand ============================= Hydrogen is consumed in the industry sector (see :ref:`Industry demand`) to produce ammonia (see :ref:`Chemicals Industry`) and direct reduced iron (DRI) (see :ref:`Iron and Steel`). Hydrogen is also consumed to produce synthetic methane (see :ref:`Methane supply`) and liquid hydrocarbons (see :ref:`Oil-based products supply`) which have multiple uses in industry and other sectors. -Hydrogen is also used for transport applications (see :ref:`Transportation`), where it is exogenously fixed. It is used in `heavy-duty land transport `_ and as liquified hydrogen in the shipping sector (see :ref:`Shipping`). Furthermore, stationary fuel cells may re-electrify hydrogen (with waste heat as a byproduct) to balance renewable fluctuations (see :ref:`Electricity supply and demand`). The waste heat from the stationary fuel cells can be used in `district-heating systems `_. +Hydrogen is also used for transport applications (see :ref:`Transportation`), where it is exogenously fixed. It is used in `heavy-duty land transport `__ and as liquified hydrogen in the shipping sector (see :ref:`Shipping`). Furthermore, stationary fuel cells may re-electrify hydrogen (with waste heat as a byproduct) to balance renewable fluctuations (see :ref:`Electricity supply and demand`). The waste heat from the stationary fuel cells can be used in `district-heating systems `__. .. _Hydrogen supply: @@ -220,7 +220,7 @@ combined with a water-gas shift reaction CO + H_2O \xrightarrow{} CO_2 + H_2 -SMR is included `here `_. +SMR is included `here `__. PyPSA-Eur-Sec allows this route of :math:`H_2` production with and without [carbon capture (CC)] (see :ref:`Carbon dioxide capture, usage and sequestration (CCU/S)`). These routes are often referred to as blue and grey hydrogen. Here, methane input can be both of fossil or synthetic origin. Green hydrogen can be produced by electrolysis to split water into hydrogen and oxygen @@ -234,12 +234,12 @@ For the electrolysis, alkaline electrolysers are chosen since they have lower co **Transport** -Hydrogen is transported by pipelines. :math:`H_2` pipelines are endogenously generated, either via a greenfield :math:`H_2` network, or by `retrofitting natural gas pipelines `_). Retrofitting is implemented in such a way that for every unit of decommissioned gas pipeline, a share (60% is used in the study by `Neumann et al. `_) of its nominal capacity (exogenously determined in the `config file `_.) is available for hydrogen transport. When the gas network is not resolved, this input denotes the potential for gas pipelines repurposed into hydrogen pipelines. +Hydrogen is transported by pipelines. :math:`H_2` pipelines are endogenously generated, either via a greenfield :math:`H_2` network, or by `retrofitting natural gas pipelines `__). Retrofitting is implemented in such a way that for every unit of decommissioned gas pipeline, a share (60% is used in the study by `Neumann et al. `__) of its nominal capacity (exogenously determined in the `config file `__.) is available for hydrogen transport. When the gas network is not resolved, this input denotes the potential for gas pipelines repurposed into hydrogen pipelines. New pipelines can be built additionally on all routes where there currently is a gas or electricity network connection. These new pipelines will be built where no sufficient retrofitting options are available. The capacities of new and repurposed pipelines are a result of the optimisation. **Storage** -Hydrogen can be stored in overground steel tanks or `underground salt caverns `_. For the latter, energy storage capacities in every country are limited to the potential estimation for onshore salt caverns within `50 km `_ of shore to avoid environmental issues associated with brine solution disposal. Underground storage potentials for hydrogen in European salt caverns is acquired from `Caglayan et al. `_ +Hydrogen can be stored in overground steel tanks or `underground salt caverns `__. For the latter, energy storage capacities in every country are limited to the potential estimation for onshore salt caverns within `50 km `__ of shore to avoid environmental issues associated with brine solution disposal. Underground storage potentials for hydrogen in European salt caverns is acquired from `Caglayan et al. `__ .. _Methane demand: @@ -253,7 +253,7 @@ Methane is used in individual and large-scale gas boilers, in CHP plants with an Methane supply =================================== -In addition to methane from fossil origins, the model also considers biogenic and synthetic sources. `The gas network can either be modelled, or it can be assumed that gas transport is not limited `_. If gas infrastructure is regionally resolved, fossil gas can enter the system only at existing and planned LNG terminals, pipeline entry-points, and intra- European gas extraction sites, which are retrieved from the SciGRID Gas IGGIELGN dataset and the GEM Wiki. +In addition to methane from fossil origins, the model also considers biogenic and synthetic sources. `The gas network can either be modelled, or it can be assumed that gas transport is not limited `__. If gas infrastructure is regionally resolved, fossil gas can enter the system only at existing and planned LNG terminals, pipeline entry-points, and intra- European gas extraction sites, which are retrieved from the SciGRID Gas IGGIELGN dataset and the GEM Wiki. Biogas can be upgraded to methane. Synthetic methane can be produced by processing hydrogen and captures :math:`CO_2` in the Sabatier reaction @@ -269,13 +269,13 @@ The existing European gas transmission network is represented based on the SciGR The following figure shows the unclustered European gas transmission network based on the SciGRID Gas IGGIELGN dataset. Pipelines are color-coded by estimated capacities. Markers indicate entry-points, sites of fossil resource extraction, and LNG terminals. -.. image:: ../graphics/gas_pipeline_figure.png +.. image:: img/gas_pipeline_figure.png .. _Biomass supply: Biomass Supply ===================== -Biomass supply potentials for each European country are taken from the `JRC ENSPRESO database `_ where data is available for various years (2010, 2020, 2030, 2040 and 2050) and scenarios (low, medium, high). No biomass import from outside Europe is assumed. More information on the data set can be found `here `_. +Biomass supply potentials for each European country are taken from the `JRC ENSPRESO database `__ where data is available for various years (2010, 2020, 2030, 2040 and 2050) and scenarios (low, medium, high). No biomass import from outside Europe is assumed. More information on the data set can be found `here `__. .. _Biomass demand: @@ -283,19 +283,19 @@ Biomass demand ===================== -Biomass supply potentials for every NUTS2 region are taken from the `JRC ENSPRESO database `_ where data is available for various years (2010, 2020, 2030, 2040 and 2050) and different availability scenarios (low, medium, high). No biomass import from outside Europe is assumed. More information on the data set can be found `here `_. The data for NUTS2 regions is mapped to PyPSA-Eur-Sec model regions in proportion to the area overlap. +Biomass supply potentials for every NUTS2 region are taken from the `JRC ENSPRESO database `__ where data is available for various years (2010, 2020, 2030, 2040 and 2050) and different availability scenarios (low, medium, high). No biomass import from outside Europe is assumed. More information on the data set can be found `here `__. The data for NUTS2 regions is mapped to PyPSA-Eur-Sec model regions in proportion to the area overlap. -The desired scenario can be selected in the PyPSA-Eur-Sec `configuration `_. The script for building the biomass potentials from the JRC ENSPRESO data base is located `here `_. Consult the script to see the keywords that specify the scenario options. +The desired scenario can be selected in the PyPSA-Eur-Sec `configuration `__. The script for building the biomass potentials from the JRC ENSPRESO data base is located `here `__. Consult the script to see the keywords that specify the scenario options. -The `configuration `_ also allows the user to define how the various types of biomass are used in the model by using the following categories: biogas, solid biomass, and not included. Feedstocks categorized as biogas, typically manure and sludge waste, are available to the model as biogas, which can be upgraded to biomethane. Feedstocks categorized as solid biomass, e.g. secondary forest residues or municipal waste, are available for combustion in combined-heat-and power (CHP) plants and for medium temperature heat (below 500 °C) applications in industry. It can also converted to gas or liquid fuels. +The `configuration `__ also allows the user to define how the various types of biomass are used in the model by using the following categories: biogas, solid biomass, and not included. Feedstocks categorized as biogas, typically manure and sludge waste, are available to the model as biogas, which can be upgraded to biomethane. Feedstocks categorized as solid biomass, e.g. secondary forest residues or municipal waste, are available for combustion in combined-heat-and power (CHP) plants and for medium temperature heat (below 500 °C) applications in industry. It can also converted to gas or liquid fuels. Feedstocks labeled as not included are ignored by the model. -A `typical use case for biomass `_ would be the medium availability scenario for 2030 where only residues from agriculture and forestry as well as biodegradable municipal waste are considered as energy feedstocks. Fuel crops are avoided because they compete with scarce land for food production, while primary wood, as well as wood chips and pellets, are avoided because of concerns about sustainability. See the supporting materials of the `paper `_ for more details. +A `typical use case for biomass `__ would be the medium availability scenario for 2030 where only residues from agriculture and forestry as well as biodegradable municipal waste are considered as energy feedstocks. Fuel crops are avoided because they compete with scarce land for food production, while primary wood, as well as wood chips and pellets, are avoided because of concerns about sustainability. See the supporting materials of the `paper `__ for more details. *Solid biomass conversion and use* @@ -303,19 +303,19 @@ A `typical use case for biomass `_ would be th Solid biomass can be used directly to provide process heat up to 500˚C in the industry. It can also be burned in CHP plants and boilers associated with heating systems. These technologies are described elsewhere (see :ref:`Large-scale CHP` and :ref:`Industry demand`). -Solid biomass can be converted to syngas if the option is enabled in the `config file `_. In this case the model will enable the technology BioSNG both with and without the option for carbon capture (see `Technology-data repository `_). +Solid biomass can be converted to syngas if the option is enabled in the `config file `__. In this case the model will enable the technology BioSNG both with and without the option for carbon capture (see `Technology-data repository `__). -Liquefaction of solid biomass `can be enabled `_ allowing the model to convert it into liquid hydrocarbons that can replace conventional oil products. This technology also comes with and without carbon capture (see `Technology-data repository `_). +Liquefaction of solid biomass `can be enabled `__ allowing the model to convert it into liquid hydrocarbons that can replace conventional oil products. This technology also comes with and without carbon capture (see `Technology-data repository `__). *Transport of solid biomass* -The transport of solid biomass can either be assumed unlimited between countries or it can be associated with a country specific cost per MWh/km. In the config file these options are toggled `here `_. If the option is off, use of solid biomass is transport. If it is turned on, a biomass transport network will be `created `_ between all nodes. This network resembles road transport of biomass and the cost of transportation is a variable cost which is proportional to distance and a country specific cost per MWh/km. The latter is `estimated `_ from the country specific costs per ton/km used in the publication `“The JRC-EU-TIMES model. Bioenergy potentials for EU and neighbouring countries” `_. +The transport of solid biomass can either be assumed unlimited between countries or it can be associated with a country specific cost per MWh/km. In the config file these options are toggled `here `__. If the option is off, use of solid biomass is transport. If it is turned on, a biomass transport network will be `created `__ between all nodes. This network resembles road transport of biomass and the cost of transportation is a variable cost which is proportional to distance and a country specific cost per MWh/km. The latter is `estimated `__ from the country specific costs per ton/km used in the publication `“The JRC-EU-TIMES model. Bioenergy potentials for EU and neighbouring countries” `__. *Biogas transport and use* -Biogas will be aggregated into a common European resources if a gas network is not modelled explicitly, i.e., the `gas_network `_ option is set to false. If, on the other hand, a gas network is included, the biogas potential will be associated with each node of origin. +Biogas will be aggregated into a common European resources if a gas network is not modelled explicitly, i.e., the `gas_network `__ option is set to false. If, on the other hand, a gas network is included, the biogas potential will be associated with each node of origin. The model can only use biogas by first upgrading it to natural gas quality [see :ref:`Methane supply`] (bio methane) which is fed into the general gas network. .. _Oil-based products demand: @@ -338,7 +338,7 @@ Oil-based products can be either of fossil origin or synthetically produced by c 𝑛CO+(2𝑛+1)H_2 → C_{n}H_{2n + 2} +𝑛H_2O -with costs as included from the `technology-data repository `_. The waste heat from the Fischer-Tropsch process is supplied to `district heating networks `_. The share of fossil and synthetic oil is an optimisation result depending on the techno-economic assumptions. +with costs as included from the `technology-data repository `__. The waste heat from the Fischer-Tropsch process is supplied to `district heating networks `__. The share of fossil and synthetic oil is an optimisation result depending on the techno-economic assumptions. *Oil-based transport* @@ -361,41 +361,41 @@ The Subsection overview below provides a general description of the modelling ap Greenhouse gas emissions associated with industry can be classified into energy-related and process-related emissions. Today, fossil fuels are used for process heat energy in the chemicals industry, but also as a non-energy feedstock for chemicals like ammonia ( :math:`NH_3`), ethylene ( :math:`C_2H_4`) and methanol ( :math:`CH_3OH`). Energy-related emissions can be curbed by using low-emission energy sources. The only option to reduce process-related emissions is by using an alternative manufacturing process or by assuming a certain rate of recycling so that a lower amount of virgin material is needed. -The overarching modelling procedure can be described as follows. First, the energy demands and process emissions for every unit of material output are estimated based on data from the `JRC-IDEES database `_ and the fuel and process switching described in the subsequent sections. Second, the 2050 energy demands and process emissions are calculated using the per-unit-of-material ratios based on the industry transformations and the `country-level material production in 2015 `_, assuming constant material demand. +The overarching modelling procedure can be described as follows. First, the energy demands and process emissions for every unit of material output are estimated based on data from the `JRC-IDEES database `__ and the fuel and process switching described in the subsequent sections. Second, the 2050 energy demands and process emissions are calculated using the per-unit-of-material ratios based on the industry transformations and the `country-level material production in 2015 `__, assuming constant material demand. -Missing or too coarsely aggregated data in the JRC-IDEES database is supplemented with additional datasets: `Eurostat energy balances `_, `United States `_, `Geological Survey `_ for ammonia production, `DECHEMA `_ for methanol and chlorine, and `national statistics from Switzerland `_. +Missing or too coarsely aggregated data in the JRC-IDEES database is supplemented with additional datasets: `Eurostat energy balances `__, `United States `__, `Geological Survey `__ for ammonia production, `DECHEMA `__ for methanol and chlorine, and `national statistics from Switzerland `__. Where there are fossil and electrified alternatives for the same process (e.g. in glass manufacture or drying), we assume that the process is completely electrified. Current electricity demands (lighting, air compressors, motor drives, fans, pumps) will remain electric. Processes that require temperatures below 500 °C are supplied with solid biomass, since we assume that residues and wastes are not suitable for high-temperature applications. We see solid biomass use primarily in the pulp and paper industry, where it is already widespread, and in food, beverages and tobacco, where it replaces natural gas. Industries which require high temperatures (above 500 °C), such as metals, chemicals and non-metallic minerals are either electrified where suitable processes already exist, or the heat is provided with synthetic methane. Hydrogen for high-temperature process heat is not part of the model currently. -Where process heat is required, our approach depends on the necessary temperature. For example, due to the high share of high-temperature process heat demand (see `Naegler et al. `_ and `Rehfeldt el al. `_), we disregard geothermal and solar thermal energy as sources for process heat since they cannot attain high-temperature heat. +Where process heat is required, our approach depends on the necessary temperature. For example, due to the high share of high-temperature process heat demand (see `Naegler et al. `__ and `Rehfeldt el al. `__), we disregard geothermal and solar thermal energy as sources for process heat since they cannot attain high-temperature heat. -The following figure shows the final consumption of energy and non-energy feedstocks in industry today in comparison to the scenario in 2050 assumed in `Neumann et al `_. +The following figure shows the final consumption of energy and non-energy feedstocks in industry today in comparison to the scenario in 2050 assumed in `Neumann et al `__. -.. image:: ../graphics/fec_industry_today_tomorrow.png +.. image:: img/fec_industry_today_tomorrow.png The following figure shows the process emissions in industry today (top bar) and in 2050 without -carbon capture (bottom bar) assumed in `Neumann et al `_. +carbon capture (bottom bar) assumed in `Neumann et al `__. -.. image:: ../graphics/process-emissions.png +.. image:: img/process-emissions.png -Inside each country the industrial demand is then distributed using the `Hotmaps Industrial Database `_, which is illustrated in the figure below. This open database includes georeferenced industrial sites of energy-intensive industry sectors in EU28, including cement, basic chemicals, glass, iron and steel, non-ferrous metals, non-metallic minerals, paper, and refineries subsectors. The use of this spatial dataset enables the calculation of regional and process-specific energy demands. This approach assumes that there will be no significant migration of energy-intensive industries. +Inside each country the industrial demand is then distributed using the `Hotmaps Industrial Database `__, which is illustrated in the figure below. This open database includes georeferenced industrial sites of energy-intensive industry sectors in EU28, including cement, basic chemicals, glass, iron and steel, non-ferrous metals, non-metallic minerals, paper, and refineries subsectors. The use of this spatial dataset enables the calculation of regional and process-specific energy demands. This approach assumes that there will be no significant migration of energy-intensive industries. -.. image:: ../graphics/hotmaps.png +.. image:: img/hotmaps.png .. _Iron and Steel: **Iron and Steel** -Two alternative routes are used today to manufacture steel in Europe. The primary route (integrated steelworks) represents 60% of steel production, while the secondary route (electric arc furnaces, EAF), represents the other 40% `(Lechtenböhmer et. al) `_. +Two alternative routes are used today to manufacture steel in Europe. The primary route (integrated steelworks) represents 60% of steel production, while the secondary route (electric arc furnaces, EAF), represents the other 40% `(Lechtenböhmer et. al) `__. The primary route uses blast furnaces in which coke is used to reduce iron ore into molten iron, which is then converted into steel: @@ -415,9 +415,9 @@ The primary route uses blast furnaces in which coke is used to reduce iron ore i FeO + CO \xrightarrow{} Fe + CO_2 -The primary route of steelmaking implies large process emissions of 0.22 t :math:`_{CO_2}` /t of steel, amounting to 7% of global greenhouse gas emissions `(Vogl et. al) `_. +The primary route of steelmaking implies large process emissions of 0.22 t :math:`_{CO_2}` /t of steel, amounting to 7% of global greenhouse gas emissions `(Vogl et. al) `__. -In the secondary route, electric arc furnaces are used to melt scrap metal. This limits the :math:`CO_2` emissions to the burning of graphite electrodes `(Friedrichsen et. al) `_, and reduces process emissions to 0.03 t :math:`_{CO_2}` /t of steel. +In the secondary route, electric arc furnaces are used to melt scrap metal. This limits the :math:`CO_2` emissions to the burning of graphite electrodes `(Friedrichsen et. al) `__, and reduces process emissions to 0.03 t :math:`_{CO_2}` /t of steel. We assume that the primary route can be replaced by a third route in 2050, using direct reduced iron (DRI) and subsequent processing in an EAF. @@ -433,10 +433,10 @@ We assume that the primary route can be replaced by a third route in 2050, using FeO + H_2 \xrightarrow{} Fe + H_2O -This circumvents the process emissions associated with the use of coke. For hydrogen- based DRI, we assume energy requirements of 1.7 MWh :math:`_{H_2}` /t steel `(Vogl et. al) `_ and 0.322 MWh :math:`_{el}`/t steel `(HYBRIT 2016) `_. +This circumvents the process emissions associated with the use of coke. For hydrogen- based DRI, we assume energy requirements of 1.7 MWh :math:`_{H_2}` /t steel `(Vogl et. al) `__ and 0.322 MWh :math:`_{el}`/t steel `(HYBRIT 2016) `__. -The share of steel produced via the primary route is exogenously set in the `config file `_. The share of steel obtained via hydrogen-based DRI plus EAF is also set exogenously in the `config file `_. The remaining share is manufactured through the secondary route using scrap metal in EAF. Bioenergy as alternative to coke in blast furnaces is not considered in the model (`Mandova et.al `_, `Suopajärvi et.al `_). +The share of steel produced via the primary route is exogenously set in the `config file `__. The share of steel obtained via hydrogen-based DRI plus EAF is also set exogenously in the `config file `__. The remaining share is manufactured through the secondary route using scrap metal in EAF. Bioenergy as alternative to coke in blast furnaces is not considered in the model (`Mandova et.al `__, `Suopajärvi et.al `__). For the remaining subprocesses in this sector, the following transformations are assumed. Methane is used as energy source for the smelting process. Activities associated with furnaces, refining and rolling, and product finishing are electrified assuming the current efficiency values for these cases. These transformations result in changes in process emissions as outlined in the process emissions figure presented in the industry overview section (see :ref:`Overview`). @@ -446,28 +446,27 @@ For the remaining subprocesses in this sector, the following transformations are The chemicals industry includes a wide range of diverse industries, including the production of basic organic compounds (olefins, alcohols, aromatics), basic inorganic compounds (ammonia, chlorine), polymers (plastics), and end-user products (cosmetics, pharmaceutics). -The chemicals industry consumes large amounts of fossil-fuel based feedstocks (see `Levi et. al `_), which can also be produced from renewables as outlined for hydrogen (see :ref:`Hydrogen supply`), for methane (see :ref:`Methane supply`), and for oil-based products (see :ref:`Oil-based products supply`). The ratio between synthetic and fossil-based fuels used in the industry is an endogenous result of the optimisation. +The chemicals industry consumes large amounts of fossil-fuel based feedstocks (see `Levi et. al `__), which can also be produced from renewables as outlined for hydrogen (see :ref:`Hydrogen supply`), for methane (see :ref:`Methane supply`), and for oil-based products (see :ref:`Oil-based products supply`). The ratio between synthetic and fossil-based fuels used in the industry is an endogenous result of the optimisation. -The basic chemicals consumption data from the `JRC IDEES `_ database comprises high- value chemicals (ethylene, propylene and BTX), chlorine, methanol and ammonia. However, it is necessary to separate out these chemicals because their current and future production routes are different. +The basic chemicals consumption data from the `JRC IDEES `__ database comprises high- value chemicals (ethylene, propylene and BTX), chlorine, methanol and ammonia. However, it is necessary to separate out these chemicals because their current and future production routes are different. -Statistics for the production of ammonia, which is commonly used as a fertilizer, are taken from the `USGS `_ for every country. Ammonia can be made from hydrogen and nitrogen using the Haber-Bosch process. +Statistics for the production of ammonia, which is commonly used as a fertilizer, are taken from the `USGS `__ for every country. Ammonia can be made from hydrogen and nitrogen using the Haber-Bosch process. .. math:: N_2 + 3H_2 \xrightarrow{} 2NH_3 -The Haber-Bosch process is not explicitly represented in the model, such that demand for ammonia enters the model as a demand for hydrogen ( 6.5 MWh :math:`_{H_2}` / t :math:`_{NH_3}` ) and electricity ( 1.17 MWh :math:`_{el}` /t :math:`_{NH_3}` ) (see `Wang et. al `_). Today, natural gas dominates in Europe as the source for the hydrogen used in the Haber-Bosch process, but the model can choose among the various hydrogen supply options described in the hydrogen section (see :ref:`Hydrogen supply`) +The Haber-Bosch process is not explicitly represented in the model, such that demand for ammonia enters the model as a demand for hydrogen ( 6.5 MWh :math:`_{H_2}` / t :math:`_{NH_3}` ) and electricity ( 1.17 MWh :math:`_{el}` /t :math:`_{NH_3}` ) (see `Wang et. al `__). Today, natural gas dominates in Europe as the source for the hydrogen used in the Haber-Bosch process, but the model can choose among the various hydrogen supply options described in the hydrogen section (see :ref:`Hydrogen supply`) -The total production and specific energy consumption of chlorine and methanol is taken from a `DECHEMA report `_. According to this source, the production of chlorine amounts to 9.58 MtCl/a, which is assumed to require electricity at 3.6 MWh :math:`_{el}`/t of chlorine and yield hydrogen at 0.937 MWh :math:`_{H_2}`/t of chlorine in the chloralkali process. The production of methanol adds up to 1.5 MtMeOH/a, requiring electricity at 0.167 MWh :math:`_{el}`/t of methanol and methane at 10.25 MWh :math:`_{CH_4}`/t of methanol. +The total production and specific energy consumption of chlorine and methanol is taken from a `DECHEMA report `__. According to this source, the production of chlorine amounts to 9.58 MtCl/a, which is assumed to require electricity at 3.6 MWh :math:`_{el}`/t of chlorine and yield hydrogen at 0.937 MWh :math:`_{H_2}`/t of chlorine in the chloralkali process. The production of methanol adds up to 1.5 MtMeOH/a. Low-carbon methanol production (or methanolisation) by hydrogenation of :math:`CO_2` requires hydrogen at 6.299 MWh :math:`_{H_2}`/t of methanol, carbon dioxide at 1.373 t :math:`_{CO_2}`/t of methanol and electricity at 1.5 MWh :math:`_{el}`/t of methanol. The energy content of methanol is 5.528 MWh :math:`_{MeOH}`/t of methanol. These values are set exogenously in the config file. - -The production of ammonia, methanol, and chlorine production is deducted from the JRC IDEES basic chemicals, leaving the production totals of high-value chemicals. For this, we assume that the liquid hydrocarbon feedstock comes from synthetic or fossil- origin naphtha (14 MWh :math:`_{naphtha}`/t of HVC, similar to `Lechtenböhmer et al `_), ignoring the methanol-to-olefin route. Furthermore, we assume the following transformations of the energy-consuming processes in the production of plastics: the final energy consumption in steam processing is converted to methane since requires temperature above 500 °C (4.1 MWh :math:`_{CH_4}` /t of HVC, see `Rehfeldt et al. `_); and the remaining processes are electrified using the current efficiency of microwave for high-enthalpy heat processing, electric furnaces, electric process cooling and electric generic processes (2.85 MWh :math:`_{el}`/t of HVC). +The production of ammonia, methanol, and chlorine production is deducted from the JRC IDEES basic chemicals, leaving the production totals of high-value chemicals. For this, we assume that the liquid hydrocarbon feedstock comes from synthetic or fossil- origin naphtha (14 MWh :math:`_{naphtha}`/t of HVC, similar to `Lechtenböhmer et al `__), ignoring the methanol-to-olefin route. Furthermore, we assume the following transformations of the energy-consuming processes in the production of plastics: the final energy consumption in steam processing is converted to methane since requires temperature above 500 °C (4.1 MWh :math:`_{CH_4}` /t of HVC, see `Rehfeldt et al. `__); and the remaining processes are electrified using the current efficiency of microwave for high-enthalpy heat processing, electric furnaces, electric process cooling and electric generic processes (2.85 MWh :math:`_{el}`/t of HVC). The process emissions from feedstock in the chemical industry are as high as 0.369 t :math:`_{CO_2}`/t of ethylene equivalent. We consider process emissions for all the material output, which is a conservative approach since it assumes that all plastic-embedded :math:`CO_2` will eventually be released into the atmosphere. However, plastic disposal in landfilling will avoid, or at least delay, associated :math:`CO_2` emissions. -Circular economy practices drastically reduce the amount of primary feedstock needed for the production of plastics in the model (see `Kullmann et al. `_, `Meys et al. (2021) `_, `Meys et al. (2020) `_, `Gu et al. `_) and consequently, also the energy demands and level of process emission. The percentage of plastics that are assumed to be mechanically recycled can be selected in the `config file `_, as well as -the percentage that is chemically recycled, see `config file `_ The energy consumption for those recycling processes are respectively 0.547 MWh :math:`_{el}`/t of HVC (as indicated in the `config file `_) (`Meys et al. (2020) `_), and 6.9 MWh :math:`_{el}`/t of HVC (as indicated in the `config file `_) based on pyrolysis and electric steam cracking (see `Materials Economics `_ report). +Circular economy practices drastically reduce the amount of primary feedstock needed for the production of plastics in the model (see `Kullmann et al. `__, `Meys et al. (2021) `__, `Meys et al. (2020) `__, `Gu et al. `__) and consequently, also the energy demands and level of process emission. The percentage of plastics that are assumed to be mechanically recycled can be selected in the `config file `__, as well as +the percentage that is chemically recycled, see `config file `__ The energy consumption for those recycling processes are respectively 0.547 MWh :math:`_{el}`/t of HVC (as indicated in the `config file `__) (`Meys et al. (2020) `__), and 6.9 MWh :math:`_{el}`/t of HVC (as indicated in the `config file `__) based on pyrolysis and electric steam cracking (see `Materials Economics `__ report). **Non-metallic Mineral Products** @@ -476,7 +475,7 @@ This subsector includes the manufacturing of cement, ceramics, and glass. *Cement* -Cement is used in construction to make concrete. The production of cement involves high energy consumption and large process emissions. The calcination of limestone to chemically reactive calcium oxide, also known as lime, involves process emissions of 0.54 t :math:`_{CO_2}` /t cement (see `Akhtar et al. `_. +Cement is used in construction to make concrete. The production of cement involves high energy consumption and large process emissions. The calcination of limestone to chemically reactive calcium oxide, also known as lime, involves process emissions of 0.54 t :math:`_{CO_2}` /t cement (see `Akhtar et al. `__. .. math:: @@ -487,16 +486,16 @@ Additionally, :math:`CO_2` is emitted from the combustion of fossil fuels to pro Cement process emissions can be captured assuming a capture rate of 90%. Whether emissions are captured is decided by the model taking into account the capital costs of carbon capture modules. The electricity and heat demand of process emission carbon capture is currently ignored. For net-zero emission scenarios, the remaining process emissions need to be compensated by negative emissions. -With the exception of electricity demand and biomass demand for low-temperature heat (0.06 MWh/t and 0.2 MWh/t), the final energy consumption of this subsector is assumed to be supplied by methane (0.52 MWh/t), which is capable of delivering the required high-temperature heat. This implies a switch from burning solid fuels to burning gas which will require adjustments of the `kilns <10.1109/CITCON.2013.6525276>`_. The share of fossil vs. synthetic methane consumed is a result of the optimisation +With the exception of electricity demand and biomass demand for low-temperature heat (0.06 MWh/t and 0.2 MWh/t), the final energy consumption of this subsector is assumed to be supplied by methane (0.52 MWh/t), which is capable of delivering the required high-temperature heat. This implies a switch from burning solid fuels to burning gas which will require adjustments of the `kilns <10.1109/CITCON.2013.6525276>`__. The share of fossil vs. synthetic methane consumed is a result of the optimisation *Ceramics* -The ceramics sector is assumed to be fully electrified based on the current efficiency of already electrified processes which include microwave drying and sintering of raw materials, electric kilns for primary production processes, electric furnaces for the `product finishing `_. In total, the final electricity consumption is 0.44 MWh/t of ceramic. The manufacturing of ceramics includes process emissions of 0.03 t :math:`_{CO_2}`/t of ceramic. For a detailed overview of the ceramics industry sector see `Furszyfer Del Rio et al `_. +The ceramics sector is assumed to be fully electrified based on the current efficiency of already electrified processes which include microwave drying and sintering of raw materials, electric kilns for primary production processes, electric furnaces for the `product finishing `__. In total, the final electricity consumption is 0.44 MWh/t of ceramic. The manufacturing of ceramics includes process emissions of 0.03 t :math:`_{CO_2}`/t of ceramic. For a detailed overview of the ceramics industry sector see `Furszyfer Del Rio et al `__. *Glass* -The production of glass is assumed to be fully electrified based on the current efficiency of electric melting tanks and electric annealing which adds up to an electricity demand of 2.07 MWh :math:`_{el}`/t of `glass `_. The manufacturing of glass incurs process emissions of 0.1 t :math:`_{CO_2}`/t of glass. Potential efficiency improvements, which according to `Lechtenböhmer et al `_ could reduce energy demands to 0.85 MW :math:`_{el}`/t of glass, have not been considered. For a detailed overview of the glass industry sector see `Furszyfer Del Rio et al `_. +The production of glass is assumed to be fully electrified based on the current efficiency of electric melting tanks and electric annealing which adds up to an electricity demand of 2.07 MWh :math:`_{el}`/t of `glass `__. The manufacturing of glass incurs process emissions of 0.1 t :math:`_{CO_2}`/t of glass. Potential efficiency improvements, which according to `Lechtenböhmer et al `__ could reduce energy demands to 0.85 MW :math:`_{el}`/t of glass, have not been considered. For a detailed overview of the glass industry sector see `Furszyfer Del Rio et al `__. **Non-ferrous Metals** @@ -511,75 +510,77 @@ The primary route involves two energy-intensive processes: the production of alu 2Al_2O_3 +3C \xrightarrow{} 4Al+3CO_2 -The primary route requires high-enthalpy heat (2.3 MWh/t) to produce alumina which is supplied by methane and causes process emissions of 1.5 t :math:`_{CO_2}`/t aluminium. According to `Friedrichsen et al. `_, inert anodes might become commercially available by 2030 that would eliminate the process emissions, but they are not included in the model. Assuming all subprocesses are electrified, the primary route requires 15.4 MWh :math:`_{el}`/t of aluminium. +The primary route requires high-enthalpy heat (2.3 MWh/t) to produce alumina which is supplied by methane and causes process emissions of 1.5 t :math:`_{CO_2}`/t aluminium. According to `Friedrichsen et al. `__, inert anodes might become commercially available by 2030 that would eliminate the process emissions, but they are not included in the model. Assuming all subprocesses are electrified, the primary route requires 15.4 MWh :math:`_{el}`/t of aluminium. -In the secondary route, scrap aluminium is remelted. The energy demand for this process is only 10% of the primary route and there are no associated process emissions. Assuming all subprocesses are electrified, the secondary route requires 1.7 MWh/t of aluminium. The share of aliminum manufactured by the primary and secondary route can be selected in the `config file `_] +In the secondary route, scrap aluminium is remelted. The energy demand for this process is only 10% of the primary route and there are no associated process emissions. Assuming all subprocesses are electrified, the secondary route requires 1.7 MWh/t of aluminium. The share of aliminum manufactured by the primary and secondary route can be selected in the `config file `__] For the other non-ferrous metals, we assume the electrification of the entire manufacturing process with an average electricity demand of 3.2 MWh :math:`_{el}`/t lead equivalent. **Other Industry Subsectors** -The remaining industry subsectors include (a) pulp, paper, printing, (b) food, beverages, tobacco, (c) textiles and leather, (d) machinery equipment, (e) transport equipment, (f) wood and wood products, (g) others. Low- and mid-temperature process heat in these industries is assumed to be `supplied by biomass `_ while the remaining processes are electrified. None of the subsectors involve process emissions. +The remaining industry subsectors include (a) pulp, paper, printing, (b) food, beverages, tobacco, (c) textiles and leather, (d) machinery equipment, (e) transport equipment, (f) wood and wood products, (g) others. Low- and mid-temperature process heat in these industries is assumed to be `supplied by biomass `__ while the remaining processes are electrified. None of the subsectors involve process emissions. Agriculture demand ========================= -Energy demands for the agriculture, forestry and fishing sector per country are taken from the `JRC-IDEES database `_. Missing countries are filled with `Eurostat data `_. Agricultural energy demands are split into electricity (lighting, ventilation, specific electricity uses, electric pumping devices), heat (specific heat uses, low enthalpy heat), and machinery oil (motor drives, farming machine drives, diesel-fueled pumping devices). Heat demand is assigned at “services rural heat” buses. Time series for demands are assumed to be constant and distributed inside countries by population. +Energy demands for the agriculture, forestry and fishing sector per country are taken from the `JRC-IDEES database `__. Missing countries are filled with `Eurostat data `__. Agricultural energy demands are split into electricity (lighting, ventilation, specific electricity uses, electric pumping devices), heat (specific heat uses, low enthalpy heat), and machinery oil (motor drives, farming machine drives, diesel-fueled pumping devices). Heat demand is assigned at “services rural heat” buses. Time series for demands are assumed to be constant and distributed inside countries by population. .. _Transportation: Transportation ========================= -Annual energy demands for land transport, aviation and shipping for every country are retrieved from `JRC-IDEES data set `_. Below, the details of how each of these categories are treated is explained. +Annual energy demands for land transport, aviation and shipping for every country are retrieved from `JRC-IDEES data set `__. Below, the details of how each of these categories are treated is explained. .. _Land transport: **Land transport** -Both road and rail transport is combined as `land transport demand `_ although electrified rail transport is excluded because that demand is included in the current electricity demand. +Both road and rail transport is combined as `land transport demand `__ although electrified rail transport is excluded because that demand is included in the current electricity demand. -The most important settings for land transport are the exogenously fixed fuel mix (an option enabling the endogeous optimization of transport electrification is planned but not yet implemented). In the `config file `_, the share of battery electric vehicles (BEV) and hydrogen fuel cell vehicles (FCEV) can be set. The remaining percentage will be treated as internal combustion engines (ICE) that consume oil products. +The most important settings for land transport are the exogenously fixed fuel mix (an option enabling the endogeous optimization of transport electrification is planned but not yet implemented). In the `config file `__, the share of battery electric vehicles (BEV) and hydrogen fuel cell vehicles (FCEV) can be set. The remaining percentage will be treated as internal combustion engines (ICE) that consume oil products. *Battery Electric vehicles (BEV)* -For the electrified land transport, country-specific factors are computed by comparing the `current car final energy consumption per km in `_ (average for Europe 0.7 kWh/km) to the 0.18 kWh/km value assumed for battery-to-wheels efficiency in EVs. The characteristic `weekly profile `_ provided by the German Federal Highway Research Institute (BASt) is used to obtain hourly time series for European countries taking into account the corresponding local times. Furthermore, a temperature dependence is included in the time series to account for heating/cooling demand in transport. For temperatures `below `_/`above `_ certain threshold values, e.g. 15 °C/20 °C, `temperature coefficients `_ of typically 0.98%/°C and 0.63%/°C are assumed, based on the `paper `_. +For the electrified land transport, country-specific factors are computed by comparing the `current car final energy consumption per km in `__ (average for Europe 0.7 kWh/km) to the 0.18 kWh/km value assumed for battery-to-wheels efficiency in EVs. The characteristic `weekly profile `__ provided by the German Federal Highway Research Institute (BASt) is used to obtain hourly time series for European countries taking into account the corresponding local times. Furthermore, a temperature dependence is included in the time series to account for heating/cooling demand in transport. For temperatures `below `__/`above `__ certain threshold values, e.g. 15 °C/20 °C, `temperature coefficients `__ of typically 0.98%/°C and 0.63%/°C are assumed, based on the `paper `__. -For BEVs the user can define the `storage energy capacity `_, `charging power capacity `_, and `charging efficiency `_. +For BEVs the user can define the `storage energy capacity `__, `charging power capacity `__, and `charging efficiency `__. -For BEV, smart charging is an option. A `certain share `_ of the BEV fleet can shift their charging time. The BEV state of charge is forced to be higher than a `set percentage `_, e.g. 75%, every day at a `specified hour `_, e.g., 7 am, to ensure that the batteries are sufficiently charged for peak usage in the morning and they not behave as seasonal storage. They also have the option to participate in vehicle-to-grid (V2G) services to facilitate system operation if that `is enabled `_. +For BEV, smart charging is an option. A `certain share `__ of the BEV fleet can shift their charging time. The BEV state of charge is forced to be higher than a `set percentage `__, e.g. 75%, every day at a `specified hour `__, e.g., 7 am, to ensure that the batteries are sufficiently charged for peak usage in the morning and they not behave as seasonal storage. They also have the option to participate in vehicle-to-grid (V2G) services to facilitate system operation if that `is enabled `__. The battery cost of BEV is not included in the model since it is assumed that BEV owners buy them to primarily satisfy their mobility needs. *Hydrogen fuel cell vehicles (FCEV)* The share of all land transport that is specified to be be FCEV will be converted to a demand for hydrogen (see :ref:`Hydrogen supply`) using the `FCEV efficiency -`_. +`__. FCEVs are typically used to simulate demand for transport that is hard to electrify directly, e.g. heavy construction machinery. But it may also be used to investigate a more widespread adoption of the technology. *Internal combustion engine vehicles (ICE)* All land transport that is not specified to be either BEV or FCEV will be treated as conventional ICEs. The transport demand is converted to a demand for oil products (see :ref:`Oil-based products supply`) using the `ICE efficiency -`_. +`__. .. _Aviation: **Aviation** -The `demand for aviation `_ includes international and domestic use. It is modelled as an oil demand since aviation consumes kerosene. This can be produced synthetically or have fossil-origin (see :ref:`Oil-based products supply`). +The `demand for aviation `__ includes international and domestic use. It is modelled as an oil demand since aviation consumes kerosene. This can be produced synthetically or have fossil-origin (see :ref:`Oil-based products supply`). .. _Shipping: **Shipping** -Shipping energy demand is covered by a combination of oil and hydrogen. Other fuel options, like methanol or ammonia, are currently not included in PyPSA-Eur-Sec. The share of shipping that is assumed to be supplied by hydrogen can be selected in the `config file `_. +Shipping energy demand is covered by a combination of oil, hydrogen and methanol. Other fuel options, like ammonia, are currently not included in PyPSA-Eur-Sec. The share of shipping that is assumed to be supplied by hydrogen or methanol can be selected in the `config file `__. -To estimate the `hydrogen demand `_, the average fuel efficiency of the fleet is used in combination with the efficiency of the fuel cell defined in the technology-data repository. The average fuel efficiency is set in the `config file `_. +To estimate the `hydrogen demand `__, the average fuel efficiency of the fleet is used in combination with the efficiency of the fuel cell defined in the technology-data repository. The average fuel efficiency is set in the `config file `__. The consumed hydrogen comes from the general hydrogen bus where it can be produced by SMR, SMR+CC or electrolysers (see :ref:`Hydrogen supply`). The fraction that is not converted into hydrogen use oil products, i.e. is connected to the general oil bus. -The energy demand for liquefaction of the hydrogen used for shipping can be `included `_. If this option is selected, liquifaction will happen at the `node where the shipping demand occurs `_. +The energy demand for liquefaction of the hydrogen used for shipping can be `included `__. If this option is selected, liquifaction will happen at the `node where the shipping demand occurs `__. + +The consumed methanol comes from the general methanol bus where it is produced through methanolisation (see :ref:`Chemicals Industry`). .. _Carbon dioxide capture, usage and sequestration (CCU/S): @@ -600,12 +601,12 @@ For the following point source emissions, carbon capture is applicable: • CHP plants using biomass or methane -• `Coal power plants `_. +• `Coal power plants `__. -Point source emissions are captured assuming a capture rate, e.g. 90%, which can be specified in the `config file `_. The electricity and heat demand of process emission carbon capture +Point source emissions are captured assuming a capture rate, e.g. 90%, which can be specified in the `config file `__. The electricity and heat demand of process emission carbon capture is currently ignored. -DAC (if `included `_) includes the adsorption phase where electricity and heat consumptionsare required to assist the adsorption process and regenerate the adsorbent. It also includes the drying and compression of :math:`CO_2` prior to storage which consumes electricity and rejects heat. +DAC (if `included `__) includes the adsorption phase where electricity and heat consumptionsare required to assist the adsorption process and regenerate the adsorbent. It also includes the drying and compression of :math:`CO_2` prior to storage which consumes electricity and rejects heat. *Carbon dioxide usage* @@ -614,8 +615,8 @@ naphtha). If captured carbon is used, the :math:`CO_2` emissions of the syntheti *Carbon dioxide sequestration* -Captured :math:`CO_2` can also be sequestered underground up to an annual sequestration limit of 200 Mt :math:`_{CO_2}`/a. This limit can be chosen in the `config file `_. As stored carbon dioxide is modelled as a single node for Europe, :math:`CO_2` transport constraints are neglected. Since :math:`CO_2` sequestration is an immature technology, the cost assumption is defined in the `config file `_. +Captured :math:`CO_2` can also be sequestered underground up to an annual sequestration limit of 200 Mt :math:`_{CO_2}`/a. This limit can be chosen in the `config file `__. As stored carbon dioxide is modelled as a single node for Europe, :math:`CO_2` transport constraints are neglected. Since :math:`CO_2` sequestration is an immature technology, the cost assumption is defined in the `config file `__. *Carbon dioxide transport* -Carbon dioxide can be modelled as a single node for Europe (in this case, :math:`CO_2` transport constraints are neglected). A network for modelling the transport of :math:`CO_2` among the different nodes can also be created if selected in the `config file `_. +Carbon dioxide can be modelled as a single node for Europe (in this case, :math:`CO_2` transport constraints are neglected). A network for modelling the transport of :math:`CO_2` among the different nodes can also be created if selected in the `config file `__. diff --git a/doc/support.rst b/doc/support.rst index 1a3e6d08..fc86a998 100644 --- a/doc/support.rst +++ b/doc/support.rst @@ -7,8 +7,8 @@ Support ####################### -* In case of code-related **questions**, please post on `stack overflow `_. -* For non-programming related and more general questions please refer to the `mailing list `_. -* To **discuss** with other PyPSA users, organise projects, share news, and get in touch with the community you can use the `discord server `_. -* For **bugs and feature requests**, please use the `issue tracker `_. -* We strongly welcome anyone interested in providing **contributions** to this project. If you have any ideas, suggestions or encounter problems, feel invited to file issues or make pull requests on `Github `_. For further information on how to contribute, please refer to :ref:`contributing`. +* In case of code-related **questions**, please post on `stack overflow `__. +* For non-programming related and more general questions please refer to the `mailing list `__. +* To **discuss** with other PyPSA users, organise projects, share news, and get in touch with the community you can use the `discord server `__. +* For **bugs and feature requests**, please use the `issue tracker `__. +* We strongly welcome anyone interested in providing **contributions** to this project. If you have any ideas, suggestions or encounter problems, feel invited to file issues or make pull requests on `Github `__. For further information on how to contribute, please refer to :ref:`contributing`. diff --git a/doc/tutorial.rst b/doc/tutorial.rst index 4f272292..4a07132b 100644 --- a/doc/tutorial.rst +++ b/doc/tutorial.rst @@ -32,10 +32,9 @@ configuration, execute .. code:: bash :class: full-width - snakemake -call results/test-elec/networks/elec_s_6_ec_lcopt_Co2L-24H.nc --configfile config/test/config.electricity.yaml + snakemake -call results/test-elec/networks/elec_s_6_ec_lcopt_.nc --configfile config/test/config.electricity.yaml -This configuration is set to download a reduced data set via the rules :mod:`retrieve_databundle`, -:mod:`retrieve_natura_raster`, :mod:`retrieve_cutout`. +This configuration is set to download a reduced cutout via the rule :mod:`retrieve_cutout`. For more information on the data dependencies of PyPSA-Eur, continue reading :ref:`data`. How to configure runs? @@ -115,9 +114,9 @@ clustered down to 6 buses and every 24 hours aggregated to one snapshot. The com .. code:: bash - snakemake -call results/test-elec/networks/elec_s_6_ec_lcopt_Co2L-24H.nc --configfile config/test/config.electricity.yaml + snakemake -call results/test-elec/networks/elec_s_6_ec_lcopt_.nc --configfile config/test/config.electricity.yaml -orders ``snakemake`` to run the rule :mod:`solve_network` that produces the solved network and stores it in ``results/networks`` with the name ``elec_s_6_ec_lcopt_Co2L-24H.nc``: +orders ``snakemake`` to run the rule :mod:`solve_network` that produces the solved network and stores it in ``results/networks`` with the name ``elec_s_6_ec_lcopt_.nc``: .. literalinclude:: ../rules/solve_electricity.smk :start-at: rule solve_network: @@ -133,32 +132,31 @@ This triggers a workflow of multiple preceding jobs that depend on each rule's i graph[bgcolor=white, margin=0]; node[shape=box, style=rounded, fontname=sans, fontsize=10, penwidth=2]; edge[penwidth=2, color=grey]; - 0[label = "solve_network", color = "0.39 0.6 0.85", style="rounded"]; - 1[label = "prepare_network\nll: copt\nopts: Co2L-24H", color = "0.29 0.6 0.85", style="rounded"]; - 2[label = "add_extra_components", color = "0.28 0.6 0.85", style="rounded"]; - 3[label = "cluster_network\nclusters: 6", color = "0.19 0.6 0.85", style="rounded"]; - 4[label = "simplify_network\nsimpl: ", color = "0.01 0.6 0.85", style="rounded"]; - 5[label = "add_electricity", color = "0.49 0.6 0.85", style="rounded"]; - 6[label = "build_renewable_profiles\ntechnology: solar", color = "0.21 0.6 0.85", style="rounded"]; - 7[label = "base_network", color = "0.27 0.6 0.85", style="rounded"]; - 8[label = "build_shapes", color = "0.26 0.6 0.85", style="rounded"]; - 9[label = "retrieve_databundle", color = "0.59 0.6 0.85", style="rounded"]; - 10[label = "retrieve_natura_raster", color = "0.47 0.6 0.85", style="rounded"]; - 11[label = "build_bus_regions", color = "0.13 0.6 0.85", style="rounded"]; - 12[label = "retrieve_cutout\ncutout: be-03-2013-era5", color = "0.36 0.6 0.85", style="rounded,dashed"]; - 13[label = "build_renewable_profiles\ntechnology: onwind", color = "0.21 0.6 0.85", style="rounded"]; - 14[label = "build_renewable_profiles\ntechnology: offwind-ac", color = "0.21 0.6 0.85", style="rounded"]; - 15[label = "build_ship_raster", color = "0.00 0.6 0.85", style="rounded"]; - 16[label = "retrieve_ship_raster", color = "0.51 0.6 0.85", style="rounded,dashed"]; - 17[label = "build_renewable_profiles\ntechnology: offwind-dc", color = "0.21 0.6 0.85", style="rounded"]; - 18[label = "build_line_rating", color = "0.05 0.6 0.85", style="rounded"]; - 19[label = "retrieve_cost_data\nyear: 2030", color = "0.15 0.6 0.85", style="rounded"]; - 20[label = "build_powerplants", color = "0.54 0.6 0.85", style="rounded"]; - 21[label = "build_electricity_demand", color = "0.52 0.6 0.85", style="rounded"]; - 22[label = "retrieve_electricity_demand", color = "0.22 0.6 0.85", style="rounded"]; - 23[label = "copy_config", color = "0.44 0.6 0.85", style="rounded"]; + 0[label = "solve_network", color = "0.21 0.6 0.85", style="rounded"]; + 1[label = "prepare_network\nll: copt\nopts: ", color = "0.51 0.6 0.85", style="rounded"]; + 2[label = "add_extra_components", color = "0.43 0.6 0.85", style="rounded"]; + 3[label = "cluster_network\nclusters: 6", color = "0.17 0.6 0.85", style="rounded"]; + 4[label = "simplify_network\nsimpl: ", color = "0.49 0.6 0.85", style="rounded"]; + 5[label = "add_electricity", color = "0.26 0.6 0.85", style="rounded"]; + 6[label = "build_renewable_profiles\ntechnology: solar", color = "0.02 0.6 0.85", style="rounded"]; + 7[label = "base_network", color = "0.35 0.6 0.85", style="rounded"]; + 8[label = "build_shapes", color = "0.62 0.6 0.85", style="rounded"]; + 9[label = "retrieve_databundle", color = "0.24 0.6 0.85", style="rounded"]; + 10[label = "retrieve_cutout\ncutout: be-03-2013-era5", color = "0.36 0.6 0.85", style="rounded"]; + 11[label = "build_renewable_profiles\ntechnology: solar-hsat", color = "0.02 0.6 0.85", style="rounded"]; + 12[label = "build_renewable_profiles\ntechnology: onwind", color = "0.02 0.6 0.85", style="rounded"]; + 13[label = "build_renewable_profiles\ntechnology: offwind-ac", color = "0.02 0.6 0.85", style="rounded"]; + 14[label = "build_ship_raster", color = "0.08 0.6 0.85", style="rounded"]; + 15[label = "retrieve_ship_raster", color = "0.28 0.6 0.85", style="rounded"]; + 16[label = "build_renewable_profiles\ntechnology: offwind-dc", color = "0.02 0.6 0.85", style="rounded"]; + 17[label = "build_renewable_profiles\ntechnology: offwind-float", color = "0.02 0.6 0.85", style="rounded"]; + 18[label = "build_line_rating", color = "0.07 0.6 0.85", style="rounded"]; + 19[label = "retrieve_cost_data\nyear: 2030", color = "0.47 0.6 0.85", style="rounded"]; + 20[label = "build_powerplants", color = "0.11 0.6 0.85", style="rounded"]; + 21[label = "build_electricity_demand", color = "0.05 0.6 0.85", style="rounded"]; + 22[label = "retrieve_electricity_demand", color = "0.58 0.6 0.85", style="rounded"]; + 23[label = "retrieve_synthetic_electricity_demand", color = "0.11 0.6 0.85", style="rounded"]; 1 -> 0 - 23 -> 0 2 -> 1 19 -> 1 3 -> 2 @@ -167,55 +165,55 @@ This triggers a workflow of multiple preceding jobs that depend on each rule's i 19 -> 3 5 -> 4 19 -> 4 - 11 -> 4 + 7 -> 4 6 -> 5 + 11 -> 5 + 12 -> 5 13 -> 5 - 14 -> 5 + 16 -> 5 17 -> 5 7 -> 5 18 -> 5 19 -> 5 - 11 -> 5 20 -> 5 - 9 -> 5 21 -> 5 8 -> 5 7 -> 6 9 -> 6 - 10 -> 6 8 -> 6 - 11 -> 6 - 12 -> 6 + 10 -> 6 8 -> 7 9 -> 8 - 8 -> 11 7 -> 11 + 9 -> 11 + 8 -> 11 + 10 -> 11 + 7 -> 12 + 9 -> 12 + 8 -> 12 + 10 -> 12 7 -> 13 9 -> 13 - 10 -> 13 + 14 -> 13 8 -> 13 - 11 -> 13 - 12 -> 13 - 7 -> 14 - 9 -> 14 - 10 -> 14 + 10 -> 13 15 -> 14 - 8 -> 14 - 11 -> 14 - 12 -> 14 - 16 -> 15 - 12 -> 15 + 10 -> 14 + 7 -> 16 + 9 -> 16 + 14 -> 16 + 8 -> 16 + 10 -> 16 7 -> 17 9 -> 17 - 10 -> 17 - 15 -> 17 + 14 -> 17 8 -> 17 - 11 -> 17 - 12 -> 17 + 10 -> 17 7 -> 18 - 12 -> 18 + 10 -> 18 7 -> 20 22 -> 21 + 23 -> 21 } | @@ -226,28 +224,28 @@ In the terminal, this will show up as a list of jobs to be run: Building DAG of jobs... Job stats: - job count - --------------------------- ------- - add_electricity 1 - add_extra_components 1 - base_network 1 - build_bus_regions 1 - build_electricity_demand 1 - build_line_rating 1 - build_powerplants 1 - build_renewable_profiles 4 - build_shapes 1 - build_ship_raster 1 - cluster_network 1 - copy_config 1 - prepare_network 1 - retrieve_cost_data 1 - retrieve_databundle 1 - retrieve_electricity_demand 1 - retrieve_natura_raster 1 - simplify_network 1 - solve_network 1 - total 22 + job count + ------------------------------------- ------- + add_electricity 1 + add_extra_components 1 + base_network 1 + build_electricity_demand 1 + build_line_rating 1 + build_powerplants 1 + build_renewable_profiles 6 + build_shapes 1 + build_ship_raster 1 + cluster_network 1 + prepare_network 1 + retrieve_cost_data 1 + retrieve_cutout 1 + retrieve_databundle 1 + retrieve_electricity_demand 1 + retrieve_ship_raster 1 + retrieve_synthetic_electricity_demand 1 + simplify_network 1 + solve_network 1 + total 24 ``snakemake`` then runs these jobs in the correct order. @@ -256,16 +254,15 @@ A job (here ``simplify_network``) will display its attributes and normally some .. code:: bash - [Mon Feb 19 17:06:17 2024] rule simplify_network: - input: resources/test/networks/elec.nc, data/costs_2030.csv, resources/test/regions_onshore.geojson, resources/test/regions_offshore.geojson - output: resources/test/networks/elec_s.nc, resources/test/regions_onshore_elec_s.geojson, resources/test/regions_offshore_elec_s.geojson, resources/test/busmap_elec_s.csv, resources/test/connection_costs_s.csv - log: logs/test-elec/simplify_network/elec_s.log + input: resources/test/networks/elec.nc, resources/test/costs_2030.csv, resources/test/regions_onshore.geojson, resources/test/regions_offshore.geojson + output: resources/test/networks/elec_s.nc, resources/test/regions_onshore_elec_s.geojson, resources/test/regions_offshore_elec_s.geojson, resources/test/busmap_elec_s.csv + log: logs/test/simplify_network/elec_s.log jobid: 4 - benchmark: benchmarks/test-elec/simplify_network/elec_s - reason: Missing output files: resources/test/regions_offshore_elec_s.geojson, resources/test/busmap_elec_s.csv, resources/test/regions_onshore_elec_s.geojson, resources/test/networks/elec_s.nc; Input files updated by another job: resources/test/regions_offshore.geojson, resources/test/networks/elec.nc, resources/test/regions_onshore.geojson, data/costs_2030.csv + benchmark: benchmarks/test/simplify_network/elec_s + reason: Forced execution wildcards: simpl= - resources: tmpdir=/tmp, mem_mb=12000, mem_mib=11445 + resources: tmpdir=, mem_mb=12000, mem_mib=11445 Once the whole worktree is finished, it should state so in the terminal. @@ -283,7 +280,7 @@ For example, you can explore the evolution of the PyPSA networks by running #. ``snakemake resources/networks/elec.nc -call --configfile config/test/config.electricity.yaml`` #. ``snakemake resources/networks/elec_s.nc -call --configfile config/test/config.electricity.yaml`` #. ``snakemake resources/networks/elec_s_6.nc -call --configfile config/test/config.electricity.yaml`` -#. ``snakemake resources/networks/elec_s_6_ec_lcopt_Co2L-24H.nc -call --configfile config/test/config.electricity.yaml`` +#. ``snakemake resources/networks/elec_s_6_ec_lcopt_.nc -call --configfile config/test/config.electricity.yaml`` To run all combinations of wildcard values provided in the ``config/config.yaml`` under ``scenario:``, you can use the collection rule ``solve_elec_networks``. @@ -321,6 +318,6 @@ Jupyter Notebooks). import pypsa - n = pypsa.Network("results/networks/elec_s_6_ec_lcopt_Co2L-24H.nc") + n = pypsa.Network("results/networks/elec_s_6_ec_lcopt_.nc") -For inspiration, read the `examples section in the PyPSA documentation `_. +For inspiration, read the `examples section in the PyPSA documentation `__. diff --git a/doc/tutorial_sector.rst b/doc/tutorial_sector.rst index a1556150..450f1e69 100644 --- a/doc/tutorial_sector.rst +++ b/doc/tutorial_sector.rst @@ -74,7 +74,6 @@ which were already included in the electricity-only tutorial: base_network 1 build_ammonia_production 1 build_biomass_potentials 1 - build_bus_regions 1 build_clustered_population_layouts 1 build_cop_profiles 1 build_daily_heat_demand 1 @@ -83,6 +82,7 @@ which were already included in the electricity-only tutorial: build_energy_totals 1 build_gas_input_locations 1 build_gas_network 1 + build_heat_totals 1 build_hourly_heat_demand 1 build_industrial_distribution_key 1 build_industrial_energy_demand_per_country_today 1 @@ -94,19 +94,19 @@ which were already included in the electricity-only tutorial: build_industry_sector_ratios 1 build_industry_sector_ratios_intermediate 1 build_population_layouts 1 - build_population_weighted_energy_totals 1 + build_population_weighted_energy_totals 2 build_powerplants 1 - build_renewable_profiles 4 + build_renewable_profiles 6 build_salt_cavern_potentials 1 build_shapes 1 build_ship_raster 1 build_shipping_demand 1 build_simplified_population_layouts 1 + build_solar_thermal_profiles 3 build_temperature_profiles 3 build_transport_demand 1 cluster_gas_network 1 cluster_network 1 - copy_config 1 make_summary 1 plot_gas_network 1 plot_hydrogen_network 1 @@ -116,14 +116,18 @@ which were already included in the electricity-only tutorial: prepare_network 1 prepare_sector_network 1 retrieve_cost_data 1 + retrieve_cutout 1 retrieve_databundle 1 retrieve_electricity_demand 1 + retrieve_eurostat_data 1 + retrieve_eurostat_household_data 1 retrieve_gas_infrastructure_data 1 - retrieve_natura_raster 1 - retrieve_sector_databundle 1 + retrieve_ship_raster 1 + retrieve_synthetic_electricity_demand 1 simplify_network 1 solve_sector_network 1 - total 60 + time_aggregation 1 + total 69 This covers the retrieval of additional raw data from online resources and preprocessing data about the transport, industry, and heating sectors as well as @@ -142,234 +146,264 @@ successfully. graph[bgcolor=white, margin=0]; node[shape=box, style=rounded, fontname=sans, fontsize=10, penwidth=2]; edge[penwidth=2, color=grey]; - 0[label = "all", color = "0.55 0.6 0.85", style="rounded"]; - 1[label = "plot_summary", color = "0.31 0.6 0.85", style="rounded"]; - 2[label = "make_summary", color = "0.37 0.6 0.85", style="rounded"]; - 3[label = "plot_power_network_clustered", color = "0.50 0.6 0.85", style="rounded"]; - 4[label = "cluster_network\nclusters: 5", color = "0.62 0.6 0.85", style="rounded"]; - 5[label = "simplify_network\nsimpl: ", color = "0.18 0.6 0.85", style="rounded"]; - 6[label = "add_electricity", color = "0.33 0.6 0.85", style="rounded"]; - 7[label = "build_renewable_profiles\ntechnology: solar", color = "0.20 0.6 0.85", style="rounded"]; - 8[label = "base_network", color = "0.31 0.6 0.85", style="rounded"]; - 9[label = "build_shapes", color = "0.36 0.6 0.85", style="rounded"]; - 10[label = "retrieve_databundle", color = "0.29 0.6 0.85", style="rounded"]; - 11[label = "retrieve_natura_raster", color = "0.01 0.6 0.85", style="rounded"]; - 12[label = "build_bus_regions", color = "0.10 0.6 0.85", style="rounded"]; - 13[label = "retrieve_cutout\ncutout: be-03-2013-era5", color = "0.37 0.6 0.85", style="rounded,dashed"]; - 14[label = "build_renewable_profiles\ntechnology: onwind", color = "0.20 0.6 0.85", style="rounded"]; - 15[label = "build_renewable_profiles\ntechnology: offwind-ac", color = "0.20 0.6 0.85", style="rounded"]; - 16[label = "build_ship_raster", color = "0.64 0.6 0.85", style="rounded"]; - 17[label = "retrieve_ship_raster", color = "0.64 0.6 0.85", style="rounded,dashed"]; - 18[label = "build_renewable_profiles\ntechnology: offwind-dc", color = "0.20 0.6 0.85", style="rounded"]; - 19[label = "retrieve_cost_data\nyear: 2030", color = "0.12 0.6 0.85", style="rounded"]; - 20[label = "build_powerplants", color = "0.23 0.6 0.85", style="rounded"]; - 21[label = "build_electricity_demand", color = "0.54 0.6 0.85", style="rounded"]; - 22[label = "retrieve_electricity_demand", color = "0.07 0.6 0.85", style="rounded"]; - 23[label = "solve_sector_network", color = "0.41 0.6 0.85", style="rounded"]; - 24[label = "prepare_sector_network\nsector_opts: CO2L0-24h-T-H-B-I-A-dist1", color = "0.22 0.6 0.85", style="rounded"]; - 25[label = "cluster_gas_network", color = "0.24 0.6 0.85", style="rounded"]; - 26[label = "build_gas_network", color = "0.10 0.6 0.85", style="rounded"]; - 27[label = "retrieve_gas_infrastructure_data", color = "0.17 0.6 0.85", style="rounded"]; - 28[label = "build_gas_input_locations", color = "0.16 0.6 0.85", style="rounded"]; - 29[label = "prepare_network\nll: v1.5\nopts: ", color = "0.49 0.6 0.85", style="rounded"]; - 30[label = "add_extra_components", color = "0.14 0.6 0.85", style="rounded"]; - 31[label = "build_energy_totals", color = "0.39 0.6 0.85", style="rounded"]; - 32[label = "retrieve_sector_databundle", color = "0.58 0.6 0.85", style="rounded"]; - 33[label = "build_population_weighted_energy_totals", color = "0.56 0.6 0.85", style="rounded"]; - 34[label = "build_clustered_population_layouts", color = "0.49 0.6 0.85", style="rounded"]; - 35[label = "build_population_layouts", color = "0.06 0.6 0.85", style="rounded"]; - 36[label = "build_shipping_demand", color = "0.47 0.6 0.85", style="rounded"]; - 37[label = "build_transport_demand", color = "0.45 0.6 0.85", style="rounded"]; - 38[label = "build_temperature_profiles\nscope: total", color = "0.04 0.6 0.85", style="rounded"]; - 39[label = "build_biomass_potentials\nplanning_horizons: 2030", color = "0.11 0.6 0.85", style="rounded"]; - 40[label = "build_salt_cavern_potentials", color = "0.15 0.6 0.85", style="rounded"]; - 41[label = "build_simplified_population_layouts", color = "0.46 0.6 0.85", style="rounded"]; - 42[label = "build_industrial_energy_demand_per_node", color = "0.63 0.6 0.85", style="rounded"]; - 43[label = "build_industry_sector_ratios_intermediate\nplanning_horizons: 2030", color = "0.07 0.6 0.85", style="rounded"]; - 44[label = "build_industry_sector_ratios", color = "0.59 0.6 0.85", style="rounded"]; - 45[label = "build_ammonia_production", color = "0.04 0.6 0.85", style="rounded"]; - 46[label = "build_industrial_energy_demand_per_country_today", color = "0.44 0.6 0.85", style="rounded"]; - 47[label = "build_industrial_production_per_country", color = "0.34 0.6 0.85", style="rounded"]; - 48[label = "build_industrial_production_per_node", color = "0.26 0.6 0.85", style="rounded"]; - 49[label = "build_industrial_distribution_key", color = "0.13 0.6 0.85", style="rounded"]; - 50[label = "build_industrial_production_per_country_tomorrow\nplanning_horizons: 2030", color = "0.32 0.6 0.85", style="rounded"]; - 51[label = "build_industrial_energy_demand_per_node_today", color = "0.48 0.6 0.85", style="rounded"]; - 52[label = "build_hourly_heat_demand", color = "0.28 0.6 0.85", style="rounded"]; - 53[label = "build_daily_heat_demand\nscope: total", color = "0.28 0.6 0.85", style="rounded"]; - 54[label = "build_district_heat_share\nplanning_horizons: 2030", color = "0.52 0.6 0.85", style="rounded"]; - 55[label = "build_temperature_profiles\nscope: rural", color = "0.04 0.6 0.85", style="rounded"]; - 56[label = "build_temperature_profiles\nscope: urban", color = "0.04 0.6 0.85", style="rounded"]; - 57[label = "build_cop_profiles", color = "0.38 0.6 0.85", style="rounded"]; - 58[label = "copy_config", color = "0.19 0.6 0.85", style="rounded"]; - 59[label = "plot_power_network", color = "0.60 0.6 0.85", style="rounded"]; - 60[label = "plot_hydrogen_network", color = "0.27 0.6 0.85", style="rounded"]; - 61[label = "plot_gas_network", color = "0.08 0.6 0.85", style="rounded"]; + 0[label = "all", color = "0.28 0.6 0.85", style="rounded"]; + 1[label = "plot_summary", color = "0.60 0.6 0.85", style="rounded"]; + 2[label = "make_summary", color = "0.30 0.6 0.85", style="rounded"]; + 3[label = "solve_sector_network", color = "0.36 0.6 0.85", style="rounded"]; + 4[label = "prepare_sector_network\nsector_opts: ", color = "0.22 0.6 0.85", style="rounded"]; + 5[label = "build_renewable_profiles\ntechnology: offwind-ac", color = "0.20 0.6 0.85", style="rounded"]; + 6[label = "base_network", color = "0.00 0.6 0.85", style="rounded"]; + 7[label = "build_shapes", color = "0.25 0.6 0.85", style="rounded"]; + 8[label = "retrieve_databundle", color = "0.06 0.6 0.85", style="rounded"]; + 9[label = "build_ship_raster", color = "0.06 0.6 0.85", style="rounded"]; + 10[label = "retrieve_ship_raster", color = "0.27 0.6 0.85", style="rounded"]; + 11[label = "retrieve_cutout\ncutout: be-03-2013-era5", color = "0.26 0.6 0.85", style="rounded"]; + 12[label = "build_renewable_profiles\ntechnology: offwind-dc", color = "0.20 0.6 0.85", style="rounded"]; + 13[label = "build_renewable_profiles\ntechnology: offwind-float", color = "0.20 0.6 0.85", style="rounded"]; + 14[label = "cluster_gas_network", color = "0.37 0.6 0.85", style="rounded"]; + 15[label = "build_gas_network", color = "0.44 0.6 0.85", style="rounded"]; + 16[label = "retrieve_gas_infrastructure_data", color = "0.43 0.6 0.85", style="rounded"]; + 17[label = "cluster_network\nclusters: 5", color = "0.08 0.6 0.85", style="rounded"]; + 18[label = "simplify_network\nsimpl: ", color = "0.01 0.6 0.85", style="rounded"]; + 19[label = "add_electricity", color = "0.53 0.6 0.85", style="rounded"]; + 20[label = "build_renewable_profiles\ntechnology: solar", color = "0.20 0.6 0.85", style="rounded"]; + 21[label = "build_renewable_profiles\ntechnology: solar-hsat", color = "0.20 0.6 0.85", style="rounded"]; + 22[label = "build_renewable_profiles\ntechnology: onwind", color = "0.20 0.6 0.85", style="rounded"]; + 23[label = "retrieve_cost_data\nyear: 2030", color = "0.11 0.6 0.85", style="rounded"]; + 24[label = "build_powerplants", color = "0.62 0.6 0.85", style="rounded"]; + 25[label = "build_electricity_demand", color = "0.66 0.6 0.85", style="rounded"]; + 26[label = "retrieve_electricity_demand", color = "0.20 0.6 0.85", style="rounded"]; + 27[label = "retrieve_synthetic_electricity_demand", color = "0.52 0.6 0.85", style="rounded"]; + 28[label = "build_gas_input_locations", color = "0.21 0.6 0.85", style="rounded"]; + 29[label = "time_aggregation", color = "0.58 0.6 0.85", style="rounded"]; + 30[label = "prepare_network\nll: v1.5\nopts: ", color = "0.61 0.6 0.85", style="rounded"]; + 31[label = "add_extra_components", color = "0.59 0.6 0.85", style="rounded"]; + 32[label = "build_hourly_heat_demand", color = "0.48 0.6 0.85", style="rounded"]; + 33[label = "build_daily_heat_demand\nscope: total", color = "0.12 0.6 0.85", style="rounded"]; + 34[label = "build_population_layouts", color = "0.62 0.6 0.85", style="rounded"]; + 35[label = "build_solar_thermal_profiles\nscope: total", color = "0.23 0.6 0.85", style="rounded"]; + 36[label = "retrieve_eurostat_data", color = "0.45 0.6 0.85", style="rounded"]; + 37[label = "build_population_weighted_energy_totals\nkind: energy", color = "0.22 0.6 0.85", style="rounded"]; + 38[label = "build_energy_totals", color = "0.65 0.6 0.85", style="rounded"]; + 39[label = "retrieve_eurostat_household_data", color = "0.36 0.6 0.85", style="rounded"]; + 40[label = "build_clustered_population_layouts", color = "0.02 0.6 0.85", style="rounded"]; + 41[label = "build_population_weighted_energy_totals\nkind: heat", color = "0.22 0.6 0.85", style="rounded"]; + 42[label = "build_heat_totals", color = "0.53 0.6 0.85", style="rounded"]; + 43[label = "build_shipping_demand", color = "0.17 0.6 0.85", style="rounded"]; + 44[label = "build_transport_demand", color = "0.49 0.6 0.85", style="rounded"]; + 45[label = "build_temperature_profiles\nscope: total", color = "0.32 0.6 0.85", style="rounded"]; + 46[label = "build_biomass_potentials\nplanning_horizons: 2030", color = "0.34 0.6 0.85", style="rounded"]; + 47[label = "build_salt_cavern_potentials", color = "0.55 0.6 0.85", style="rounded"]; + 48[label = "build_simplified_population_layouts", color = "0.46 0.6 0.85", style="rounded"]; + 49[label = "build_industrial_energy_demand_per_node", color = "0.14 0.6 0.85", style="rounded"]; + 50[label = "build_industry_sector_ratios_intermediate\nplanning_horizons: 2030", color = "0.27 0.6 0.85", style="rounded"]; + 51[label = "build_industry_sector_ratios", color = "0.11 0.6 0.85", style="rounded"]; + 52[label = "build_ammonia_production", color = "0.25 0.6 0.85", style="rounded"]; + 53[label = "build_industrial_energy_demand_per_country_today", color = "0.44 0.6 0.85", style="rounded"]; + 54[label = "build_industrial_production_per_country", color = "0.18 0.6 0.85", style="rounded"]; + 55[label = "build_industrial_production_per_node", color = "0.41 0.6 0.85", style="rounded"]; + 56[label = "build_industrial_distribution_key", color = "0.04 0.6 0.85", style="rounded"]; + 57[label = "build_industrial_production_per_country_tomorrow\nplanning_horizons: 2030", color = "0.09 0.6 0.85", style="rounded"]; + 58[label = "build_industrial_energy_demand_per_node_today", color = "0.46 0.6 0.85", style="rounded"]; + 59[label = "build_district_heat_share\nplanning_horizons: 2030", color = "0.39 0.6 0.85", style="rounded"]; + 60[label = "build_temperature_profiles\nscope: rural", color = "0.32 0.6 0.85", style="rounded"]; + 61[label = "build_temperature_profiles\nscope: urban", color = "0.32 0.6 0.85", style="rounded"]; + 62[label = "build_cop_profiles", color = "0.55 0.6 0.85", style="rounded"]; + 63[label = "build_solar_thermal_profiles\nscope: urban", color = "0.23 0.6 0.85", style="rounded"]; + 64[label = "build_solar_thermal_profiles\nscope: rural", color = "0.23 0.6 0.85", style="rounded"]; + 65[label = "plot_power_network_clustered", color = "0.41 0.6 0.85", style="rounded"]; + 66[label = "plot_power_network", color = "0.40 0.6 0.85", style="rounded"]; + 67[label = "plot_hydrogen_network", color = "0.42 0.6 0.85", style="rounded"]; + 68[label = "plot_gas_network", color = "0.32 0.6 0.85", style="rounded"]; 1 -> 0 2 -> 1 - 32 -> 1 + 36 -> 1 + 8 -> 1 3 -> 2 23 -> 2 - 19 -> 2 - 59 -> 2 - 60 -> 2 - 61 -> 2 + 65 -> 2 + 66 -> 2 + 67 -> 2 + 68 -> 2 4 -> 3 5 -> 4 - 19 -> 4 + 12 -> 4 + 13 -> 4 + 14 -> 4 + 28 -> 4 + 29 -> 4 + 30 -> 4 + 36 -> 4 + 37 -> 4 + 41 -> 4 + 43 -> 4 + 44 -> 4 + 38 -> 4 + 8 -> 4 + 46 -> 4 + 23 -> 4 + 47 -> 4 + 18 -> 4 + 17 -> 4 + 40 -> 4 + 48 -> 4 + 49 -> 4 + 32 -> 4 + 59 -> 4 + 45 -> 4 + 60 -> 4 + 61 -> 4 + 62 -> 4 + 35 -> 4 + 63 -> 4 + 64 -> 4 6 -> 5 - 19 -> 5 - 12 -> 5 + 8 -> 5 + 9 -> 5 + 7 -> 5 + 11 -> 5 7 -> 6 - 14 -> 6 - 15 -> 6 - 18 -> 6 - 8 -> 6 - 19 -> 6 - 12 -> 6 - 20 -> 6 - 10 -> 6 - 21 -> 6 - 9 -> 6 8 -> 7 - 10 -> 7 - 11 -> 7 - 9 -> 7 - 12 -> 7 - 13 -> 7 - 9 -> 8 10 -> 9 - 9 -> 12 + 11 -> 9 + 6 -> 12 8 -> 12 - 8 -> 14 - 10 -> 14 - 11 -> 14 - 9 -> 14 - 12 -> 14 - 13 -> 14 - 8 -> 15 - 10 -> 15 - 11 -> 15 + 9 -> 12 + 7 -> 12 + 11 -> 12 + 6 -> 13 + 8 -> 13 + 9 -> 13 + 7 -> 13 + 11 -> 13 + 15 -> 14 + 17 -> 14 16 -> 15 - 9 -> 15 - 12 -> 15 - 13 -> 15 - 17 -> 16 - 13 -> 16 - 8 -> 18 - 10 -> 18 - 11 -> 18 - 16 -> 18 - 9 -> 18 - 12 -> 18 - 13 -> 18 + 18 -> 17 + 23 -> 17 + 19 -> 18 + 23 -> 18 + 6 -> 18 + 20 -> 19 + 21 -> 19 + 22 -> 19 + 5 -> 19 + 12 -> 19 + 13 -> 19 + 6 -> 19 + 23 -> 19 + 24 -> 19 + 25 -> 19 + 7 -> 19 + 6 -> 20 8 -> 20 - 22 -> 21 - 24 -> 23 - 58 -> 23 - 25 -> 24 - 28 -> 24 - 29 -> 24 - 31 -> 24 - 32 -> 24 - 33 -> 24 - 36 -> 24 - 37 -> 24 - 39 -> 24 - 19 -> 24 - 15 -> 24 - 18 -> 24 - 40 -> 24 - 5 -> 24 - 4 -> 24 - 34 -> 24 - 41 -> 24 - 42 -> 24 - 52 -> 24 - 54 -> 24 - 38 -> 24 - 55 -> 24 - 56 -> 24 - 57 -> 24 + 7 -> 20 + 11 -> 20 + 6 -> 21 + 8 -> 21 + 7 -> 21 + 11 -> 21 + 6 -> 22 + 8 -> 22 + 7 -> 22 + 11 -> 22 + 6 -> 24 26 -> 25 - 4 -> 25 - 27 -> 26 - 27 -> 28 - 4 -> 28 + 27 -> 25 + 16 -> 28 + 17 -> 28 30 -> 29 - 19 -> 29 - 4 -> 30 - 19 -> 30 - 9 -> 31 - 32 -> 31 - 31 -> 33 + 32 -> 29 + 35 -> 29 + 31 -> 30 + 23 -> 30 + 17 -> 31 + 23 -> 31 + 33 -> 32 34 -> 33 - 35 -> 34 - 4 -> 34 - 13 -> 34 - 9 -> 35 - 13 -> 35 - 9 -> 36 - 4 -> 36 - 31 -> 36 - 34 -> 37 - 33 -> 37 - 31 -> 37 - 32 -> 37 + 17 -> 33 + 11 -> 33 + 7 -> 34 + 11 -> 34 + 34 -> 35 + 17 -> 35 + 11 -> 35 38 -> 37 - 35 -> 38 - 4 -> 38 - 13 -> 38 - 32 -> 39 - 4 -> 39 - 10 -> 39 - 9 -> 39 - 32 -> 40 - 4 -> 40 - 35 -> 41 - 5 -> 41 - 13 -> 41 - 43 -> 42 - 48 -> 42 - 51 -> 42 - 44 -> 43 - 46 -> 43 - 47 -> 43 + 40 -> 37 + 7 -> 38 + 8 -> 38 + 36 -> 38 + 39 -> 38 + 34 -> 40 + 17 -> 40 + 11 -> 40 + 42 -> 41 + 40 -> 41 + 38 -> 42 + 7 -> 43 + 17 -> 43 + 38 -> 43 + 40 -> 44 + 37 -> 44 + 38 -> 44 + 8 -> 44 45 -> 44 - 32 -> 44 - 32 -> 45 - 32 -> 46 - 47 -> 46 - 45 -> 47 - 32 -> 47 - 49 -> 48 - 50 -> 48 - 4 -> 49 - 34 -> 49 - 32 -> 49 - 47 -> 50 - 49 -> 51 - 46 -> 51 - 53 -> 52 - 35 -> 53 - 4 -> 53 - 13 -> 53 - 31 -> 54 - 34 -> 54 - 35 -> 55 - 4 -> 55 - 13 -> 55 - 35 -> 56 - 4 -> 56 - 13 -> 56 - 38 -> 57 - 55 -> 57 - 56 -> 57 - 23 -> 59 - 4 -> 59 - 23 -> 60 - 4 -> 60 - 23 -> 61 - 4 -> 61 + 34 -> 45 + 17 -> 45 + 11 -> 45 + 8 -> 46 + 17 -> 46 + 7 -> 46 + 8 -> 47 + 17 -> 47 + 34 -> 48 + 18 -> 48 + 11 -> 48 + 50 -> 49 + 55 -> 49 + 58 -> 49 + 51 -> 50 + 53 -> 50 + 54 -> 50 + 52 -> 51 + 8 -> 51 + 8 -> 52 + 8 -> 53 + 54 -> 53 + 52 -> 54 + 8 -> 54 + 36 -> 54 + 56 -> 55 + 57 -> 55 + 17 -> 56 + 40 -> 56 + 54 -> 57 + 56 -> 58 + 53 -> 58 + 38 -> 59 + 40 -> 59 + 34 -> 60 + 17 -> 60 + 11 -> 60 + 34 -> 61 + 17 -> 61 + 11 -> 61 + 45 -> 62 + 60 -> 62 + 61 -> 62 + 34 -> 63 + 17 -> 63 + 11 -> 63 + 34 -> 64 + 17 -> 64 + 11 -> 64 + 17 -> 65 + 3 -> 66 + 17 -> 66 + 3 -> 67 + 17 -> 67 + 3 -> 68 + 17 -> 68 } | @@ -429,386 +463,430 @@ workflow: graph[bgcolor=white, margin=0]; node[shape=box, style=rounded, fontname=sans, fontsize=10, penwidth=2]; edge[penwidth=2, color=grey]; - 0[label = "all", color = "0.46 0.6 0.85", style="rounded"]; - 1[label = "plot_summary", color = "0.40 0.6 0.85", style="rounded"]; - 2[label = "make_summary", color = "0.59 0.6 0.85", style="rounded"]; - 3[label = "plot_power_network_clustered", color = "0.17 0.6 0.85", style="rounded"]; - 4[label = "cluster_network\nclusters: 5", color = "0.49 0.6 0.85", style="rounded"]; - 5[label = "simplify_network\nsimpl: ", color = "0.16 0.6 0.85", style="rounded"]; - 6[label = "add_electricity", color = "0.32 0.6 0.85", style="rounded"]; - 7[label = "build_renewable_profiles\ntechnology: solar", color = "0.63 0.6 0.85", style="rounded"]; - 8[label = "base_network", color = "0.12 0.6 0.85", style="rounded"]; - 9[label = "build_shapes", color = "0.23 0.6 0.85", style="rounded"]; - 10[label = "retrieve_databundle", color = "0.61 0.6 0.85", style="rounded"]; - 11[label = "retrieve_natura_raster", color = "0.50 0.6 0.85", style="rounded"]; - 12[label = "build_bus_regions", color = "0.51 0.6 0.85", style="rounded"]; - 13[label = "retrieve_cutout\ncutout: be-03-2013-era5", color = "0.37 0.6 0.85", style="rounded,dashed"]; - 14[label = "build_renewable_profiles\ntechnology: onwind", color = "0.63 0.6 0.85", style="rounded"]; - 15[label = "build_renewable_profiles\ntechnology: offwind-ac", color = "0.63 0.6 0.85", style="rounded"]; - 16[label = "build_ship_raster", color = "0.24 0.6 0.85", style="rounded"]; - 17[label = "retrieve_ship_raster", color = "0.14 0.6 0.85", style="rounded,dashed"]; - 18[label = "build_renewable_profiles\ntechnology: offwind-dc", color = "0.63 0.6 0.85", style="rounded"]; - 19[label = "retrieve_cost_data\nyear: 2030", color = "0.04 0.6 0.85", style="rounded"]; - 20[label = "build_powerplants", color = "0.58 0.6 0.85", style="rounded"]; - 21[label = "build_electricity_demand", color = "0.04 0.6 0.85", style="rounded"]; - 22[label = "retrieve_electricity_demand", color = "0.62 0.6 0.85", style="rounded"]; - 23[label = "solve_sector_network_myopic", color = "0.30 0.6 0.85", style="rounded"]; - 24[label = "add_existing_baseyear", color = "0.34 0.6 0.85", style="rounded"]; - 25[label = "prepare_sector_network\nsector_opts: 24h-T-H-B-I-A-dist1", color = "0.42 0.6 0.85", style="rounded"]; - 26[label = "cluster_gas_network", color = "0.39 0.6 0.85", style="rounded"]; - 27[label = "build_gas_network", color = "0.59 0.6 0.85", style="rounded"]; - 28[label = "retrieve_gas_infrastructure_data", color = "0.15 0.6 0.85", style="rounded"]; - 29[label = "build_gas_input_locations", color = "0.07 0.6 0.85", style="rounded"]; - 30[label = "prepare_network\nll: v1.5\nopts: ", color = "0.56 0.6 0.85", style="rounded"]; - 31[label = "add_extra_components", color = "0.11 0.6 0.85", style="rounded"]; - 32[label = "build_energy_totals", color = "0.18 0.6 0.85", style="rounded"]; - 33[label = "retrieve_sector_databundle", color = "0.06 0.6 0.85", style="rounded"]; - 34[label = "build_population_weighted_energy_totals", color = "0.03 0.6 0.85", style="rounded"]; - 35[label = "build_clustered_population_layouts", color = "0.25 0.6 0.85", style="rounded"]; - 36[label = "build_population_layouts", color = "0.57 0.6 0.85", style="rounded"]; - 37[label = "build_shipping_demand", color = "0.45 0.6 0.85", style="rounded"]; - 38[label = "build_transport_demand", color = "0.18 0.6 0.85", style="rounded"]; - 39[label = "build_temperature_profiles\nscope: total", color = "0.54 0.6 0.85", style="rounded"]; - 40[label = "build_biomass_potentials\nplanning_horizons: 2030", color = "0.41 0.6 0.85", style="rounded"]; - 41[label = "build_salt_cavern_potentials", color = "0.02 0.6 0.85", style="rounded"]; - 42[label = "build_simplified_population_layouts", color = "0.15 0.6 0.85", style="rounded"]; - 43[label = "build_industrial_energy_demand_per_node", color = "0.47 0.6 0.85", style="rounded"]; - 44[label = "build_industry_sector_ratios_intermediate\nplanning_horizons: 2030", color = "0.31 0.6 0.85", style="rounded"]; - 45[label = "build_industry_sector_ratios", color = "0.48 0.6 0.85", style="rounded"]; - 46[label = "build_ammonia_production", color = "0.00 0.6 0.85", style="rounded"]; - 47[label = "build_industrial_energy_demand_per_country_today", color = "0.32 0.6 0.85", style="rounded"]; - 48[label = "build_industrial_production_per_country", color = "0.60 0.6 0.85", style="rounded"]; - 49[label = "build_industrial_production_per_node", color = "0.05 0.6 0.85", style="rounded"]; - 50[label = "build_industrial_distribution_key", color = "0.21 0.6 0.85", style="rounded"]; - 51[label = "build_industrial_production_per_country_tomorrow\nplanning_horizons: 2030", color = "0.33 0.6 0.85", style="rounded"]; - 52[label = "build_industrial_energy_demand_per_node_today", color = "0.62 0.6 0.85", style="rounded"]; - 53[label = "build_hourly_heat_demand", color = "0.28 0.6 0.85", style="rounded"]; - 54[label = "build_daily_heat_demand\nscope: total", color = "0.22 0.6 0.85", style="rounded"]; - 55[label = "build_district_heat_share\nplanning_horizons: 2030", color = "0.21 0.6 0.85", style="rounded"]; - 56[label = "build_temperature_profiles\nscope: rural", color = "0.54 0.6 0.85", style="rounded"]; - 57[label = "build_temperature_profiles\nscope: urban", color = "0.54 0.6 0.85", style="rounded"]; - 58[label = "build_cop_profiles", color = "0.52 0.6 0.85", style="rounded"]; - 59[label = "build_existing_heating_distribution", color = "0.09 0.6 0.85", style="rounded"]; - 60[label = "copy_config", color = "0.42 0.6 0.85", style="rounded"]; - 61[label = "solve_sector_network_myopic", color = "0.30 0.6 0.85", style="rounded"]; - 62[label = "add_brownfield", color = "0.10 0.6 0.85", style="rounded"]; - 63[label = "prepare_sector_network\nsector_opts: 24h-T-H-B-I-A-dist1", color = "0.42 0.6 0.85", style="rounded"]; - 64[label = "build_biomass_potentials\nplanning_horizons: 2040", color = "0.41 0.6 0.85", style="rounded"]; - 65[label = "retrieve_cost_data\nyear: 2040", color = "0.04 0.6 0.85", style="rounded"]; - 66[label = "build_industrial_energy_demand_per_node", color = "0.47 0.6 0.85", style="rounded"]; - 67[label = "build_industry_sector_ratios_intermediate\nplanning_horizons: 2040", color = "0.31 0.6 0.85", style="rounded"]; - 68[label = "build_industrial_production_per_node", color = "0.05 0.6 0.85", style="rounded"]; - 69[label = "build_industrial_production_per_country_tomorrow\nplanning_horizons: 2040", color = "0.33 0.6 0.85", style="rounded"]; - 70[label = "build_district_heat_share\nplanning_horizons: 2040", color = "0.21 0.6 0.85", style="rounded"]; - 71[label = "solve_sector_network_myopic", color = "0.30 0.6 0.85", style="rounded"]; - 72[label = "add_brownfield", color = "0.10 0.6 0.85", style="rounded"]; - 73[label = "prepare_sector_network\nsector_opts: 24h-T-H-B-I-A-dist1", color = "0.42 0.6 0.85", style="rounded"]; - 74[label = "build_biomass_potentials\nplanning_horizons: 2050", color = "0.41 0.6 0.85", style="rounded"]; - 75[label = "retrieve_cost_data\nyear: 2050", color = "0.04 0.6 0.85", style="rounded"]; - 76[label = "build_industrial_energy_demand_per_node", color = "0.47 0.6 0.85", style="rounded"]; - 77[label = "build_industry_sector_ratios_intermediate\nplanning_horizons: 2050", color = "0.31 0.6 0.85", style="rounded"]; - 78[label = "build_industrial_production_per_node", color = "0.05 0.6 0.85", style="rounded"]; - 79[label = "build_industrial_production_per_country_tomorrow\nplanning_horizons: 2050", color = "0.33 0.6 0.85", style="rounded"]; - 80[label = "build_district_heat_share\nplanning_horizons: 2050", color = "0.21 0.6 0.85", style="rounded"]; - 81[label = "plot_power_network", color = "0.48 0.6 0.85", style="rounded"]; - 82[label = "plot_power_network", color = "0.48 0.6 0.85", style="rounded"]; - 83[label = "plot_power_network", color = "0.48 0.6 0.85", style="rounded"]; - 84[label = "plot_hydrogen_network", color = "0.37 0.6 0.85", style="rounded"]; - 85[label = "plot_hydrogen_network", color = "0.37 0.6 0.85", style="rounded"]; - 86[label = "plot_hydrogen_network", color = "0.37 0.6 0.85", style="rounded"]; + 0[label = "all", color = "0.65 0.6 0.85", style="rounded"]; + 1[label = "plot_summary", color = "0.14 0.6 0.85", style="rounded"]; + 2[label = "make_summary", color = "0.38 0.6 0.85", style="rounded"]; + 3[label = "solve_sector_network_myopic", color = "0.19 0.6 0.85", style="rounded"]; + 4[label = "add_existing_baseyear", color = "0.32 0.6 0.85", style="rounded"]; + 5[label = "prepare_sector_network\nsector_opts: ", color = "0.36 0.6 0.85", style="rounded"]; + 6[label = "build_renewable_profiles\ntechnology: offwind-ac", color = "0.57 0.6 0.85", style="rounded"]; + 7[label = "base_network", color = "0.24 0.6 0.85", style="rounded"]; + 8[label = "build_shapes", color = "0.51 0.6 0.85", style="rounded"]; + 9[label = "retrieve_databundle", color = "0.31 0.6 0.85", style="rounded"]; + 10[label = "build_ship_raster", color = "0.23 0.6 0.85", style="rounded"]; + 11[label = "retrieve_ship_raster", color = "0.58 0.6 0.85", style="rounded"]; + 12[label = "retrieve_cutout\ncutout: be-03-2013-era5", color = "0.21 0.6 0.85", style="rounded"]; + 13[label = "build_renewable_profiles\ntechnology: offwind-dc", color = "0.57 0.6 0.85", style="rounded"]; + 14[label = "build_renewable_profiles\ntechnology: offwind-float", color = "0.57 0.6 0.85", style="rounded"]; + 15[label = "cluster_gas_network", color = "0.06 0.6 0.85", style="rounded"]; + 16[label = "build_gas_network", color = "0.29 0.6 0.85", style="rounded"]; + 17[label = "retrieve_gas_infrastructure_data", color = "0.18 0.6 0.85", style="rounded"]; + 18[label = "cluster_network\nclusters: 5", color = "0.59 0.6 0.85", style="rounded"]; + 19[label = "simplify_network\nsimpl: ", color = "0.41 0.6 0.85", style="rounded"]; + 20[label = "add_electricity", color = "0.25 0.6 0.85", style="rounded"]; + 21[label = "build_renewable_profiles\ntechnology: solar", color = "0.57 0.6 0.85", style="rounded"]; + 22[label = "build_renewable_profiles\ntechnology: solar-hsat", color = "0.57 0.6 0.85", style="rounded"]; + 23[label = "build_renewable_profiles\ntechnology: onwind", color = "0.57 0.6 0.85", style="rounded"]; + 24[label = "retrieve_cost_data\nyear: 2030", color = "0.41 0.6 0.85", style="rounded"]; + 25[label = "build_powerplants", color = "0.36 0.6 0.85", style="rounded"]; + 26[label = "build_electricity_demand", color = "0.12 0.6 0.85", style="rounded"]; + 27[label = "retrieve_electricity_demand", color = "0.23 0.6 0.85", style="rounded"]; + 28[label = "retrieve_synthetic_electricity_demand", color = "0.35 0.6 0.85", style="rounded"]; + 29[label = "build_gas_input_locations", color = "0.10 0.6 0.85", style="rounded"]; + 30[label = "time_aggregation", color = "0.60 0.6 0.85", style="rounded"]; + 31[label = "prepare_network\nll: v1.5\nopts: ", color = "0.37 0.6 0.85", style="rounded"]; + 32[label = "add_extra_components", color = "0.10 0.6 0.85", style="rounded"]; + 33[label = "build_hourly_heat_demand", color = "0.11 0.6 0.85", style="rounded"]; + 34[label = "build_daily_heat_demand\nscope: total", color = "0.39 0.6 0.85", style="rounded"]; + 35[label = "build_population_layouts", color = "0.40 0.6 0.85", style="rounded"]; + 36[label = "build_solar_thermal_profiles\nscope: total", color = "0.20 0.6 0.85", style="rounded"]; + 37[label = "retrieve_eurostat_data", color = "0.45 0.6 0.85", style="rounded"]; + 38[label = "build_population_weighted_energy_totals\nkind: energy", color = "0.60 0.6 0.85", style="rounded"]; + 39[label = "build_energy_totals", color = "0.48 0.6 0.85", style="rounded"]; + 40[label = "retrieve_eurostat_household_data", color = "0.08 0.6 0.85", style="rounded"]; + 41[label = "build_clustered_population_layouts", color = "0.07 0.6 0.85", style="rounded"]; + 42[label = "build_population_weighted_energy_totals\nkind: heat", color = "0.60 0.6 0.85", style="rounded"]; + 43[label = "build_heat_totals", color = "0.52 0.6 0.85", style="rounded"]; + 44[label = "build_shipping_demand", color = "0.50 0.6 0.85", style="rounded"]; + 45[label = "build_transport_demand", color = "0.49 0.6 0.85", style="rounded"]; + 46[label = "build_temperature_profiles\nscope: total", color = "0.63 0.6 0.85", style="rounded"]; + 47[label = "build_biomass_potentials\nplanning_horizons: 2030", color = "0.56 0.6 0.85", style="rounded"]; + 48[label = "build_salt_cavern_potentials", color = "0.43 0.6 0.85", style="rounded"]; + 49[label = "build_simplified_population_layouts", color = "0.58 0.6 0.85", style="rounded"]; + 50[label = "build_industrial_energy_demand_per_node", color = "0.27 0.6 0.85", style="rounded"]; + 51[label = "build_industry_sector_ratios_intermediate\nplanning_horizons: 2030", color = "0.26 0.6 0.85", style="rounded"]; + 52[label = "build_industry_sector_ratios", color = "0.46 0.6 0.85", style="rounded"]; + 53[label = "build_ammonia_production", color = "0.17 0.6 0.85", style="rounded"]; + 54[label = "build_industrial_energy_demand_per_country_today", color = "0.02 0.6 0.85", style="rounded"]; + 55[label = "build_industrial_production_per_country", color = "0.30 0.6 0.85", style="rounded"]; + 56[label = "build_industrial_production_per_node", color = "0.30 0.6 0.85", style="rounded"]; + 57[label = "build_industrial_distribution_key", color = "0.05 0.6 0.85", style="rounded"]; + 58[label = "build_industrial_production_per_country_tomorrow\nplanning_horizons: 2030", color = "0.01 0.6 0.85", style="rounded"]; + 59[label = "build_industrial_energy_demand_per_node_today", color = "0.14 0.6 0.85", style="rounded"]; + 60[label = "build_district_heat_share\nplanning_horizons: 2030", color = "0.56 0.6 0.85", style="rounded"]; + 61[label = "build_temperature_profiles\nscope: rural", color = "0.63 0.6 0.85", style="rounded"]; + 62[label = "build_temperature_profiles\nscope: urban", color = "0.63 0.6 0.85", style="rounded"]; + 63[label = "build_cop_profiles", color = "0.64 0.6 0.85", style="rounded"]; + 64[label = "build_solar_thermal_profiles\nscope: urban", color = "0.20 0.6 0.85", style="rounded"]; + 65[label = "build_solar_thermal_profiles\nscope: rural", color = "0.20 0.6 0.85", style="rounded"]; + 66[label = "build_existing_heating_distribution", color = "0.21 0.6 0.85", style="rounded"]; + 67[label = "solve_sector_network_myopic", color = "0.19 0.6 0.85", style="rounded"]; + 68[label = "add_brownfield", color = "0.27 0.6 0.85", style="rounded"]; + 69[label = "prepare_sector_network\nsector_opts: ", color = "0.36 0.6 0.85", style="rounded"]; + 70[label = "build_biomass_potentials\nplanning_horizons: 2040", color = "0.56 0.6 0.85", style="rounded"]; + 71[label = "retrieve_cost_data\nyear: 2040", color = "0.41 0.6 0.85", style="rounded"]; + 72[label = "build_industrial_energy_demand_per_node", color = "0.27 0.6 0.85", style="rounded"]; + 73[label = "build_industry_sector_ratios_intermediate\nplanning_horizons: 2040", color = "0.26 0.6 0.85", style="rounded"]; + 74[label = "build_industrial_production_per_node", color = "0.30 0.6 0.85", style="rounded"]; + 75[label = "build_industrial_production_per_country_tomorrow\nplanning_horizons: 2040", color = "0.01 0.6 0.85", style="rounded"]; + 76[label = "build_district_heat_share\nplanning_horizons: 2040", color = "0.56 0.6 0.85", style="rounded"]; + 77[label = "solve_sector_network_myopic", color = "0.19 0.6 0.85", style="rounded"]; + 78[label = "add_brownfield", color = "0.27 0.6 0.85", style="rounded"]; + 79[label = "prepare_sector_network\nsector_opts: ", color = "0.36 0.6 0.85", style="rounded"]; + 80[label = "build_biomass_potentials\nplanning_horizons: 2050", color = "0.56 0.6 0.85", style="rounded"]; + 81[label = "retrieve_cost_data\nyear: 2050", color = "0.41 0.6 0.85", style="rounded"]; + 82[label = "build_industrial_energy_demand_per_node", color = "0.27 0.6 0.85", style="rounded"]; + 83[label = "build_industry_sector_ratios_intermediate\nplanning_horizons: 2050", color = "0.26 0.6 0.85", style="rounded"]; + 84[label = "build_industrial_production_per_node", color = "0.30 0.6 0.85", style="rounded"]; + 85[label = "build_industrial_production_per_country_tomorrow\nplanning_horizons: 2050", color = "0.01 0.6 0.85", style="rounded"]; + 86[label = "build_district_heat_share\nplanning_horizons: 2050", color = "0.56 0.6 0.85", style="rounded"]; + 87[label = "plot_power_network_clustered", color = "0.03 0.6 0.85", style="rounded"]; + 88[label = "plot_power_network", color = "0.16 0.6 0.85", style="rounded"]; + 89[label = "plot_power_network", color = "0.16 0.6 0.85", style="rounded"]; + 90[label = "plot_power_network", color = "0.16 0.6 0.85", style="rounded"]; + 91[label = "plot_hydrogen_network", color = "0.54 0.6 0.85", style="rounded"]; + 92[label = "plot_hydrogen_network", color = "0.54 0.6 0.85", style="rounded"]; + 93[label = "plot_hydrogen_network", color = "0.54 0.6 0.85", style="rounded"]; 1 -> 0 2 -> 1 - 33 -> 1 + 37 -> 1 + 9 -> 1 3 -> 2 - 23 -> 2 - 61 -> 2 - 71 -> 2 - 19 -> 2 - 81 -> 2 - 82 -> 2 - 83 -> 2 - 84 -> 2 - 85 -> 2 - 86 -> 2 + 67 -> 2 + 77 -> 2 + 24 -> 2 + 87 -> 2 + 88 -> 2 + 89 -> 2 + 90 -> 2 + 91 -> 2 + 92 -> 2 + 93 -> 2 4 -> 3 + 24 -> 3 5 -> 4 + 25 -> 4 19 -> 4 + 18 -> 4 + 41 -> 4 + 24 -> 4 + 63 -> 4 + 66 -> 4 6 -> 5 + 13 -> 5 + 14 -> 5 + 15 -> 5 + 29 -> 5 + 30 -> 5 + 31 -> 5 + 37 -> 5 + 38 -> 5 + 42 -> 5 + 44 -> 5 + 45 -> 5 + 39 -> 5 + 9 -> 5 + 47 -> 5 + 24 -> 5 + 48 -> 5 19 -> 5 - 12 -> 5 + 18 -> 5 + 41 -> 5 + 49 -> 5 + 50 -> 5 + 33 -> 5 + 60 -> 5 + 46 -> 5 + 61 -> 5 + 62 -> 5 + 63 -> 5 + 36 -> 5 + 64 -> 5 + 65 -> 5 7 -> 6 - 14 -> 6 - 15 -> 6 - 18 -> 6 - 8 -> 6 - 19 -> 6 - 12 -> 6 - 20 -> 6 - 10 -> 6 - 21 -> 6 9 -> 6 + 10 -> 6 + 8 -> 6 + 12 -> 6 8 -> 7 - 10 -> 7 - 11 -> 7 - 9 -> 7 - 12 -> 7 - 13 -> 7 9 -> 8 - 10 -> 9 - 9 -> 12 - 8 -> 12 - 8 -> 14 - 10 -> 14 - 11 -> 14 + 11 -> 10 + 12 -> 10 + 7 -> 13 + 9 -> 13 + 10 -> 13 + 8 -> 13 + 12 -> 13 + 7 -> 14 9 -> 14 + 10 -> 14 + 8 -> 14 12 -> 14 - 13 -> 14 - 8 -> 15 - 10 -> 15 - 11 -> 15 16 -> 15 - 9 -> 15 - 12 -> 15 - 13 -> 15 + 18 -> 15 17 -> 16 - 13 -> 16 - 8 -> 18 - 10 -> 18 - 11 -> 18 - 16 -> 18 - 9 -> 18 - 12 -> 18 - 13 -> 18 + 19 -> 18 + 24 -> 18 + 20 -> 19 + 24 -> 19 + 7 -> 19 + 21 -> 20 + 22 -> 20 + 23 -> 20 + 6 -> 20 + 13 -> 20 + 14 -> 20 + 7 -> 20 + 24 -> 20 + 25 -> 20 + 26 -> 20 8 -> 20 - 22 -> 21 - 24 -> 23 - 19 -> 23 - 60 -> 23 - 25 -> 24 - 20 -> 24 - 5 -> 24 - 4 -> 24 - 35 -> 24 - 19 -> 24 - 58 -> 24 - 59 -> 24 - 26 -> 25 - 29 -> 25 - 30 -> 25 - 32 -> 25 - 33 -> 25 - 34 -> 25 - 37 -> 25 - 38 -> 25 - 40 -> 25 - 19 -> 25 - 15 -> 25 - 18 -> 25 - 41 -> 25 - 5 -> 25 - 4 -> 25 - 35 -> 25 - 42 -> 25 - 43 -> 25 - 53 -> 25 - 55 -> 25 - 39 -> 25 - 56 -> 25 - 57 -> 25 - 58 -> 25 + 7 -> 21 + 9 -> 21 + 8 -> 21 + 12 -> 21 + 7 -> 22 + 9 -> 22 + 8 -> 22 + 12 -> 22 + 7 -> 23 + 9 -> 23 + 8 -> 23 + 12 -> 23 + 7 -> 25 27 -> 26 - 4 -> 26 - 28 -> 27 - 28 -> 29 - 4 -> 29 + 28 -> 26 + 17 -> 29 + 18 -> 29 31 -> 30 - 19 -> 30 - 4 -> 31 - 19 -> 31 - 9 -> 32 - 33 -> 32 - 32 -> 34 + 33 -> 30 + 36 -> 30 + 32 -> 31 + 24 -> 31 + 18 -> 32 + 24 -> 32 + 34 -> 33 35 -> 34 - 36 -> 35 - 4 -> 35 - 13 -> 35 - 9 -> 36 - 13 -> 36 - 9 -> 37 - 4 -> 37 - 32 -> 37 - 35 -> 38 - 34 -> 38 - 32 -> 38 - 33 -> 38 + 18 -> 34 + 12 -> 34 + 8 -> 35 + 12 -> 35 + 35 -> 36 + 18 -> 36 + 12 -> 36 39 -> 38 - 36 -> 39 - 4 -> 39 - 13 -> 39 - 33 -> 40 - 4 -> 40 - 10 -> 40 - 9 -> 40 - 33 -> 41 - 4 -> 41 - 36 -> 42 - 5 -> 42 - 13 -> 42 - 44 -> 43 - 49 -> 43 - 52 -> 43 - 45 -> 44 - 47 -> 44 - 48 -> 44 + 41 -> 38 + 8 -> 39 + 9 -> 39 + 37 -> 39 + 40 -> 39 + 35 -> 41 + 18 -> 41 + 12 -> 41 + 43 -> 42 + 41 -> 42 + 39 -> 43 + 8 -> 44 + 18 -> 44 + 39 -> 44 + 41 -> 45 + 38 -> 45 + 39 -> 45 + 9 -> 45 46 -> 45 - 33 -> 45 - 33 -> 46 - 33 -> 47 - 48 -> 47 - 46 -> 48 - 33 -> 48 - 50 -> 49 - 51 -> 49 - 4 -> 50 - 35 -> 50 - 33 -> 50 - 48 -> 51 - 50 -> 52 - 47 -> 52 - 54 -> 53 - 36 -> 54 - 4 -> 54 - 13 -> 54 - 32 -> 55 - 35 -> 55 - 36 -> 56 - 4 -> 56 - 13 -> 56 - 36 -> 57 - 4 -> 57 - 13 -> 57 - 39 -> 58 - 56 -> 58 - 57 -> 58 - 35 -> 59 - 34 -> 59 - 55 -> 59 - 62 -> 61 - 65 -> 61 - 60 -> 61 - 7 -> 62 - 14 -> 62 - 15 -> 62 + 35 -> 46 + 18 -> 46 + 12 -> 46 + 9 -> 47 + 18 -> 47 + 8 -> 47 + 9 -> 48 + 18 -> 48 + 35 -> 49 + 19 -> 49 + 12 -> 49 + 51 -> 50 + 56 -> 50 + 59 -> 50 + 52 -> 51 + 54 -> 51 + 55 -> 51 + 53 -> 52 + 9 -> 52 + 9 -> 53 + 9 -> 54 + 55 -> 54 + 53 -> 55 + 9 -> 55 + 37 -> 55 + 57 -> 56 + 58 -> 56 + 18 -> 57 + 41 -> 57 + 55 -> 58 + 57 -> 59 + 54 -> 59 + 39 -> 60 + 41 -> 60 + 35 -> 61 + 18 -> 61 + 12 -> 61 + 35 -> 62 18 -> 62 - 5 -> 62 - 4 -> 62 - 63 -> 62 - 23 -> 62 - 65 -> 62 - 58 -> 62 - 26 -> 63 - 29 -> 63 - 30 -> 63 - 32 -> 63 - 33 -> 63 - 34 -> 63 - 37 -> 63 - 38 -> 63 - 64 -> 63 - 65 -> 63 - 15 -> 63 - 18 -> 63 - 41 -> 63 - 5 -> 63 - 4 -> 63 - 35 -> 63 - 42 -> 63 - 66 -> 63 - 53 -> 63 - 70 -> 63 - 39 -> 63 - 56 -> 63 - 57 -> 63 - 58 -> 63 - 33 -> 64 - 4 -> 64 - 10 -> 64 - 9 -> 64 - 67 -> 66 - 68 -> 66 - 52 -> 66 - 45 -> 67 - 47 -> 67 - 48 -> 67 - 50 -> 68 + 12 -> 62 + 46 -> 63 + 61 -> 63 + 62 -> 63 + 35 -> 64 + 18 -> 64 + 12 -> 64 + 35 -> 65 + 18 -> 65 + 12 -> 65 + 41 -> 66 + 38 -> 66 + 60 -> 66 + 68 -> 67 + 71 -> 67 + 21 -> 68 + 22 -> 68 + 23 -> 68 + 6 -> 68 + 13 -> 68 + 14 -> 68 + 19 -> 68 + 18 -> 68 69 -> 68 + 3 -> 68 + 71 -> 68 + 63 -> 68 + 6 -> 69 + 13 -> 69 + 14 -> 69 + 15 -> 69 + 29 -> 69 + 30 -> 69 + 31 -> 69 + 37 -> 69 + 38 -> 69 + 42 -> 69 + 44 -> 69 + 45 -> 69 + 39 -> 69 + 9 -> 69 + 70 -> 69 + 71 -> 69 48 -> 69 - 32 -> 70 - 35 -> 70 - 72 -> 71 - 75 -> 71 - 60 -> 71 - 7 -> 72 - 14 -> 72 - 15 -> 72 - 18 -> 72 - 5 -> 72 - 4 -> 72 + 19 -> 69 + 18 -> 69 + 41 -> 69 + 49 -> 69 + 72 -> 69 + 33 -> 69 + 76 -> 69 + 46 -> 69 + 61 -> 69 + 62 -> 69 + 63 -> 69 + 36 -> 69 + 64 -> 69 + 65 -> 69 + 9 -> 70 + 18 -> 70 + 8 -> 70 73 -> 72 - 61 -> 72 - 75 -> 72 - 58 -> 72 - 26 -> 73 - 29 -> 73 - 30 -> 73 - 32 -> 73 - 33 -> 73 - 34 -> 73 - 37 -> 73 - 38 -> 73 - 74 -> 73 - 75 -> 73 - 15 -> 73 - 18 -> 73 - 41 -> 73 - 5 -> 73 - 4 -> 73 - 35 -> 73 - 42 -> 73 - 76 -> 73 - 53 -> 73 - 80 -> 73 - 39 -> 73 - 56 -> 73 - 57 -> 73 - 58 -> 73 - 33 -> 74 - 4 -> 74 - 10 -> 74 - 9 -> 74 - 77 -> 76 - 78 -> 76 - 52 -> 76 - 45 -> 77 - 47 -> 77 - 48 -> 77 - 50 -> 78 + 74 -> 72 + 59 -> 72 + 52 -> 73 + 54 -> 73 + 55 -> 73 + 57 -> 74 + 75 -> 74 + 55 -> 75 + 39 -> 76 + 41 -> 76 + 78 -> 77 + 81 -> 77 + 21 -> 78 + 22 -> 78 + 23 -> 78 + 6 -> 78 + 13 -> 78 + 14 -> 78 + 19 -> 78 + 18 -> 78 79 -> 78 + 67 -> 78 + 81 -> 78 + 63 -> 78 + 6 -> 79 + 13 -> 79 + 14 -> 79 + 15 -> 79 + 29 -> 79 + 30 -> 79 + 31 -> 79 + 37 -> 79 + 38 -> 79 + 42 -> 79 + 44 -> 79 + 45 -> 79 + 39 -> 79 + 9 -> 79 + 80 -> 79 + 81 -> 79 48 -> 79 - 32 -> 80 - 35 -> 80 - 23 -> 81 - 4 -> 81 - 61 -> 82 - 4 -> 82 - 71 -> 83 - 4 -> 83 - 23 -> 84 - 4 -> 84 - 61 -> 85 - 4 -> 85 - 71 -> 86 - 4 -> 86 + 19 -> 79 + 18 -> 79 + 41 -> 79 + 49 -> 79 + 82 -> 79 + 33 -> 79 + 86 -> 79 + 46 -> 79 + 61 -> 79 + 62 -> 79 + 63 -> 79 + 36 -> 79 + 64 -> 79 + 65 -> 79 + 9 -> 80 + 18 -> 80 + 8 -> 80 + 83 -> 82 + 84 -> 82 + 59 -> 82 + 52 -> 83 + 54 -> 83 + 55 -> 83 + 57 -> 84 + 85 -> 84 + 55 -> 85 + 39 -> 86 + 41 -> 86 + 18 -> 87 + 3 -> 88 + 18 -> 88 + 67 -> 89 + 18 -> 89 + 77 -> 90 + 18 -> 90 + 3 -> 91 + 18 -> 91 + 67 -> 92 + 18 -> 92 + 77 -> 93 + 18 -> 93 } | diff --git a/doc/validation.rst b/doc/validation.rst index e538717c..b32a786d 100644 --- a/doc/validation.rst +++ b/doc/validation.rst @@ -9,7 +9,7 @@ Validation The PyPSA-Eur model workflow provides a built-in mechanism for validation. This allows users to contrast the outcomes of network optimization against the historical behaviour of the European power system. The snakemake rule ``validate_elec_networks`` enables this by generating comparative figures that encapsulate key data points such as dispatch carrier, cross-border flows, and market prices per price zone. -These comparisons utilize data from the 2019 ENTSO-E Transparency Platform. To enable this, an ENTSO-E API key must be inserted into the ``config.yaml`` file. Detailed steps for this process can be found in the user guide `here `_. +These comparisons utilize data from the 2019 ENTSO-E Transparency Platform. To enable this, an ENTSO-E API key must be inserted into the ``config.yaml`` file. Detailed steps for this process can be found in the user guide `here `__. Once the API key is set, the validation workflow can be triggered by running the following command: @@ -29,11 +29,11 @@ Results By the time of writing the comparison with the historical data shows partially accurate, partially improvable results. The following figures show the comparison of the dispatch of the different carriers. -.. image:: ../graphics/validation_seasonal_operation_area_elec_s_37_ec_lv1.0_Ept.png +.. image:: img/validation_seasonal_operation_area_elec_s_37_ec_lv1.0_Ept.png :width: 100% :align: center -.. image:: ../graphics/validation_production_bar_elec_s_37_ec_lv1.0_Ept.png +.. image:: img/validation_production_bar_elec_s_37_ec_lv1.0_Ept.png :width: 100% :align: center diff --git a/doc/wildcards.rst b/doc/wildcards.rst index f86ff311..f8e60e20 100644 --- a/doc/wildcards.rst +++ b/doc/wildcards.rst @@ -17,7 +17,7 @@ what data to retrieve and what files to produce. .. note:: Detailed explanations of how wildcards work in ``snakemake`` can be found in the - `relevant section of the documentation `_. + `relevant section of the documentation `__. .. _cutout_wc: @@ -35,8 +35,8 @@ The ``{technology}`` wildcard The ``{technology}`` wildcard specifies for which renewable energy technology to produce availability time series and potentials using the rule :mod:`build_renewable_profiles`. -It can take the values ``onwind``, ``offwind-ac``, ``offwind-dc``, and ``solar`` but **not** ``hydro`` -(since hydroelectric plant profiles are created by a different rule). +It can take the values ``onwind``, ``offwind-ac``, ``offwind-dc``, ``offwind-float``, and ``solar`` but **not** ``hydro`` +(since hydroelectric plant profiles are created by a different rule)`` .. _simpl: @@ -101,7 +101,7 @@ The ``{opts}`` wildcard The ``{opts}`` wildcard is used for electricity-only studies. It triggers optional constraints, which are activated in either :mod:`prepare_network` or the :mod:`solve_network` step. It may hold multiple triggers separated by ``-``, -i.e. ``Co2L-3H`` contains the ``Co2L`` trigger and the ``3H`` switch. There are +i.e. ``Co2L-3h`` contains the ``Co2L`` trigger and the ``3h`` switch. There are currently: @@ -121,7 +121,7 @@ The ``{sector_opts}`` wildcard # Co2Lx specifies the CO2 target in x% of the 1990 values; default will give default (5%); # Co2L0p25 will give 25% CO2 emissions; Co2Lm0p05 will give 5% negative emissions - # xH is the temporal resolution; 3H is 3-hourly, i.e. one snapshot every 3 hours + # xH is the temporal resolution; 3h is 3-hourly, i.e. one snapshot every 3 hours # single letters are sectors: T for land transport, H for building heating, # B for biomass supply, I for industry, shipping and aviation, # A for agriculture, forestry and fishing diff --git a/envs/environment.fixed.yaml b/envs/environment.fixed.yaml index 8bbd70bf..67fff66f 100644 --- a/envs/environment.fixed.yaml +++ b/envs/environment.fixed.yaml @@ -4,58 +4,61 @@ name: pypsa-eur channels: -- bioconda -- http://conda.anaconda.org/gurobi - conda-forge +- bioconda +- gurobi - defaults dependencies: - _libgcc_mutex=0.1 - _openmp_mutex=4.5 - affine=2.4.0 -- alsa-lib=1.2.10 +- alsa-lib=1.2.11 - ampl-mp=3.1.0 - amply=0.1.6 - appdirs=1.4.4 +- argparse-dataclass=2.0.0 - asttokens=2.4.1 - atk-1.0=2.38.0 - atlite=0.2.12 - attr=2.5.1 - attrs=23.2.0 -- aws-c-auth=0.7.15 -- aws-c-cal=0.6.9 -- aws-c-common=0.9.12 -- aws-c-compression=0.2.17 -- aws-c-event-stream=0.4.1 -- aws-c-http=0.8.0 -- aws-c-io=0.14.3 -- aws-c-mqtt=0.10.1 -- aws-c-s3=0.5.0 -- aws-c-sdkutils=0.1.14 -- aws-checksums=0.1.17 -- aws-crt-cpp=0.26.1 -- aws-sdk-cpp=1.11.242 -- azure-core-cpp=1.10.3 +- aws-c-auth=0.7.22 +- aws-c-cal=0.6.14 +- aws-c-common=0.9.19 +- aws-c-compression=0.2.18 +- aws-c-event-stream=0.4.2 +- aws-c-http=0.8.1 +- aws-c-io=0.14.8 +- aws-c-mqtt=0.10.4 +- aws-c-s3=0.5.9 +- aws-c-sdkutils=0.1.16 +- aws-checksums=0.1.18 +- aws-crt-cpp=0.26.9 +- aws-sdk-cpp=1.11.329 +- azure-core-cpp=1.11.1 +- azure-identity-cpp=1.6.0 - azure-storage-blobs-cpp=12.10.0 - azure-storage-common-cpp=12.5.0 - beautifulsoup4=4.12.3 - blosc=1.21.5 -- bokeh=3.3.4 -- bottleneck=1.3.7 -- branca=0.7.1 +- bokeh=3.4.1 +- bottleneck=1.3.8 +- branca=0.7.2 - brotli=1.1.0 - brotli-bin=1.1.0 - brotli-python=1.1.0 - bzip2=1.0.8 -- c-ares=1.26.0 -- c-blosc2=2.13.2 +- c-ares=1.28.1 +- c-blosc2=2.14.4 - ca-certificates=2024.2.2 +- cads-api-client=1.0.3 - cairo=1.18.0 -- cartopy=0.22.0 -- cdsapi=0.6.1 +- cartopy=0.23.0 +- cdsapi=0.7.0 - certifi=2024.2.2 - cffi=1.16.0 - cfgv=3.3.1 -- cfitsio=4.3.1 +- cfitsio=4.4.0 - cftime=1.6.3 - charset-normalizer=3.3.2 - click=8.1.7 @@ -65,37 +68,40 @@ dependencies: - coin-or-cbc=2.10.10 - coin-or-cgl=0.60.7 - coin-or-clp=1.17.8 -- coin-or-osi=0.108.8 -- coin-or-utils=2.11.9 +- coin-or-osi=0.108.10 +- coin-or-utils=2.11.11 - coincbc=2.10.10 - colorama=0.4.6 +- conda-inject=1.3.1 - configargparse=1.7 - connection_pool=0.0.3 -- contourpy=1.2.0 +- contourpy=1.2.1 - country_converter=1.2 -- cppad=20240000.2 +- cppad=20240000.4 - cycler=0.12.1 - cytoolz=0.12.3 -- dask=2024.2.0 -- dask-core=2024.2.0 +- dask=2024.5.1 +- dask-core=2024.5.1 +- dask-expr=1.1.1 - datrie=0.8.2 - dbus=1.13.6 - decorator=5.1.1 - deprecation=2.1.0 - descartes=1.1.0 - distlib=0.3.8 -- distributed=2024.2.0 +- distributed=2024.5.1 - distro=1.9.0 -- docutils=0.20.1 +- docutils=0.21.2 - dpath=2.1.6 -- entsoe-py=0.6.6 +- entsoe-py=0.6.7 - et_xmlfile=1.1.0 - exceptiongroup=1.2.0 - executing=2.0.1 -- expat=2.5.0 -- filelock=3.13.1 -- fiona=1.9.5 -- folium=0.15.1 +- expat=2.6.2 +- filelock=3.14.0 +- fiona=1.9.6 +- fmt=10.2.1 +- folium=0.16.0 - font-ttf-dejavu-sans-mono=2.37 - font-ttf-inconsolata=3.000 - font-ttf-source-code-pro=2.038 @@ -103,56 +109,59 @@ dependencies: - fontconfig=2.14.2 - fonts-conda-ecosystem=1 - fonts-conda-forge=1 -- fonttools=4.49.0 +- fonttools=4.52.1 - freetype=2.12.1 - freexl=2.0.0 - fribidi=1.0.10 -- fsspec=2024.2.0 -- gdal=3.8.4 -- gdk-pixbuf=2.42.10 -- geographiclib=1.52 +- fsspec=2024.5.0 +- gdal=3.8.5 +- gdk-pixbuf=2.42.12 +- geographiclib=2.0 - geojson-rewind=1.1.0 -- geopandas=0.14.3 -- geopandas-base=0.14.3 +- geopandas=0.14.4 +- geopandas-base=0.14.4 - geopy=2.4.1 - geos=3.12.1 -- geotiff=1.7.1 -- gettext=0.21.1 +- geotiff=1.7.3 +- gettext=0.22.5 +- gettext-tools=0.22.5 - gflags=2.2.2 -- giflib=5.2.1 +- giflib=5.2.2 - gitdb=4.0.11 -- gitpython=3.1.42 -- glib=2.78.4 -- glib-tools=2.78.4 -- glog=0.6.0 +- gitpython=3.1.43 +- glib=2.80.2 +- glib-tools=2.80.2 +- glog=0.7.0 - glpk=5.0 - gmp=6.3.0 - graphite2=1.3.13 -- graphviz=9.0.0 -- gst-plugins-base=1.22.9 -- gstreamer=1.22.9 +- graphviz=11.0.0 +- gst-plugins-base=1.24.3 +- gstreamer=1.24.3 - gtk2=2.24.33 - gts=0.7.6 -- harfbuzz=8.3.0 +- gurobi=11.0.2 +- harfbuzz=8.5.0 - hdf4=4.2.15 - hdf5=1.14.3 - humanfriendly=10.0 - icu=73.2 -- identify=2.5.35 -- idna=3.6 -- importlib-metadata=7.0.1 -- importlib_metadata=7.0.1 -- importlib_resources=6.1.1 +- identify=2.5.36 +- idna=3.7 +- immutables=0.20 +- importlib-metadata=7.1.0 +- importlib_metadata=7.1.0 +- importlib_resources=6.4.0 - iniconfig=2.0.0 -- ipopt=3.14.14 -- ipython=8.21.0 +- ipopt=3.14.16 +- ipython=8.24.0 - jedi=0.19.1 -- jinja2=3.1.3 -- joblib=1.3.2 +- jinja2=3.1.4 +- joblib=1.4.2 - json-c=0.17 -- jsonschema=4.21.1 +- jsonschema=4.22.0 - jsonschema-specifications=2023.12.1 -- jupyter_core=5.7.1 +- jupyter_core=5.7.2 - kealib=1.5.3 - keyutils=1.6.1 - kiwisolver=1.4.5 @@ -161,46 +170,48 @@ dependencies: - lcms2=2.16 - ld_impl_linux-64=2.40 - lerc=4.0.0 -- libabseil=20230802.1 -- libaec=1.1.2 -- libarchive=3.7.2 -- libarrow=15.0.0 -- libarrow-acero=15.0.0 -- libarrow-dataset=15.0.0 -- libarrow-flight=15.0.0 -- libarrow-flight-sql=15.0.0 -- libarrow-gandiva=15.0.0 -- libarrow-substrait=15.0.0 +- libabseil=20240116.2 +- libaec=1.1.3 +- libarchive=3.7.4 +- libarrow=16.1.0 +- libarrow-acero=16.1.0 +- libarrow-dataset=16.1.0 +- libarrow-substrait=16.1.0 +- libasprintf=0.22.5 +- libasprintf-devel=0.22.5 - libblas=3.9.0 -- libboost-headers=1.84.0 +- libboost-headers=1.85.0 - libbrotlicommon=1.1.0 - libbrotlidec=1.1.0 - libbrotlienc=1.1.0 - libcap=2.69 - libcblas=3.9.0 -- libclang=15.0.7 -- libclang13=15.0.7 +- libclang-cpp15=15.0.7 +- libclang13=18.1.5 - libcrc32c=1.1.2 - libcups=2.3.3 -- libcurl=8.5.0 -- libdeflate=1.19 +- libcurl=8.8.0 +- libdeflate=1.20 - libedit=3.1.20191231 - libev=4.33 - libevent=2.1.12 -- libexpat=2.5.0 +- libexpat=2.6.2 - libffi=3.4.2 - libflac=1.4.3 - libgcc-ng=13.2.0 - libgcrypt=1.10.3 - libgd=2.3.3 -- libgdal=3.8.4 +- libgdal=3.8.5 +- libgettextpo=0.22.5 +- libgettextpo-devel=0.22.5 - libgfortran-ng=13.2.0 - libgfortran5=13.2.0 -- libglib=2.78.4 +- libglib=2.80.2 - libgomp=13.2.0 -- libgoogle-cloud=2.12.0 -- libgpg-error=1.47 -- libgrpc=1.60.1 +- libgoogle-cloud=2.24.0 +- libgoogle-cloud-storage=2.24.0 +- libgpg-error=1.49 +- libgrpc=1.62.2 - libhwloc=2.9.3 - libiconv=1.17 - libjpeg-turbo=3.0.0 @@ -208,27 +219,26 @@ dependencies: - liblapack=3.9.0 - liblapacke=3.9.0 - libllvm15=15.0.7 +- libllvm18=18.1.6 - libnetcdf=4.9.2 - libnghttp2=1.58.0 -- libnl=3.9.0 - libnsl=2.0.1 -- libnuma=2.0.16 - libogg=1.3.4 -- libopenblas=0.3.26 +- libopenblas=0.3.27 - libopus=1.3.1 -- libparquet=15.0.0 -- libpng=1.6.42 -- libpq=16.2 -- libprotobuf=4.25.1 -- libre2-11=2023.06.02 -- librsvg=2.56.3 +- libparquet=16.1.0 +- libpng=1.6.43 +- libpq=16.3 +- libprotobuf=4.25.3 +- libre2-11=2023.09.01 +- librsvg=2.58.0 - librttopo=1.1.0 - libscotch=7.0.4 - libsndfile=1.2.2 - libspatialindex=1.9.3 - libspatialite=5.1.0 -- libspral=2023.09.07 -- libsqlite=3.45.1 +- libspral=2024.01.18 +- libsqlite=3.45.3 - libssh2=1.11.0 - libstdcxx-ng=13.2.0 - libsystemd0=255 @@ -237,101 +247,104 @@ dependencies: - libutf8proc=2.8.0 - libuuid=2.38.1 - libvorbis=1.3.7 -- libwebp=1.3.2 -- libwebp-base=1.3.2 +- libwebp=1.4.0 +- libwebp-base=1.4.0 - libxcb=1.15 - libxcrypt=4.4.36 -- libxkbcommon=1.6.0 -- libxml2=2.12.5 +- libxkbcommon=1.7.0 +- libxml2=2.12.7 - libxslt=1.1.39 - libzip=1.10.1 - libzlib=1.2.13 -- linopy=0.3.4 +- linopy=0.3.9 - locket=1.0.0 -- lxml=5.1.0 +- lxml=5.2.2 - lz4=4.3.3 - lz4-c=1.9.4 - lzo=2.10 - mapclassify=2.6.1 - markupsafe=2.1.5 -- matplotlib=3.8.3 -- matplotlib-base=3.8.3 -- matplotlib-inline=0.1.6 +- matplotlib=3.8.4 +- matplotlib-base=3.8.4 +- matplotlib-inline=0.1.7 - memory_profiler=0.61.0 - metis=5.1.0 -- minizip=4.0.4 -- mpg123=1.32.4 -- msgpack-python=1.0.7 -- mumps-include=5.6.2 -- mumps-seq=5.6.2 +- minizip=4.0.5 +- mpfr=4.2.1 +- mpg123=1.32.6 +- msgpack-python=1.0.8 +- multiurl=0.3.1 +- mumps-include=5.7.1 +- mumps-seq=5.7.1 - munkres=1.1.4 -- mysql-common=8.0.33 -- mysql-libs=8.0.33 -- nbformat=5.9.2 -- ncurses=6.4 +- mysql-common=8.3.0 +- mysql-libs=8.3.0 +- nbformat=5.10.4 +- ncurses=6.5 - netcdf4=1.6.5 -- networkx=3.2.1 +- networkx=3.3 - nodeenv=1.8.0 - nomkl=1.0 - nspr=4.35 -- nss=3.98 +- nss=3.100 - numexpr=2.9.0 - numpy=1.26.4 -- openjdk=21.0.2 -- openjpeg=2.5.0 +- openjdk=22.0.1 +- openjpeg=2.5.2 - openpyxl=3.1.2 -- openssl=3.2.1 -- orc=1.9.2 -- packaging=23.2 -- pandas=2.2.0 -- pango=1.50.14 -- parso=0.8.3 -- partd=1.4.1 +- openssl=3.3.0 +- orc=2.0.1 +- packaging=24.0 +- pandas=2.2.2 +- pango=1.52.2 +- parso=0.8.4 +- partd=1.4.2 - patsy=0.5.6 -- pcre2=10.42 +- pcre2=10.43 - pexpect=4.9.0 - pickleshare=0.7.5 -- pillow=10.2.0 +- pillow=10.3.0 - pip=24.0 - pixman=0.43.2 - pkgutil-resolve-name=1.3.10 -- plac=1.4.2 -- platformdirs=4.2.0 -- pluggy=1.4.0 +- plac=1.4.3 +- platformdirs=4.2.2 +- pluggy=1.5.0 - ply=3.11 -- poppler=24.02.0 +- poppler=24.04.0 - poppler-data=0.4.12 -- postgresql=16.2 -- powerplantmatching=0.5.11 -- pre-commit=3.6.2 -- progressbar2=4.3.2 -- proj=9.3.1 +- postgresql=16.3 +- powerplantmatching=0.5.15 +- pre-commit=3.7.1 +- progressbar2=4.4.2 +- proj=9.4.0 - prompt-toolkit=3.0.42 - psutil=5.9.8 - pthread-stubs=0.4 - ptyprocess=0.7.0 -- pulp=2.7.0 -- pulseaudio-client=16.1 +- pulp=2.8.0 +- pulseaudio-client=17.0 - pure_eval=0.2.2 - py-cpuinfo=9.0.0 -- pyarrow=15.0.0 +- pyarrow=16.1.0 +- pyarrow-core=16.1.0 - pyarrow-hotfix=0.6 - pycountry=22.3.5 -- pycparser=2.21 -- pygments=2.17.2 +- pycparser=2.22 +- pygments=2.18.0 - pyomo=6.6.1 -- pyparsing=3.1.1 +- pyparsing=3.1.2 - pyproj=3.6.1 -- pypsa=0.27.0 +- pypsa=0.28.0 - pyqt=5.15.9 - pyqt5-sip=12.12.2 -- pyscipopt=4.4.0 +- pyscipopt=5.0.1 - pyshp=2.3.1 - pysocks=1.7.1 - pytables=3.9.2 -- pytest=8.0.0 -- python=3.11.8 -- python-dateutil=2.8.2 +- pytest=8.2.1 +- python=3.11.9 +- python-dateutil=2.9.0 - python-fastjsonschema=2.19.1 - python-tzdata=2024.1 - python-utils=3.8.2 @@ -340,71 +353,74 @@ dependencies: - pyxlsb=1.0.10 - pyyaml=6.0.1 - qt-main=5.15.8 -- rasterio=1.3.9 -- rdma-core=50.0 -- re2=2023.06.02 +- rasterio=1.3.10 +- re2=2023.09.01 - readline=8.2 -- referencing=0.33.0 -- requests=2.31.0 +- referencing=0.35.1 +- requests=2.32.2 - reretry=0.11.8 -- rioxarray=0.15.1 -- rpds-py=0.18.0 +- rioxarray=0.15.5 +- rpds-py=0.18.1 - rtree=1.2.0 -- s2n=1.4.3 -- scikit-learn=1.4.1.post1 -- scip=8.1.0 -- scipy=1.12.0 +- s2n=1.4.15 +- scikit-learn=1.5.0 +- scip=9.0.1 +- scipy=1.13.1 - scotch=7.0.4 - seaborn=0.13.2 - seaborn-base=0.13.2 -- setuptools=69.1.0 -- setuptools-scm=8.0.4 -- setuptools_scm=8.0.4 -- shapely=2.0.2 +- setuptools=70.0.0 +- setuptools-scm=8.1.0 +- setuptools_scm=8.1.0 +- shapely=2.0.4 - sip=6.7.12 - six=1.16.0 -- smart_open=6.4.0 +- smart_open=7.0.4 - smmap=5.0.0 -- snakemake-minimal=7.32.4 -- snappy=1.1.10 +- snakemake-interface-common=1.17.2 +- snakemake-interface-executor-plugins=9.1.1 +- snakemake-interface-report-plugins=1.0.0 +- snakemake-interface-storage-plugins=3.2.2 +- snakemake-minimal=8.11.6 +- snappy=1.2.0 - snuggs=1.4.7 - sortedcontainers=2.4.0 - soupsieve=2.5 -- sqlite=3.45.1 +- spdlog=1.13.0 +- sqlite=3.45.3 - stack_data=0.6.2 -- statsmodels=0.14.1 +- statsmodels=0.14.2 - stopit=1.1.2 - tabula-py=2.7.0 - tabulate=0.9.0 - tbb=2021.11.0 - tblib=3.0.0 -- threadpoolctl=3.3.0 +- threadpoolctl=3.5.0 - throttler=1.2.2 -- tiledb=2.20.0 +- tiledb=2.23.0 - tk=8.6.13 - toml=0.10.2 - tomli=2.0.1 - toolz=0.12.1 - toposort=1.10 -- tornado=6.3.3 -- tqdm=4.66.2 -- traitlets=5.14.1 -- typing-extensions=4.9.0 -- typing_extensions=4.9.0 +- tornado=6.4 +- tqdm=4.66.4 +- traitlets=5.14.3 +- typing-extensions=4.11.0 +- typing_extensions=4.11.0 - tzcode=2024a - tzdata=2024a -- ucx=1.15.0 - ukkonen=1.0.1 - unidecode=1.3.8 - unixodbc=2.3.12 -- uriparser=0.9.7 +- uriparser=0.9.8 - urllib3=2.2.1 -- validators=0.22.0 -- virtualenv=20.25.0 +- validators=0.28.2 +- virtualenv=20.26.2 - wcwidth=0.2.13 -- wheel=0.42.0 +- wheel=0.43.0 - wrapt=1.16.0 -- xarray=2024.2.0 +- xarray=2024.5.0 - xcb-util=0.4.0 - xcb-util-image=0.4.0 - xcb-util-keysyms=0.4.0 @@ -418,7 +434,7 @@ dependencies: - xorg-kbproto=1.0.7 - xorg-libice=1.1.1 - xorg-libsm=1.2.4 -- xorg-libx11=1.8.7 +- xorg-libx11=1.8.9 - xorg-libxau=1.0.11 - xorg-libxdmcp=1.1.3 - xorg-libxext=1.3.4 @@ -432,7 +448,7 @@ dependencies: - xorg-xextproto=7.3.0 - xorg-xf86vidmodeproto=2.3.1 - xorg-xproto=7.0.31 -- xyzservices=2023.10.1 +- xyzservices=2024.4.0 - xz=5.2.6 - yaml=0.2.5 - yte=1.5.4 @@ -440,7 +456,13 @@ dependencies: - zipp=3.17.0 - zlib=1.2.13 - zlib-ng=2.0.7 -- zstd=1.5.5 +- zstd=1.5.6 - pip: - highspy==1.5.3 + - oauthlib==3.2.2 + - requests-oauthlib==1.3.1 + - snakemake-executor-plugin-cluster-generic==1.0.9 + - snakemake-executor-plugin-slurm==0.5.1 + - snakemake-executor-plugin-slurm-jobstep==0.2.1 + - snakemake-storage-plugin-http==0.2.3 - tsam==2.3.1 diff --git a/envs/environment.yaml b/envs/environment.yaml index ee1d1605..37c3870b 100644 --- a/envs/environment.yaml +++ b/envs/environment.yaml @@ -1,6 +1,6 @@ # SPDX-FileCopyrightText: : 2017-2024 The PyPSA-Eur Authors # -# SPDX-License-Identifier: MIT +# SPDX-License-Identifier: CC0-1.0 name: pypsa-eur channels: @@ -11,7 +11,7 @@ dependencies: - pip - atlite>=0.2.9 -- pypsa>=0.26.1 +- pypsa>=0.28 - linopy - dask @@ -20,12 +20,12 @@ dependencies: - openpyxl!=3.1.1 - pycountry - seaborn -- snakemake-minimal>=8.5 +- snakemake-minimal>=8.11 - memory_profiler - yaml - pytables - lxml -- powerplantmatching>=0.5.5,!=0.5.9 +- powerplantmatching>=0.5.15 - numpy - pandas>=2.1 - geopandas>=0.11.0 diff --git a/graphics/elec_s_37.png b/graphics/elec_s_37.png deleted file mode 100644 index 23607334..00000000 Binary files a/graphics/elec_s_37.png and /dev/null differ diff --git a/graphics/elec_s_512.png b/graphics/elec_s_512.png deleted file mode 100644 index f9409a84..00000000 Binary files a/graphics/elec_s_512.png and /dev/null differ diff --git a/graphics/workflow.png b/graphics/workflow.png deleted file mode 100644 index a7fbc5ad..00000000 Binary files a/graphics/workflow.png and /dev/null differ diff --git a/rules/build_electricity.smk b/rules/build_electricity.smk index f84a9969..43cadb93 100644 --- a/rules/build_electricity.smk +++ b/rules/build_electricity.smk @@ -86,7 +86,9 @@ rule base_network: offshore_shapes=resources("offshore_shapes.geojson"), europe_shape=resources("europe_shape.geojson"), output: - resources("networks/base.nc"), + base_network=resources("networks/base.nc"), + regions_onshore=resources("regions_onshore.geojson"), + regions_offshore=resources("regions_offshore.geojson"), log: logs("base_network.log"), benchmark: @@ -109,7 +111,7 @@ rule build_shapes: nuts3=ancient("data/bundle/NUTS_2013_60M_SH/data/NUTS_RG_60M_2013.shp"), nuts3pop=ancient("data/bundle/nama_10r_3popgdp.tsv.gz"), nuts3gdp=ancient("data/bundle/nama_10r_3gdp.tsv.gz"), - ch_cantons=ancient("data/bundle/ch_cantons.csv"), + ch_cantons=ancient("data/ch_cantons.csv"), ch_popgdp=ancient("data/bundle/je-e-21.03.02.xls"), output: country_shapes=resources("country_shapes.geojson"), @@ -127,27 +129,6 @@ rule build_shapes: "../scripts/build_shapes.py" -rule build_bus_regions: - params: - countries=config_provider("countries"), - input: - country_shapes=resources("country_shapes.geojson"), - offshore_shapes=resources("offshore_shapes.geojson"), - base_network=resources("networks/base.nc"), - output: - regions_onshore=resources("regions_onshore.geojson"), - regions_offshore=resources("regions_offshore.geojson"), - log: - logs("build_bus_regions.log"), - threads: 1 - resources: - mem_mb=1000, - conda: - "../envs/environment.yaml" - script: - "../scripts/build_bus_regions.py" - - if config["enable"].get("build_cutout", False): rule build_cutout: @@ -172,27 +153,6 @@ if config["enable"].get("build_cutout", False): "../scripts/build_cutout.py" -if config["enable"].get("build_natura_raster", False): - - rule build_natura_raster: - input: - natura=ancient("data/bundle/natura/Natura2000_end2015.shp"), - cutout=lambda w: "cutouts/" - + CDIR - + config_provider("atlite", "default_cutout")(w) - + ".nc", - output: - resources("natura.tiff"), - resources: - mem_mb=5000, - log: - logs("build_natura_raster.log"), - conda: - "../envs/environment.yaml" - script: - "../scripts/build_natura_raster.py" - - rule build_ship_raster: input: ship_density="data/shipdensity_global.zip", @@ -220,7 +180,7 @@ rule determine_availability_matrix_MD_UA: wdpa="data/WDPA.gpkg", wdpa_marine="data/WDPA_WDOECM_marine.gpkg", gebco=lambda w: ( - "data/bundle/GEBCO_2014_2D.nc" + "data/bundle/gebco/GEBCO_2014_2D.nc" if config_provider("renewable", w.technology)(w).get("max_depth") else [] ), @@ -233,7 +193,7 @@ rule determine_availability_matrix_MD_UA: offshore_shapes=resources("offshore_shapes.geojson"), regions=lambda w: ( resources("regions_onshore.geojson") - if w.technology in ("onwind", "solar") + if w.technology in ("onwind", "solar", "solar-hsat") else resources("regions_offshore.geojson") ), cutout=lambda w: "cutouts/" @@ -276,7 +236,7 @@ rule build_renewable_profiles: base_network=resources("networks/base.nc"), corine=ancient("data/bundle/corine/g250_clc06_V18_5.tif"), natura=lambda w: ( - resources("natura.tiff") + "data/bundle/natura/natura.tiff" if config_provider("renewable", w.technology, "natura")(w) else [] ), @@ -287,8 +247,11 @@ rule build_renewable_profiles: ), gebco=ancient( lambda w: ( - "data/bundle/GEBCO_2014_2D.nc" - if config_provider("renewable", w.technology)(w).get("max_depth") + "data/bundle/gebco/GEBCO_2014_2D.nc" + if ( + config_provider("renewable", w.technology)(w).get("max_depth") + or config_provider("renewable", w.technology)(w).get("min_depth") + ) else [] ) ), @@ -301,7 +264,7 @@ rule build_renewable_profiles: offshore_shapes=resources("offshore_shapes.geojson"), regions=lambda w: ( resources("regions_onshore.geojson") - if w.technology in ("onwind", "solar") + if w.technology in ("onwind", "solar", "solar-hsat") else resources("regions_offshore.geojson") ), cutout=lambda w: "cutouts/" @@ -360,7 +323,6 @@ rule build_hydro_profile: + ".nc", output: profile=resources("profile_hydro.nc"), - eia_hydro=resources("eia_hydro_stats.csv"), log: logs("build_hydro_profile.log"), resources: @@ -423,6 +385,7 @@ rule add_electricity: electricity=config_provider("electricity"), conventional=config_provider("conventional"), costs=config_provider("costs"), + foresight=config_provider("foresight"), drop_leap_day=config_provider("enable", "drop_leap_day"), input: unpack(input_profile_tech), @@ -438,7 +401,7 @@ rule add_electricity: ), regions=resources("regions_onshore.geojson"), powerplants=resources("powerplants.csv"), - hydro_capacities=ancient("data/bundle/hydro_capacities.csv"), + hydro_capacities=ancient("data/hydro_capacities.csv"), geth_hydro_capacities="data/geth2015_hydro_capacities.csv", unit_commitment="data/unit_commitment.csv", fuel_price=lambda w: ( @@ -488,7 +451,6 @@ rule simplify_network: regions_onshore=resources("regions_onshore_elec_s{simpl}.geojson"), regions_offshore=resources("regions_offshore_elec_s{simpl}.geojson"), busmap=resources("busmap_elec_s{simpl}.csv"), - connection_costs=resources("connection_costs_s{simpl}.csv"), log: logs("simplify_network/elec_s{simpl}.log"), benchmark: diff --git a/rules/build_sector.smk b/rules/build_sector.smk index 2c079cc5..dea9e4a1 100644 --- a/rules/build_sector.smk +++ b/rules/build_sector.smk @@ -287,12 +287,13 @@ rule build_energy_totals: energy=config_provider("energy"), input: nuts3_shapes=resources("nuts3_shapes.geojson"), - co2="data/bundle-sector/eea/UNFCCC_v23.csv", + co2="data/bundle/eea/UNFCCC_v23.csv", swiss="data/switzerland-new_format-all_years.csv", swiss_transport="data/gr-e-11.03.02.01.01-cc.csv", - idees="data/bundle-sector/jrc-idees-2015", + idees="data/bundle/jrc-idees-2015", district_heat_share="data/district_heat_share.csv", - eurostat="data/eurostat/eurostat-energy_balances-april_2023_edition", + eurostat="data/eurostat/Balances-April2023", + eurostat_households="data/eurostat/eurostat-household_energy_balances-february_2024.csv", output: energy_name=resources("energy_totals.csv"), co2_name=resources("co2_totals.csv"), @@ -338,10 +339,10 @@ rule build_biomass_potentials: "https://zenodo.org/records/10356004/files/ENSPRESO_BIOMASS.xlsx", keep_local=True, ), - nuts2="data/bundle-sector/nuts/NUTS_RG_10M_2013_4326_LEVL_2.geojson", # https://gisco-services.ec.europa.eu/distribution/v2/nuts/download/#nuts21 + nuts2="data/bundle/nuts/NUTS_RG_10M_2013_4326_LEVL_2.geojson", # https://gisco-services.ec.europa.eu/distribution/v2/nuts/download/#nuts21 regions_onshore=resources("regions_onshore_elec_s{simpl}_{clusters}.geojson"), nuts3_population=ancient("data/bundle/nama_10r_3popgdp.tsv.gz"), - swiss_cantons=ancient("data/bundle/ch_cantons.csv"), + swiss_cantons=ancient("data/ch_cantons.csv"), swiss_population=ancient("data/bundle/je-e-21.03.02.xls"), country_shapes=resources("country_shapes.geojson"), output: @@ -416,7 +417,7 @@ rule build_sequestration_potentials: rule build_salt_cavern_potentials: input: - salt_caverns="data/bundle-sector/h2_salt_caverns_GWh_per_sqkm.geojson", + salt_caverns="data/bundle/h2_salt_caverns_GWh_per_sqkm.geojson", regions_onshore=resources("regions_onshore_elec_s{simpl}_{clusters}.geojson"), regions_offshore=resources("regions_offshore_elec_s{simpl}_{clusters}.geojson"), output: @@ -436,7 +437,7 @@ rule build_salt_cavern_potentials: rule build_ammonia_production: input: - usgs="data/bundle-sector/myb1-2017-nitro.xls", + usgs="data/bundle/myb1-2017-nitro.xls", output: ammonia_production=resources("ammonia_production.csv"), threads: 1 @@ -458,7 +459,7 @@ rule build_industry_sector_ratios: ammonia=config_provider("sector", "ammonia", default=False), input: ammonia_production=resources("ammonia_production.csv"), - idees="data/bundle-sector/jrc-idees-2015", + idees="data/bundle/jrc-idees-2015", output: industry_sector_ratios=resources("industry_sector_ratios.csv"), threads: 1 @@ -508,8 +509,8 @@ rule build_industrial_production_per_country: countries=config_provider("countries"), input: ammonia_production=resources("ammonia_production.csv"), - jrc="data/bundle-sector/jrc-idees-2015", - eurostat="data/eurostat/eurostat-energy_balances-april_2023_edition", + jrc="data/bundle/jrc-idees-2015", + eurostat="data/eurostat/Balances-April2023", output: industrial_production_per_country=resources( "industrial_production_per_country.csv" @@ -564,7 +565,10 @@ rule build_industrial_distribution_key: input: regions_onshore=resources("regions_onshore_elec_s{simpl}_{clusters}.geojson"), clustered_pop_layout=resources("pop_layout_elec_s{simpl}_{clusters}.csv"), - hotmaps_industrial_database="data/bundle-sector/Industrial_Database.csv", + hotmaps_industrial_database=storage( + "https://gitlab.com/hotmaps/industrial_sites/industrial_sites_Industrial_Database/-/raw/master/data/Industrial_Database.csv", + keep_local=True, + ), output: industrial_distribution_key=resources( "industrial_distribution_key_elec_s{simpl}_{clusters}.csv" @@ -652,7 +656,7 @@ rule build_industrial_energy_demand_per_country_today: countries=config_provider("countries"), industry=config_provider("industry"), input: - jrc="data/bundle-sector/jrc-idees-2015", + jrc="data/bundle/jrc-idees-2015", industrial_production_per_country=resources( "industrial_production_per_country.csv" ), @@ -704,7 +708,7 @@ rule build_retro_cost: countries=config_provider("countries"), input: building_stock="data/retro/data_building_stock.csv", - data_tabula="data/bundle-sector/retro/tabula-calculator-calcsetbuilding.csv", + data_tabula="data/bundle/retro/tabula-calculator-calcsetbuilding.csv", air_temperature=resources("temp_air_total_elec_s{simpl}_{clusters}.nc"), u_values_PL="data/retro/u_values_poland.csv", tax_w="data/retro/electricity_taxes_eu.csv", @@ -780,8 +784,8 @@ rule build_transport_demand: "pop_weighted_energy_totals_s{simpl}_{clusters}.csv" ), transport_data=resources("transport_data.csv"), - traffic_data_KFZ="data/bundle-sector/emobility/KFZ__count", - traffic_data_Pkw="data/bundle-sector/emobility/Pkw__count", + traffic_data_KFZ="data/bundle/emobility/KFZ__count", + traffic_data_Pkw="data/bundle/emobility/Pkw__count", temp_air_total=resources("temp_air_total_elec_s{simpl}_{clusters}.nc"), output: transport_demand=resources("transport_demand_s{simpl}_{clusters}.csv"), @@ -856,10 +860,44 @@ rule build_existing_heating_distribution: "../scripts/build_existing_heating_distribution.py" +rule time_aggregation: + params: + time_resolution=config_provider("clustering", "temporal", "resolution_sector"), + drop_leap_day=config_provider("enable", "drop_leap_day"), + solver_name=config_provider("solving", "solver", "name"), + input: + network=resources("networks/elec_s{simpl}_{clusters}_ec_l{ll}_{opts}.nc"), + hourly_heat_demand_total=lambda w: ( + resources("hourly_heat_demand_total_elec_s{simpl}_{clusters}.nc") + if config_provider("sector", "heating")(w) + else [] + ), + solar_thermal_total=lambda w: ( + resources("solar_thermal_total_elec_s{simpl}_{clusters}.nc") + if config_provider("sector", "solar_thermal")(w) + else [] + ), + output: + snapshot_weightings=resources( + "snapshot_weightings_elec_s{simpl}_{clusters}_ec_l{ll}_{opts}.csv" + ), + threads: 1 + resources: + mem_mb=5000, + log: + logs("time_aggregation_elec_s{simpl}_{clusters}_ec_l{ll}_{opts}.log"), + benchmark: + benchmarks("time_aggregation_elec_s{simpl}_{clusters}_ec_l{ll}_{opts}") + conda: + "../envs/environment.yaml" + script: + "../scripts/time_aggregation.py" + + def input_profile_offwind(w): return { f"profile_{tech}": resources(f"profile_{tech}.nc") - for tech in ["offwind-ac", "offwind-dc"] + for tech in ["offwind-ac", "offwind-dc", "offwind-float"] if (tech in config_provider("electricity", "renewable_carriers")(w)) } @@ -895,7 +933,6 @@ rule build_egs_potentials: rule prepare_sector_network: params: time_resolution=config_provider("clustering", "temporal", "resolution_sector"), - drop_leap_day=config_provider("enable", "drop_leap_day"), co2_budget=config_provider("co2_budget"), conventional_carriers=config_provider( "existing_capacities", "conventional_carriers" @@ -916,6 +953,9 @@ rule prepare_sector_network: unpack(input_profile_offwind), **rules.cluster_gas_network.output, **rules.build_gas_input_locations.output, + snapshot_weightings=resources( + "snapshot_weightings_elec_s{simpl}_{clusters}_ec_l{ll}_{opts}.csv" + ), retro_cost=lambda w: ( resources("retro_cost_elec_s{simpl}_{clusters}.csv") if config_provider("sector", "retrofitting", "retro_endogen")(w) @@ -940,7 +980,7 @@ rule prepare_sector_network: else [] ), network=resources("networks/elec_s{simpl}_{clusters}_ec_l{ll}_{opts}.nc"), - eurostat="data/eurostat/eurostat-energy_balances-april_2023_edition", + eurostat="data/eurostat/Balances-April2023", pop_weighted_energy_totals=resources( "pop_weighted_energy_totals_s{simpl}_{clusters}.csv" ), @@ -953,7 +993,7 @@ rule prepare_sector_network: avail_profile=resources("avail_profile_s{simpl}_{clusters}.csv"), dsm_profile=resources("dsm_profile_s{simpl}_{clusters}.csv"), co2_totals_name=resources("co2_totals.csv"), - co2="data/bundle-sector/eea/UNFCCC_v23.csv", + co2="data/bundle/eea/UNFCCC_v23.csv", biomass_potentials=lambda w: ( resources( "biomass_potentials_s{simpl}_{clusters}_" diff --git a/rules/postprocess.smk b/rules/postprocess.smk index e7df2e66..39fd46c9 100644 --- a/rules/postprocess.smk +++ b/rules/postprocess.smk @@ -199,6 +199,7 @@ rule make_summary: energy=RESULTS + "csvs/energy.csv", supply=RESULTS + "csvs/supply.csv", supply_energy=RESULTS + "csvs/supply_energy.csv", + nodal_supply_energy=RESULTS + "csvs/nodal_supply_energy.csv", prices=RESULTS + "csvs/prices.csv", weighted_prices=RESULTS + "csvs/weighted_prices.csv", market_values=RESULTS + "csvs/market_values.csv", @@ -229,8 +230,8 @@ rule plot_summary: costs=RESULTS + "csvs/costs.csv", energy=RESULTS + "csvs/energy.csv", balances=RESULTS + "csvs/supply_energy.csv", - eurostat="data/eurostat/eurostat-energy_balances-april_2023_edition", - co2="data/bundle-sector/eea/UNFCCC_v23.csv", + eurostat="data/eurostat/Balances-April2023", + co2="data/bundle/eea/UNFCCC_v23.csv", output: costs=RESULTS + "graphs/costs.pdf", energy=RESULTS + "graphs/energy.pdf", diff --git a/rules/retrieve.smk b/rules/retrieve.smk index 4b244483..10ad9684 100644 --- a/rules/retrieve.smk +++ b/rules/retrieve.smk @@ -14,23 +14,27 @@ if config["enable"]["retrieve"] is False: if config["enable"]["retrieve"] and config["enable"].get("retrieve_databundle", True): datafiles = [ - "ch_cantons.csv", "je-e-21.03.02.xls", "eez/World_EEZ_v8_2014.shp", - "hydro_capacities.csv", "naturalearth/ne_10m_admin_0_countries.shp", "NUTS_2013_60M_SH/data/NUTS_RG_60M_2013.shp", "nama_10r_3popgdp.tsv.gz", "nama_10r_3gdp.tsv.gz", "corine/g250_clc06_V18_5.tif", + "eea/UNFCCC_v23.csv", + "nuts/NUTS_RG_10M_2013_4326_LEVL_2.geojson", + "myb1-2017-nitro.xls", + "emobility/KFZ__count", + "emobility/Pkw__count", + "h2_salt_caverns_GWh_per_sqkm.geojson", + "natura/natura.tiff", + "gebco/GEBCO_2014_2D.nc", ] - if not config.get("tutorial", False): - datafiles.extend(["natura/Natura2000_end2015.shp", "GEBCO_2014_2D.nc"]) - rule retrieve_databundle: output: - protected(expand("data/bundle/{file}", file=datafiles)), + expand("data/bundle/{file}", file=datafiles), + directory("data/bundle/jrc-idees-2015"), log: "logs/retrieve_databundle.log", resources: @@ -41,23 +45,23 @@ if config["enable"]["retrieve"] and config["enable"].get("retrieve_databundle", script: "../scripts/retrieve_databundle.py" - -if config["enable"].get("retrieve_irena"): - - rule retrieve_irena: + rule retrieve_eurostat_data: output: - offwind="data/existing_infrastructure/offwind_capacity_IRENA.csv", - onwind="data/existing_infrastructure/onwind_capacity_IRENA.csv", - solar="data/existing_infrastructure/solar_capacity_IRENA.csv", + directory("data/eurostat/Balances-April2023"), log: - "logs/retrieve_irena.log", - resources: - mem_mb=1000, + "logs/retrieve_eurostat_data.log", retries: 2 - conda: - "../envs/retrieve.yaml" script: - "../scripts/retrieve_irena.py" + "../scripts/retrieve_eurostat_data.py" + + rule retrieve_eurostat_household_data: + output: + "data/eurostat/eurostat-household_energy_balances-february_2024.csv", + log: + "logs/retrieve_eurostat_household_data.log", + retries: 2 + script: + "../scripts/retrieve_eurostat_household_data.py" if config["enable"]["retrieve"] and config["enable"].get("retrieve_cutout", True): @@ -65,7 +69,7 @@ if config["enable"]["retrieve"] and config["enable"].get("retrieve_cutout", True rule retrieve_cutout: input: storage( - "https://zenodo.org/record/6382570/files/{cutout}.nc", + "https://zenodo.org/records/6382570/files/{cutout}.nc", ), output: protected("cutouts/" + CDIR + "{cutout}.nc"), @@ -97,64 +101,6 @@ if config["enable"]["retrieve"] and config["enable"].get("retrieve_cost_data", T "../scripts/retrieve_cost_data.py" -if config["enable"]["retrieve"] and config["enable"].get( - "retrieve_natura_raster", True -): - - rule retrieve_natura_raster: - input: - storage( - "https://zenodo.org/record/4706686/files/natura.tiff", - keep_local=True, - ), - output: - resources("natura.tiff"), - log: - logs("retrieve_natura_raster.log"), - resources: - mem_mb=5000, - retries: 2 - run: - copyfile(input[0], output[0]) - validate_checksum(output[0], input[0]) - - -if config["enable"]["retrieve"] and config["enable"].get( - "retrieve_sector_databundle", True -): - datafiles = [ - "eea/UNFCCC_v23.csv", - "switzerland-sfoe/switzerland-new_format.csv", - "nuts/NUTS_RG_10M_2013_4326_LEVL_2.geojson", - "myb1-2017-nitro.xls", - "Industrial_Database.csv", - "emobility/KFZ__count", - "emobility/Pkw__count", - "h2_salt_caverns_GWh_per_sqkm.geojson", - ] - - rule retrieve_sector_databundle: - output: - protected(expand("data/bundle-sector/{files}", files=datafiles)), - protected(directory("data/bundle-sector/jrc-idees-2015")), - log: - "logs/retrieve_sector_databundle.log", - retries: 2 - conda: - "../envs/retrieve.yaml" - script: - "../scripts/retrieve_sector_databundle.py" - - rule retrieve_eurostat_data: - output: - directory("data/eurostat/eurostat-energy_balances-april_2023_edition"), - log: - "logs/retrieve_eurostat_data.log", - retries: 2 - script: - "../scripts/retrieve_eurostat_data.py" - - if config["enable"]["retrieve"]: datafiles = [ "IGGIELGN_LNGs.geojson", @@ -217,11 +163,11 @@ if config["enable"]["retrieve"]: rule retrieve_ship_raster: input: storage( - "https://zenodo.org/record/6953563/files/shipdensity_global.zip", + "https://zenodo.org/records/10973944/files/shipdensity_global.zip", keep_local=True, ), output: - protected("data/shipdensity_global.zip"), + "data/shipdensity_global.zip", log: "logs/retrieve_ship_raster.log", resources: @@ -239,7 +185,7 @@ if config["enable"]["retrieve"]: rule download_copernicus_land_cover: input: storage( - "https://zenodo.org/record/3939050/files/PROBAV_LC100_global_v3.0.1_2019-nrt_Discrete-Classification-map_EPSG-4326.tif", + "https://zenodo.org/records/3939050/files/PROBAV_LC100_global_v3.0.1_2019-nrt_Discrete-Classification-map_EPSG-4326.tif", ), output: "data/Copernicus_LC100_global_v3.0.1_2019-nrt_Discrete-Classification-map_EPSG-4326.tif", @@ -303,7 +249,7 @@ if config["enable"]["retrieve"]: zip="data/WDPA_shp.zip", folder=directory("data/WDPA"), output: - gpkg=protected("data/WDPA.gpkg"), + gpkg="data/WDPA.gpkg", run: shell("cp {input} {params.zip}") shell("unzip -o {params.zip} -d {params.folder}") @@ -312,7 +258,7 @@ if config["enable"]["retrieve"]: layer_path = ( f"/vsizip/{params.folder}/WDPA_{bYYYY}_Public_shp_{i}.zip" ) - print(f"Adding layer {i + 1} of 3 to combined output file.") + print(f"Adding layer {i+1} of 3 to combined output file.") shell("ogr2ogr -f gpkg -update -append {output.gpkg} {layer_path}") rule download_wdpa_marine: @@ -328,14 +274,14 @@ if config["enable"]["retrieve"]: zip="data/WDPA_WDOECM_marine.zip", folder=directory("data/WDPA_WDOECM_marine"), output: - gpkg=protected("data/WDPA_WDOECM_marine.gpkg"), + gpkg="data/WDPA_WDOECM_marine.gpkg", run: shell("cp {input} {params.zip}") shell("unzip -o {params.zip} -d {params.folder}") for i in range(3): # vsizip is special driver for directly working with zipped shapefiles in ogr2ogr layer_path = f"/vsizip/{params.folder}/WDPA_WDOECM_{bYYYY}_Public_marine_shp_{i}.zip" - print(f"Adding layer {i + 1} of 3 to combined output file.") + print(f"Adding layer {i+1} of 3 to combined output file.") shell("ogr2ogr -f gpkg -update -append {output.gpkg} {layer_path}") diff --git a/rules/solve_myopic.smk b/rules/solve_myopic.smk index fe291c6d..6220af2a 100644 --- a/rules/solve_myopic.smk +++ b/rules/solve_myopic.smk @@ -26,9 +26,6 @@ rule add_existing_baseyear: existing_heating_distribution=resources( "existing_heating_distribution_elec_s{simpl}_{clusters}_{planning_horizons}.csv" ), - existing_solar="data/existing_infrastructure/solar_capacity_IRENA.csv", - existing_onwind="data/existing_infrastructure/onwind_capacity_IRENA.csv", - existing_offwind="data/existing_infrastructure/offwind_capacity_IRENA.csv", output: RESULTS + "prenetworks-brownfield/elec_s{simpl}_{clusters}_l{ll}_{opts}_{sector_opts}_{planning_horizons}.nc", diff --git a/rules/solve_perfect.smk b/rules/solve_perfect.smk index d4cbd6f3..51cb3920 100644 --- a/rules/solve_perfect.smk +++ b/rules/solve_perfect.smk @@ -25,9 +25,6 @@ rule add_existing_baseyear: "existing_heating_distribution_elec_s{simpl}_{clusters}_{planning_horizons}.csv" ), existing_heating="data/existing_infrastructure/existing_heating_raw.csv", - existing_solar="data/existing_infrastructure/solar_capacity_IRENA.csv", - existing_onwind="data/existing_infrastructure/onwind_capacity_IRENA.csv", - existing_offwind="data/existing_infrastructure/offwind_capacity_IRENA.csv", output: RESULTS + "prenetworks-brownfield/elec_s{simpl}_{clusters}_l{ll}_{opts}_{sector_opts}_{planning_horizons}.nc", @@ -35,7 +32,8 @@ rule add_existing_baseyear: planning_horizons=config["scenario"]["planning_horizons"][0], #only applies to baseyear threads: 1 resources: - mem_mb=2000, + mem_mb=config_provider("solving", "mem_mb"), + runtime=config_provider("solving", "runtime", default="24h"), log: logs( "add_existing_baseyear_elec_s{simpl}_{clusters}_l{ll}_{opts}_{sector_opts}_{planning_horizons}.log" diff --git a/scripts/_helpers.py b/scripts/_helpers.py index 64ccab87..5008b32a 100644 --- a/scripts/_helpers.py +++ b/scripts/_helpers.py @@ -59,10 +59,15 @@ def get_rdir(run): RDIR = run["name"] + "/" else: RDIR = "" + + prefix = run.get("prefix", "") + if prefix: + RDIR = f"{prefix}/{RDIR}" + return RDIR -def get_run_path(fn, dir, rdir, shared_resources): +def get_run_path(fn, dir, rdir, shared_resources, exclude_from_shared): """ Dynamically provide paths based on shared resources and filename. @@ -82,6 +87,8 @@ def get_run_path(fn, dir, rdir, shared_resources): - If string is "base", special handling for shared "base" resources (see notes). - If random string other than "base", this folder is used instead of the `rdir` keyword. - If boolean, directly specifies if the resource is shared. + exclude_from_shared: list + List of filenames to exclude from shared resources. Only relevant if shared_resources is "base". Returns ------- @@ -99,10 +106,12 @@ def get_run_path(fn, dir, rdir, shared_resources): existing_wildcards = set(re.findall(pattern, fn)) irrelevant_wildcards = {"technology", "year", "scope", "kind"} no_relevant_wildcards = not existing_wildcards - irrelevant_wildcards - no_elec_rule = not fn.startswith("networks/elec") and not fn.startswith( - "add_electricity" + not_shared_rule = ( + not fn.startswith("networks/elec") + and not fn.startswith("add_electricity") + and not any(fn.startswith(ex) for ex in exclude_from_shared) ) - is_shared = no_relevant_wildcards and no_elec_rule + is_shared = no_relevant_wildcards and not_shared_rule rdir = "" if is_shared else rdir elif isinstance(shared_resources, str): rdir = shared_resources + "/" @@ -116,7 +125,7 @@ def get_run_path(fn, dir, rdir, shared_resources): return f"{dir}{rdir}{fn}" -def path_provider(dir, rdir, shared_resources): +def path_provider(dir, rdir, shared_resources, exclude_from_shared): """ Returns a partial function that dynamically provides paths based on shared resources and the filename. @@ -127,7 +136,13 @@ def path_provider(dir, rdir, shared_resources): A partial function that takes a filename as input and returns the path to the file based on the shared_resources parameter. """ - return partial(get_run_path, dir=dir, rdir=rdir, shared_resources=shared_resources) + return partial( + get_run_path, + dir=dir, + rdir=rdir, + shared_resources=shared_resources, + exclude_from_shared=exclude_from_shared, + ) def get_opt(opts, expr, flags=None): @@ -702,7 +717,7 @@ def update_config_from_wildcards(config, w, inplace=True): def get_checksum_from_zenodo(file_url): parts = file_url.split("/") - record_id = parts[parts.index("record") + 1] + record_id = parts[parts.index("records") + 1] filename = parts[-1] response = requests.get(f"https://zenodo.org/api/records/{record_id}", timeout=30) @@ -741,7 +756,7 @@ def validate_checksum(file_path, zenodo_url=None, checksum=None): >>> validate_checksum("/path/to/file", checksum="md5:abc123...") >>> validate_checksum( ... "/path/to/file", - ... zenodo_url="https://zenodo.org/record/12345/files/example.txt", + ... zenodo_url="https://zenodo.org/records/12345/files/example.txt", ... ) If the checksum is invalid, an AssertionError will be raised. diff --git a/scripts/add_brownfield.py b/scripts/add_brownfield.py index 7e49031b..672f9e62 100644 --- a/scripts/add_brownfield.py +++ b/scripts/add_brownfield.py @@ -40,8 +40,8 @@ def add_brownfield(n, n_p, year): # CO2 or global EU values since these are already in n n_p.mremove(c.name, c.df.index[c.df.lifetime == np.inf]) - # remove assets whose build_year + lifetime < year - n_p.mremove(c.name, c.df.index[c.df.build_year + c.df.lifetime < year]) + # remove assets whose build_year + lifetime <= year + n_p.mremove(c.name, c.df.index[c.df.build_year + c.df.lifetime <= year]) # remove assets if their optimized nominal capacity is lower than a threshold # since CHP heat Link is proportional to CHP electric Link, make sure threshold is compatible @@ -86,43 +86,39 @@ def add_brownfield(n, n_p, year): for tattr in n.component_attrs[c.name].index[selection]: n.import_series_from_dataframe(c.pnl[tattr], c.name, tattr) - # deal with gas network - pipe_carrier = ["gas pipeline"] - if snakemake.params.H2_retrofit: - # drop capacities of previous year to avoid duplicating - to_drop = n.links.carrier.isin(pipe_carrier) & (n.links.build_year != year) - n.mremove("Link", n.links.loc[to_drop].index) + # deal with gas network + pipe_carrier = ["gas pipeline"] + if snakemake.params.H2_retrofit: + # drop capacities of previous year to avoid duplicating + to_drop = n.links.carrier.isin(pipe_carrier) & (n.links.build_year != year) + n.mremove("Link", n.links.loc[to_drop].index) - # subtract the already retrofitted from today's gas grid capacity - h2_retrofitted_fixed_i = n.links[ - (n.links.carrier == "H2 pipeline retrofitted") - & (n.links.build_year != year) - ].index - gas_pipes_i = n.links[n.links.carrier.isin(pipe_carrier)].index - CH4_per_H2 = 1 / snakemake.params.H2_retrofit_capacity_per_CH4 - fr = "H2 pipeline retrofitted" - to = "gas pipeline" - # today's pipe capacity - pipe_capacity = n.links.loc[gas_pipes_i, "p_nom"] - # already retrofitted capacity from gas -> H2 - already_retrofitted = ( - n.links.loc[h2_retrofitted_fixed_i, "p_nom"] - .rename(lambda x: x.split("-2")[0].replace(fr, to)) - .groupby(level=0) - .sum() - ) - remaining_capacity = ( - pipe_capacity - - CH4_per_H2 - * already_retrofitted.reindex(index=pipe_capacity.index).fillna(0) - ) - n.links.loc[gas_pipes_i, "p_nom"] = remaining_capacity - else: - new_pipes = n.links.carrier.isin(pipe_carrier) & ( - n.links.build_year == year - ) - n.links.loc[new_pipes, "p_nom"] = 0.0 - n.links.loc[new_pipes, "p_nom_min"] = 0.0 + # subtract the already retrofitted from today's gas grid capacity + h2_retrofitted_fixed_i = n.links[ + (n.links.carrier == "H2 pipeline retrofitted") + & (n.links.build_year != year) + ].index + gas_pipes_i = n.links[n.links.carrier.isin(pipe_carrier)].index + CH4_per_H2 = 1 / snakemake.params.H2_retrofit_capacity_per_CH4 + fr = "H2 pipeline retrofitted" + to = "gas pipeline" + # today's pipe capacity + pipe_capacity = n.links.loc[gas_pipes_i, "p_nom"] + # already retrofitted capacity from gas -> H2 + already_retrofitted = ( + n.links.loc[h2_retrofitted_fixed_i, "p_nom"] + .rename(lambda x: x.split("-2")[0].replace(fr, to) + f"-{year}") + .groupby(level=0) + .sum() + ) + remaining_capacity = pipe_capacity - CH4_per_H2 * already_retrofitted.reindex( + index=pipe_capacity.index + ).fillna(0) + n.links.loc[gas_pipes_i, "p_nom"] = remaining_capacity + else: + new_pipes = n.links.carrier.isin(pipe_carrier) & (n.links.build_year == year) + n.links.loc[new_pipes, "p_nom"] = 0.0 + n.links.loc[new_pipes, "p_nom_min"] = 0.0 def disable_grid_expansion_if_limit_hit(n): @@ -136,22 +132,20 @@ def disable_grid_expansion_if_limit_hit(n): minimum and extendable is turned off; the corresponding global constraint is then dropped. """ - cols = {"cost": "capital_cost", "volume": "length"} - for limit_type in ["cost", "volume"]: - glcs = n.global_constraints.query( - f"type == 'transmission_expansion_{limit_type}_limit'" - ) + types = {"expansion_cost": "capital_cost", "volume_expansion": "length"} + for limit_type in types: + glcs = n.global_constraints.query(f"type == 'transmission_{limit_type}_limit'") for name, glc in glcs.iterrows(): total_expansion = ( ( n.lines.query("s_nom_extendable") - .eval(f"s_nom_min * {cols[limit_type]}") + .eval(f"s_nom_min * {types[limit_type]}") .sum() ) + ( n.links.query("carrier == 'DC' and p_nom_extendable") - .eval(f"p_nom_min * {cols[limit_type]}") + .eval(f"p_nom_min * {types[limit_type]}") .sum() ) ).sum() @@ -201,6 +195,7 @@ def adjust_renewable_profiles(n, input_profiles, params, year): for carrier in params["carriers"]: if carrier == "hydro": continue + with xr.open_dataset(getattr(input_profiles, "profile_" + carrier)) as ds: if ds.indexes["bus"].empty or "year" not in ds.indexes: continue diff --git a/scripts/add_electricity.py b/scripts/add_electricity.py index 7e60203f..49d0bdf7 100755 --- a/scripts/add_electricity.py +++ b/scripts/add_electricity.py @@ -46,7 +46,7 @@ Inputs ------ - ``resources/costs.csv``: The database of cost assumptions for all included technologies for specific years from various sources; e.g. discount rate, lifetime, investment (CAPEX), fixed operation and maintenance (FOM), variable operation and maintenance (VOM), fuel costs, efficiency, carbon-dioxide intensity. -- ``data/bundle/hydro_capacities.csv``: Hydropower plant store/discharge power capacities, energy storage capacity, and average hourly inflow by country. +- ``data/hydro_capacities.csv``: Hydropower plant store/discharge power capacities, energy storage capacity, and average hourly inflow by country. .. image:: img/hydrocapacities.png :scale: 34 % @@ -230,10 +230,9 @@ def load_costs(tech_costs, config, max_hours, Nyears=1.0): costs.at["OCGT", "co2_emissions"] = costs.at["gas", "co2_emissions"] costs.at["CCGT", "co2_emissions"] = costs.at["gas", "co2_emissions"] - costs.at["solar", "capital_cost"] = ( - config["rooftop_share"] * costs.at["solar-rooftop", "capital_cost"] - + (1 - config["rooftop_share"]) * costs.at["solar-utility", "capital_cost"] - ) + costs.at["solar", "capital_cost"] = 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): capital_cost = link1["capital_cost"] + max_hours * store["capital_cost"] @@ -271,7 +270,6 @@ def load_powerplants(ppl_fn): "bioenergy": "biomass", "ccgt, thermal": "CCGT", "hard coal": "coal", - "natural gas": "OCGT", } return ( pd.read_csv(ppl_fn, index_col=0, dtype={"bus": "str"}) @@ -450,8 +448,6 @@ def attach_conventional_generators( fuel_price=None, ): carriers = list(set(conventional_carriers) | set(extendable_carriers["Generator"])) - add_missing_carriers(n, carriers) - add_co2_emissions(n, costs, carriers) # Replace carrier "natural gas" with the respective technology (OCGT or # CCGT) to align with PyPSA names of "carriers" and avoid filtering "natural @@ -467,6 +463,11 @@ def attach_conventional_generators( ) ppl["efficiency"] = ppl.efficiency.fillna(ppl.efficiency_r) + # reduce carriers to those in power plant dataset + carriers = list(set(carriers) & set(ppl.carrier.unique())) + add_missing_carriers(n, carriers) + add_co2_emissions(n, costs, carriers) + if unit_commitment is not None: committable_attrs = ppl.carrier.isin(unit_commitment).to_frame("committable") for attr in unit_commitment.index: @@ -883,15 +884,22 @@ if __name__ == "__main__": estimate_renewable_caps = params.electricity["estimate_renewable_capacities"] if estimate_renewable_caps["enable"]: - tech_map = estimate_renewable_caps["technology_mapping"] - expansion_limit = estimate_renewable_caps["expansion_limit"] - year = estimate_renewable_caps["year"] + if params.foresight != "overnight": + logger.info( + "Skipping renewable capacity estimation because they are added later " + "in rule `add_existing_baseyear` with foresight mode 'myopic'." + ) + else: + tech_map = estimate_renewable_caps["technology_mapping"] + expansion_limit = estimate_renewable_caps["expansion_limit"] + year = estimate_renewable_caps["year"] - if estimate_renewable_caps["from_opsd"]: - attach_OPSD_renewables(n, tech_map) - estimate_renewable_capacities( - n, year, tech_map, expansion_limit, params.countries - ) + if estimate_renewable_caps["from_opsd"]: + attach_OPSD_renewables(n, tech_map) + + estimate_renewable_capacities( + n, year, tech_map, expansion_limit, params.countries + ) update_p_nom_max(n) diff --git a/scripts/add_existing_baseyear.py b/scripts/add_existing_baseyear.py index 2c313927..8b8ea6b0 100644 --- a/scripts/add_existing_baseyear.py +++ b/scripts/add_existing_baseyear.py @@ -13,6 +13,7 @@ from types import SimpleNamespace import country_converter as coco import numpy as np import pandas as pd +import powerplantmatching as pm import pypsa import xarray as xr from _helpers import ( @@ -55,19 +56,27 @@ def add_build_year_to_new_assets(n, baseyear): c.pnl[attr] = c.pnl[attr].rename(columns=rename) -def add_existing_renewables(df_agg): +def add_existing_renewables(df_agg, costs): """ Append existing renewables to the df_agg pd.DataFrame with the conventional power plants. """ - carriers = {"solar": "solar", "onwind": "onwind", "offwind": "offwind-ac"} + tech_map = {"solar": "PV", "onwind": "Onshore", "offwind": "Offshore"} - for tech in ["solar", "onwind", "offwind"]: - carrier = carriers[tech] + countries = snakemake.config["countries"] # noqa: F841 + irena = pm.data.IRENASTAT().powerplant.convert_country_to_alpha2() + irena = irena.query("Country in @countries") + irena = irena.groupby(["Technology", "Country", "Year"]).Capacity.sum() - df = pd.read_csv(snakemake.input[f"existing_{tech}"], index_col=0).fillna(0.0) + irena = irena.unstack().reset_index() + + for carrier, tech in tech_map.items(): + df = ( + irena[irena.Technology.str.contains(tech)] + .drop(columns=["Technology"]) + .set_index("Country") + ) df.columns = df.columns.astype(int) - df.index = cc.convert(df.index, to="iso2") # calculate yearly differences df.insert(loc=0, value=0.0, column="1999") @@ -97,12 +106,16 @@ def add_existing_renewables(df_agg): for year in nodal_df.columns: for node in nodal_df.index: - name = f"{node}-{tech}-{year}" + name = f"{node}-{carrier}-{year}" capacity = nodal_df.loc[node, year] if capacity > 0.0: - df_agg.at[name, "Fueltype"] = tech + df_agg.at[name, "Fueltype"] = carrier df_agg.at[name, "Capacity"] = capacity df_agg.at[name, "DateIn"] = year + df_agg.at[name, "lifetime"] = costs.at[carrier, "lifetime"] + df_agg.at[name, "DateOut"] = ( + year + costs.at[carrier, "lifetime"] - 1 + ) df_agg.at[name, "cluster_bus"] = node @@ -150,7 +163,7 @@ def add_power_capacities_installed_before_baseyear(n, grouping_years, costs, bas technology_to_drop = ["Pv", "Storage Technologies"] - # drop unused fueltyps and technologies + # drop unused fueltypes and technologies df_agg.drop(df_agg.index[df_agg.Fueltype.isin(fueltype_to_drop)], inplace=True) df_agg.drop(df_agg.index[df_agg.Technology.isin(technology_to_drop)], inplace=True) df_agg.Fueltype = df_agg.Fueltype.map(rename_fuel) @@ -167,10 +180,6 @@ def add_power_capacities_installed_before_baseyear(n, grouping_years, costs, bas ) df_agg.loc[biomass_i, "DateOut"] = df_agg.loc[biomass_i, "DateOut"].fillna(dateout) - # drop assets which are already phased out / decommissioned - phased_out = df_agg[df_agg["DateOut"] < baseyear].index - df_agg.drop(phased_out, inplace=True) - # assign clustered bus busmap_s = pd.read_csv(snakemake.input.busmap_s, index_col=0).squeeze() busmap = pd.read_csv(snakemake.input.busmap, index_col=0).squeeze() @@ -185,10 +194,25 @@ def add_power_capacities_installed_before_baseyear(n, grouping_years, costs, bas df_agg["cluster_bus"] = df_agg.bus.map(clustermaps) # include renewables in df_agg - add_existing_renewables(df_agg) + add_existing_renewables(df_agg, costs) + + # drop assets which are already phased out / decommissioned + phased_out = df_agg[df_agg["DateOut"] < baseyear].index + df_agg.drop(phased_out, inplace=True) + + older_assets = (df_agg.DateIn < min(grouping_years)).sum() + if older_assets: + logger.warning( + f"There are {older_assets} assets with build year " + f"before first power grouping year {min(grouping_years)}. " + "These assets are dropped and not considered." + "Consider to redefine the grouping years to keep them." + ) + to_drop = df_agg[df_agg.DateIn < min(grouping_years)].index + df_agg.drop(to_drop, inplace=True) df_agg["grouping_year"] = np.take( - grouping_years, np.digitize(df_agg.DateIn, grouping_years, right=True) + grouping_years[::-1], np.digitize(df_agg.DateIn, grouping_years[::-1]) ) # calculate (adjusted) remaining lifetime before phase-out (+1 because assuming @@ -228,6 +252,7 @@ def add_power_capacities_installed_before_baseyear(n, grouping_years, costs, bas ] suffix = "-ac" if generator == "offwind" else "" name_suffix = f" {generator}{suffix}-{grouping_year}" + name_suffix_by = f" {generator}{suffix}-{baseyear}" asset_i = capacity.index + name_suffix if generator in ["solar", "onwind", "offwind"]: # to consider electricity grid connection costs or a split between @@ -257,11 +282,11 @@ def add_power_capacities_installed_before_baseyear(n, grouping_years, costs, bas # for offshore the splitting only includes coastal regions inv_ind = [ - i for i in inv_ind if (i + name_suffix) in n.generators.index + i for i in inv_ind if (i + name_suffix_by) in n.generators.index ] p_max_pu = n.generators_t.p_max_pu[ - [i + name_suffix for i in inv_ind] + [i + name_suffix_by for i in inv_ind] ] p_max_pu.columns = [i + name_suffix for i in inv_ind] @@ -281,15 +306,13 @@ def add_power_capacities_installed_before_baseyear(n, grouping_years, costs, bas ) else: - p_max_pu = n.generators_t.p_max_pu[ - capacity.index + f" {generator}{suffix}-{baseyear}" - ] + p_max_pu = n.generators_t.p_max_pu[capacity.index + name_suffix_by] if not new_build.empty: n.madd( "Generator", new_capacity.index, - suffix=" " + name_suffix, + suffix=name_suffix, bus=new_capacity.index, carrier=generator, p_nom=new_capacity, @@ -352,13 +375,20 @@ def add_power_capacities_installed_before_baseyear(n, grouping_years, costs, bas ) else: key = "central solid biomass CHP" + central_heat = n.buses.query( + "carrier == 'urban central heat'" + ).location.unique() + heat_buses = new_capacity.index.map( + lambda i: i + " urban central heat" if i in central_heat else "" + ) + n.madd( "Link", new_capacity.index, suffix=name_suffix, bus0=spatial.biomass.df.loc[new_capacity.index]["nodes"].values, bus1=new_capacity.index, - bus2=new_capacity.index + " urban central heat", + bus2=heat_buses, carrier=generator, p_nom=new_capacity / costs.at[key, "efficiency"], capital_cost=costs.at[key, "fixed"] @@ -402,7 +432,7 @@ def add_heating_capacities_installed_before_baseyear( linear decommissioning of heating capacities from 2020 to 2045 is currently assumed heating capacities split between residential and services proportional to heating load in both 50% capacities - in rural busess 50% in urban buses + in rural buses 50% in urban buses """ logger.debug(f"Adding heating capacities installed before {baseyear}") @@ -410,8 +440,6 @@ def add_heating_capacities_installed_before_baseyear( snakemake.input.existing_heating_distribution, header=[0, 1], index_col=0 ) - techs = existing_heating.columns.get_level_values(1).unique() - for name in existing_heating.columns.get_level_values(0).unique(): name_type = "central" if name == "urban central" else "decentral" @@ -434,12 +462,25 @@ def add_heating_capacities_installed_before_baseyear( else: efficiency = costs.at[costs_name, "efficiency"] - for i, grouping_year in enumerate(grouping_years): - if int(grouping_year) + default_lifetime <= int(baseyear): - continue + valid_grouping_years = pd.Series( + [ + int(grouping_year) + for grouping_year in grouping_years + if int(grouping_year) + default_lifetime > int(baseyear) + and int(grouping_year) < int(baseyear) + ] + ) - # installation is assumed to be linear for the past default_lifetime years - ratio = (int(grouping_year) - int(grouping_years[i - 1])) / default_lifetime + # get number of years of each interval + _years = ( + valid_grouping_years.diff() + .shift(-1) + .fillna(baseyear - valid_grouping_years.iloc[-1]) + ) + # Installation is assumed to be linear for the past + ratios = _years / _years.sum() + + for ratio, grouping_year in zip(ratios, valid_grouping_years): n.madd( "Link", diff --git a/scripts/add_extra_components.py b/scripts/add_extra_components.py index eb14436e..90e7eaec 100644 --- a/scripts/add_extra_components.py +++ b/scripts/add_extra_components.py @@ -246,7 +246,8 @@ if __name__ == "__main__": attach_hydrogen_pipelines(n, costs, extendable_carriers) sanitize_carriers(n, snakemake.config) - sanitize_locations(n) + if "location" in n.buses: + sanitize_locations(n) n.meta = dict(snakemake.config, **dict(wildcards=dict(snakemake.wildcards))) n.export_to_netcdf(snakemake.output[0]) diff --git a/scripts/base_network.py b/scripts/base_network.py index 346f99a5..df3bc2b2 100644 --- a/scripts/base_network.py +++ b/scripts/base_network.py @@ -5,10 +5,7 @@ # coding: utf-8 """ -Creates the network topology from a `ENTSO-E map extract. - -`_ (March 2022) as a PyPSA -network. +Creates the network topology from an `ENTSO-E map extract `_ (March 2022) as a PyPSA network. Relevant Settings ----------------- @@ -59,8 +56,19 @@ Outputs .. image:: img/base.png :scale: 33 % +- ``resources/regions_onshore.geojson``: + + .. image:: img/regions_onshore.png + :scale: 33 % + +- ``resources/regions_offshore.geojson``: + + .. image:: img/regions_offshore.png + :scale: 33 % + Description ----------- +Creates the network topology from an ENTSO-E map extract, and create Voronoi shapes for each bus representing both onshore and offshore regions. """ import logging @@ -75,11 +83,11 @@ import shapely import shapely.prepared import shapely.wkt import yaml -from _helpers import configure_logging, get_snapshots, set_scenario_config +from _helpers import REGION_COLS, configure_logging, get_snapshots, set_scenario_config from packaging.version import Version, parse from scipy import spatial from scipy.sparse import csgraph -from shapely.geometry import LineString, Point +from shapely.geometry import LineString, Point, Polygon PD_GE_2_2 = parse(pd.__version__) >= Version("2.2") @@ -264,14 +272,15 @@ def _add_links_from_tyndp(buses, links, links_tyndp, europe_shape): if links_tyndp.empty: return buses, links - tree = spatial.KDTree(buses[["x", "y"]]) + tree_buses = buses.query("carrier=='AC'") + tree = spatial.KDTree(tree_buses[["x", "y"]]) _, ind0 = tree.query(links_tyndp[["x1", "y1"]]) - ind0_b = ind0 < len(buses) - links_tyndp.loc[ind0_b, "bus0"] = buses.index[ind0[ind0_b]] + ind0_b = ind0 < len(tree_buses) + links_tyndp.loc[ind0_b, "bus0"] = tree_buses.index[ind0[ind0_b]] _, ind1 = tree.query(links_tyndp[["x2", "y2"]]) - ind1_b = ind1 < len(buses) - links_tyndp.loc[ind1_b, "bus1"] = buses.index[ind1[ind1_b]] + ind1_b = ind1 < len(tree_buses) + links_tyndp.loc[ind1_b, "bus1"] = tree_buses.index[ind1[ind1_b]] links_tyndp_located_b = ( links_tyndp["bus0"].notnull() & links_tyndp["bus1"].notnull() @@ -561,7 +570,7 @@ def _set_countries_and_substations(n, config, country_shapes, offshore_shapes): buses["substation_lv"] = ( lv_b & onshore_b & (~buses["under_construction"]) & has_connections_b ) - buses["substation_off"] = (offshore_b | (hv_b & onshore_b)) & ( + buses["substation_off"] = ((hv_b & offshore_b) | (hv_b & onshore_b)) & ( ~buses["under_construction"] ) @@ -698,6 +707,22 @@ def _adjust_capacities_of_under_construction_branches(n, config): return n +def _set_shapes(n, country_shapes, offshore_shapes): + # Write the geodataframes country_shapes and offshore_shapes to the network.shapes component + country_shapes = gpd.read_file(country_shapes).rename(columns={"name": "idx"}) + country_shapes["type"] = "country" + offshore_shapes = gpd.read_file(offshore_shapes).rename(columns={"name": "idx"}) + offshore_shapes["type"] = "offshore" + all_shapes = pd.concat([country_shapes, offshore_shapes], ignore_index=True) + n.madd( + "Shape", + all_shapes.index, + geometry=all_shapes.geometry, + idx=all_shapes.idx, + type=all_shapes["type"], + ) + + def base_network( eg_buses, eg_converters, @@ -758,9 +783,150 @@ def base_network( n = _adjust_capacities_of_under_construction_branches(n, config) + _set_shapes(n, country_shapes, offshore_shapes) + return n +def voronoi_partition_pts(points, outline): + """ + Compute the polygons of a voronoi partition of `points` within the polygon + `outline`. Taken from + https://github.com/FRESNA/vresutils/blob/master/vresutils/graph.py. + + Attributes + ---------- + points : Nx2 - ndarray[dtype=float] + outline : Polygon + Returns + ------- + polygons : N - ndarray[dtype=Polygon|MultiPolygon] + """ + points = np.asarray(points) + + if len(points) == 1: + polygons = [outline] + else: + xmin, ymin = np.amin(points, axis=0) + xmax, ymax = np.amax(points, axis=0) + xspan = xmax - xmin + yspan = ymax - ymin + + # to avoid any network positions outside all Voronoi cells, append + # the corners of a rectangle framing these points + vor = spatial.Voronoi( + np.vstack( + ( + points, + [ + [xmin - 3.0 * xspan, ymin - 3.0 * yspan], + [xmin - 3.0 * xspan, ymax + 3.0 * yspan], + [xmax + 3.0 * xspan, ymin - 3.0 * yspan], + [xmax + 3.0 * xspan, ymax + 3.0 * yspan], + ], + ) + ) + ) + + polygons = [] + for i in range(len(points)): + poly = Polygon(vor.vertices[vor.regions[vor.point_region[i]]]) + + if not poly.is_valid: + poly = poly.buffer(0) + + with np.errstate(invalid="ignore"): + poly = poly.intersection(outline) + + polygons.append(poly) + + return polygons + + +def build_bus_shapes(n, country_shapes, offshore_shapes, countries): + country_shapes = gpd.read_file(country_shapes).set_index("name")["geometry"] + offshore_shapes = gpd.read_file(offshore_shapes) + offshore_shapes = offshore_shapes.reindex(columns=REGION_COLS).set_index("name")[ + "geometry" + ] + + onshore_regions = [] + offshore_regions = [] + + for country in countries: + c_b = n.buses.country == country + + onshore_shape = country_shapes[country] + onshore_locs = ( + n.buses.loc[c_b & n.buses.onshore_bus] + .sort_values( + by="substation_lv", ascending=False + ) # preference for substations + .drop_duplicates(subset=["x", "y"], keep="first")[["x", "y"]] + ) + onshore_regions.append( + gpd.GeoDataFrame( + { + "name": onshore_locs.index, + "x": onshore_locs["x"], + "y": onshore_locs["y"], + "geometry": voronoi_partition_pts( + onshore_locs.values, onshore_shape + ), + "country": country, + } + ) + ) + + if country not in offshore_shapes.index: + continue + offshore_shape = offshore_shapes[country] + offshore_locs = n.buses.loc[c_b & n.buses.substation_off, ["x", "y"]] + offshore_regions_c = gpd.GeoDataFrame( + { + "name": offshore_locs.index, + "x": offshore_locs["x"], + "y": offshore_locs["y"], + "geometry": voronoi_partition_pts(offshore_locs.values, offshore_shape), + "country": country, + } + ) + offshore_regions_c = offshore_regions_c.loc[offshore_regions_c.area > 1e-2] + offshore_regions.append(offshore_regions_c) + + shapes = pd.concat(onshore_regions, ignore_index=True) + + return onshore_regions, offshore_regions, shapes, offshore_shapes + + +def append_bus_shapes(n, shapes, type): + """ + Append shapes to the network. If shapes with the same component and type + already exist, they will be removed. + + Parameters: + n (pypsa.Network): The network to which the shapes will be appended. + shapes (geopandas.GeoDataFrame): The shapes to be appended. + **kwargs: Additional keyword arguments used in `n.madd`. + + Returns: + None + """ + remove = n.shapes.query("component == 'Bus' and type == @type").index + n.mremove("Shape", remove) + + offset = n.shapes.index.astype(int).max() + 1 if not n.shapes.empty else 0 + shapes = shapes.rename(lambda x: int(x) + offset) + n.madd( + "Shape", + shapes.index, + geometry=shapes.geometry, + idx=shapes.name, + component="Bus", + type=type, + ) + + if __name__ == "__main__": if "snakemake" not in globals(): from _helpers import mock_snakemake @@ -784,5 +950,22 @@ if __name__ == "__main__": snakemake.config, ) + onshore_regions, offshore_regions, shapes, offshore_shapes = build_bus_shapes( + n, + snakemake.input.country_shapes, + snakemake.input.offshore_shapes, + snakemake.params.countries, + ) + + shapes.to_file(snakemake.output.regions_onshore) + append_bus_shapes(n, shapes, "onshore") + + if offshore_regions: + shapes = pd.concat(offshore_regions, ignore_index=True) + shapes.to_file(snakemake.output.regions_offshore) + append_bus_shapes(n, shapes, "offshore") + else: + offshore_shapes.to_frame().to_file(snakemake.output.regions_offshore) + n.meta = snakemake.config - n.export_to_netcdf(snakemake.output[0]) + n.export_to_netcdf(snakemake.output.base_network) diff --git a/scripts/build_biomass_potentials.py b/scripts/build_biomass_potentials.py index 79e2c203..883734eb 100644 --- a/scripts/build_biomass_potentials.py +++ b/scripts/build_biomass_potentials.py @@ -12,12 +12,11 @@ import logging import geopandas as gpd import numpy as np import pandas as pd +from _helpers import configure_logging, set_scenario_config logger = logging.getLogger(__name__) AVAILABLE_BIOMASS_YEARS = [2010, 2020, 2030, 2040, 2050] -from _helpers import configure_logging, set_scenario_config - def build_nuts_population_data(year=2013): pop = pd.read_csv( diff --git a/scripts/build_biomass_transport_costs.py b/scripts/build_biomass_transport_costs.py index 9c825c47..085a0f00 100644 --- a/scripts/build_biomass_transport_costs.py +++ b/scripts/build_biomass_transport_costs.py @@ -17,20 +17,27 @@ assuming as an approximation energy content of wood pellets @author: bw0928 """ +import platform + import pandas as pd import tabula as tbl ENERGY_CONTENT = 4.8 # unit MWh/t (wood pellets) +system = platform.system() +encoding = "cp1252" if system == "Windows" else None def get_countries(): - pandas_options = dict(skiprows=range(6), header=None, index_col=0) + pandas_options = dict( + skiprows=range(6), header=None, index_col=0, encoding=encoding + ) return tbl.read_pdf( str(snakemake.input.transport_cost_data), pages="145", multiple_tables=False, pandas_options=pandas_options, + encoding=encoding, )[0].index @@ -41,6 +48,7 @@ def get_cost_per_tkm(page, countries): sep=" |,", engine="python", index_col=False, + encoding=encoding, ) sc = tbl.read_pdf( @@ -48,6 +56,7 @@ def get_cost_per_tkm(page, countries): pages=page, multiple_tables=False, pandas_options=pandas_options, + encoding=encoding, )[0] sc.index = countries sc.columns = sc.columns.str.replace("€", "EUR") diff --git a/scripts/build_bus_regions.py b/scripts/build_bus_regions.py deleted file mode 100644 index 9d993c17..00000000 --- a/scripts/build_bus_regions.py +++ /dev/null @@ -1,184 +0,0 @@ -# -*- coding: utf-8 -*- -# SPDX-FileCopyrightText: : 2017-2024 The PyPSA-Eur Authors -# -# SPDX-License-Identifier: MIT -""" -Creates Voronoi shapes for each bus representing both onshore and offshore -regions. - -Relevant Settings ------------------ - -.. code:: yaml - - countries: - -.. seealso:: - Documentation of the configuration file ``config/config.yaml`` at - :ref:`toplevel_cf` - -Inputs ------- - -- ``resources/country_shapes.geojson``: confer :ref:`shapes` -- ``resources/offshore_shapes.geojson``: confer :ref:`shapes` -- ``networks/base.nc``: confer :ref:`base` - -Outputs -------- - -- ``resources/regions_onshore.geojson``: - - .. image:: img/regions_onshore.png - :scale: 33 % - -- ``resources/regions_offshore.geojson``: - - .. image:: img/regions_offshore.png - :scale: 33 % - -Description ------------ -""" - -import logging - -import geopandas as gpd -import numpy as np -import pandas as pd -import pypsa -from _helpers import REGION_COLS, configure_logging, set_scenario_config -from scipy.spatial import Voronoi -from shapely.geometry import Polygon - -logger = logging.getLogger(__name__) - - -def voronoi_partition_pts(points, outline): - """ - Compute the polygons of a voronoi partition of `points` within the polygon - `outline`. Taken from - https://github.com/FRESNA/vresutils/blob/master/vresutils/graph.py. - - Attributes - ---------- - points : Nx2 - ndarray[dtype=float] - outline : Polygon - Returns - ------- - polygons : N - ndarray[dtype=Polygon|MultiPolygon] - """ - points = np.asarray(points) - - if len(points) == 1: - polygons = [outline] - else: - xmin, ymin = np.amin(points, axis=0) - xmax, ymax = np.amax(points, axis=0) - xspan = xmax - xmin - yspan = ymax - ymin - - # to avoid any network positions outside all Voronoi cells, append - # the corners of a rectangle framing these points - vor = Voronoi( - np.vstack( - ( - points, - [ - [xmin - 3.0 * xspan, ymin - 3.0 * yspan], - [xmin - 3.0 * xspan, ymax + 3.0 * yspan], - [xmax + 3.0 * xspan, ymin - 3.0 * yspan], - [xmax + 3.0 * xspan, ymax + 3.0 * yspan], - ], - ) - ) - ) - - polygons = [] - for i in range(len(points)): - poly = Polygon(vor.vertices[vor.regions[vor.point_region[i]]]) - - if not poly.is_valid: - poly = poly.buffer(0) - - with np.errstate(invalid="ignore"): - poly = poly.intersection(outline) - - polygons.append(poly) - - return polygons - - -if __name__ == "__main__": - if "snakemake" not in globals(): - from _helpers import mock_snakemake - - snakemake = mock_snakemake("build_bus_regions") - configure_logging(snakemake) - set_scenario_config(snakemake) - - countries = snakemake.params.countries - - n = pypsa.Network(snakemake.input.base_network) - - country_shapes = gpd.read_file(snakemake.input.country_shapes).set_index("name")[ - "geometry" - ] - offshore_shapes = gpd.read_file(snakemake.input.offshore_shapes) - offshore_shapes = offshore_shapes.reindex(columns=REGION_COLS).set_index("name")[ - "geometry" - ] - - onshore_regions = [] - offshore_regions = [] - - for country in countries: - c_b = n.buses.country == country - - onshore_shape = country_shapes[country] - onshore_locs = ( - n.buses.loc[c_b & n.buses.onshore_bus] - .sort_values( - by="substation_lv", ascending=False - ) # preference for substations - .drop_duplicates(subset=["x", "y"], keep="first")[["x", "y"]] - ) - onshore_regions.append( - gpd.GeoDataFrame( - { - "name": onshore_locs.index, - "x": onshore_locs["x"], - "y": onshore_locs["y"], - "geometry": voronoi_partition_pts( - onshore_locs.values, onshore_shape - ), - "country": country, - } - ) - ) - - if country not in offshore_shapes.index: - continue - offshore_shape = offshore_shapes[country] - offshore_locs = n.buses.loc[c_b & n.buses.substation_off, ["x", "y"]] - offshore_regions_c = gpd.GeoDataFrame( - { - "name": offshore_locs.index, - "x": offshore_locs["x"], - "y": offshore_locs["y"], - "geometry": voronoi_partition_pts(offshore_locs.values, offshore_shape), - "country": country, - } - ) - offshore_regions_c = offshore_regions_c.loc[offshore_regions_c.area > 1e-2] - offshore_regions.append(offshore_regions_c) - - pd.concat(onshore_regions, ignore_index=True).to_file( - snakemake.output.regions_onshore - ) - if offshore_regions: - pd.concat(offshore_regions, ignore_index=True).to_file( - snakemake.output.regions_offshore - ) - else: - offshore_shapes.to_frame().to_file(snakemake.output.regions_offshore) diff --git a/scripts/build_daily_heat_demand.py b/scripts/build_daily_heat_demand.py index 54c5c386..b5a69e89 100644 --- a/scripts/build_daily_heat_demand.py +++ b/scripts/build_daily_heat_demand.py @@ -9,7 +9,6 @@ Build heat demand time series using heating degree day (HDD) approximation. import atlite import geopandas as gpd import numpy as np -import pandas as pd import xarray as xr from _helpers import get_snapshots, set_scenario_config from dask.distributed import Client, LocalCluster diff --git a/scripts/build_electricity_demand.py b/scripts/build_electricity_demand.py index 9174672d..fc8af372 100755 --- a/scripts/build_electricity_demand.py +++ b/scripts/build_electricity_demand.py @@ -129,7 +129,7 @@ def copy_timeslice(load, cntry, start, stop, delta, fn_load=None): load.loc[start:stop, cntry] = load.loc[ start - delta : stop - delta, cntry ].values - elif fn_load is not None: + elif fn_load is not None and cntry in load: duration = pd.date_range(freq="h", start=start - delta, end=stop - delta) load_raw = load_timeseries(fn_load, duration, [cntry]) load.loc[start:stop, cntry] = load_raw.loc[ @@ -311,6 +311,8 @@ if __name__ == "__main__": logger.info("Supplement missing data with synthetic data.") fn = snakemake.input.synthetic synthetic_load = pd.read_csv(fn, index_col=0, parse_dates=True) + # "UA" does not appear in synthetic load data + countries = list(set(countries) - set(["UA"])) synthetic_load = synthetic_load.loc[snapshots, countries] load = load.combine_first(synthetic_load) diff --git a/scripts/build_energy_totals.py b/scripts/build_energy_totals.py index b56d3294..fff95733 100644 --- a/scripts/build_energy_totals.py +++ b/scripts/build_energy_totals.py @@ -8,7 +8,6 @@ Build total energy demands per country using JRC IDEES, eurostat, and EEA data. import logging import multiprocessing as mp -import os from functools import partial import country_converter as coco @@ -125,7 +124,6 @@ def build_eurostat(input_eurostat, countries, nprocesses=1, disable_progressbar= df = pd.concat([temp, df.loc[~int_avia]]) # Fill in missing data on "Domestic aviation" for each country. - domestic_avia = df.index.get_level_values(4) == "Domestic aviation" for country in countries: slicer = idx[country, :, :, :, "Domestic aviation"] # For the Total and Fossil energy columns, fill in zeros with @@ -142,6 +140,7 @@ def build_eurostat(input_eurostat, countries, nprocesses=1, disable_progressbar= "Domestic navigation": "Domestic Navigation", "International maritime bunkers": "Bunkers", "UK": "GB", + "EL": "GR", } columns_rename = {"Total": "Total all products"} df.rename(index=index_rename, columns=columns_rename, inplace=True) @@ -395,13 +394,12 @@ def build_idees(countries): names=["country", "year"], ) + # efficiency kgoe/100km -> ktoe/100km so that after conversion TWh/100km + totals.loc[:, "passenger car efficiency"] /= 1e6 # 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 @@ -768,9 +766,8 @@ def build_transport_data(countries, population, idees): transport_data = transport_data.combine_first(fill_values) - # collect average fuel efficiency in kWh/km - - transport_data["average fuel efficiency"] = idees["passenger car efficiency"] + # collect average fuel efficiency in MWh/100km, taking passengar car efficiency in TWh/100km + transport_data["average fuel efficiency"] = idees["passenger car efficiency"] * 1e6 missing = transport_data.index[transport_data["average fuel efficiency"].isna()] if not missing.empty: @@ -854,6 +851,7 @@ def rescale_idees_from_eurostat( "total passenger cars", "total other road passenger", "total light duty road freight", + "total heavy duty road freight", ], "elec": [ "electricity road", @@ -891,6 +889,7 @@ def rescale_idees_from_eurostat( navigation = [ "total domestic navigation", ] + # international navigation is already read in from the eurostat data directly for country in idees_countries: filling_years = [(2015, slice(2016, 2021)), (2000, slice(1990, 1999))] @@ -940,9 +939,68 @@ def rescale_idees_from_eurostat( energy.loc[slicer_source, navigation].squeeze(axis=0), ).values + # set the total of agriculture/road to the sum of all agriculture/road categories (corresponding to the IDEES data) + rows = idx[country, :] + cols = [ + "total agriculture electricity", + "total agriculture heat", + "total agriculture machinery", + ] + energy.loc[rows, "total agriculture"] = energy.loc[rows, cols].sum(axis=1) + + cols = [ + "total passenger cars", + "total other road passenger", + "total light duty road freight", + "total heavy duty road freight", + ] + energy.loc[rows, "total road"] = energy.loc[rows, cols].sum(axis=1) + return energy +def update_residential_from_eurostat(energy): + """ + Updates energy balances for residential from disaggregated data from + Eurostat. + """ + eurostat_households = pd.read_csv(snakemake.input.eurostat_households) + + # Column mapping for energy type + nrg_type = { + "total residential": ("FC_OTH_HH_E", "TOTAL"), + "total residential space": ("FC_OTH_HH_E_SH", "TOTAL"), + "total residential water": ("FC_OTH_HH_E_WH", "TOTAL"), + "total residential cooking": ("FC_OTH_HH_E_CK", "TOTAL"), + "electricity residential": ("FC_OTH_HH_E", "E7000"), + "electricity residential space": ("FC_OTH_HH_E_SH", "E7000"), + "electricity residential water": ("FC_OTH_HH_E_WH", "E7000"), + "electricity residential cooking": ("FC_OTH_HH_E_CK", "E7000"), + } + + for nrg_name, (code, siec) in nrg_type.items(): + + # Select energy balance type, rename columns and countries to match IDEES data, + # convert TJ to TWh, and drop XK data already since included in RS data + col_to_rename = {"geo": "country", "TIME_PERIOD": "year", "OBS_VALUE": nrg_name} + idx_to_rename = {v: k for k, v in idees_rename.items()} + drop_geo = ["EU27_2020", "EA20", "XK"] + nrg_data = eurostat_households.query( + "nrg_bal == @code and siec == @siec and geo not in @drop_geo and OBS_VALUE > 0" + ).copy() + nrg_data.rename(columns=col_to_rename, inplace=True) + nrg_data = nrg_data.set_index(["country", "year"])[nrg_name] / 3.6e3 + nrg_data.rename(index=idx_to_rename, inplace=True) + + # update energy balance from household-specific eurostat data + idx = nrg_data.index.intersection(energy.index) + energy.loc[idx, nrg_name] = nrg_data[idx] + + logger.info( + "Updated energy balances for residential using disaggregate final energy consumption data in Households from Eurostat" + ) + + if __name__ == "__main__": if "snakemake" not in globals(): from _helpers import mock_snakemake @@ -976,6 +1034,8 @@ if __name__ == "__main__": logger.info("Extrapolate IDEES data based on eurostat for years 2015-2021.") energy = rescale_idees_from_eurostat(idees_countries, energy, eurostat) + update_residential_from_eurostat(energy) + energy.to_csv(snakemake.output.energy_name) # use rescaled idees data to calculate district heat share diff --git a/scripts/build_existing_heating_distribution.py b/scripts/build_existing_heating_distribution.py index eb2361c2..af4a50a7 100644 --- a/scripts/build_existing_heating_distribution.py +++ b/scripts/build_existing_heating_distribution.py @@ -55,7 +55,6 @@ def build_existing_heating(): nodal_heating = nodal_heating.multiply(pop_layout.fraction, axis=0) district_heat_info = pd.read_csv(snakemake.input.district_heat_share, index_col=0) - dist_fraction = district_heat_info["district fraction of node"] urban_fraction = district_heat_info["urban fraction"] energy_layout = pd.read_csv( diff --git a/scripts/build_hydro_profile.py b/scripts/build_hydro_profile.py index c4e9701e..6a0315c7 100644 --- a/scripts/build_hydro_profile.py +++ b/scripts/build_hydro_profile.py @@ -139,7 +139,10 @@ def approximate_missing_eia_stats(eia_stats, runoff_fn, countries): runoff.index = runoff.index.astype(int) # fix outliers; exceptional floods in 1977-1979 in ES & PT - runoff.loc[1978, ["ES", "PT"]] = runoff.loc[1979, ["ES", "PT"]] + if "ES" in runoff: + runoff.loc[1978, "ES"] = runoff.loc[1979, "ES"] + if "PT" in runoff: + runoff.loc[1978, "PT"] = runoff.loc[1979, "PT"] runoff_eia = runoff.loc[eia_stats.index] @@ -198,8 +201,6 @@ if __name__ == "__main__": fn = snakemake.input.era5_runoff eia_stats = approximate_missing_eia_stats(eia_stats, fn, countries) - eia_stats.to_csv(snakemake.output.eia_hydro) - contained_years = pd.date_range(freq="YE", **snakemake.params.snapshots).year norm_year = config_hydro.get("eia_norm_year") missing_years = contained_years.difference(eia_stats.index) diff --git a/scripts/build_industry_sector_ratios.py b/scripts/build_industry_sector_ratios.py index b3ef9321..06c8ecb7 100644 --- a/scripts/build_industry_sector_ratios.py +++ b/scripts/build_industry_sector_ratios.py @@ -68,6 +68,7 @@ index = [ "heat", "naphtha", "ammonia", + "methanol", "process emission", "process emission from feedstock", ] @@ -303,7 +304,7 @@ def chemicals_industry(): # There are Solids, Refinery gas, LPG, Diesel oil, Residual fuel oil, # Other liquids, Naphtha, Natural gas for feedstock. # Naphta represents 47%, methane 17%. LPG (18%) solids, refinery gas, - # diesel oil, residual fuel oils and other liquids are asimilated to Naphtha + # diesel oil, residual fuel oils and other liquids are assimilated to Naphtha s_fec = idees["fec"][13:22] assert s_fec.index[0] == subsector @@ -313,7 +314,7 @@ def chemicals_industry(): df.loc["methane", sector] += s_fec["Natural gas"] # LPG and other feedstock materials are assimilated to naphtha - # since they will be produced through Fischer-Tropsh process + # since they will be produced through Fischer-Tropsch process sel = [ "Solids", "Refinery gas", @@ -456,8 +457,7 @@ def chemicals_industry(): sector = "Methanol" df[sector] = 0.0 - df.loc["methane", sector] = params["MWh_CH4_per_tMeOH"] - df.loc["elec", sector] = params["MWh_elec_per_tMeOH"] + df.loc["methanol", sector] = params["MWh_MeOH_per_tMeOH"] # Other chemicals diff --git a/scripts/build_industry_sector_ratios_intermediate.py b/scripts/build_industry_sector_ratios_intermediate.py index 14e09505..a9cda852 100644 --- a/scripts/build_industry_sector_ratios_intermediate.py +++ b/scripts/build_industry_sector_ratios_intermediate.py @@ -51,11 +51,14 @@ def build_industry_sector_ratios_intermediate(): intermediate_sector_ratios = {} for ct, group in today_sector_ratios.T.groupby(level=0): - today_sector_ratios_ct = ( - group.droplevel(0) - .T.reindex_like(future_sector_ratios) - .fillna(future_sector_ratios) - ) + today_sector_ratios_ct = group.droplevel(0).T.reindex_like(future_sector_ratios) + missing_mask = today_sector_ratios_ct.isna().all() + today_sector_ratios_ct.loc[:, missing_mask] = future_sector_ratios.loc[ + :, missing_mask + ] + today_sector_ratios_ct.loc[:, ~missing_mask] = today_sector_ratios_ct.loc[ + :, ~missing_mask + ].fillna(0) intermediate_sector_ratios[ct] = ( today_sector_ratios_ct * (1 - fraction_future) + future_sector_ratios * fraction_future diff --git a/scripts/build_line_rating.py b/scripts/build_line_rating.py index f9c71ea3..f4b01fe0 100755 --- a/scripts/build_line_rating.py +++ b/scripts/build_line_rating.py @@ -55,7 +55,6 @@ import re import atlite import geopandas as gpd import numpy as np -import pandas as pd import pypsa import xarray as xr from _helpers import configure_logging, get_snapshots, set_scenario_config diff --git a/scripts/build_natura_raster.py b/scripts/build_natura_raster.py deleted file mode 100644 index 35fb0dbd..00000000 --- a/scripts/build_natura_raster.py +++ /dev/null @@ -1,118 +0,0 @@ -# -*- coding: utf-8 -*- -# SPDX-FileCopyrightText: : 2017-2024 The PyPSA-Eur Authors -# -# SPDX-License-Identifier: MIT -""" -Rasters the vector data of the `Natura 2000. - -`_ natural protection areas onto all -cutout regions. - -Relevant Settings ------------------ - -.. code:: yaml - - renewable: - {technology}: - cutout: - -.. seealso:: - Documentation of the configuration file ``config/config.yaml`` at - :ref:`renewable_cf` - -Inputs ------- - -- ``data/bundle/natura/Natura2000_end2015.shp``: `Natura 2000 `_ natural protection areas. - - .. image:: img/natura.png - :scale: 33 % - -Outputs -------- - -- ``resources/natura.tiff``: Rasterized version of `Natura 2000 `_ natural protection areas to reduce computation times. - - .. image:: img/natura.png - :scale: 33 % - -Description ------------ -""" - -import logging - -import atlite -import geopandas as gpd -import rasterio as rio -from _helpers import configure_logging, set_scenario_config -from rasterio.features import geometry_mask -from rasterio.warp import transform_bounds - -logger = logging.getLogger(__name__) - - -def determine_cutout_xXyY(cutout_name): - """ - Determine the full extent of a cutout. - - Since the coordinates of the cutout data are given as the - center of the grid cells, the extent of the cutout is - calculated by adding/subtracting half of the grid cell size. - - - Parameters - ---------- - cutout_name : str - Path to the cutout. - - Returns - ------- - A list of extent coordinates in the order [x, X, y, Y]. - """ - cutout = atlite.Cutout(cutout_name) - assert cutout.crs.to_epsg() == 4326 - x, X, y, Y = cutout.extent - dx, dy = cutout.dx, cutout.dy - return [x - dx / 2.0, X + dx / 2.0, y - dy / 2.0, Y + dy / 2.0] - - -def get_transform_and_shape(bounds, res): - left, bottom = [(b // res) * res for b in bounds[:2]] - right, top = [(b // res + 1) * res for b in bounds[2:]] - shape = int((top - bottom) // res), int((right - left) / res) - transform = rio.Affine(res, 0, left, 0, -res, top) - return transform, shape - - -if __name__ == "__main__": - if "snakemake" not in globals(): - from _helpers import mock_snakemake - - snakemake = mock_snakemake("build_natura_raster") - configure_logging(snakemake) - set_scenario_config(snakemake) - - x, X, y, Y = determine_cutout_xXyY(snakemake.input.cutout) - bounds = transform_bounds(4326, 3035, x, y, X, Y) - transform, out_shape = get_transform_and_shape(bounds, res=100) - - # adjusted boundaries - shapes = gpd.read_file(snakemake.input.natura).to_crs(3035) - raster = ~geometry_mask(shapes.geometry, out_shape, transform) - raster = raster.astype(rio.uint8) - - with rio.open( - snakemake.output[0], - "w", - driver="GTiff", - dtype=rio.uint8, - count=1, - transform=transform, - crs=3035, - compress="lzw", - width=raster.shape[1], - height=raster.shape[0], - ) as dst: - dst.write(raster, indexes=1) diff --git a/scripts/build_powerplants.py b/scripts/build_powerplants.py index 66a01624..4e2bb88f 100755 --- a/scripts/build_powerplants.py +++ b/scripts/build_powerplants.py @@ -148,7 +148,11 @@ def add_everywhere_powerplants(ppl, substations, everywhere_powerplants): def replace_natural_gas_technology(df): - mapping = {"Steam Turbine": "CCGT", "Combustion Engine": "OCGT"} + mapping = { + "Steam Turbine": "CCGT", + "Combustion Engine": "OCGT", + "Not Found": "CCGT", + } tech = df.Technology.replace(mapping).fillna("CCGT") return df.Technology.mask(df.Fueltype == "Natural Gas", tech) diff --git a/scripts/build_renewable_profiles.py b/scripts/build_renewable_profiles.py index f1eb5e15..0aef89bc 100644 --- a/scripts/build_renewable_profiles.py +++ b/scripts/build_renewable_profiles.py @@ -26,7 +26,7 @@ Relevant settings renewable: {technology}: - cutout: corine: luisa: grid_codes: distance: natura: max_depth: + cutout: corine: luisa: grid_codes: distance: natura: max_depth: min_depth: max_shore_distance: min_shore_distance: capacity_per_sqkm: correction_factor: min_p_max_pu: clip_p_max_pu: resource: @@ -52,7 +52,7 @@ Inputs CORINE land cover, see `Annex 1 of the technical documentation `_. -- ``data/bundle/GEBCO_2014_2D.nc``: A `bathymetric +- ``data/bundle/gebco/GEBCO_2014_2D.nc``: A `bathymetric `_ data set with a global terrain model for ocean and land at 15 arc-second intervals by the `General Bathymetric Chart of the Oceans (GEBCO) @@ -284,6 +284,12 @@ if __name__ == "__main__": func = functools.partial(np.greater, -params["max_depth"]) excluder.add_raster(snakemake.input.gebco, codes=func, crs=4326, nodata=-1000) + if params.get("min_depth"): + func = functools.partial(np.greater, -params["min_depth"]) + excluder.add_raster( + snakemake.input.gebco, codes=func, crs=4326, nodata=-1000, invert=True + ) + if "min_shore_distance" in params: buffer = params["min_shore_distance"] excluder.add_geometry(snakemake.input.country_shapes, buffer=buffer) diff --git a/scripts/build_sequestration_potentials.py b/scripts/build_sequestration_potentials.py index 2a362a77..0d70448d 100644 --- a/scripts/build_sequestration_potentials.py +++ b/scripts/build_sequestration_potentials.py @@ -23,13 +23,15 @@ def area(gdf): def allocate_sequestration_potential( gdf, regions, attr="conservative estimate Mt", threshold=3 ): - gdf = gdf.loc[gdf[attr] > threshold, [attr, "geometry"]] + if isinstance(attr, str): + attr = [attr] + gdf = gdf.loc[gdf[attr].sum(axis=1) > threshold, attr + ["geometry"]] gdf["area_sqkm"] = area(gdf) overlay = gpd.overlay(regions, gdf, keep_geom_type=True) overlay["share"] = area(overlay) / overlay["area_sqkm"] adjust_cols = overlay.columns.difference({"name", "area_sqkm", "geometry", "share"}) overlay[adjust_cols] = overlay[adjust_cols].multiply(overlay["share"], axis=0) - return overlay.dissolve("name", aggfunc="sum")[attr] + return overlay.dissolve("name", aggfunc="sum")[attr].sum(axis=1) if __name__ == "__main__": @@ -37,7 +39,7 @@ if __name__ == "__main__": from _helpers import mock_snakemake snakemake = mock_snakemake( - "build_sequestration_potentials", simpl="", clusters="181" + "build_sequestration_potentials", simpl="", clusters="128" ) set_scenario_config(snakemake) diff --git a/scripts/build_shapes.py b/scripts/build_shapes.py index fd64411a..85afdaea 100644 --- a/scripts/build_shapes.py +++ b/scripts/build_shapes.py @@ -38,7 +38,7 @@ Inputs - ``data/bundle/nama_10r_3popgdp.tsv.gz``: Average annual population by NUTS3 region (`eurostat `__) - ``data/bundle/nama_10r_3gdp.tsv.gz``: Gross domestic product (GDP) by NUTS 3 regions (`eurostat `__) -- ``data/bundle/ch_cantons.csv``: Mapping between Swiss Cantons and NUTS3 regions +- ``data/ch_cantons.csv``: Mapping between Swiss Cantons and NUTS3 regions - ``data/bundle/je-e-21.03.02.xls``: Population and GDP data per Canton (`BFS - Swiss Federal Statistical Office `_ ) Outputs diff --git a/scripts/build_ship_raster.py b/scripts/build_ship_raster.py index 47d725d8..12befe99 100644 --- a/scripts/build_ship_raster.py +++ b/scripts/build_ship_raster.py @@ -45,12 +45,38 @@ import logging import zipfile from pathlib import Path +import atlite import rioxarray from _helpers import configure_logging, set_scenario_config -from build_natura_raster import determine_cutout_xXyY logger = logging.getLogger(__name__) + +def determine_cutout_xXyY(cutout_name): + """ + Determine the full extent of a cutout. + + Since the coordinates of the cutout data are given as the + center of the grid cells, the extent of the cutout is + calculated by adding/subtracting half of the grid cell size. + + + Parameters + ---------- + cutout_name : str + Path to the cutout. + + Returns + ------- + A list of extent coordinates in the order [x, X, y, Y]. + """ + cutout = atlite.Cutout(cutout_name) + assert cutout.crs.to_epsg() == 4326 + x, X, y, Y = cutout.extent + dx, dy = cutout.dx, cutout.dy + return [x - dx / 2.0, X + dx / 2.0, y - dy / 2.0, Y + dy / 2.0] + + if __name__ == "__main__": if "snakemake" not in globals(): from _helpers import mock_snakemake diff --git a/scripts/build_transport_demand.py b/scripts/build_transport_demand.py index 35f22a80..4a29667a 100644 --- a/scripts/build_transport_demand.py +++ b/scripts/build_transport_demand.py @@ -24,14 +24,17 @@ 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 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", @@ -41,10 +44,13 @@ def build_nodal_transport_data(fn, pop_layout, year): def build_transport_demand(traffic_fn, airtemp_fn, nodes, nodal_transport_data): - ## Get overall demand curve for all vehicles - + """ + 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") + # create annual profile take account time zone + summer time transport_shape = generate_periodic_profiles( dt_index=snapshots, nodes=nodes, @@ -52,15 +58,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() @@ -73,28 +70,21 @@ 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() + # unit TWh energy_totals_transport = ( pop_weighted_energy_totals["total road"] + pop_weighted_energy_totals["total rail"] - pop_weighted_energy_totals["electricity rail"] ) - return ( - (transport_shape.multiply(energy_totals_transport) * 1e6 * nyears) - .divide(efficiency_gain * ice_correction) - .multiply(1 + dd_EV) + # average fuel efficiency in MWh/100 km + eff = nodal_transport_data["average fuel efficiency"] + + return (transport_shape.multiply(energy_totals_transport) * 1e6 * nyears).divide( + eff * ice_correction ) @@ -131,11 +121,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() ) @@ -156,6 +149,8 @@ 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" ] @@ -167,6 +162,7 @@ def bev_dsm_profile(snapshots, nodes, options): ) +# %% if __name__ == "__main__": if "snakemake" not in globals(): from _helpers import mock_snakemake @@ -174,7 +170,7 @@ if __name__ == "__main__": snakemake = mock_snakemake( "build_transport_demand", simpl="", - clusters=60, + clusters=128, ) configure_logging(snakemake) set_scenario_config(snakemake) diff --git a/scripts/cluster_network.py b/scripts/cluster_network.py index 903cbe9b..da7bd178 100644 --- a/scripts/cluster_network.py +++ b/scripts/cluster_network.py @@ -122,7 +122,6 @@ Exemplary unsolved network clustered to 37 nodes: """ import logging -import os import warnings from functools import reduce @@ -135,6 +134,7 @@ import pypsa import seaborn as sns from _helpers import configure_logging, set_scenario_config, update_p_nom_max from add_electricity import load_costs +from base_network import append_bus_shapes from packaging.version import Version, parse from pypsa.clustering.spatial import ( busmap_by_greedy_modularity, @@ -428,20 +428,27 @@ def clustering_for_n_clusters( return clustering -def cluster_regions(busmaps, input=None, output=None): +def cluster_regions(busmaps, regions): + """ + Cluster regions based on busmaps and save the results to a file and to the + network. + + Parameters: + - busmaps (list): A list of busmaps used for clustering. + - which (str): The type of regions to cluster. + + Returns: + None + """ busmap = reduce(lambda x, y: x.map(y), busmaps[1:], busmaps[0]) - - for which in ("regions_onshore", "regions_offshore"): - regions = gpd.read_file(getattr(input, which)) - regions = regions.reindex(columns=["name", "geometry"]).set_index("name") - regions_c = regions.dissolve(busmap) - regions_c.index.name = "name" - regions_c = regions_c.reset_index() - regions_c.to_file(getattr(output, which)) + regions = regions.reindex(columns=["name", "geometry"]).set_index("name") + regions_c = regions.dissolve(busmap) + regions_c.index.name = "name" + return regions_c.reset_index() -def plot_busmap_for_n_clusters(n, n_clusters, fn=None): - busmap = busmap_for_n_clusters(n, n_clusters) +def plot_busmap_for_n_clusters(n, n_clusters, solver_name="scip", fn=None): + busmap = busmap_for_n_clusters(n, n_clusters, solver_name) cs = busmap.unique() cr = sns.color_palette("hls", len(cs)) n.plot(bus_colors=busmap.map(dict(zip(cs, cr)))) @@ -519,8 +526,8 @@ if __name__ == "__main__": custom_busmap = params.custom_busmap if custom_busmap: custom_busmap = pd.read_csv( - snakemake.input.custom_busmap, index_col=0, squeeze=True - ) + snakemake.input.custom_busmap, index_col=0 + ).squeeze() custom_busmap.index = custom_busmap.index.astype(str) logger.info(f"Imported custom busmap from {snakemake.input.custom_busmap}") @@ -538,21 +545,25 @@ if __name__ == "__main__": params.focus_weights, ) - update_p_nom_max(clustering.network) + nc = clustering.network + update_p_nom_max(nc) if params.cluster_network.get("consider_efficiency_classes"): labels = [f" {label} efficiency" for label in ["low", "medium", "high"]] - nc = clustering.network nc.generators["carrier"] = nc.generators.carrier.replace(labels, "", regex=True) - clustering.network.meta = dict( - snakemake.config, **dict(wildcards=dict(snakemake.wildcards)) - ) - clustering.network.export_to_netcdf(snakemake.output.network) for attr in ( "busmap", "linemap", ): # also available: linemap_positive, linemap_negative getattr(clustering, attr).to_csv(snakemake.output[attr]) - cluster_regions((clustering.busmap,), snakemake.input, snakemake.output) + nc.shapes = n.shapes.copy() + for which in ["regions_onshore", "regions_offshore"]: + regions = gpd.read_file(snakemake.input[which]) + clustered_regions = cluster_regions((clustering.busmap,), regions) + clustered_regions.to_file(snakemake.output[which]) + append_bus_shapes(nc, clustered_regions, type=which.split("_")[1]) + + nc.meta = dict(snakemake.config, **dict(wildcards=dict(snakemake.wildcards))) + nc.export_to_netcdf(snakemake.output.network) diff --git a/scripts/make_summary.py b/scripts/make_summary.py index 8c2a1aea..5746697b 100644 --- a/scripts/make_summary.py +++ b/scripts/make_summary.py @@ -413,6 +413,85 @@ def calculate_supply_energy(n, label, supply_energy): return supply_energy +def calculate_nodal_supply_energy(n, label, nodal_supply_energy): + """ + Calculate the total energy supply/consumption of each component at the + buses aggregated by carrier and node. + """ + + bus_carriers = n.buses.carrier.unique() + + for i in bus_carriers: + bus_map = n.buses.carrier == i + bus_map.at[""] = False + + for c in n.iterate_components(n.one_port_components): + items = c.df.index[c.df.bus.map(bus_map).fillna(False)] + + if len(items) == 0: + continue + + s = ( + pd.concat( + [ + ( + c.pnl.p[items] + .multiply(n.snapshot_weightings.generators, axis=0) + .sum() + .multiply(c.df.loc[items, "sign"]) + ), + c.df.loc[items][["bus", "carrier"]], + ], + axis=1, + ) + .groupby(by=["bus", "carrier"]) + .sum()[0] + ) + s = pd.concat([s], keys=[c.list_name]) + s = pd.concat([s], keys=[i]) + + nodal_supply_energy = nodal_supply_energy.reindex( + s.index.union(nodal_supply_energy.index) + ) + nodal_supply_energy.loc[s.index, label] = s + + for c in n.iterate_components(n.branch_components): + for end in [col[3:] for col in c.df.columns if col[:3] == "bus"]: + items = c.df.index[c.df["bus" + str(end)].map(bus_map).fillna(False)] + + if (len(items) == 0) or c.pnl["p" + end].empty: + continue + + s = ( + pd.concat( + [ + ( + (-1) + * c.pnl["p" + end][items] + .multiply(n.snapshot_weightings.generators, axis=0) + .sum() + ), + c.df.loc[items][["bus0", "carrier"]], + ], + axis=1, + ) + .groupby(by=["bus0", "carrier"]) + .sum()[0] + ) + + s.index = s.index.map(lambda x: (x[0], x[1] + end)) + s = pd.concat([s], keys=[c.list_name]) + s = pd.concat([s], keys=[i]) + + nodal_supply_energy = nodal_supply_energy.reindex( + s.index.union(nodal_supply_energy.index) + ) + + nodal_supply_energy.loc[s.index, label] = s + + return nodal_supply_energy + + def calculate_metrics(n, label, metrics): metrics_list = [ "line_volume", @@ -637,6 +716,7 @@ def make_summaries(networks_dict): "energy", "supply", "supply_energy", + "nodal_supply_energy", "prices", "weighted_prices", "price_statistics", diff --git a/scripts/plot_summary.py b/scripts/plot_summary.py index bfe9995f..39fbba03 100644 --- a/scripts/plot_summary.py +++ b/scripts/plot_summary.py @@ -60,6 +60,7 @@ def rename_techs(label): "offwind": "offshore wind", "offwind-ac": "offshore wind (AC)", "offwind-dc": "offshore wind (DC)", + "offwind-float": "offshore wind (Float)", "onwind": "onshore wind", "ror": "hydroelectricity", "hydro": "hydroelectricity", @@ -476,9 +477,10 @@ def plot_carbon_budget_distribution(input_eurostat, options): ) emissions = historical_emissions(countries) # add other years https://sdi.eea.europa.eu/data/0569441f-2853-4664-a7cd-db969ef54de0 - emissions.loc[2019] = 2.971372 - emissions.loc[2020] = 2.691958 - emissions.loc[2021] = 2.869355 + emissions.loc[2019] = 3.414362 + emissions.loc[2020] = 3.092434 + emissions.loc[2021] = 3.290418 + emissions.loc[2022] = 3.213025 if snakemake.config["foresight"] == "myopic": path_cb = "results/" + snakemake.params.RDIR + "/csvs/" diff --git a/scripts/prepare_perfect_foresight.py b/scripts/prepare_perfect_foresight.py index fea0cef4..c5e4caf9 100644 --- a/scripts/prepare_perfect_foresight.py +++ b/scripts/prepare_perfect_foresight.py @@ -7,7 +7,6 @@ Concats pypsa networks of single investment periods to one network. """ import logging -import re import numpy as np import pandas as pd diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index 322cde99..7198c081 100644 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -29,6 +29,7 @@ from build_energy_totals import ( 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 @@ -97,7 +98,7 @@ def define_spatial(nodes, options): spatial.gas.industry = nodes + " gas for industry" spatial.gas.industry_cc = nodes + " gas for industry CC" spatial.gas.biogas_to_gas = nodes + " biogas to gas" - spatial.gas.biogas_to_gas_cc = nodes + "biogas to gas CC" + spatial.gas.biogas_to_gas_cc = nodes + " biogas to gas CC" else: spatial.gas.nodes = ["EU gas"] spatial.gas.locations = ["EU"] @@ -145,10 +146,12 @@ def define_spatial(nodes, options): if options["regional_methanol_demand"]: spatial.methanol.demand_locations = nodes + spatial.methanol.industry = nodes + " industry methanol" spatial.methanol.shipping = nodes + " shipping methanol" else: spatial.methanol.demand_locations = ["EU"] spatial.methanol.shipping = ["EU shipping methanol"] + spatial.methanol.industry = ["EU industry methanol"] # oil spatial.oil = SimpleNamespace() @@ -814,33 +817,6 @@ def add_co2limit(n, options, nyears=1.0, limit=0.0): ) -# TODO PyPSA-Eur merge issue -def average_every_nhours(n, offset): - logger.info(f"Resampling the network to {offset}") - m = n.copy(with_time=False) - - snapshot_weightings = n.snapshot_weightings.resample(offset).sum() - sns = snapshot_weightings.index - if snakemake.params.drop_leap_day: - sns = sns[~((sns.month == 2) & (sns.day == 29))] - snapshot_weightings = snapshot_weightings.loc[sns] - m.set_snapshots(snapshot_weightings.index) - m.snapshot_weightings = snapshot_weightings - - for c in n.iterate_components(): - pnl = getattr(m, c.list_name + "_t") - for k, df in c.pnl.items(): - if not df.empty: - if c.list_name == "stores" and k == "e_max_pu": - pnl[k] = df.resample(offset).min() - elif c.list_name == "stores" and k == "e_min_pu": - pnl[k] = df.resample(offset).max() - else: - pnl[k] = df.resample(offset).mean() - - return m - - def cycling_shift(df, steps=1): """ Cyclic shift on index of pd.Series|pd.DataFrame by number of steps. @@ -911,8 +887,6 @@ def add_ammonia(n, costs): nodes = pop_layout.index - cf_industry = snakemake.params.industry - n.add("Carrier", "NH3") n.madd( @@ -999,6 +973,18 @@ def insert_electricity_distribution_grid(n, costs): capital_cost=costs.at["electricity distribution grid", "fixed"] * cost_factor, ) + # 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: {100*(1-efficiency)}%" + ) + n.loads_t.p_set.loc[:, n.loads.carrier == "electricity"] *= efficiency + # this catches regular electricity load and "industry electricity" and # "agriculture machinery electric" and "agriculture electricity" loads = n.loads.index[n.loads.carrier.str.contains("electric")] @@ -1030,9 +1016,9 @@ def insert_electricity_distribution_grid(n, costs): else: pop_solar = pop_layout.total.rename(index=lambda x: x + " solar") - # add max solar rooftop potential assuming 0.1 kW/m2 and 10 m2/person, - # i.e. 1 kW/person (population data is in thousands of people) so we get MW - potential = 0.1 * 10 * pop_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", @@ -1120,7 +1106,7 @@ def insert_gas_distribution_costs(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)] @@ -1508,11 +1494,224 @@ 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 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, + avail_profile, + dsm_profile, + p_set, + electric_share, + number_cars, + temperature, +): + + n.add("Carrier", "Li ion") + + n.madd( + "Bus", + spatial.nodes, + suffix=" EV battery", + location=spatial.nodes, + carrier="Li ion", + unit="MWh_el", + ) + + car_efficiency = options["transport_electric_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"], + ) + + 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 + + profile = electric_share * p_set.div(efficiency) + + n.madd( + "Load", + spatial.nodes, + suffix=" land transport EV", + bus=spatial.nodes + " EV battery", + carrier="land transport EV", + p_set=profile, + ) + + p_nom = number_cars * options.get("bev_charge_rate", 0.011) * electric_share + + n.madd( + "Link", + spatial.nodes, + suffix=" BEV charger", + bus0=spatial.nodes, + bus1=spatial.nodes + " EV battery", + p_nom=p_nom, + carrier="BEV charger", + p_max_pu=avail_profile[spatial.nodes], + lifetime=1, + efficiency=options.get("bev_charge_efficiency", 0.9), + ) + + if options["v2g"]: + n.madd( + "Link", + spatial.nodes, + suffix=" V2G", + bus1=spatial.nodes, + bus0=spatial.nodes + " EV battery", + p_nom=p_nom, + carrier="V2G", + p_max_pu=avail_profile[spatial.nodes], + lifetime=1, + 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", + spatial.nodes, + suffix=" battery storage", + bus=spatial.nodes + " EV battery", + carrier="battery storage", + e_cyclic=True, + e_nom=e_nom, + e_max_pu=1, + e_min_pu=dsm_profile[spatial.nodes], + ) + + +def add_fuel_cell_cars(n, 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"], + ) + + profile = fuel_cell_share * p_set.div(efficiency) + + n.madd( + "Load", + spatial.nodes, + suffix=" land transport fuel cell", + bus=spatial.h2.nodes, + carrier="land transport fuel cell", + p_set=profile, + ) + + +def add_ice_cars(n, p_set, ice_share, temperature): + + add_carrier_buses(n, "oil") + + car_efficiency = options["transport_ice_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"], + ) + + profile = ice_share * p_set.div(efficiency).rename( + columns=lambda x: x + " land transport oil" + ) + + 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", + 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, + ) + + def add_land_transport(n, costs): - # TODO options? logger.info("Add land transport") + # read in transport demand in units driven km [100 km] transport = pd.read_csv( snakemake.input.transport_demand, index_col=0, parse_dates=True ) @@ -1526,158 +1725,36 @@ def add_land_transport(n, costs): snakemake.input.dsm_profile, index_col=0, parse_dates=True ) - 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) + # 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}%") - 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." + check_land_transport_shares(shares) + + p_set = transport[spatial.nodes] + + # 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, + avail_profile, + dsm_profile, + p_set, + shares["electric"], + number_cars, + temperature, ) - 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}%") + if shares["fuel_cell"] > 0: + add_fuel_cell_cars(n, p_set, shares["fuel_cell"], temperature) - nodes = pop_layout.index - - if electric_share > 0: - n.add("Carrier", "Li ion") - - n.madd( - "Bus", - nodes, - suffix=" EV battery", - location=nodes, - carrier="Li ion", - unit="MWh_el", - ) - - 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, p_set, shares["ice"], temperature) def build_heat_demand(n): @@ -2607,6 +2684,82 @@ def add_industry(n, costs): p_set=industrial_demand.loc[nodes, "hydrogen"] / nhours, ) + # methanol for industry + + n.madd( + "Bus", + spatial.methanol.nodes, + carrier="methanol", + location=spatial.methanol.locations, + unit="MWh_LHV", + ) + + n.madd( + "Store", + spatial.methanol.nodes, + suffix=" Store", + bus=spatial.methanol.nodes, + e_nom_extendable=True, + e_cyclic=True, + carrier="methanol", + ) + + n.madd( + "Bus", + spatial.methanol.industry, + carrier="industry methanol", + location=spatial.methanol.demand_locations, + unit="MWh_LHV", + ) + + p_set_methanol = ( + industrial_demand["methanol"].rename(lambda x: x + " industry methanol") + / nhours + ) + + if not options["regional_methanol_demand"]: + p_set_methanol = p_set_methanol.sum() + + n.madd( + "Load", + spatial.methanol.industry, + bus=spatial.methanol.industry, + carrier="industry methanol", + p_set=p_set_methanol, + ) + + n.madd( + "Link", + spatial.methanol.industry, + bus0=spatial.methanol.nodes, + bus1=spatial.methanol.industry, + bus2="co2 atmosphere", + carrier="industry methanol", + p_nom_extendable=True, + efficiency2=1 / options["MWh_MeOH_per_tCO2"], + # CO2 intensity methanol based on stoichiometric calculation with 22.7 GJ/t methanol (32 g/mol), CO2 (44 g/mol), 277.78 MWh/TJ = 0.218 t/MWh + ) + + n.madd( + "Link", + spatial.h2.locations + " methanolisation", + bus0=spatial.h2.nodes, + bus1=spatial.methanol.nodes, + bus2=nodes, + bus3=spatial.co2.nodes, + carrier="methanolisation", + p_nom_extendable=True, + p_min_pu=options.get("min_part_load_methanolisation", 0), + capital_cost=costs.at["methanolisation", "fixed"] + * options["MWh_MeOH_per_MWh_H2"], # EUR/MW_H2/a + marginal_cost=options["MWh_MeOH_per_MWh_H2"] + * costs.at["methanolisation", "VOM"], + lifetime=costs.at["methanolisation", "lifetime"], + efficiency=options["MWh_MeOH_per_MWh_H2"], + efficiency2=-options["MWh_MeOH_per_MWh_H2"] / options["MWh_MeOH_per_MWh_e"], + efficiency3=-options["MWh_MeOH_per_MWh_H2"] / options["MWh_MeOH_per_tCO2"], + ) + shipping_hydrogen_share = get(options["shipping_hydrogen_share"], investment_year) shipping_methanol_share = get(options["shipping_methanol_share"], investment_year) shipping_oil_share = get(options["shipping_oil_share"], investment_year) @@ -2676,56 +2829,18 @@ def add_industry(n, costs): ) if shipping_methanol_share: - n.madd( - "Bus", - spatial.methanol.nodes, - carrier="methanol", - location=spatial.methanol.locations, - unit="MWh_LHV", - ) - - n.madd( - "Store", - spatial.methanol.nodes, - suffix=" Store", - bus=spatial.methanol.nodes, - e_nom_extendable=True, - e_cyclic=True, - carrier="methanol", - ) - - n.madd( - "Link", - spatial.h2.locations + " methanolisation", - bus0=spatial.h2.nodes, - bus1=spatial.methanol.nodes, - bus2=nodes, - bus3=spatial.co2.nodes, - carrier="methanolisation", - p_nom_extendable=True, - p_min_pu=options.get("min_part_load_methanolisation", 0), - capital_cost=costs.at["methanolisation", "fixed"] - * options["MWh_MeOH_per_MWh_H2"], # EUR/MW_H2/a - marginal_cost=options["MWh_MeOH_per_MWh_H2"] - * costs.at["methanolisation", "VOM"], - lifetime=costs.at["methanolisation", "lifetime"], - efficiency=options["MWh_MeOH_per_MWh_H2"], - efficiency2=-options["MWh_MeOH_per_MWh_H2"] / options["MWh_MeOH_per_MWh_e"], - efficiency3=-options["MWh_MeOH_per_MWh_H2"] / options["MWh_MeOH_per_tCO2"], - ) - efficiency = ( options["shipping_oil_efficiency"] / options["shipping_methanol_efficiency"] ) - p_set_methanol = ( + p_set_methanol_shipping = ( shipping_methanol_share * p_set.rename(lambda x: x + " shipping methanol") * efficiency ) if not options["regional_methanol_demand"]: - p_set_methanol = p_set_methanol.sum() + p_set_methanol_shipping = p_set_methanol_shipping.sum() n.madd( "Bus", @@ -2740,7 +2855,7 @@ def add_industry(n, costs): spatial.methanol.shipping, bus=spatial.methanol.shipping, carrier="shipping methanol", - p_set=p_set_methanol, + p_set=p_set_methanol_shipping, ) n.madd( @@ -2870,7 +2985,7 @@ def add_industry(n, costs): if demand_factor != 1: logger.warning(f"Changing HVC demand by {demand_factor*100-100:+.2f}%.") - p_set_plastics = ( + p_set_naphtha = ( demand_factor * industrial_demand.loc[nodes, "naphtha"].rename( lambda x: x + " naphtha for industry" @@ -2879,7 +2994,7 @@ def add_industry(n, costs): ) if not options["regional_oil_demand"]: - p_set_plastics = p_set_plastics.sum() + p_set_naphtha = p_set_naphtha.sum() n.madd( "Bus", @@ -2894,7 +3009,7 @@ def add_industry(n, costs): spatial.oil.naphtha, bus=spatial.oil.naphtha, carrier="naphtha for industry", - p_set=p_set_plastics, + p_set=p_set_naphtha, ) # some CO2 from naphtha are process emissions from steam cracker @@ -2905,19 +3020,114 @@ def add_industry(n, costs): ) emitted_co2_per_naphtha = costs.at["oil", "CO2 intensity"] - process_co2_per_naphtha - n.madd( - "Link", - spatial.oil.naphtha, - bus0=spatial.oil.nodes, - bus1=spatial.oil.naphtha, - bus2="co2 atmosphere", - bus3=spatial.co2.process_emissions, - carrier="naphtha for industry", - p_nom_extendable=True, - efficiency2=emitted_co2_per_naphtha, - efficiency3=process_co2_per_naphtha, + non_sequestered = 1 - get( + cf_industry["HVC_environment_sequestration_fraction"], + investment_year, ) + if cf_industry["waste_to_energy"] or cf_industry["waste_to_energy_cc"]: + + non_sequestered_hvc_locations = ( + pd.Index(spatial.oil.demand_locations) + " non-sequestered HVC" + ) + + n.madd( + "Bus", + non_sequestered_hvc_locations, + location=spatial.oil.demand_locations, + carrier="non-sequestered HVC", + unit="MWh_LHV", + ) + + n.madd( + "Link", + spatial.oil.naphtha, + bus0=spatial.oil.nodes, + bus1=spatial.oil.naphtha, + bus2=non_sequestered_hvc_locations, + bus3=spatial.co2.process_emissions, + carrier="naphtha for industry", + p_nom_extendable=True, + efficiency2=non_sequestered + * emitted_co2_per_naphtha + / costs.at["oil", "CO2 intensity"], + efficiency3=process_co2_per_naphtha, + ) + + n.madd( + "Link", + spatial.oil.demand_locations, + suffix=" HVC to air", + bus0=non_sequestered_hvc_locations, + bus1="co2 atmosphere", + carrier="HVC to air", + p_nom_extendable=True, + efficiency=costs.at["oil", "CO2 intensity"], + ) + + if len(non_sequestered_hvc_locations) == 1: + waste_source = non_sequestered_hvc_locations[0] + else: + waste_source = non_sequestered_hvc_locations + + if cf_industry["waste_to_energy"]: + + n.madd( + "Link", + spatial.nodes + " waste CHP", + bus0=waste_source, + bus1=spatial.nodes, + bus2=spatial.nodes + " urban central heat", + bus3="co2 atmosphere", + carrier="waste CHP", + p_nom_extendable=True, + capital_cost=costs.at["waste CHP", "fixed"] + * costs.at["waste CHP", "efficiency"], + marginal_cost=costs.at["waste CHP", "VOM"], + efficiency=costs.at["waste CHP", "efficiency"], + efficiency2=costs.at["waste CHP", "efficiency-heat"], + efficiency3=costs.at["oil", "CO2 intensity"], + lifetime=costs.at["waste CHP", "lifetime"], + ) + + if cf_industry["waste_to_energy_cc"]: + + n.madd( + "Link", + spatial.nodes + " waste CHP CC", + bus0=waste_source, + bus1=spatial.nodes, + bus2=spatial.nodes + " urban central heat", + bus3="co2 atmosphere", + bus4=spatial.co2.nodes, + carrier="waste CHP CC", + p_nom_extendable=True, + capital_cost=costs.at["waste CHP CC", "fixed"] + * costs.at["waste CHP CC", "efficiency"], + marginal_cost=costs.at["waste CHP CC", "VOM"], + efficiency=costs.at["waste CHP CC", "efficiency"], + efficiency2=costs.at["waste CHP CC", "efficiency-heat"], + efficiency3=costs.at["oil", "CO2 intensity"] + * (1 - options["cc_fraction"]), + efficiency4=costs.at["oil", "CO2 intensity"] * options["cc_fraction"], + lifetime=costs.at["waste CHP CC", "lifetime"], + ) + + else: + + n.madd( + "Link", + spatial.oil.naphtha, + bus0=spatial.oil.nodes, + bus1=spatial.oil.naphtha, + bus2="co2 atmosphere", + bus3=spatial.co2.process_emissions, + carrier="naphtha for industry", + p_nom_extendable=True, + efficiency2=emitted_co2_per_naphtha * non_sequestered, + efficiency3=process_co2_per_naphtha, + ) + # aviation demand_factor = options.get("aviation_demand_factor", 1) if demand_factor != 1: @@ -3075,13 +3285,7 @@ def add_industry(n, costs): p_set=p_set, ) - primary_steel = get( - snakemake.config["industry"]["St_primary_fraction"], investment_year - ) - dri_steel = get(snakemake.config["industry"]["DRI_fraction"], investment_year) - bof_steel = primary_steel - dri_steel - - if bof_steel > 0: + if industrial_demand[["coke", "coal"]].sum().sum() > 0: add_carrier_buses(n, "coal") mwh_coal_per_mwh_coke = 1.366 # from eurostat energy balance @@ -3127,7 +3331,6 @@ def add_waste_heat(n): # TODO options? logger.info("Add possibility to use industrial waste heat in district heating") - cf_industry = snakemake.params.industry # AC buses with district heating urban_central = n.buses.index[n.buses.carrier == "urban central heat"] @@ -3426,100 +3629,56 @@ def cluster_heat_buses(n): import_components_from_dataframe(n, df.loc[to_add], c.name) -def apply_time_segmentation( - n, segments, solver_name="cbc", overwrite_time_dependent=True -): +def set_temporal_aggregation(n, resolution, snapshot_weightings): """ - Aggregating time series to segments with different lengths. - - Input: - n: pypsa Network - segments: (int) number of segments in which the typical period should be - subdivided - solver_name: (str) name of solver - overwrite_time_dependent: (bool) overwrite time dependent data of pypsa network - with typical time series created by tsam - """ - try: - import tsam.timeseriesaggregation as tsam - except ImportError: - raise ModuleNotFoundError( - "Optional dependency 'tsam' not found." "Install via 'pip install tsam'" - ) - - # get all time-dependent data - columns = pd.MultiIndex.from_tuples([], names=["component", "key", "asset"]) - raw = pd.DataFrame(index=n.snapshots, columns=columns) - for c in n.iterate_components(): - for attr, pnl in c.pnl.items(): - # exclude e_min_pu which is used for SOC of EVs in the morning - if not pnl.empty and attr != "e_min_pu": - df = pnl.copy() - df.columns = pd.MultiIndex.from_product([[c.name], [attr], df.columns]) - raw = pd.concat([raw, df], axis=1) - - # normalise all time-dependent data - annual_max = raw.max().replace(0, 1) - raw = raw.div(annual_max, level=0) - - # get representative segments - agg = tsam.TimeSeriesAggregation( - raw, - hoursPerPeriod=len(raw), - noTypicalPeriods=1, - noSegments=int(segments), - segmentation=True, - solver=solver_name, - ) - segmented = agg.createTypicalPeriods() - - weightings = segmented.index.get_level_values("Segment Duration") - offsets = np.insert(np.cumsum(weightings[:-1]), 0, 0) - timesteps = [raw.index[0] + pd.Timedelta(f"{offset}h") for offset in offsets] - snapshots = pd.DatetimeIndex(timesteps) - sn_weightings = pd.Series( - weightings, index=snapshots, name="weightings", dtype="float64" - ) - logger.info(f"Distribution of snapshot durations:\n{weightings.value_counts()}") - - n.set_snapshots(sn_weightings.index) - n.snapshot_weightings = n.snapshot_weightings.mul(sn_weightings, axis=0) - - # overwrite time-dependent data with timeseries created by tsam - if overwrite_time_dependent: - values_t = segmented.mul(annual_max).set_index(snapshots) - for component, key in values_t.columns.droplevel(2).unique(): - n.pnl(component)[key] = values_t[component, key] - - return n - - -def set_temporal_aggregation(n, resolution, solver_name): - """ - Aggregate network temporally. + Aggregate time-varying data to the given snapshots. """ if not resolution: + logger.info("No temporal aggregation. Using native resolution.") return n - - # representative snapshots - if "sn" in resolution.lower(): + elif "sn" in resolution.lower(): + # Representative snapshots are dealt with directly sn = int(resolution[:-2]) logger.info("Use every %s snapshot as representative", sn) n.set_snapshots(n.snapshots[::sn]) n.snapshot_weightings *= sn + return n + else: + # Otherwise, use the provided snapshots + snapshot_weightings = pd.read_csv( + snapshot_weightings, index_col=0, parse_dates=True + ) - # segments with package tsam - elif "seg" in resolution.lower(): - segments = int(resolution[:-3]) - logger.info("Use temporal segmentation with %s segments", segments) - n = apply_time_segmentation(n, segments, solver_name=solver_name) + # Define a series used for aggregation, mapping each hour in + # n.snapshots to the closest previous timestep in + # snapshot_weightings.index + aggregation_map = ( + pd.Series( + snapshot_weightings.index.get_indexer(n.snapshots), index=n.snapshots + ) + .replace(-1, np.nan) + .ffill() + .astype(int) + .map(lambda i: snapshot_weightings.index[i]) + ) - # temporal averaging - elif "h" in resolution.lower(): - logger.info("Aggregate to frequency %s", resolution) - n = average_every_nhours(n, resolution) + m = n.copy(with_time=False) + m.set_snapshots(snapshot_weightings.index) + m.snapshot_weightings = snapshot_weightings - return n + # Aggregation all time-varying data. + for c in n.iterate_components(): + pnl = getattr(m, c.list_name + "_t") + for k, df in c.pnl.items(): + if not df.empty: + if c.list_name == "stores" and k == "e_max_pu": + pnl[k] = df.groupby(aggregation_map).min() + elif c.list_name == "stores" and k == "e_min_pu": + pnl[k] = df.groupby(aggregation_map).max() + else: + pnl[k] = df.groupby(aggregation_map).mean() + + return m def lossy_bidirectional_links(n, carrier, efficiencies={}): @@ -3572,12 +3731,7 @@ def lossy_bidirectional_links(n, carrier, efficiencies={}): ) -def add_enhanced_geothermal( - n, - egs_potentials, - egs_overlap, - costs, -): +def add_enhanced_geothermal(n, egs_potentials, egs_overlap, costs): """ Adds EGS potential to model. @@ -3769,19 +3923,20 @@ def add_enhanced_geothermal( ) +# %% 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", - planning_horizons="2030", + sector_opts="730H-T-H-B-I-A-dist1", + planning_horizons="2050", ) configure_logging(snakemake) @@ -3789,6 +3944,7 @@ if __name__ == "__main__": update_config_from_wildcards(snakemake.config, snakemake.wildcards) options = snakemake.params.sector + cf_industry = snakemake.params.industry investment_year = int(snakemake.wildcards.planning_horizons[-4:]) @@ -3867,9 +4023,9 @@ if __name__ == "__main__": if options["allam_cycle"]: add_allam(n, costs) - solver_name = snakemake.config["solving"]["solver"]["name"] - resolution = snakemake.params.time_resolution - n = set_temporal_aggregation(n, resolution, solver_name) + n = set_temporal_aggregation( + n, snakemake.params.time_resolution, snakemake.input.snapshot_weightings + ) co2_budget = snakemake.params.co2_budget if isinstance(co2_budget, str) and co2_budget.startswith("cb"): diff --git a/scripts/retrieve_databundle.py b/scripts/retrieve_databundle.py index 996bbeab..e2736f63 100644 --- a/scripts/retrieve_databundle.py +++ b/scripts/retrieve_databundle.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2019-2022 Fabian Hofmann (TUB, FIAS) +# Copyright 2019-2024 Fabian Hofmann (TUB, FIAS), Fabian Neumann (TUB) # SPDX-FileCopyrightText: : 2017-2024 The PyPSA-Eur Authors # # SPDX-License-Identifier: MIT @@ -7,24 +7,15 @@ .. image:: https://zenodo.org/badge/DOI/10.5281/zenodo.3517935.svg :target: https://doi.org/10.5281/zenodo.3517935 -The data bundle (1.4 GB) contains common GIS datasets like NUTS3 shapes, EEZ shapes, CORINE Landcover, Natura 2000 and also electricity specific summary statistics like historic per country yearly totals of hydro generation, GDP and POP on NUTS3 levels and per-country load time-series. +The data bundle contains common GIS datasets like NUTS3 shapes, EEZ shapes, +CORINE Landcover, Natura 2000 and also electricity specific summary statistics +like historic per country yearly totals of hydro generation, GDP and population +data on NUTS3 levels and energy balances. -This rule downloads the data bundle from `zenodo `_ and extracts it in the ``data`` sub-directory, such that all files of the bundle are stored in the ``data/bundle`` subdirectory. - -The :ref:`tutorial` uses a smaller `data bundle `_ than required for the full model (188 MB) - -.. image:: https://zenodo.org/badge/DOI/10.5281/zenodo.3517921.svg - :target: https://doi.org/10.5281/zenodo.3517921 - -**Relevant Settings** - -.. code:: yaml - - tutorial: - -.. seealso:: - Documentation of the configuration file ``config/config.yaml`` at - :ref:`toplevel_cf` +This rule downloads the data bundle from `zenodo +`_ and extracts it in the ``data`` +sub-directory, such that all files of the bundle are stored in the +``data/bundle`` subdirectory. **Outputs** @@ -57,10 +48,7 @@ if __name__ == "__main__": configure_logging(snakemake) set_scenario_config(snakemake) - if snakemake.config["tutorial"]: - url = "https://zenodo.org/record/3517921/files/pypsa-eur-tutorial-data-bundle.tar.xz" - else: - url = "https://zenodo.org/record/3517935/files/pypsa-eur-data-bundle.tar.xz" + url = "https://zenodo.org/records/10973944/files/bundle.tar.xz" tarball_fn = Path(f"{rootpath}/bundle.tar.xz") to_fn = Path(rootpath) / Path(snakemake.output[0]).parent.parent @@ -74,6 +62,7 @@ if __name__ == "__main__": logger.info("Extracting databundle.") tarfile.open(tarball_fn).extractall(to_fn) + logger.info("Unlinking tarball.") tarball_fn.unlink() logger.info(f"Databundle available in '{to_fn}'.") diff --git a/scripts/retrieve_electricity_demand.py b/scripts/retrieve_electricity_demand.py index 94077fdf..d4b8d758 100644 --- a/scripts/retrieve_electricity_demand.py +++ b/scripts/retrieve_electricity_demand.py @@ -9,11 +9,10 @@ Retrieve electricity prices from OPSD. import logging import pandas as pd +from _helpers import configure_logging, set_scenario_config logger = logging.getLogger(__name__) -from _helpers import configure_logging, set_scenario_config - if __name__ == "__main__": if "snakemake" not in globals(): from _helpers import mock_snakemake diff --git a/scripts/retrieve_eurostat_data.py b/scripts/retrieve_eurostat_data.py index 4b4cea4a..085da064 100644 --- a/scripts/retrieve_eurostat_data.py +++ b/scripts/retrieve_eurostat_data.py @@ -27,11 +27,11 @@ if __name__ == "__main__": set_scenario_config(snakemake) disable_progress = snakemake.config["run"].get("disable_progressbar", False) - url_eurostat = "https://ec.europa.eu/eurostat/documents/38154/4956218/Balances-December2022.zip/f7cf0d19-5c0f-60ad-4e48-098a5ddd6e48?t=1671184070589" - tarball_fn = Path(f"{rootpath}/data/eurostat/eurostat_2023.zip") - to_fn = Path( - f"{rootpath}/data/eurostat/eurostat-energy_balances-april_2023_edition/" + url_eurostat = ( + "https://ec.europa.eu/eurostat/documents/38154/4956218/Balances-April2023.zip" ) + tarball_fn = Path(f"{rootpath}/data/eurostat/eurostat_2023.zip") + to_fn = Path(f"{rootpath}/data/eurostat/Balances-April2023/") logger.info(f"Downloading Eurostat data from '{url_eurostat}'.") progress_retrieve(url_eurostat, tarball_fn, disable=disable_progress) diff --git a/scripts/retrieve_eurostat_household_data.py b/scripts/retrieve_eurostat_household_data.py new file mode 100644 index 00000000..0ad9e34a --- /dev/null +++ b/scripts/retrieve_eurostat_household_data.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- +# SPDX-FileCopyrightText: : 2024- The PyPSA-Eur Authors +# +# SPDX-License-Identifier: MIT +""" +Retrieve and extract eurostat household energy balances data. +""" + + +import gzip +import logging +import shutil +from pathlib import Path + +from _helpers import configure_logging, progress_retrieve, set_scenario_config + +logger = logging.getLogger(__name__) + +if __name__ == "__main__": + if "snakemake" not in globals(): + from _helpers import mock_snakemake + + snakemake = mock_snakemake("retrieve_eurostat_data") + rootpath = ".." + else: + rootpath = "." + configure_logging(snakemake) + set_scenario_config(snakemake) + + disable_progress = snakemake.config["run"].get("disable_progressbar", False) + + url_eurostat_household = "https://ec.europa.eu/eurostat/databrowser-backend/api/extraction/1.0/LIVE/false/sdmx/csv/nrg_d_hhq__custom_11480365?startPeriod=2013&endPeriod=2022&i&compressed=true" + tarball_fn = Path(f"{rootpath}/data/eurostat/eurostat_household.gz") + to_fn = Path( + f"{rootpath}/data/eurostat/eurostat-household_energy_balances-february_2024.csv" + ) + + logger.info( + f"Downloading Eurostats' disaggregated household energy balances data from '{url_eurostat_household}'." + ) + progress_retrieve(url_eurostat_household, tarball_fn, disable=disable_progress) + + logger.info("Extracting Eurostat's disaggregated household energy balance data.") + with gzip.open(tarball_fn, "rb") as f_in, open(to_fn, "wb") as f_out: + shutil.copyfileobj(f_in, f_out) + + logger.info( + f"Eurostat's disaggregated household energy balance data available in '{to_fn}'." + ) diff --git a/scripts/retrieve_gas_infrastructure_data.py b/scripts/retrieve_gas_infrastructure_data.py index 8d7d0e08..972e08c5 100644 --- a/scripts/retrieve_gas_infrastructure_data.py +++ b/scripts/retrieve_gas_infrastructure_data.py @@ -4,7 +4,7 @@ # SPDX-License-Identifier: MIT """ Retrieve gas infrastructure data from -https://zenodo.org/record/4767098/files/IGGIELGN.zip. +https://zenodo.org/records/4767098/files/IGGIELGN.zip. """ import logging @@ -32,7 +32,7 @@ if __name__ == "__main__": configure_logging(snakemake) set_scenario_config(snakemake) - url = "https://zenodo.org/record/4767098/files/IGGIELGN.zip" + url = "https://zenodo.org/records/4767098/files/IGGIELGN.zip" # Save locations zip_fn = Path(f"{rootpath}/IGGIELGN.zip") diff --git a/scripts/retrieve_irena.py b/scripts/retrieve_irena.py deleted file mode 100644 index 04e48db1..00000000 --- a/scripts/retrieve_irena.py +++ /dev/null @@ -1,108 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright 2023 Thomas Gilon (Climact) -# SPDX-FileCopyrightText: : 2017-2024 The PyPSA-Eur Authors -# -# SPDX-License-Identifier: MIT -""" -This rule downloads the existing capacities from `IRENASTAT `_ and extracts it in the ``data/existing_capacities`` sub-directory. - -**Relevant Settings** - -.. code:: yaml - - enable: - retrieve_irena: - -.. seealso:: - Documentation of the configuration file ``config.yaml`` at - :ref:`enable_cf` - -**Outputs** - -- ``data/existing_capacities``: existing capacities for offwind, onwind and solar - -""" - -import logging - -import pandas as pd -from _helpers import configure_logging, set_scenario_config - -logger = logging.getLogger(__name__) - -REGIONS = [ - "Albania", - "Austria", - "Belgium", - "Bosnia and Herzegovina", - "Bulgaria", - "Croatia", - "Czechia", - "Denmark", - "Estonia", - "Finland", - "France", - "Germany", - "Greece", - "Hungary", - "Ireland", - "Italy", - "Latvia", - "Lithuania", - "Luxembourg", - "Montenegro", - # "Netherlands", - "Netherlands (Kingdom of the)", - "North Macedonia", - "Norway", - "Poland", - "Portugal", - "Romania", - "Serbia", - "Slovakia", - "Slovenia", - "Spain", - "Sweden", - "Switzerland", - # "United Kingdom", - "United Kingdom of Great Britain and Northern Ireland (the)", -] - -REGIONS_DICT = { - "Bosnia and Herzegovina": "Bosnia Herzg", - "Netherlands (Kingdom of the)": "Netherlands", - "United Kingdom of Great Britain and Northern Ireland (the)": "UK", -} - -if __name__ == "__main__": - if "snakemake" not in globals(): - from _helpers import mock_snakemake - - snakemake = mock_snakemake("retrieve_irena") - configure_logging(snakemake) - set_scenario_config(snakemake) - - irena_raw = pd.read_csv( - "https://pxweb.irena.org:443/sq/99e64b12-fe03-4a7b-92ea-a22cc3713b92", - skiprows=2, - index_col=[0, 1, 3], - encoding="latin-1", - ) - - var = "Installed electricity capacity (MW)" - irena = irena_raw[var].unstack(level=2).reset_index(level=1).replace(0, "") - - irena = irena[irena.index.isin(REGIONS)] - irena.rename(index=REGIONS_DICT, inplace=True) - - df_offwind = irena[irena.Technology.str.contains("Offshore")].drop( - columns=["Technology"] - ) - df_onwind = irena[irena.Technology.str.contains("Onshore")].drop( - columns=["Technology"] - ) - df_pv = irena[irena.Technology.str.contains("Solar")].drop(columns=["Technology"]) - - df_offwind.to_csv(snakemake.output["offwind"]) - df_onwind.to_csv(snakemake.output["onwind"]) - df_pv.to_csv(snakemake.output["solar"]) diff --git a/scripts/retrieve_sector_databundle.py b/scripts/retrieve_sector_databundle.py deleted file mode 100644 index 3b825da2..00000000 --- a/scripts/retrieve_sector_databundle.py +++ /dev/null @@ -1,49 +0,0 @@ -# -*- coding: utf-8 -*- -# SPDX-FileCopyrightText: : 2021-2024 The PyPSA-Eur Authors -# -# SPDX-License-Identifier: MIT -""" -Retrieve and extract data bundle for sector-coupled studies. -""" - -import logging -import tarfile -from pathlib import Path - -from _helpers import ( - configure_logging, - progress_retrieve, - set_scenario_config, - validate_checksum, -) - -logger = logging.getLogger(__name__) - -if __name__ == "__main__": - if "snakemake" not in globals(): - from _helpers import mock_snakemake - - snakemake = mock_snakemake("retrieve_databundle") - rootpath = ".." - else: - rootpath = "." - configure_logging(snakemake) - set_scenario_config(snakemake) - - url = "https://zenodo.org/record/5824485/files/pypsa-eur-sec-data-bundle.tar.gz" - - tarball_fn = Path(f"{rootpath}/sector-bundle.tar.gz") - to_fn = Path(rootpath) / Path(snakemake.output[0]).parent.parent - - logger.info(f"Downloading databundle from '{url}'.") - disable_progress = snakemake.config["run"].get("disable_progressbar", False) - progress_retrieve(url, tarball_fn, disable=disable_progress) - - validate_checksum(tarball_fn, url) - - logger.info("Extracting databundle.") - tarfile.open(tarball_fn).extractall(to_fn) - - tarball_fn.unlink() - - logger.info(f"Databundle available in '{to_fn}'.") diff --git a/scripts/simplify_network.py b/scripts/simplify_network.py index eac026a4..558e4cf2 100644 --- a/scripts/simplify_network.py +++ b/scripts/simplify_network.py @@ -88,12 +88,14 @@ The rule :mod:`simplify_network` does up to four things: import logging from functools import reduce +import geopandas as gpd import numpy as np import pandas as pd import pypsa import scipy as sp from _helpers import configure_logging, set_scenario_config, update_p_nom_max from add_electricity import load_costs +from base_network import append_bus_shapes from cluster_network import cluster_regions, clustering_for_n_clusters from pypsa.clustering.spatial import ( aggregateoneport, @@ -207,7 +209,7 @@ def _compute_connection_costs_to_bus( return connection_costs_to_bus -def _adjust_capital_costs_using_connection_costs(n, connection_costs_to_bus, output): +def _adjust_capital_costs_using_connection_costs(n, connection_costs_to_bus): connection_costs = {} for tech in connection_costs_to_bus: tech_b = n.generators.carrier == tech @@ -228,14 +230,12 @@ def _adjust_capital_costs_using_connection_costs(n, connection_costs_to_bus, out ) ) connection_costs[tech] = costs - pd.DataFrame(connection_costs).to_csv(output.connection_costs) def _aggregate_and_move_components( n, busmap, connection_costs_to_bus, - output, aggregate_one_ports={"Load", "StorageUnit"}, aggregation_strategies=dict(), exclude_carriers=None, @@ -248,7 +248,7 @@ def _aggregate_and_move_components( if not df.empty: import_series_from_dataframe(n, df, c, attr) - _adjust_capital_costs_using_connection_costs(n, connection_costs_to_bus, output) + _adjust_capital_costs_using_connection_costs(n, connection_costs_to_bus) generator_strategies = aggregation_strategies["generators"] @@ -281,7 +281,6 @@ def simplify_links( length_factor, p_max_pu, exclude_carriers, - output, aggregation_strategies=dict(), ): ## Complex multi-node links are folded into end-points @@ -406,7 +405,6 @@ def simplify_links( n, busmap, connection_costs_to_bus, - output, aggregation_strategies=aggregation_strategies, exclude_carriers=exclude_carriers, ) @@ -419,7 +417,6 @@ def remove_stubs( renewable_carriers, length_factor, simplify_network, - output, aggregation_strategies=dict(), ): logger.info("Removing stubs") @@ -436,7 +433,6 @@ def remove_stubs( n, busmap, connection_costs_to_bus, - output, aggregation_strategies=aggregation_strategies, exclude_carriers=simplify_network["exclude_carriers"], ) @@ -556,7 +552,6 @@ if __name__ == "__main__": params.length_factor, params.p_max_pu, params.simplify_network["exclude_carriers"], - snakemake.output, params.aggregation_strategies, ) @@ -569,7 +564,6 @@ if __name__ == "__main__": params.renewable_carriers, params.length_factor, params.simplify_network, - snakemake.output, aggregation_strategies=params.aggregation_strategies, ) busmaps.append(stub_map) @@ -594,18 +588,6 @@ if __name__ == "__main__": ) busmaps.append(busmap_hac) - if snakemake.wildcards.simpl: - n, cluster_map = cluster( - n, - int(snakemake.wildcards.simpl), - params.focus_weights, - solver_name, - params.simplify_network["algorithm"], - params.simplify_network["feature"], - params.aggregation_strategies, - ) - busmaps.append(cluster_map) - # some entries in n.buses are not updated in previous functions, therefore can be wrong. as they are not needed # and are lost when clustering (for example with the simpl wildcard), we remove them for consistency: remove = [ @@ -621,12 +603,30 @@ if __name__ == "__main__": n.buses.drop(remove, axis=1, inplace=True, errors="ignore") n.lines.drop(remove, axis=1, errors="ignore", inplace=True) - update_p_nom_max(n) + if snakemake.wildcards.simpl: + shapes = n.shapes + n, cluster_map = cluster( + n, + int(snakemake.wildcards.simpl), + params.focus_weights, + solver_name, + params.simplify_network["algorithm"], + params.simplify_network["feature"], + params.aggregation_strategies, + ) + n.shapes = shapes + busmaps.append(cluster_map) - n.meta = dict(snakemake.config, **dict(wildcards=dict(snakemake.wildcards))) - n.export_to_netcdf(snakemake.output.network) + update_p_nom_max(n) busmap_s = reduce(lambda x, y: x.map(y), busmaps[1:], busmaps[0]) busmap_s.to_csv(snakemake.output.busmap) - cluster_regions(busmaps, snakemake.input, snakemake.output) + for which in ["regions_onshore", "regions_offshore"]: + regions = gpd.read_file(snakemake.input[which]) + clustered_regions = cluster_regions(busmaps, regions) + clustered_regions.to_file(snakemake.output[which]) + append_bus_shapes(n, clustered_regions, type=which.split("_")[1]) + + n.meta = dict(snakemake.config, **dict(wildcards=dict(snakemake.wildcards))) + n.export_to_netcdf(snakemake.output.network) diff --git a/scripts/solve_network.py b/scripts/solve_network.py index b0dedda8..e5884b3f 100644 --- a/scripts/solve_network.py +++ b/scripts/solve_network.py @@ -123,7 +123,15 @@ def add_land_use_constraint_perfect(n): def _add_land_use_constraint(n): # 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"]: + 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 n.generators.loc[extendable_i, "p_nom_min"] = 0 @@ -155,10 +163,20 @@ 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"]: + for carrier in [ + "solar", + "solar rooftop", + "solar-hsat", + "onwind", + "offwind-ac", + "offwind-dc", + ]: + extendable_i = (n.generators.carrier == carrier) & n.generators.p_nom_extendable + n.generators.loc[extendable_i, "p_nom_min"] = 0 + existing = n.generators.loc[n.generators.carrier == carrier, "p_nom"] ind = list( {i.split(sep=" ")[0] + " " + i.split(sep=" ")[1] for i in existing.index} @@ -180,9 +198,99 @@ def _add_land_use_constraint_m(n, planning_horizons, config): sel_p_year ].rename(lambda x: x[:-4] + current_horizon) + # check if existing capacities are larger than technical potential + existing_large = n.generators[ + n.generators["p_nom_min"] > n.generators["p_nom_max"] + ].index + if len(existing_large): + logger.warning( + f"Existing capacities larger than technical potential for {existing_large},\ + adjust technical potential to existing capacities" + ) + n.generators.loc[existing_large, "p_nom_max"] = n.generators.loc[ + existing_large, "p_nom_min" + ] + 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): """ Add a global constraint on the amount of Mt CO2 that can be sequestered. @@ -422,23 +530,66 @@ def add_CCL_constraints(n, config): agg_p_nom_limits: data/agg_p_nom_minmax.csv """ agg_p_nom_minmax = pd.read_csv( - config["electricity"]["agg_p_nom_limits"], index_col=[0, 1] - ) + config["solving"]["agg_p_nom_limits"]["file"], index_col=[0, 1], header=[0, 1] + )[snakemake.wildcards.planning_horizons] logger.info("Adding generation capacity constraints per carrier and country") p_nom = n.model["Generator-p_nom"] gens = n.generators.query("p_nom_extendable").rename_axis(index="Generator-ext") - grouper = pd.concat([gens.bus.map(n.buses.country), gens.carrier]) + if config["solving"]["agg_p_nom_limits"]["agg_offwind"]: + rename_offwind = { + "offwind-ac": "offwind-all", + "offwind-dc": "offwind-all", + "offwind": "offwind-all", + } + gens = gens.replace(rename_offwind) + grouper = pd.concat([gens.bus.map(n.buses.country), gens.carrier], axis=1) lhs = p_nom.groupby(grouper).sum().rename(bus="country") - minimum = xr.DataArray(agg_p_nom_minmax["min"].dropna()).rename(dim_0="group") + if config["solving"]["agg_p_nom_limits"]["include_existing"]: + gens_cst = n.generators.query("~p_nom_extendable").rename_axis( + index="Generator-cst" + ) + gens_cst = gens_cst[ + (gens_cst["build_year"] + gens_cst["lifetime"]) + >= int(snakemake.wildcards.planning_horizons) + ] + if config["solving"]["agg_p_nom_limits"]["agg_offwind"]: + gens_cst = gens_cst.replace(rename_offwind) + rhs_cst = ( + pd.concat( + [gens_cst.bus.map(n.buses.country), gens_cst[["carrier", "p_nom"]]], + axis=1, + ) + .groupby(["bus", "carrier"]) + .sum() + ) + rhs_cst.index = rhs_cst.index.rename({"bus": "country"}) + rhs_min = agg_p_nom_minmax["min"].dropna() + idx_min = rhs_min.index.join(rhs_cst.index, how="left") + rhs_min = rhs_min.reindex(idx_min).fillna(0) + rhs = (rhs_min - rhs_cst.reindex(idx_min).fillna(0).p_nom).dropna() + rhs[rhs < 0] = 0 + minimum = xr.DataArray(rhs).rename(dim_0="group") + else: + minimum = xr.DataArray(agg_p_nom_minmax["min"].dropna()).rename(dim_0="group") + index = minimum.indexes["group"].intersection(lhs.indexes["group"]) if not index.empty: n.model.add_constraints( lhs.sel(group=index) >= minimum.loc[index], name="agg_p_nom_min" ) - maximum = xr.DataArray(agg_p_nom_minmax["max"].dropna()).rename(dim_0="group") + if config["solving"]["agg_p_nom_limits"]["include_existing"]: + rhs_max = agg_p_nom_minmax["max"].dropna() + idx_max = rhs_max.index.join(rhs_cst.index, how="left") + rhs_max = rhs_max.reindex(idx_max).fillna(0) + rhs = (rhs_max - rhs_cst.reindex(idx_max).fillna(0).p_nom).dropna() + rhs[rhs < 0] = 0 + maximum = xr.DataArray(rhs).rename(dim_0="group") + else: + maximum = xr.DataArray(agg_p_nom_minmax["max"].dropna()).rename(dim_0="group") + index = maximum.indexes["group"].intersection(lhs.indexes["group"]) if not index.empty: n.model.add_constraints( @@ -542,8 +693,7 @@ def add_BAU_constraints(n, config): ext_i = n.generators.query("p_nom_extendable") ext_carrier_i = xr.DataArray(ext_i.carrier.rename_axis("Generator-ext")) lhs = p_nom.groupby(ext_carrier_i).sum() - index = mincaps.index.intersection(lhs.indexes["carrier"]) - rhs = mincaps[index].rename_axis("carrier") + rhs = mincaps[lhs.indexes["carrier"]].rename_axis("carrier") n.model.add_constraints(lhs >= rhs, name="bau_mincaps") @@ -626,9 +776,9 @@ def add_operational_reserve_margin(n, sns, config): .loc[vres_i.intersection(ext_i)] .rename({"Generator-ext": "Generator"}) ) - lhs = summed_reserve + (p_nom_vres * (-EPSILON_VRES * capacity_factor)).sum( - "Generator" - ) + lhs = summed_reserve + ( + p_nom_vres * (-EPSILON_VRES * xr.DataArray(capacity_factor)) + ).sum("Generator") # Total demand per t demand = get_as_dense(n, "Load", "p_set").sum(axis=1) @@ -658,7 +808,7 @@ def add_operational_reserve_margin(n, sns, config): p_max_pu = get_as_dense(n, "Generator", "p_max_pu") - lhs = dispatch + reserve - capacity_variable * p_max_pu[ext_i] + lhs = dispatch + reserve - capacity_variable * xr.DataArray(p_max_pu[ext_i]) rhs = (p_max_pu[fix_i] * capacity_fixed).reindex(columns=gen_i, fill_value=0) @@ -865,6 +1015,13 @@ def extra_functionality(n, snapshots): if EQ_o := constraints["EQ"]: add_EQ_constraints(n, EQ_o.replace("EQ", "")) + if {"solar-hsat", "solar"}.issubset( + config["electricity"]["renewable_carriers"] + ) and {"solar-hsat", "solar"}.issubset( + config["electricity"]["extendable_carriers"]["Generator"] + ): + add_solar_potential_constraints(n, config) + add_battery_constraints(n) add_lossy_bidirectional_link_constraints(n) add_pipe_retrofit_constraint(n) @@ -917,7 +1074,7 @@ def solve_network(n, config, solving, **kwargs): # add to network for extra_functionality n.config = config - if rolling_horizon: + if rolling_horizon and snakemake.rule == "solve_operations_network": kwargs["horizon"] = cf_solving.get("horizon", 365) kwargs["overlap"] = cf_solving.get("overlap", 0) n.optimize.optimize_with_rolling_horizon(**kwargs) @@ -925,9 +1082,12 @@ def solve_network(n, config, solving, **kwargs): elif skip_iterations: status, condition = n.optimize(**kwargs) else: - kwargs["track_iterations"] = (cf_solving.get("track_iterations", False),) - kwargs["min_iterations"] = (cf_solving.get("min_iterations", 4),) - kwargs["max_iterations"] = (cf_solving.get("max_iterations", 6),) + kwargs["track_iterations"] = cf_solving["track_iterations"] + kwargs["min_iterations"] = cf_solving["min_iterations"] + kwargs["max_iterations"] = cf_solving["max_iterations"] + if cf_solving["post_discretization"].pop("enable"): + logger.info("Add post-discretization parameters.") + kwargs.update(cf_solving["post_discretization"]) status, condition = n.optimize.optimize_transmission_expansion_iteratively( **kwargs ) diff --git a/scripts/time_aggregation.py b/scripts/time_aggregation.py new file mode 100644 index 00000000..7ed69112 --- /dev/null +++ b/scripts/time_aggregation.py @@ -0,0 +1,152 @@ +# -*- coding: utf-8 -*- +# SPDX-FileCopyrightText: : 2017-2024 The PyPSA-Eur Authors +# +# SPDX-License-Identifier: MIT +""" +Defines the time aggregation to be used for sector-coupled network. + +Relevant Settings +----------------- + +.. code:: yaml + + clustering: + temporal: + resolution_sector: + + enable: + drop_leap_day: + +Inputs +------ + +- ``networks/elec_s{simpl}_{clusters}_ec_l{ll}_{opts}.nc``: the network whose + snapshots are to be aggregated +- ``resources/hourly_heat_demand_total_elec_s{simpl}_{clusters}.nc``: the total + hourly heat demand +- ``resources/solar_thermal_total_elec_s{simpl}_{clusters}.nc``: the total + hourly solar thermal generation + +Outputs +------- + +- ``snapshot_weightings_elec_s{simpl}_{clusters}_ec_l{ll}_{opts}.csv`` + +Description +----------- +Computes a time aggregation scheme for the given network, in the form of a CSV +file with the snapshot weightings, indexed by the new subset of snapshots. This +rule only computes said aggregation scheme; aggregation of time-varying network +data is done in ``prepare_sector_network.py``. +""" + + +import logging + +import numpy as np +import pandas as pd +import pypsa +import tsam.timeseriesaggregation as tsam +import xarray as xr +from _helpers import ( + configure_logging, + set_scenario_config, + update_config_from_wildcards, +) + +logger = logging.getLogger(__name__) + +if __name__ == "__main__": + if "snakemake" not in globals(): + from _helpers import mock_snakemake + + snakemake = mock_snakemake( + "time_aggregation", + configfiles="test/config.overnight.yaml", + simpl="", + opts="", + clusters="37", + ll="v1.0", + sector_opts="Co2L0-24h-T-H-B-I-A-dist1", + planning_horizons="2030", + ) + + configure_logging(snakemake) + set_scenario_config(snakemake) + update_config_from_wildcards(snakemake.config, snakemake.wildcards) + + n = pypsa.Network(snakemake.input.network) + resolution = snakemake.params.time_resolution + + # Representative snapshots + if not resolution or isinstance(resolution, str) and "sn" in resolution.lower(): + logger.info("Use representative snapshot or no aggregation at all") + # Output an empty csv; this is taken care of in prepare_sector_network.py + pd.DataFrame().to_csv(snakemake.output.snapshot_weightings) + + # Plain resampling + elif isinstance(resolution, str) and "h" in resolution.lower(): + offset = resolution.lower() + logger.info(f"Averaging every {offset} hours") + snapshot_weightings = n.snapshot_weightings.resample(offset).sum() + sns = snapshot_weightings.index + if snakemake.params.drop_leap_day: + sns = sns[~((sns.month == 2) & (sns.day == 29))] + snapshot_weightings = snapshot_weightings.loc[sns] + snapshot_weightings.to_csv(snakemake.output.snapshot_weightings) + + # Temporal segmentation + elif isinstance(resolution, str) and "seg" in resolution.lower(): + segments = int(resolution[:-3]) + logger.info(f"Use temporal segmentation with {segments} segments") + + # Get all time-dependent data + dfs = [ + pnl + for c in n.iterate_components() + for attr, pnl in c.pnl.items() + if not pnl.empty and attr != "e_min_pu" + ] + if snakemake.input.hourly_heat_demand_total: + dfs.append( + xr.open_dataset(snakemake.input.hourly_heat_demand_total) + .to_dataframe() + .unstack(level=1) + ) + if snakemake.input.solar_thermal_total: + dfs.append( + xr.open_dataset(snakemake.input.solar_thermal_total) + .to_dataframe() + .unstack(level=1) + ) + df = pd.concat(dfs, axis=1) + + # Reset columns to flat index + df = df.T.reset_index(drop=True).T + + # Normalise all time-dependent data + annual_max = df.max().replace(0, 1) + df = df.div(annual_max, level=0) + + # Get representative segments + agg = tsam.TimeSeriesAggregation( + df, + hoursPerPeriod=len(df), + noTypicalPeriods=1, + noSegments=segments, + segmentation=True, + solver=snakemake.params.solver_name, + ) + agg = agg.createTypicalPeriods() + + weightings = agg.index.get_level_values("Segment Duration") + offsets = np.insert(np.cumsum(weightings[:-1]), 0, 0) + snapshot_weightings = n.snapshot_weightings.loc[n.snapshots[offsets]].mul( + weightings, axis=0 + ) + + logger.info( + f"Distribution of snapshot durations:\n{snapshot_weightings.objective.value_counts()}" + ) + + snapshot_weightings.to_csv(snakemake.output.snapshot_weightings)