From 93b09446137f9e561eddbc7f8d8e0009e4f1e9e6 Mon Sep 17 00:00:00 2001 From: lmezilis Date: Thu, 16 Oct 2025 12:28:13 +0200 Subject: [PATCH 01/19] Initial commit of updated pypsa-eur-mrel --- config/test/config.electricity.yaml | 753 ++++++++++++++++++++++++++-- config/test/config.myopic.yaml | 133 ++++- envs/environment.yaml | 2 +- rules/build_electricity.smk | 57 +-- scripts/_helpers.py | 125 ++++- scripts/add_electricity.py | 124 ++++- scripts/build_renewable_profiles.py | 4 +- scripts/plot_summary.py | 14 +- scripts/prepare_sector_network.py | 39 +- scripts/solve_network.py | 37 +- 10 files changed, 1151 insertions(+), 137 deletions(-) diff --git a/config/test/config.electricity.yaml b/config/test/config.electricity.yaml index 147d735f32..cc6aad1697 100644 --- a/config/test/config.electricity.yaml +++ b/config/test/config.electricity.yaml @@ -2,95 +2,756 @@ # # SPDX-License-Identifier: CC0-1.0 -tutorial: true +# snakemake -call results/test/networks/base_s__elec_lv1.25_Co2L-1h.nc --configfile config/test/config.electricity.yaml + +tutorial: false run: - name: "test-elec" # use this to keep track of runs with different settings + name: "test" # use this to keep track of runs with different settings disable_progressbar: true shared_resources: - policy: "test" + policy: false shared_cutouts: true scenario: + ll: + - lv1.25 clusters: - - 5 - opts: - - '' + - 130 + opts: + - Co2L-1h -countries: ['BE'] +countries: ['AL', 'AT', 'BA', 'BE', 'BG', 'CH', 'CZ', 'DE', 'DK', 'EE', 'ES', 'FI', 'FR', 'GB', 'GR', 'HR', 'HU', 'IE', 'IT', 'LT', 'LU', 'LV', 'ME', 'MK', 'NL', 'NO', 'PL', 'PT', 'RO', 'RS', 'SE', 'SI', 'SK'] snapshots: - start: "2013-03-01" - end: "2013-03-08" + start: "2020-01-01" + end: "2021-01-01" + +enable: + retrieve: auto + retrieve_databundle: false + retrieve_cost_data: false + build_cutout: false + custom_busmap: false + build_renewable_profiles: true + # retrieve_cutout: true + # custom_busmap: false + drop_leap_day: true + + electricity: - co2limit_enable: true - co2limit: 100.e+6 + # BAU_mincapacities: + # solar: 0 + # solar-hsat: 0 + # onwind: 100000 + # offwind-ac: 0 + # offwind-dc: 100000 + # offwind-float: 100000 + + voltages: [200., 220., 300., 380., 400., 500., 750.] + base_network: osm-prebuilt + osm-prebuilt-version: 0.4 + gaslimit_enable: false + gaslimit: false + co2limit_enable: false + co2limit: 7.75e+7 + co2base: 1.487e+9 + + operational_reserve: + activate: false + epsilon_load: 0.02 + epsilon_vres: 0.02 + contingency: 4000 + + max_hours: + battery: 6 + H2: 168 extendable_carriers: - Generator: [OCGT] - StorageUnit: [battery] - Store: [H2] - Link: [H2 pipeline] + Generator: [solar, solar-hsat, offsolar, onwind, offwind-ac, offwind-fl, wave-farshore, wave-nearshore, wave-shallow ] # [solar, solar-hsat, offsolar, onwind, offwind-ac, offwind-fl, wave-farshore, wave-nearshore, wave-shallow ] + StorageUnit: [battery, H2] # battery, H2 + Store: [] + Link: [H2 pipeline] # H2 pipeline + + powerplants_filter: (DateOut >= 2023 or DateOut != DateOut) and not (Country == 'Germany' and Fueltype == 'Nuclear') + custom_powerplants: false + everywhere_powerplants: [] + + conventional_carriers: [] + renewable_carriers: [solar, solar-hsat, offsolar, onwind, offwind-ac, offwind-fl, wave-farshore, wave-nearshore, wave-shallow, hydro ] # [solar, solar-hsat, offsolar, onwind, offwind-ac, offwind-fl, wave-farshore, wave-nearshore, wave-shallow ] - renewable_carriers: [solar, solar-hsat, onwind, offwind-ac, offwind-dc, offwind-float] + estimate_renewable_capacities: + enable: true + from_opsd: true + year: 2022 + expansion_limit: false + # technology_mapping: + # Offshore: [offsolar, offwind-ac, offwind-fl, wave-farshore, wave-nearshore, wave-shallow] + # Onshore: [onwind] + # PV: [solar, solar-hsat] + + autarky: + enable: false + by_country: false atlite: - default_cutout: be-03-2013-era5 + default_cutout: era5_2020_cutout + nprocesses: 4 cutouts: - be-03-2013-era5: + era5_2020_cutout: module: era5 - x: [4., 15.] - y: [46., 56.] - time: ["2013-03-01", "2013-03-08"] + x: [-12., 42.] + y: [32., 73] + time: ["2020-01-01", "2021-01-01"] + # mrel_2020_cutout: + # module: cerra + # x: [-12., 42.] + # y: [32., 73] + # time: ["2020-01-01", "2021-01-01"] + # cerra_2020_cutout: + # module: mrel_wave + # x: [-12., 42.] + # y: [32., 73] + # time: ["2020-01-01", "2021-01-01"] + renewable: onwind: - cutout: be-03-2013-era5 + cutout: era5_2020_cutout + resource: + method: wind + turbine: Vestas_V112_3MW + smooth: false + add_cutout_windspeed: true + capacity_per_sqkm: 3 + # correction_factor: 0.93 + corine: + grid_codes: [12, 13, 14, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 31, 32] + distance: 5000 + distance_grid_codes: [1, 2, 3, 4, 5, 6, 15, 16, 17, 33, 34, 35, 38, 42, 43] + luisa: false + # grid_codes: [1111, 1121, 1122, 1123, 1130, 1210, 1221, 1222, 1230, 1241, 1242] + # distance: 1000 + # distance_grid_codes: [1111, 1121, 1122, 1123, 1130, 1210, 1221, 1222, 1230, 1241, 1242] + natura: true + excluder_resolution: 100 + clip_p_max_pu: 1.e-2 offwind-ac: - cutout: be-03-2013-era5 - max_depth: false - offwind-dc: - cutout: be-03-2013-era5 - max_depth: false - offwind-float: - cutout: be-03-2013-era5 - max_depth: false - min_depth: false + cutout: era5_2020_cutout + resource: + method: wind + turbine: NREL_ReferenceTurbine_2020ATB_12MW_offshore + smooth: false + add_cutout_windspeed: true + capacity_per_sqkm: 7 + correction_factor: 0.8855 + corine: [44, 255] + luisa: false # [0, 5230] + natura: true + ship_threshold: 400 + max_depth: 60 + min_depth: 5 + max_shore_distance: 10000 # 10 Km + min_shore_distance: 3000 # 3 Km + excluder_resolution: 200 + clip_p_max_pu: 1.e-2 + landfall_length: 10 + offwind-float: #Floating offshore wind dc + cutout: era5_2020_cutout #cerra_2020_cutout + resource: + method: wind + turbine: NREL_ReferenceTurbine_2020ATB_15MW_offshore + smooth: false + add_cutout_windspeed: true + capacity_per_sqkm: 9 + correction_factor: 0.8855 + corine: [44, 255] + natura: true # area excluded if true + ship_threshold: 400 + max_depth: 250 + min_depth: 60 + max_shore_distance: 50000 # 50 Km + min_shore_distance: 10000 # 40 Km + excluder_resolution: 200 + potential: simple # or conservative + clip_p_max_pu: 1.e-2 + landfall_length: 10 solar: - cutout: be-03-2013-era5 + cutout: era5_2020_cutout + resource: + method: pv + panel: CSi + orientation: + slope: 35. + azimuth: 180. + capacity_per_sqkm: 1 + # correction_factor: 0.854337 + corine: + grid_codes: [12, 13, 14, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 31, 32] + distance: 3000 + distance_grid_codes: [1, 2, 3, 4, 5, 6, 15, 16, 17, 33, 34, 35, 38, 42, 43] + 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 solar-hsat: - cutout: be-03-2013-era5 + cutout: era5_2020_cutout + resource: + method: pv + panel: CSi + orientation: + slope: 35. + azimuth: 180. + tracking: horizontal + capacity_per_sqkm: 0.85 # 15% higher land usage acc. to NREL + corine: + grid_codes: [12, 13, 14, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 31, 32] + distance: 1000 + distance_grid_codes: [1, 2, 3, 4, 5, 6, 15, 16, 17, 33, 34, 35, 38, 42, 43] + 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 + offsolar: + cutout: era5_2020_cutout + resource: + method: pv + panel: CSi + orientation: + slope: 35. + azimuth: 180. + capacity_per_sqkm: 1.7 + # correction_factor: 0.854337 + corine: [44, 255] + natura: true + ship_threshold: 400 + max_depth: 150 + min_depth: 0 + excluder_resolution: 200 + clip_p_max_pu: 1.e-2 + hydro: + cutout: era5_2020_cutout + carriers: [ror, PHS, hydro] + PHS_max_hours: 6 + hydro_max_hours: "energy_capacity_totals_by_country" # one of energy_capacity_totals_by_country, estimate_by_large_installations or a float + flatten_dispatch: false + flatten_dispatch_buffer: 0.2 + clip_min_inflow: 1.0 + eia_norm_year: false + eia_correct_by_capacity: false + eia_approximate_missing: false + wave-farshore: + cutout: era5_2020_cutout #mrel_2020.cutout + resource: + method: wave + wec_type: Farshore_750kW + capacity_per_sqkm: 50 #for the packing density the safe assumption is 10-20 MW/Km2, but with good assessment this is 20-50 MW/Km2 + correction_factor: 1 + corine: [44, 255] + luisa: false # [0, 5230] + natura: false # area excluded if true + max_depth: 250 + min_depth: 80 + max_shore_distance: 100000 # 100 Km +# min_shore_distance: 40000 # 40 Km + excluder_resolution: 200 + # potential: simple # or conservative + clip_p_max_pu: 1.e-2 + wave-nearshore: + cutout: era5_2020_cutout + resource: + method: wave + wec_type: Nearshore_400kW + capacity_per_sqkm: 35 #for the packing density the safe assumption is 10-20 MW/Km2, but with good assessment this is 20-40 MW/Km2 + correction_factor: 1 + corine: [44, 255] + luisa: false # [0, 5230] + natura: false # area excluded if true + max_depth: 100 + min_depth: 20 + max_shore_distance: 100000 # 100 Km + min_shore_distance: 10000 # 40 Km + # potential: simple # or conservative + clip_p_max_pu: 1.e-2 + wave-shallow: + cutout: era5_2020_cutout + resource: + method: wave + wec_type: Shallow_290kW + capacity_per_sqkm: 30 #for the packing density the safe assumption is 10-20 MW/Km2, but with good assessment this is 20-40 MW/Km2 + correction_factor: 1 + corine: [44, 255] + luisa: false # [0, 5230] + natura: false # area excluded if true + max_depth: 20 + min_depth: 5 + max_shore_distance: 100000 # 100 Km + # potential: simple # or conservative + clip_p_max_pu: 1.e-2 clustering: - exclude_carriers: ["OCGT", "offwind-ac", "coal"] + focus_weights: false + simplify_network: + to_substations: false + algorithm: kmeans # choose from: [hac, kmeans] + feature: solar+onwind-time + exclude_carriers: [] + remove_stubs: true + remove_stubs_across_borders: true + cluster_network: + algorithm: kmeans + feature: solar+onwind-time + exclude_carriers: [] + consider_efficiency_classes: false + aggregation_strategies: + generators: + committable: any + ramp_limit_up: max + ramp_limit_down: max temporal: - resolution_elec: 24h + resolution_elec: false + resolution_sector: false lines: + types: + 200.: "Al/St 240/40 2-bundle 200.0" + 220.: "Al/St 240/40 2-bundle 220.0" + 300.: "Al/St 240/40 3-bundle 300.0" + 380.: "Al/St 240/40 4-bundle 380.0" + 400.: "Al/St 240/40 4-bundle 380.0" + 500.: "Al/St 240/40 4-bundle 380.0" + 750.: "Al/St 560/50 4-bundle 750.0" + s_max_pu: 0.7 + s_nom_max: .inf + max_extension: 20000 #MW + length_factor: 1 + reconnect_crimea: true + under_construction: 'keep' # 'zero': set capacity to zero, 'remove': remove, 'keep': with full capacity for lines in grid extract dynamic_line_rating: - activate: true - cutout: be-03-2013-era5 - max_line_rating: 1.3 + activate: false + cutout: era5_2020_cutout + correction_factor: 0.95 + max_voltage_difference: false + max_line_rating: false + +load: + interpolate_limit: 3 + time_shift_for_large_gaps: 1w + manual_adjustments: true # false + scaling_factor: 1.5 + fixed_year: false # false or year (e.g. 2013) + supplement_synthetic: true + distribution_key: + gdp: 0.6 + population: 0.4 +# docs in https://pypsa-eur.readthedocs.io/en/latest/configuration.html#transmission_projects +transmission_projects: + enable: false + include: + tyndp2020: false + nep: false + manual: false + skip: + - upgraded_lines + - upgraded_links + status: + - under_construction + - in_permitting + - confirmed + #- planned_not_yet_permitted + #- under_consideration + new_link_capacity: zero #keep or zero + +costs: + year: 2030 + version: v0.9.2 + social_discountrate: 0.02 + fill_values: + FOM: 0 + VOM: 0.01 + efficiency: 1 + fuel: 0 + investment: 0 + lifetime: 25 + "CO2 intensity": 0 + "discount rate": 0.07 + # Marginal and capital costs can be overwritten + # capital_cost: + # onwind: 500 + marginal_cost: #VOM + solar: 0.01 + solar-hsat: 0.01 + onwind: 0.02 + offwind-ac: 0.02 + offwind-float: 0.02 + wave-farshore: 0.02 + wave-nearshore: 0.02 + wave-shallow: 0.02 + offwind: 1000 + # hydro: 0. + # H2: 0. + # electrolysis: 0. + # fuel cell: 0. + # battery: 0. + # battery inverter: 0. + emission_prices: + enable: false + co2: 0. + co2_monthly_prices: false solving: + constraints: + BAU: false + EQ: false solver: - name: highs - options: highs-default - + name: gurobi + options: gurobi-default plotting: map: - boundaries: + boundaries: [-11, 30, 34, 71] + color_geomap: + ocean: lightblue + land: white + projection: + name: "EqualEarth" + # See https://scitools.org.uk/cartopy/docs/latest/reference/projections.html for alternatives, for example: + # name: "LambertConformal" + # central_longitude: 10. + # central_latitude: 50. + # standard_parallels: [35, 65] eu_node_location: x: -5.5 y: 46. costs_max: 1000 - costs_threshold: 0.0000001 - energy_max: - energy_min: - energy_threshold: 0.000001 + costs_threshold: 1 + energy_max: 20000 + energy_min: -20000 + energy_threshold: 50. + + nice_names: + + onwind: "Onshore Wind" + offwind-ac: "BF Offshore Wind" + offwind-dc: "Offshore Wind (DC)" + offwind-float: "FL Offshore Wind" + wave-farshore: "WEC Farshore" + wave-nearshore: "WEC Nearshore" + wave-shallow: "WEC Shallow" + solar: "Solar" + solar-hsat: "Solar-HSAT" + offsolar: "FL Solar" + PHS: "Pumped Hydro Storage" + hydro: "Reservoir & Dam" + battery: "Battery Storage" + H2: "Hydrogen Storage" + lines: "Transmission Lines" + ror: "Run of River" + load: "Load Shedding" + ac: "AC" + dc: "DC" + + tech_colors: + # wind + onwind: "#235ebc" + # onshore wind: "#235ebc" + # offwind: "#6895dd" + # offshore wind: "#6895dd" + offwind-ac: "#6895dd" + # offshore wind (AC): "#6895dd" + # offshore wind ac: "#6895dd" + 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' + ror: '#3dbfb0' + run of river: '#3dbfb0' + hydroelectricity: '#298c81' + PHS: '#51dbcc' + hydro+PHS: "#08ad97" + # solar + solar: "#f9d002" + solar PV: "#f9d002" + solar-hsat: "#fdb915" + solar thermal: '#ffbf2b' + residential rural solar thermal: '#f1c069' + services rural solar thermal: '#eabf61' + residential urban decentral solar thermal: '#e5bc5a' + services urban decentral solar thermal: '#dfb953' + urban central solar thermal: '#d7b24c' + solar rooftop: '#ffea80' + # gas + OCGT: '#e0986c' + OCGT marginal: '#e0986c' + OCGT-heat: '#e0986c' + gas boiler: '#db6a25' + gas boilers: '#db6a25' + gas boiler marginal: '#db6a25' + residential rural gas boiler: '#d4722e' + residential urban decentral gas boiler: '#cb7a36' + services rural gas boiler: '#c4813f' + services urban decentral gas boiler: '#ba8947' + urban central gas boiler: '#b0904f' + gas: '#e05b09' + fossil gas: '#e05b09' + natural gas: '#e05b09' + biogas to gas: '#e36311' + biogas to gas CC: '#e51245' + CCGT: '#a85522' + CCGT marginal: '#a85522' + allam: '#B98F76' + gas for industry co2 to atmosphere: '#692e0a' + gas for industry co2 to stored: '#8a3400' + gas for industry: '#853403' + gas for industry CC: '#692e0a' + gas pipeline: '#ebbca0' + gas pipeline new: '#a87c62' + # oil + oil: '#c9c9c9' + oil primary: '#d2d2d2' + oil refining: '#e6e6e6' + imported oil: '#a3a3a3' + oil boiler: '#adadad' + residential rural oil boiler: '#a9a9a9' + services rural oil boiler: '#a5a5a5' + residential urban decentral oil boiler: '#a1a1a1' + urban central oil boiler: '#9d9d9d' + services urban decentral oil boiler: '#999999' + agriculture machinery oil: '#949494' + shipping oil: "#808080" + land transport oil: '#afafaf' + # nuclear + Nuclear: '#ff8c00' + Nuclear marginal: '#ff8c00' + nuclear: '#ff8c00' + uranium: '#ff8c00' + # coal + Coal: '#545454' + coal: '#545454' + Coal marginal: '#545454' + coal for industry: '#343434' + solid: '#545454' + Lignite: '#826837' + lignite: '#826837' + Lignite marginal: '#826837' + # biomass + biogas: '#e3d37d' + biomass: '#baa741' + solid biomass: '#baa741' + municipal solid waste: '#91ba41' + solid biomass import: '#d5ca8d' + solid biomass transport: '#baa741' + solid biomass for industry: '#7a6d26' + solid biomass for industry CC: '#47411c' + solid biomass for industry co2 from atmosphere: '#736412' + solid biomass for industry co2 to stored: '#47411c' + urban central solid biomass CHP: '#9d9042' + urban central solid biomass CHP CC: '#6c5d28' + biomass boiler: '#8A9A5B' + residential rural biomass boiler: '#a1a066' + residential urban decentral biomass boiler: '#b0b87b' + services rural biomass boiler: '#c6cf98' + services urban decentral biomass boiler: '#dde5b5' + biomass to liquid: '#32CD32' + unsustainable solid biomass: '#998622' + unsustainable bioliquids: '#32CD32' + electrobiofuels: 'red' + BioSNG: '#123456' + solid biomass to hydrogen: '#654321' + # power transmission + lines: '#6c9459' + transmission lines: '#6c9459' + electricity distribution grid: '#97ad8c' + low voltage: '#97ad8c' + # electricity demand + Electric load: '#110d63' + electric demand: '#110d63' + electricity: '#110d63' + industry electricity: '#2d2a66' + industry new electricity: '#2d2a66' + agriculture electricity: '#494778' + # battery + EVs + battery: '#ace37f' + battery storage: '#ace37f' + battery charger: '#88a75b' + battery discharger: '#5d4e29' + home battery: '#80c944' + home battery storage: '#80c944' + home battery charger: '#5e8032' + home battery discharger: '#3c5221' + BEV charger: '#baf238' + V2G: '#e5ffa8' + land transport EV: '#baf238' + land transport demand: '#38baf2' + EV battery: '#baf238' + # hot water storage + water tanks: '#e69487' + residential rural water tanks: '#f7b7a3' + services rural water tanks: '#f3afa3' + residential urban decentral water tanks: '#f2b2a3' + services urban decentral water tanks: '#f1b4a4' + urban central water tanks: '#e9977d' + hot water storage: '#e69487' + hot water charging: '#e8998b' + urban central water tanks charger: '#b57a67' + residential rural water tanks charger: '#b4887c' + residential urban decentral water tanks charger: '#b39995' + services rural water tanks charger: '#b3abb0' + services urban decentral water tanks charger: '#b3becc' + hot water discharging: '#e99c8e' + urban central water tanks discharger: '#b9816e' + residential rural water tanks discharger: '#ba9685' + residential urban decentral water tanks discharger: '#baac9e' + services rural water tanks discharger: '#bbc2b8' + services urban decentral water tanks discharger: '#bdd8d3' + # heat demand + Heat load: '#cc1f1f' + heat: '#cc1f1f' + heat vent: '#aa3344' + heat demand: '#cc1f1f' + rural heat: '#ff5c5c' + residential rural heat: '#ff7c7c' + services rural heat: '#ff9c9c' + central heat: '#cc1f1f' + urban central heat: '#d15959' + urban central heat vent: '#a74747' + decentral heat: '#750606' + residential urban decentral heat: '#a33c3c' + services urban decentral heat: '#cc1f1f' + low-temperature heat for industry: '#8f2727' + process heat: '#ff0000' + agriculture heat: '#d9a5a5' + # heat supply + heat pumps: '#2fb537' + heat pump: '#2fb537' + air heat pump: '#36eb41' + residential urban decentral air heat pump: '#48f74f' + services urban decentral air heat pump: '#5af95d' + services rural air heat pump: '#5af95d' + urban central air heat pump: '#6cfb6b' + ground heat pump: '#2fb537' + residential rural ground heat pump: '#48f74f' + residential rural air heat pump: '#48f74f' + services rural ground heat pump: '#5af95d' + Ambient: '#98eb9d' + CHP: '#8a5751' + urban central gas CHP: '#8d5e56' + CHP CC: '#634643' + urban central gas CHP CC: '#6e4e4c' + CHP heat: '#8a5751' + CHP electric: '#8a5751' + district heating: '#e8beac' + resistive heater: '#d8f9b8' + residential rural resistive heater: '#bef5b5' + residential urban decentral resistive heater: '#b2f1a9' + services rural resistive heater: '#a5ed9d' + services urban decentral resistive heater: '#98e991' + urban central resistive heater: '#8cdf85' + retrofitting: '#8487e8' + building retrofitting: '#8487e8' + # hydrogen + H2 for industry: "#f073da" + H2 for shipping: "#ebaee0" + H2: '#bf13a0' + hydrogen: '#bf13a0' + retrofitted H2 boiler: '#e5a0d9' + SMR: '#870c71' + SMR CC: '#4f1745' + H2 liquefaction: '#d647bd' + hydrogen storage: '#bf13a0' + H2 Store: '#bf13a0' + H2 storage: '#bf13a0' + land transport fuel cell: '#6b3161' + H2 pipeline: '#f081dc' + H2 pipeline retrofitted: '#ba99b5' + H2 Fuel Cell: '#c251ae' + H2 fuel cell: '#c251ae' + H2 turbine: '#991f83' + H2 Electrolysis: '#ff29d9' + H2 electrolysis: '#ff29d9' + # ammonia + NH3: '#46caf0' + ammonia: '#46caf0' + ammonia store: '#00ace0' + ammonia cracker: '#87d0e6' + Haber-Bosch: '#076987' + # syngas + Sabatier: '#9850ad' + methanation: '#c44ce6' + methane: '#c44ce6' + # synfuels + Fischer-Tropsch: '#25c49a' + liquid: '#25c49a' + kerosene for aviation: '#a1ffe6' + naphtha for industry: '#57ebc4' + methanol-to-kerosene: '#C98468' + methanol-to-olefins/aromatics: '#FFA07A' + Methanol steam reforming: '#FFBF00' + Methanol steam reforming CC: '#A2EA8A' + methanolisation: '#00FFBF' + biomass-to-methanol: '#EAD28A' + biomass-to-methanol CC: '#EADBAD' + allam methanol: '#B98F76' + CCGT methanol: '#B98F76' + CCGT methanol CC: '#B98F76' + OCGT methanol: '#B98F76' + methanol: '#FF7B00' + methanol transport: '#FF7B00' + shipping methanol: '#468c8b' + industry methanol: '#468c8b' + # co2 + CC: '#f29dae' + CCS: '#f29dae' + CO2 sequestration: '#f29dae' + DAC: '#ff5270' + co2 stored: '#f2385a' + co2 sequestered: '#f2682f' + co2: '#f29dae' + co2 vent: '#ffd4dc' + CO2 pipeline: '#f5627f' + # emissions + process emissions CC: '#000000' + process emissions: '#222222' + process emissions to stored: '#444444' + process emissions to atmosphere: '#888888' + oil emissions: '#aaaaaa' + shipping oil emissions: "#555555" + shipping methanol emissions: '#666666' + land transport oil emissions: '#777777' + agriculture machinery oil emissions: '#333333' + # other + shipping: '#03a2ff' + power-to-heat: '#2fb537' + power-to-gas: '#c44ce6' + power-to-H2: '#ff29d9' + power-to-liquid: '#25c49a' + gas-to-power/heat: '#ee8340' + waste: '#e3d37d' + other: '#000000' + geothermal: '#ba91b1' + geothermal heat: '#ba91b1' + geothermal district heat: '#d19D00' + geothermal organic rankine cycle: '#ffbf00' + AC: "#70af1d" + AC-AC: "#70af1d" + AC line: "#70af1d" + links: "#8a1caf" + HVDC links: "#8a1caf" + DC: "#8a1caf" + DC-DC: "#8a1caf" + DC link: "#8a1caf" + load: "#dd2e23" + waste CHP: '#e3d37d' + waste CHP CC: '#e3d3ff' + HVC to air: 'k' + diff --git a/config/test/config.myopic.yaml b/config/test/config.myopic.yaml index ba4c77795e..0492edf0a6 100644 --- a/config/test/config.myopic.yaml +++ b/config/test/config.myopic.yaml @@ -1,14 +1,17 @@ # SPDX-FileCopyrightText: : 2017-2024 The PyPSA-Eur Authors # # SPDX-License-Identifier: CC0-1.0 +# base_s_{clusters}_l{ll}_{opts}_{sector_opts}_{planning_horizons} +# snakemake -call results/greece_test_myopic/prenetworks-brownfield/base_s_44_lv1.5__Co2L0-1h-GtCO2_2050.nc --configfile config/test/config.myopic.yaml -tutorial: true + +tutorial: false run: - name: "test-sector-myopic" + name: "greece_test_myopic" disable_progressbar: true shared_resources: - policy: "test" + policy: false shared_cutouts: true foresight: myopic @@ -17,40 +20,76 @@ scenario: ll: - v1.5 clusters: - - 5 - sector_opts: + - 45 + opts: # only relevant for PyPSA-Eur - '' + sector_opts: # this is where the main scenario settings are + - Co2L0-1h #-GtCO2 planning_horizons: - - 2030 - - 2040 - 2050 -countries: ['BE'] +countries: ['GR'] snapshots: - start: "2013-03-01" - end: "2013-03-08" + start: "2020-03-01" + end: "2020-03-08" + +# enable: +# prepare_links_p_nom: false +# retrieve_databundle: false +# retrieve_sector_databundle: true +# retrieve_cost_data: false +# build_cutout: false +# retrieve_cutout: false +# build_natura_raster: false +# retrieve_natura_raster: false +# custom_busmap: false sector: - central_heat_vent: true + central_heat_vent: false electricity: - + # BAU_mincapacities: + # solar: 0 + # solar-hsat: 0 + # onwind: 100000 + # offwind-ac: 0 + # offwind-dc: 100000 + # offwind-float: 100000 + + voltages: [200., 220., 300., 380., 400., 500., 750.] + base_network: osm-prebuilt + osm-prebuilt-version: 0.4 + gaslimit_enable: false + gaslimit: false + co2limit_enable: false + co2limit: 7.75e+7 + co2base: 1.487e+9 + + operational_reserve: + activate: false + epsilon_load: 0.02 + epsilon_vres: 0.02 + contingency: 4000 + + max_hours: + battery: 6 + H2: 168 extendable_carriers: Generator: [OCGT] StorageUnit: [battery] Store: [H2] Link: [H2 pipeline] - renewable_carriers: [solar, solar-hsat, onwind, offwind-ac, offwind-dc, offwind-float] + renewable_carriers: [solar, solar-hsat, onwind, offwind-ac, offwind-float] estimate_renewable_capacities: enable: false atlite: - default_cutout: be-03-2013-era5 + default_cutout: greece_era5_2020_cutout cutouts: - be-03-2013-era5: + greece_era5_2020_cutout: module: era5 x: [4., 15.] y: [46., 56.] @@ -58,25 +97,43 @@ atlite: renewable: onwind: - cutout: be-03-2013-era5 + cutout: greece_era5_2020_cutout offwind-ac: - cutout: be-03-2013-era5 + cutout: greece_era5_2020_cutout max_depth: false offwind-dc: - cutout: be-03-2013-era5 + cutout: greece_era5_2020_cutout max_depth: false offwind-float: - cutout: be-03-2013-era5 + cutout: greece_era5_2020_cutout max_depth: false min_depth: false solar: - cutout: be-03-2013-era5 + cutout: greece_era5_2020_cutout solar-hsat: - cutout: be-03-2013-era5 + cutout: greece_era5_2020_cutout clustering: + focus_weights: false + simplify_network: + to_substations: false + remove_stubs: false + remove_stubs_across_borders: false + cluster_network: + algorithm: kmeans + # hac_features: + # - wnd100m + # - influx_direct + exclude_carriers: [] + consider_efficiency_classes: false + aggregation_strategies: + generators: + committable: any + ramp_limit_up: max + ramp_limit_down: max temporal: - resolution_sector: 24h + resolution_elec: false + resolution_sector: false industry: St_primary_fraction: @@ -84,11 +141,35 @@ industry: 2040: 0.5 2050: 0.4 +transmission_projects: + enable: false + include: + tyndp2020: false + nep: false + manual: false + skip: + - upgraded_lines + - upgraded_links + status: + - under_construction + - in_permitting + - confirmed + #- planned_not_yet_permitted + #- under_consideration + new_link_capacity: zero #keep or zero + +existing_capacities: + grouping_years_power: [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 + threshold_capacity: 10 + +costs: + year: 2050 + solving: solver: - name: highs - options: highs-default - mem: 4000 + name: gurobi + options: gurobi-default plotting: map: @@ -100,4 +181,4 @@ plotting: costs_threshold: 0.0000001 energy_max: energy_min: - energy_threshold: 0.000001 + energy_threshold: 0.000001 \ No newline at end of file diff --git a/envs/environment.yaml b/envs/environment.yaml index 67ad6d09f3..a94b191113 100644 --- a/envs/environment.yaml +++ b/envs/environment.yaml @@ -2,7 +2,7 @@ # # SPDX-License-Identifier: CC0-1.0 -name: pypsa-eur +name: pypsa-eur-mrel channels: - conda-forge - bioconda diff --git a/rules/build_electricity.smk b/rules/build_electricity.smk index 10d253f2a9..5605e76d8e 100755 --- a/rules/build_electricity.smk +++ b/rules/build_electricity.smk @@ -282,34 +282,35 @@ rule determine_availability_matrix: "../scripts/determine_availability_matrix.py" -rule build_renewable_profiles: - params: - snapshots=config_provider("snapshots"), - drop_leap_day=config_provider("enable", "drop_leap_day"), - renewable=config_provider("renewable"), - input: - availability_matrix=resources("availability_matrix_{clusters}_{technology}.nc"), - offshore_shapes=resources("offshore_shapes.geojson"), - regions=resources("regions_onshore_base_s_{clusters}.geojson"), - cutout=lambda w: "cutouts/" - + CDIR - + config_provider("renewable", w.technology, "cutout")(w) - + ".nc", - output: - profile=resources("profile_{clusters}_{technology}.nc"), - log: - logs("build_renewable_profile_{clusters}_{technology}.log"), - benchmark: - benchmarks("build_renewable_profiles_{clusters}_{technology}") - threads: config["atlite"].get("nprocesses", 4) - resources: - mem_mb=config["atlite"].get("nprocesses", 4) * 5000, - wildcard_constraints: - technology="(?!hydro).*", # Any technology other than hydro - conda: - "../envs/environment.yaml" - script: - "../scripts/build_renewable_profiles.py" +if config["enable"].get("build_renewable_profiles", True): + rule build_renewable_profiles: + params: + snapshots=config_provider("snapshots"), + drop_leap_day=config_provider("enable", "drop_leap_day"), + renewable=config_provider("renewable"), + input: + availability_matrix=resources("availability_matrix_{clusters}_{technology}.nc"), + offshore_shapes=resources("offshore_shapes.geojson"), + regions=resources("regions_onshore_base_s_{clusters}.geojson"), + cutout=lambda w: "cutouts/" + + CDIR + + config_provider("renewable", w.technology, "cutout")(w) + + ".nc", + output: + profile=resources("profile_{clusters}_{technology}.nc"), + log: + logs("build_renewable_profile_{clusters}_{technology}.log"), + benchmark: + benchmarks("build_renewable_profiles_{clusters}_{technology}") + threads: config["atlite"].get("nprocesses", 4) + resources: + mem_mb=config["atlite"].get("nprocesses", 4) * 5000, + wildcard_constraints: + technology="(?!hydro).*", # Any technology other than hydro + conda: + "../envs/environment.yaml" + script: + "../scripts/build_renewable_profiles.py" rule build_monthly_prices: diff --git a/scripts/_helpers.py b/scripts/_helpers.py index 3f6eac405f..49f1853fd5 100644 --- a/scripts/_helpers.py +++ b/scripts/_helpers.py @@ -21,6 +21,11 @@ import yaml from snakemake.utils import update_config from tqdm import tqdm +import warnings + +# Suppress the 'highly fragmented' warning +warnings.simplefilter("ignore", category=UserWarning) + logger = logging.getLogger(__name__) @@ -389,17 +394,30 @@ def aggregate_costs(n, flatten=False, opts=None, existing_only=False): def progress_retrieve(url, file, disable=False): + headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"} + # Hotfix - Bug, tqdm not working with disable=False + disable = True + if disable: - urllib.request.urlretrieve(url, file) + response = requests.get(url, headers=headers, stream=True) + with open(file, "wb") as f: + f.write(response.content) else: - with tqdm(unit="B", unit_scale=True, unit_divisor=1024, miniters=1) as t: - - def update_to(b=1, bsize=1, tsize=None): - if tsize is not None: - t.total = tsize - t.update(b * bsize - t.n) - - urllib.request.urlretrieve(url, file, reporthook=update_to) + response = requests.get(url, headers=headers, stream=True) + total_size = int(response.headers.get("content-length", 0)) + chunk_size = 1024 + + with tqdm( + total=total_size, + unit="B", + unit_scale=True, + unit_divisor=1024, + desc=str(file), + ) as t: + with open(file, "wb") as f: + for data in response.iter_content(chunk_size=chunk_size): + f.write(data) + t.update(len(data)) def mock_snakemake( @@ -434,7 +452,7 @@ def mock_snakemake( import os import snakemake as sm - from pypsa.descriptors import Dict + from pypsa.definitions.structures import Dict from snakemake.api import Workflow from snakemake.common import SNAKEFILE_CHOICES from snakemake.script import Snakemake @@ -818,3 +836,90 @@ def get_snapshots(snapshots, drop_leap_day=False, freq="h", **kwargs): time = time[~((time.month == 2) & (time.day == 29))] return time + + +def rename_techs(label: str) -> str: + """ + Rename technology labels for better readability. + + Removes some prefixes and renames if certain conditions defined in function body are met. + + Parameters: + ---------- + label: str + Technology label to be renamed + + Returns: + ------- + str + Renamed label + """ + prefix_to_remove = [ + "residential ", + "services ", + "urban ", + "rural ", + "central ", + "decentral ", + ] + + rename_if_contains = [ + "CHP", + "gas boiler", + "biogas", + "solar thermal", + "air heat pump", + "ground heat pump", + "resistive heater", + "Fischer-Tropsch", + ] + + rename_if_contains_dict = { + "water tanks": "hot water storage", + "retrofitting": "building retrofitting", + # "H2 Electrolysis": "hydrogen storage", + # "H2 Fuel Cell": "hydrogen storage", + # "H2 pipeline": "hydrogen storage", + "battery": "battery storage", + "H2 for industry": "H2 for industry", + "land transport fuel cell": "land transport fuel cell", + "land transport oil": "land transport oil", + "oil shipping": "shipping oil", + # "CC": "CC" + } + + rename = { + "solar": "solar PV", + "Sabatier": "methanation", + # "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", + "PHS": "hydroelectricity", + "NH3": "ammonia", + "co2 Store": "DAC", + "co2 stored": "CO2 sequestration", + "AC": "transmission lines", + "DC": "transmission lines", + "B2B": "transmission lines", + } + + for ptr in prefix_to_remove: + if label[: len(ptr)] == ptr: + label = label[len(ptr) :] + + for rif in rename_if_contains: + if rif in label: + label = rif + + for old, new in rename_if_contains_dict.items(): + if old in label: + label = new + + for old, new in rename.items(): + if old == label: + label = new + return label \ No newline at end of file diff --git a/scripts/add_electricity.py b/scripts/add_electricity.py index 1bf690ba3e..0c03d7a8dd 100755 --- a/scripts/add_electricity.py +++ b/scripts/add_electricity.py @@ -109,6 +109,11 @@ discharging. This leads to three investment variables for the energy capacity, charging and discharging capacity of the storage unit. """ +import warnings +from pandas.errors import PerformanceWarning + +# Suppress PerformanceWarning globally +warnings.simplefilter("ignore", category=PerformanceWarning) import logging from pathlib import Path @@ -119,6 +124,7 @@ import powerplantmatching as pm import pypsa import xarray as xr +import _helpers from _helpers import ( configure_logging, get_snapshots, @@ -194,6 +200,7 @@ def sanitize_carriers(n, config): add_missing_carriers(n, c.df.carrier) carrier_i = n.carriers.index + print(n.carriers.index) nice_names = ( pd.Series(config["plotting"]["nice_names"]) .reindex(carrier_i) @@ -202,6 +209,7 @@ def sanitize_carriers(n, config): n.carriers["nice_name"] = n.carriers.nice_name.where( n.carriers.nice_name != "", nice_names ) + print(n.carriers["nice_name"]) colors = pd.Series(config["plotting"]["tech_colors"]).reindex(carrier_i) if colors.isna().any(): missing_i = list(colors.index[colors.isna()]) @@ -286,6 +294,7 @@ def costs_for_storage(store, link1, link2=None, max_hours=1.0): for attr in ("marginal_cost", "capital_cost"): overwrites = config.get(attr) + print("Overwrites content before filtering:", overwrites) if overwrites is not None: overwrites = pd.Series(overwrites) costs.loc[overwrites.index, attr] = overwrites @@ -482,8 +491,70 @@ def attach_wind_and_solar( if "year" in ds.indexes: ds = ds.sel(year=ds.year.min(), drop=True) - supcar = car.split("-", 2)[0] - if supcar == "offwind": + # supcar = car #.split("-", 2)[0] + + if car == "offwind-ac": + distance = ds["average_distance"].to_pandas() + submarine_cost = costs.at[car + "-connection-submarine", "capital_cost"] + underground_cost = costs.at[ + car + "-connection-underground", "capital_cost" + ] + connection_cost = line_length_factor * ( + distance * submarine_cost + landfall_length * underground_cost + ) + + capital_cost = ( + costs.at["offwind-ac", "capital_cost"] + + costs.at[car + "-station", "capital_cost"] + + connection_cost + ) + logger.info( + "Added connection cost of {:0.0f}-{:0.0f} Eur/MW/a to {}".format( + connection_cost.min(), connection_cost.max(), car + ) + ) + + if car == "offwind-float": + distance = ds["average_distance"].to_pandas() + submarine_cost = costs.at[car + "-connection-submarine", "capital_cost"] + underground_cost = costs.at[ + car + "-connection-underground", "capital_cost" + ] + connection_cost = line_length_factor * ( + distance * submarine_cost + landfall_length * underground_cost + ) + + capital_cost = ( + costs.at["offwind-float", "capital_cost"] + + costs.at[car + "-station", "capital_cost"] + + connection_cost + ) + logger.info( + "Added connection cost of {:0.0f}-{:0.0f} Eur/MW/a to {}".format( + connection_cost.min(), connection_cost.max(), car + ) + ) + if car == "offsolar": + distance = ds["average_distance"].to_pandas() + submarine_cost = costs.at[car + "-connection-submarine", "capital_cost"] + underground_cost = costs.at[ + car + "-connection-underground", "capital_cost" + ] + connection_cost = line_length_factor * ( + distance * submarine_cost + landfall_length * underground_cost + ) + + capital_cost = ( + costs.at["offsolar", "capital_cost"] + + costs.at[car + "-station", "capital_cost"] + + connection_cost + ) + logger.info( + "Added connection cost of {:0.0f}-{:0.0f} Eur/MW/a to {}".format( + connection_cost.min(), connection_cost.max(), car + ) + ) + if car == "wave-farshore": distance = ds["average_distance"].to_pandas() submarine_cost = costs.at[car + "-connection-submarine", "capital_cost"] underground_cost = costs.at[ @@ -494,7 +565,7 @@ def attach_wind_and_solar( ) capital_cost = ( - costs.at["offwind", "capital_cost"] + costs.at["wave-farshore", "capital_cost"] + costs.at[car + "-station", "capital_cost"] + connection_cost ) @@ -503,6 +574,47 @@ def attach_wind_and_solar( connection_cost.min(), connection_cost.max(), car ) ) + if car == "wave-nearshore": + distance = ds["average_distance"].to_pandas() + submarine_cost = costs.at[car + "-connection-submarine", "capital_cost"] + underground_cost = costs.at[ + car + "-connection-underground", "capital_cost" + ] + connection_cost = line_length_factor * ( + distance * submarine_cost + landfall_length * underground_cost + ) + + capital_cost = ( + costs.at["wave-nearshore", "capital_cost"] + + costs.at[car + "-station", "capital_cost"] + + connection_cost + ) + logger.info( + "Added connection cost of {:0.0f}-{:0.0f} Eur/MW/a to {}".format( + connection_cost.min(), connection_cost.max(), car + ) + ) + if car == "wave-shallow": + distance = ds["average_distance"].to_pandas() + submarine_cost = costs.at[car + "-connection-submarine", "capital_cost"] + underground_cost = costs.at[ + car + "-connection-underground", "capital_cost" + ] + connection_cost = line_length_factor * ( + distance * submarine_cost + landfall_length * underground_cost + ) + + capital_cost = ( + costs.at["wave-shallow", "capital_cost"] + + costs.at[car + "-station", "capital_cost"] + + connection_cost + ) + logger.info( + "Added connection cost of {:0.0f}-{:0.0f} Eur/MW/a to {}".format( + connection_cost.min(), connection_cost.max(), car + ) + ) + else: capital_cost = costs.at[car, "capital_cost"] @@ -514,11 +626,11 @@ def attach_wind_and_solar( carrier=car, p_nom_extendable=car in extendable_carriers["Generator"], p_nom_max=ds["p_nom_max"].to_pandas(), - marginal_cost=costs.at[supcar, "marginal_cost"], + marginal_cost=costs.at[car, "marginal_cost"], capital_cost=capital_cost, - efficiency=costs.at[supcar, "efficiency"], + efficiency=costs.at[car, "efficiency"], p_max_pu=ds["profile"].transpose("time", "bus").to_pandas(), - lifetime=costs.at[supcar, "lifetime"], + lifetime=costs.at[car, "lifetime"], ) diff --git a/scripts/build_renewable_profiles.py b/scripts/build_renewable_profiles.py index ab6a4748d5..025de5c5ef 100644 --- a/scripts/build_renewable_profiles.py +++ b/scripts/build_renewable_profiles.py @@ -149,7 +149,7 @@ resource = params["resource"] # pv panel params / wind turbine params resource["show_progress"] = not noprogress - tech = next(t for t in ["panel", "turbine"] if t in resource) + tech = next(t for t in ["panel", "turbine", "wec_type"] if t in resource) models = resource[tech] if not isinstance(models, dict): models = {0: models} @@ -179,7 +179,7 @@ ) # do not pull up, set_index does not work if geo dataframe is empty regions = regions.set_index("name").rename_axis("bus") - if snakemake.wildcards.technology.startswith("offwind"): + if snakemake.wildcards.technology.startswith("offwind") or snakemake.wildcards.technology.startswith("wave") or snakemake.wildcards.technology.startswith("offsolar"): # for offshore regions, the shortest distance to the shoreline is used offshore_regions = availability.coords["bus"].values regions = regions.loc[offshore_regions] diff --git a/scripts/plot_summary.py b/scripts/plot_summary.py index d131e93781..7c0a668998 100644 --- a/scripts/plot_summary.py +++ b/scripts/plot_summary.py @@ -58,9 +58,14 @@ def rename_techs(label): "solar": "solar PV", "Sabatier": "methanation", "offwind": "offshore wind", - "offwind-ac": "offshore wind (AC)", + "offwind-ac": "offshore wind ", "offwind-dc": "offshore wind (DC)", + "offwind-fl": "offshore floating wind", "offwind-float": "offshore wind (Float)", + "offsolar": "floating solar", + "wave-farshore": "wave farshore", + "wave-nearshore": "wave nearshore", + "wave-shallow": "wave shallow", "onwind": "onshore wind", "ror": "hydroelectricity", "hydro": "hydroelectricity", @@ -102,12 +107,17 @@ def rename_techs(label): "biogas", "onshore wind", "offshore wind", - "offshore wind (AC)", + "offshore wind", "offshore wind (DC)", + "offshore floating wind", + "wave farshore", + "wave nearshore", + "wave shallow", "solar PV", "solar thermal", "solar rooftop", "solar", + "floating solar" "building retrofitting", "ground heat pump", "air heat pump", diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index f65e2d7b31..60cc59a613 100755 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -341,7 +341,7 @@ def add_lifetime_wind_solar(n, costs): """ Add lifetime for solar and wind generators. """ - for carrier in ["solar", "onwind", "offwind"]: + for carrier in ["solar", "onwind", "offwind", "wave", "offsolar"]: gen_i = n.generators.index.str.contains(carrier) n.generators.loc[gen_i, "lifetime"] = costs.at[carrier, "lifetime"] @@ -432,7 +432,7 @@ def update_wind_solar_costs( ] # for offshore wind, need to calculated connection costs - for connection in ["dc", "ac", "float"]: + for connection in ["dc", "ac", "float", "fl"]: tech = "offwind-" + connection landfall_length = landfall_lengths.get(tech, 0.0) if tech not in n.generators.carrier.values: @@ -466,6 +466,41 @@ def update_wind_solar_costs( n.generators.loc[n.generators.carrier == tech, "capital_cost"] = ( capital_cost.rename(index=lambda node: node + " " + tech) ) + + for connection in ["farshore", "nearshore", "shallow"]: + tech = "wave-" + connection + landfall_length = landfall_lengths.get(tech, 0.0) + if tech not in n.generators.carrier.values: + continue + profile = snakemake.input["profile_wave-" + connection] + with xr.open_dataset(profile) as ds: + + # if-statement for compatibility with old profiles + if "year" in ds.indexes: + ds = ds.sel(year=ds.year.min(), drop=True) + + distance = ds["average_distance"].to_pandas() + submarine_cost = costs.at[tech + "-connection-submarine", "fixed"] + underground_cost = costs.at[tech + "-connection-underground", "fixed"] + connection_cost = line_length_factor * ( + distance * submarine_cost + landfall_length * underground_cost + ) + + capital_cost = ( + costs.at["wave", "fixed"] + + costs.at[tech + "-station", "fixed"] + + connection_cost + ) + + logger.info( + "Added connection cost of {:0.0f}-{:0.0f} Eur/MW/a to {}".format( + connection_cost.min(), connection_cost.max(), tech + ) + ) + + n.generators.loc[n.generators.carrier == tech, "capital_cost"] = ( + capital_cost.rename(index=lambda node: node + " " + tech) + ) def add_carrier_buses(n, carrier, nodes=None): diff --git a/scripts/solve_network.py b/scripts/solve_network.py index 0cbc0ec11e..c77c7be1dd 100644 --- a/scripts/solve_network.py +++ b/scripts/solve_network.py @@ -125,15 +125,19 @@ def add_land_use_constraint(n): "offwind-ac", "offwind-dc", "offwind-float", + "offsolar", + "wave-shallow", + "wave-nearshore", + "wave-farshore" ]: ext_i = (n.generators.carrier == carrier) & ~n.generators.p_nom_extendable existing = ( n.generators.loc[ext_i, "p_nom"] - .groupby(n.generators.bus.map(n.buses.location)) + .groupby(n.generators.bus.map(n.buses.country)) .sum() ) - existing.index += " " + carrier + "-" + snakemake.wildcards.planning_horizons + existing.index += " " + carrier + "-" #+ snakemake.wildcards.planning_horizons n.generators.loc[existing.index, "p_nom_max"] -= existing # check if existing capacities are larger than technical potential @@ -633,9 +637,16 @@ def add_BAU_constraints(n, config): p_nom = n.model["Generator-p_nom"] ext_i = n.generators.query("p_nom_extendable") ext_carrier_i = xr.DataArray(ext_i.carrier.rename_axis("Generator-ext")) + + # Debug output to check carrier names + print(f"Carriers in mincaps: {mincaps.index}") + print(f"Carriers in network: {ext_i.carrier.unique()}") + lhs = p_nom.groupby(ext_carrier_i).sum() rhs = mincaps[lhs.indexes["carrier"]].rename_axis("carrier") n.model.add_constraints(lhs >= rhs, name="bau_mincaps") + logger.info("BAU is done.") + # TODO: think about removing or make per country @@ -932,17 +943,15 @@ def add_co2_atmosphere_constraint(n, snapshots): def extra_functionality(n, snapshots): - """ - Collects supplementary constraints which will be passed to - ``pypsa.optimization.optimize``. - - If you want to enforce additional custom constraints, this is a good - location to add them. The arguments ``opts`` and - ``snakemake.config`` are expected to be attached to the network. - """ config = n.config + opts = config.get('scenario', {}).get('opts', []) + + # Debugging output to verify opts are passed correctly + print(f"Scenario opts: {opts}") + constraints = config["solving"].get("constraints", {}) - if constraints["BAU"] and n.generators.p_nom_extendable.any(): + + if constraints.get("BAU", False) and n.generators.p_nom_extendable.any(): add_BAU_constraints(n, config) if constraints["SAFE"] and n.generators.p_nom_extendable.any(): add_SAFE_constraints(n, config) @@ -956,9 +965,9 @@ def extra_functionality(n, snapshots): if EQ_o := constraints["EQ"]: add_EQ_constraints(n, EQ_o.replace("EQ", "")) - if {"solar-hsat", "solar"}.issubset( + if {"solar-hsat", "solar", "offsolar"}.issubset( config["electricity"]["renewable_carriers"] - ) and {"solar-hsat", "solar"}.issubset( + ) and {"solar-hsat", "solar", "offsolar"}.issubset( config["electricity"]["extendable_carriers"]["Generator"] ): add_solar_potential_constraints(n, config) @@ -1059,7 +1068,7 @@ def solve_network(n, config, params, solving, **kwargs): clusters="5", ll="v1.0", sector_opts="", - # planning_horizons="2030", + planning_horizons="2030", ) configure_logging(snakemake) set_scenario_config(snakemake) From ef68650d16efdd07c3172baa9cbe85fd4602f96d Mon Sep 17 00:00:00 2001 From: lmezilis Date: Wed, 22 Oct 2025 14:15:39 +0200 Subject: [PATCH 02/19] working environment, update config, and git --- .gitignore | 1 + config/test/config.electricity.yaml | 16 +- envs/win-64.lock.yaml | 489 ++++++++++++++++++++++++++++ rules/build_electricity.smk | 4 +- 4 files changed, 500 insertions(+), 10 deletions(-) create mode 100644 envs/win-64.lock.yaml diff --git a/.gitignore b/.gitignore index 27a2fb1827..e33765f02e 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,7 @@ doc/_build /scripts/old /scripts/create_scenarios.py /config/create_scenarios.py +# /envs/win-64.lock.yaml config/config.yaml config/scenarios.yaml diff --git a/config/test/config.electricity.yaml b/config/test/config.electricity.yaml index cc6aad1697..ecc4c3fd2a 100644 --- a/config/test/config.electricity.yaml +++ b/config/test/config.electricity.yaml @@ -2,7 +2,7 @@ # # SPDX-License-Identifier: CC0-1.0 -# snakemake -call results/test/networks/base_s__elec_lv1.25_Co2L-1h.nc --configfile config/test/config.electricity.yaml +# snakemake -call results/test/networks/base_s_38_elec_lv1.25_Co2L-24h.nc --configfile config/test/config.electricity.yaml tutorial: false @@ -18,15 +18,15 @@ scenario: ll: - lv1.25 clusters: - - 130 + - 38 opts: - - Co2L-1h + - Co2L-24h countries: ['AL', 'AT', 'BA', 'BE', 'BG', 'CH', 'CZ', 'DE', 'DK', 'EE', 'ES', 'FI', 'FR', 'GB', 'GR', 'HR', 'HU', 'IE', 'IT', 'LT', 'LU', 'LV', 'ME', 'MK', 'NL', 'NO', 'PL', 'PT', 'RO', 'RS', 'SE', 'SI', 'SK'] snapshots: start: "2020-01-01" - end: "2021-01-01" + end: "2020-01-08" enable: retrieve: auto @@ -70,7 +70,7 @@ electricity: H2: 168 extendable_carriers: - Generator: [solar, solar-hsat, offsolar, onwind, offwind-ac, offwind-fl, wave-farshore, wave-nearshore, wave-shallow ] # [solar, solar-hsat, offsolar, onwind, offwind-ac, offwind-fl, wave-farshore, wave-nearshore, wave-shallow ] + Generator: [solar, solar-hsat, offsolar, onwind, offwind-ac, offwind-float, wave-farshore, wave-nearshore, wave-shallow ] # [solar, solar-hsat, offsolar, onwind, offwind-ac, offwind-fl, wave-farshore, wave-nearshore, wave-shallow ] StorageUnit: [battery, H2] # battery, H2 Store: [] Link: [H2 pipeline] # H2 pipeline @@ -80,15 +80,15 @@ electricity: everywhere_powerplants: [] conventional_carriers: [] - renewable_carriers: [solar, solar-hsat, offsolar, onwind, offwind-ac, offwind-fl, wave-farshore, wave-nearshore, wave-shallow, hydro ] # [solar, solar-hsat, offsolar, onwind, offwind-ac, offwind-fl, wave-farshore, wave-nearshore, wave-shallow ] + renewable_carriers: [solar, solar-hsat, offsolar, onwind, offwind-ac, offwind-float, wave-farshore, wave-nearshore, wave-shallow] # [solar, solar-hsat, offsolar, onwind, offwind-ac, offwind-fl, wave-farshore, wave-nearshore, wave-shallow ] estimate_renewable_capacities: enable: true from_opsd: true year: 2022 expansion_limit: false - # technology_mapping: - # Offshore: [offsolar, offwind-ac, offwind-fl, wave-farshore, wave-nearshore, wave-shallow] + technology_mapping: + Offshore: [offsolar, offwind-ac, offwind-float, wave-farshore, wave-nearshore, wave-shallow] # Onshore: [onwind] # PV: [solar, solar-hsat] diff --git a/envs/win-64.lock.yaml b/envs/win-64.lock.yaml new file mode 100644 index 0000000000..db658fc5ea --- /dev/null +++ b/envs/win-64.lock.yaml @@ -0,0 +1,489 @@ +# Generated by conda-lock. +# platform: win-64 +# input_hash: 3bba739599ee4f2adc04058d1dfe3988478cee49ab6365df06e7134365693e8a + +channels: + - conda-forge + - bioconda +name: pypsa-eur +dependencies: + - _openmp_mutex=4.5=2_gnu + - _python_abi3_support=1.0=hd8ed1ab_2 + - affine=2.4.0=pyhd8ed1ab_1 + - aiohappyeyeballs=2.6.1=pyhd8ed1ab_0 + - aiohttp=3.13.0=py312h30f5c63_0 + - aiosignal=1.4.0=pyhd8ed1ab_0 + - ampl-asl=1.0.0=he0c23c2_2 + - amply=0.1.6=pyhd8ed1ab_1 + - anyio=4.11.0=pyhcf101f3_0 + - appdirs=1.4.4=pyhd8ed1ab_1 + - argon2-cffi=25.1.0=pyhd8ed1ab_0 + - argon2-cffi-bindings=25.1.0=py312he06e257_1 + - argparse-dataclass=2.0.0=pyhd8ed1ab_0 + - arrow=1.4.0=pyhcf101f3_0 + - astroid=4.0.1=py312h2e8e312_0 + - asttokens=2.4.1=pyhd8ed1ab_0 + - async-lru=2.0.5=pyh29332c3_0 + - atlite=0.4.1=pyhd8ed1ab_1 + - attrs=25.4.0=pyh71513ae_0 + - aws-c-auth=0.9.1=hc6331ae_3 + - aws-c-cal=0.9.2=hef2a5b8_1 + - aws-c-common=0.12.4=hfd05255_0 + - aws-c-compression=0.3.1=ha8a2810_6 + - aws-c-event-stream=0.5.6=h9e52e59_3 + - aws-c-http=0.10.4=hffefcd8_3 + - aws-c-io=0.22.0=h20b9e97_1 + - aws-c-mqtt=0.13.3=he28f3f4_6 + - aws-c-s3=0.8.6=ha4cb493_5 + - aws-c-sdkutils=0.2.4=ha8a2810_1 + - aws-checksums=0.2.7=ha8a2810_2 + - aws-crt-cpp=0.34.4=hc2cf59f_0 + - aws-sdk-cpp=1.11.606=h296c955_4 + - babel=2.17.0=pyhd8ed1ab_0 + - beautifulsoup4=4.14.2=pyha770c72_0 + - bleach=6.2.0=pyh29332c3_4 + - bleach-with-css=6.2.0=h82add2a_4 + - blinker=1.9.0=pyhff2d567_0 + - blosc=1.21.6=hfd34d9b_1 + - bokeh=3.8.0=pyhd8ed1ab_0 + - bottleneck=1.6.0=py312h196c9fc_1 + - branca=0.8.2=pyhd8ed1ab_0 + - brotli=1.1.0=hfd05255_4 + - brotli-bin=1.1.0=hfd05255_4 + - brotli-python=1.1.0=py312hbb81ca0_4 + - bzip2=1.0.8=h0ad9c76_8 + - c-ares=1.34.5=h2466b09_0 + - c-blosc2=2.21.3=h3cf07e4_0 + - ca-certificates=2025.10.5=h4c7d964_0 + - cached-property=1.5.2=hd8ed1ab_1 + - cached_property=1.5.2=pyha770c72_1 + - cachetools=6.2.1=pyhd8ed1ab_0 + - cairo=1.18.4=h5782bbf_0 + - cartopy=0.25.0=py312hc128f0a_1 + - cdsapi=0.7.7=pyhd8ed1ab_0 + - certifi=2025.10.5=pyhd8ed1ab_0 + - cffi=2.0.0=py312he06e257_0 + - cfgrib=0.9.15.1=pyhd8ed1ab_0 + - cfgv=3.3.1=pyhd8ed1ab_1 + - cftime=1.6.4=py312h196c9fc_2 + - charset-normalizer=3.4.4=pyhd8ed1ab_0 + - click=8.3.0=pyh7428d3b_0 + - click-plugins=1.1.1.2=pyhd8ed1ab_0 + - cligj=0.7.2=pyhd8ed1ab_2 + - cloudpickle=3.1.1=pyhd8ed1ab_0 + - coin-or-cbc=2.10.12=hd3ed8bd_4 + - coin-or-cgl=0.60.9=hacf86d0_6 + - coin-or-clp=1.17.10=h626fd10_3 + - coin-or-osi=0.108.11=h5b68f48_7 + - coin-or-utils=2.11.12=hdb10741_6 + - colorama=0.4.6=pyhd8ed1ab_1 + - comm=0.2.3=pyhe01879c_0 + - conda-inject=1.3.2=pyhd8ed1ab_0 + - configargparse=1.7.1=pyhe01879c_0 + - connection_pool=0.0.3=pyhd3deb0d_0 + - contourpy=1.3.3=py312hf90b1b7_2 + - country_converter=1.3.1=pyhd8ed1ab_0 + - cppad=20250000.2=he0c23c2_0 + - cpython=3.12.12=py312hd8ed1ab_0 + - cryptography=46.0.3=py312h84d000f_0 + - cycler=0.12.1=pyhd8ed1ab_1 + - cytoolz=1.1.0=py312he06e257_1 + - dask=2025.10.0=pyhcf101f3_0 + - dask-core=2025.10.0=pyhcf101f3_0 + - debugpy=1.8.17=py312ha1a9051_0 + - decorator=5.2.1=pyhd8ed1ab_0 + - defusedxml=0.7.1=pyhd8ed1ab_0 + - deprecation=2.1.0=pyh9f0ad1d_0 + - descartes=1.1.0=pyhd8ed1ab_5 + - dill=0.4.0=pyhd8ed1ab_0 + - distlib=0.4.0=pyhd8ed1ab_0 + - distributed=2025.10.0=pyhcf101f3_0 + - docutils=0.22.2=pyhd8ed1ab_0 + - double-conversion=3.3.1=he0c23c2_0 + - dpath=2.2.0=pyha770c72_0 + - eccodes=2.42.0=hb4e25be_0 + - ecmwf-datastores-client=0.4.0=pyhd8ed1ab_0 + - entsoe-py=0.7.6=pyhd8ed1ab_0 + - et_xmlfile=2.0.0=pyhd8ed1ab_1 + - exceptiongroup=1.3.0=pyhd8ed1ab_0 + - executing=2.2.1=pyhd8ed1ab_0 + - filelock=3.20.0=pyhd8ed1ab_0 + - findlibs=0.1.2=pyhd8ed1ab_0 + - fiona=1.10.1=py312h6e88f47_3 + - folium=0.20.0=pyhd8ed1ab_0 + - font-ttf-dejavu-sans-mono=2.37=hab24e00_0 + - font-ttf-inconsolata=3.000=h77eed37_0 + - font-ttf-source-code-pro=2.038=h77eed37_0 + - font-ttf-ubuntu=0.83=h77eed37_3 + - fontconfig=2.15.0=h765892d_1 + - fonts-conda-ecosystem=1=0 + - fonts-conda-forge=1=0 + - fonttools=4.60.1=py312h05f76fc_0 + - fqdn=1.5.1=pyhd8ed1ab_1 + - freeglut=3.2.2=he0c23c2_3 + - freetype=2.14.1=h57928b3_0 + - freexl=2.0.0=hf297d47_2 + - fribidi=1.0.16=hfd05255_0 + - frozenlist=1.7.0=py312hfdf67e6_0 + - fsspec=2025.9.0=pyhd8ed1ab_0 + - geographiclib=2.1=pyhd8ed1ab_0 + - geojson=3.2.0=pyhd8ed1ab_0 + - geopandas=1.1.1=pyhd8ed1ab_1 + - geopandas-base=1.1.1=pyha770c72_1 + - geopy=2.4.1=pyhd8ed1ab_2 + - geos=3.13.1=h9ea8674_0 + - geotiff=1.7.4=h86c3423_2 + - getopt-win32=0.1=h6a83c73_3 + - gitdb=4.0.12=pyhd8ed1ab_0 + - gitpython=3.1.45=pyhff2d567_0 + - glpk=5.0=h8ffe710_0 + - gmp=6.3.0=hfeafd45_2 + - google-api-core=2.25.2=pyhd8ed1ab_0 + - google-auth=2.41.1=pyhd8ed1ab_0 + - google-cloud-core=2.4.3=pyhd8ed1ab_0 + - google-cloud-storage=3.4.1=pyhd8ed1ab_0 + - google-crc32c=1.7.1=py312h3d708b0_1 + - google-resumable-media=2.7.2=pyhd8ed1ab_2 + - googleapis-common-protos=1.70.0=pyhd8ed1ab_0 + - graphite2=1.3.14=hac47afa_2 + - graphviz=13.1.2=ha5e8f4b_0 + - grpcio=1.73.1=py312h9256aa6_0 + - gts=0.7.6=h6b5321d_4 + - h11=0.16.0=pyhd8ed1ab_0 + - h2=4.3.0=pyhcf101f3_0 + - h5netcdf=1.7.2=pyhd8ed1ab_0 + - h5py=3.15.1=nompi_py312h03cd2ba_100 + - harfbuzz=11.4.5=h5f2951f_0 + - hdf4=4.2.15=h5557f11_7 + - hdf5=1.14.6=nompi_he30205f_103 + - highspy=1.11.0=np2py312ha76dc74_1 + - hpack=4.1.0=pyhd8ed1ab_0 + - httpcore=1.0.9=pyh29332c3_0 + - httpx=0.28.1=pyhd8ed1ab_0 + - humanfriendly=10.0=pyh7428d3b_8 + - hyperframe=6.1.0=pyhd8ed1ab_0 + - icu=75.1=he0c23c2_0 + - identify=2.6.15=pyhd8ed1ab_0 + - idna=3.11=pyhd8ed1ab_0 + - immutables=0.21=py312he06e257_2 + - importlib-metadata=8.7.0=pyhe01879c_1 + - iniconfig=2.3.0=pyhd8ed1ab_0 + - ipopt=3.14.19=h75e447d_1 + - ipykernel=7.0.1=pyh6dadd2b_0 + - ipython=9.6.0=pyh6be1c34_0 + - ipython_pygments_lexers=1.1.1=pyhd8ed1ab_0 + - ipywidgets=8.1.7=pyhd8ed1ab_0 + - isoduration=20.11.0=pyhd8ed1ab_1 + - isort=7.0.0=pyhd8ed1ab_0 + - jasper=4.2.8=h8ad263b_0 + - jedi=0.19.2=pyhd8ed1ab_1 + - jinja2=3.1.6=pyhd8ed1ab_0 + - joblib=1.5.2=pyhd8ed1ab_0 + - jpype1=1.6.0=py312hf90b1b7_1 + - json5=0.12.1=pyhd8ed1ab_0 + - jsonpointer=3.0.0=py312h2e8e312_2 + - jsonschema=4.25.1=pyhe01879c_0 + - jsonschema-specifications=2025.9.1=pyhcf101f3_0 + - jsonschema-with-format-nongpl=4.25.1=he01879c_0 + - jupyter=1.1.1=pyhd8ed1ab_1 + - jupyter-lsp=2.3.0=pyhcf101f3_0 + - jupyter_client=8.6.3=pyhd8ed1ab_1 + - jupyter_console=6.6.3=pyhd8ed1ab_1 + - jupyter_core=5.9.1=pyh6dadd2b_0 + - jupyter_events=0.12.0=pyh29332c3_0 + - jupyter_server=2.17.0=pyhcf101f3_0 + - jupyter_server_terminals=0.5.3=pyhd8ed1ab_1 + - jupyterlab=4.4.9=pyhd8ed1ab_0 + - jupyterlab_pygments=0.3.0=pyhd8ed1ab_2 + - jupyterlab_server=2.27.3=pyhd8ed1ab_1 + - jupyterlab_widgets=3.0.15=pyhd8ed1ab_0 + - kiwisolver=1.4.9=py312h78d62e6_1 + - krb5=1.21.3=hdf4eb48_0 + - lark=1.3.0=pyhd8ed1ab_0 + - lcms2=2.17=hbcf6048_0 + - lerc=4.0.0=h6470a55_1 + - libabseil=20250512.1=cxx17_habfad5f_0 + - libaec=1.1.4=h20038f6_0 + - libarchive=3.8.1=gpl_h1ca5a36_100 + - libarrow=21.0.0=h2031902_8_cpu + - libarrow-acero=21.0.0=h7d8d6a5_8_cpu + - libarrow-compute=21.0.0=h2db994a_8_cpu + - libarrow-dataset=21.0.0=h7d8d6a5_8_cpu + - libarrow-substrait=21.0.0=hf865cc0_8_cpu + - libblas=3.9.0=35_h5709861_mkl + - libboost=1.88.0=h9dfe17d_5 + - libbrotlicommon=1.1.0=hfd05255_4 + - libbrotlidec=1.1.0=hfd05255_4 + - libbrotlienc=1.1.0=hfd05255_4 + - libcblas=3.9.0=35_h2a3cdd5_mkl + - libclang13=21.1.3=default_ha2db4b5_0 + - libcrc32c=1.1.2=h0e60522_0 + - libcurl=8.14.1=h88aaa65_0 + - libdeflate=1.24=h76ddb4d_0 + - libevent=2.1.12=h3671451_1 + - libexpat=2.7.1=hac47afa_0 + - libffi=3.4.6=h537db12_1 + - libfreetype=2.14.1=h57928b3_0 + - libfreetype6=2.14.1=hdbac1cb_0 + - libgcc=15.2.0=h1383e82_7 + - libgd=2.3.3=h7208af6_11 + - libgdal-core=3.10.3=h228a343_13 + - libgdal-hdf4=3.10.3=ha47b6c4_13 + - libgdal-hdf5=3.10.3=h0f01001_13 + - libgdal-netcdf=3.10.3=hcb0e93c_13 + - libglib=2.84.3=h1c1036b_0 + - libgomp=15.2.0=h1383e82_7 + - libgoogle-cloud=2.39.0=h19ee442_0 + - libgoogle-cloud-storage=2.39.0=he04ea4c_0 + - libgrpc=1.73.1=h04afb49_0 + - libhwloc=2.12.1=default_h88281d1_1000 + - libiconv=1.18=hc1393d2_2 + - libintl=0.22.5=h5728263_3 + - libjpeg-turbo=3.1.0=h2466b09_0 + - libkml=1.3.0=h538826c_1021 + - liblapack=3.9.0=35_hf9ab0e9_mkl + - liblzma=5.8.1=h2466b09_2 + - libnetcdf=4.9.2=nompi_ha45073a_118 + - libparquet=21.0.0=h24c48c9_8_cpu + - libpng=1.6.50=h7351971_1 + - libprotobuf=6.31.1=hdcda5b4_2 + - libre2-11=2025.08.12=h0eb2380_1 + - librttopo=1.1.0=hbfc9ebc_18 + - libsodium=1.0.20=hc70643c_0 + - libspatialite=5.1.0=h378fb81_14 + - libsqlite=3.50.4=hf5d6505_0 + - libssh2=1.11.1=h9aa295b_0 + - libthrift=0.22.0=h23985f6_1 + - libtiff=4.7.1=h550210a_0 + - libutf8proc=2.11.0=h0b34c2f_0 + - libwebp-base=1.6.0=h4d5522a_0 + - libwinpthread=12.0.0.r4.gg4f2fc60ca=h57928b3_10 + - libxcb=1.17.0=h0e4246c_0 + - libxml2=2.13.8=h741aa76_1 + - libxslt=1.1.43=h25c3957_0 + - libzip=1.11.2=h3135430_0 + - libzlib=1.3.1=h2466b09_2 + - linopy=0.5.7=pyhd8ed1ab_0 + - llvm-openmp=21.1.3=hfa2b4ca_0 + - locket=1.0.0=pyhd8ed1ab_0 + - lxml=6.0.2=py312hc85b015_0 + - lz4=4.4.4=py312ha1aa51a_1 + - lz4-c=1.10.0=h2466b09_1 + - lzo=2.10=h6a83c73_1002 + - mapclassify=2.10.0=pyhd8ed1ab_1 + - markupsafe=3.0.3=py312h05f76fc_0 + - matplotlib=3.10.7=py312h2e8e312_0 + - matplotlib-base=3.10.7=py312h0ebf65c_0 + - matplotlib-inline=0.1.7=pyhd8ed1ab_1 + - mccabe=0.7.0=pyhd8ed1ab_1 + - memory_profiler=0.61.0=pyhd8ed1ab_1 + - minizip=4.0.10=h9fa1bad_0 + - mistune=3.1.4=pyhcf101f3_0 + - mkl=2024.2.2=h57928b3_16 + - mpfr=4.2.1=hbc20e70_3 + - msgpack-python=1.1.2=py312hf90b1b7_0 + - multidict=6.6.3=py312h05f76fc_0 + - multiurl=0.3.7=pyhd8ed1ab_0 + - mumps-seq=5.8.1=hd297af6_4 + - munkres=1.1.4=pyhd8ed1ab_1 + - narwhals=2.8.0=pyhcf101f3_0 + - nbclient=0.10.2=pyhd8ed1ab_0 + - nbconvert-core=7.16.6=pyhcf101f3_1 + - nbformat=5.10.4=pyhd8ed1ab_1 + - nest-asyncio=1.6.0=pyhd8ed1ab_1 + - netcdf4=1.7.2=nompi_py312h46ede7c_103 + - networkx=3.5=pyhe01879c_0 + - nodeenv=1.9.1=pyhd8ed1ab_1 + - notebook=7.4.7=pyhd8ed1ab_0 + - notebook-shim=0.2.4=pyhd8ed1ab_1 + - numexpr=2.14.1=mkl_py312hd035341_0 + - numpy=1.26.4=py312h8753938_0 + - oauthlib=3.3.1=pyhd8ed1ab_0 + - openjpeg=2.5.4=h24db6dd_0 + - openpyxl=3.1.5=py312h83acffa_2 + - openssl=3.5.4=h725018a_0 + - orc=2.2.1=h7414dfc_0 + - overrides=7.7.0=pyhd8ed1ab_1 + - packaging=25.0=pyh29332c3_1 + - pandas=2.3.3=py312hc128f0a_1 + - pandocfilters=1.5.0=pyhd8ed1ab_0 + - pango=1.56.4=h03d888a_0 + - parso=0.8.5=pyhcf101f3_0 + - partd=1.4.2=pyhd8ed1ab_0 + - patsy=1.0.1=pyhd8ed1ab_1 + - pcre2=10.45=h99c9b8b_0 + - pickleshare=0.7.5=pyhd8ed1ab_1004 + - pillow=11.3.0=py312h5ee8bfe_3 + - pip=25.2=pyh8b19718_0 + - pixman=0.46.4=h5112557_1 + - plac=1.4.5=pyhd8ed1ab_0 + - platformdirs=4.5.0=pyhcf101f3_0 + - plotly=6.3.1=pyhd8ed1ab_0 + - pluggy=1.6.0=pyhd8ed1ab_0 + - polars=1.34.0=pyh6a1acc5_0 + - polars-runtime-32=1.34.0=py310hca7251b_0 + - powerplantmatching=0.7.1=pyhd8ed1ab_0 + - pre-commit=4.3.0=pyha770c72_0 + - progressbar2=4.5.0=pyhd8ed1ab_1 + - proj=9.6.2=h7990399_2 + - prometheus_client=0.23.1=pyhd8ed1ab_0 + - prompt-toolkit=3.0.52=pyha770c72_0 + - prompt_toolkit=3.0.52=hd8ed1ab_0 + - propcache=0.3.1=py312h31fea79_0 + - proto-plus=1.26.1=pyhd8ed1ab_0 + - protobuf=6.31.1=py312hcb3287e_2 + - psutil=7.1.1=py312he06e257_0 + - pthread-stubs=0.4=h0e40799_1002 + - pulp=2.8.0=py312he39998a_3 + - pure_eval=0.2.3=pyhd8ed1ab_1 + - py-cpuinfo=9.0.0=pyhd8ed1ab_1 + - pyarrow=21.0.0=py312h2e8e312_1 + - pyarrow-core=21.0.0=py312h85419b5_1_cpu + - pyasn1=0.6.1=pyhd8ed1ab_2 + - pyasn1-modules=0.4.2=pyhd8ed1ab_0 + - pycountry=24.6.1=pyhd8ed1ab_0 + - pycparser=2.22=pyh29332c3_1 + - pygments=2.19.2=pyhd8ed1ab_0 + - pyjwt=2.10.1=pyhd8ed1ab_0 + - pylint=4.0.1=pyhcf101f3_0 + - pyogrio=0.11.0=py312h6e88f47_0 + - pyopenssl=25.3.0=pyhd8ed1ab_0 + - pyparsing=3.2.5=pyhcf101f3_0 + - pyproj=3.7.2=py312h235ce7f_1 + - pypsa=0.35.2=pyhd8ed1ab_0 + - pyreadline3=3.5.4=py312h2e8e312_2 + - pyscipopt=5.6.0=py312hbb81ca0_1 + - pyshp=3.0.2=pyhd8ed1ab_0 + - pyside6=6.9.2=py312h0ba07f7_1 + - pysocks=1.7.1=pyh09c184e_7 + - pytables=3.10.2=py312h20cef2e_9 + - pytest=8.4.2=pyhd8ed1ab_0 + - python=3.12.12=h30ce641_0_cpython + - python-dateutil=2.9.0.post0=pyhe01879c_2 + - python-eccodes=2.42.0=py312h196c9fc_0 + - python-fastjsonschema=2.21.2=pyhe01879c_0 + - python-gil=3.12.12=hd8ed1ab_0 + - python-json-logger=2.0.7=pyhd8ed1ab_0 + - python-tzdata=2025.2=pyhd8ed1ab_0 + - python-utils=3.9.1=pyhff2d567_1 + - python_abi=3.12=8_cp312 + - pytz=2025.2=pyhd8ed1ab_0 + - pyu2f=0.1.5=pyhd8ed1ab_1 + - pywin32=311=py312h829343e_1 + - pywinpty=2.0.15=py312h275cf98_1 + - pyxlsb=1.0.10=pyhd8ed1ab_0 + - pyyaml=6.0.3=py312h05f76fc_0 + - pyzmq=27.1.0=py312hbb5da91_0 + - qhull=2020.2=hc790b64_5 + - qt6-main=6.9.2=h236c7cd_0 + - rasterio=1.4.3=py312h9aeec68_2 + - re2=2025.08.12=ha104f34_1 + - referencing=0.37.0=pyhcf101f3_0 + - requests=2.32.5=pyhd8ed1ab_0 + - requests-oauthlib=1.4.0=pyhd8ed1ab_0 + - reretry=0.11.8=pyhd8ed1ab_1 + - rfc3339-validator=0.1.4=pyhd8ed1ab_1 + - rfc3986-validator=0.1.1=pyh9f0ad1d_0 + - rfc3987-syntax=1.1.0=pyhe01879c_1 + - rioxarray=0.19.0=pyhd8ed1ab_0 + - rpds-py=0.27.1=py312hdabe01f_1 + - rsa=4.9.1=pyhd8ed1ab_0 + - ruff=0.14.1=h3e3edff_0 + - scikit-learn=1.7.2=py312h91ac024_0 + - scip=9.2.3=h89aff08_2 + - scipy=1.16.2=py312h33376e8_0 + - seaborn=0.13.2=hd8ed1ab_3 + - seaborn-base=0.13.2=pyhd8ed1ab_3 + - send2trash=1.8.3=pyh5737063_1 + - setuptools=80.9.0=pyhff2d567_0 + - shapely=2.0.7=py312h3f81574_1 + - six=1.17.0=pyhe01879c_1 + - smart_open=7.3.1=pyhcf101f3_0 + - smmap=5.0.2=pyhd8ed1ab_0 + - snakemake-executor-plugin-cluster-generic=1.0.9=pyhdfd78af_0 + - snakemake-executor-plugin-slurm=1.8.0=pyhdfd78af_0 + - snakemake-executor-plugin-slurm-jobstep=0.3.0=pyhdfd78af_0 + - snakemake-interface-common=1.22.0=pyhd4c3c12_0 + - snakemake-interface-executor-plugins=9.3.9=pyhdfd78af_0 + - snakemake-interface-logger-plugins=1.2.4=pyhdfd78af_0 + - snakemake-interface-report-plugins=1.2.0=pyhdfd78af_0 + - snakemake-interface-scheduler-plugins=2.0.1=pyhd4c3c12_0 + - snakemake-interface-storage-plugins=4.2.3=pyhd4c3c12_0 + - snakemake-minimal=9.13.3=pyhdfd78af_0 + - snakemake-storage-plugin-http=0.3.0=pyhdfd78af_0 + - snappy=1.2.2=h7fa0ca8_0 + - sniffio=1.3.1=pyhd8ed1ab_1 + - snuggs=1.4.7=pyhd8ed1ab_2 + - sortedcontainers=2.4.0=pyhd8ed1ab_1 + - soupsieve=2.8=pyhd8ed1ab_0 + - sqlite=3.50.4=hdb435a2_0 + - stack_data=0.6.3=pyhd8ed1ab_1 + - statsmodels=0.14.5=py312h196c9fc_1 + - tabulate=0.9.0=pyhd8ed1ab_2 + - tbb=2021.13.0=h18a62a1_3 + - tblib=3.1.0=pyhd8ed1ab_0 + - tenacity=9.1.2=pyhd8ed1ab_0 + - terminado=0.18.1=pyh5737063_0 + - threadpoolctl=3.6.0=pyhecae5ae_0 + - throttler=1.2.2=pyhd8ed1ab_0 + - tinycss2=1.4.0=pyhd8ed1ab_0 + - tk=8.6.13=h2c6b04d_2 + - tomli=2.3.0=pyhcf101f3_0 + - tomlkit=0.13.3=pyha770c72_0 + - toolz=1.1.0=pyhd8ed1ab_1 + - tornado=6.5.2=py312he06e257_1 + - tqdm=4.67.1=pyhd8ed1ab_1 + - traitlets=5.14.3=pyhd8ed1ab_1 + - typing-extensions=4.15.0=h396c80c_0 + - typing_extensions=4.15.0=pyhcf101f3_0 + - typing_utils=0.1.0=pyhd8ed1ab_1 + - tzdata=2025b=h78e105d_0 + - ucrt=10.0.26100.0=h57928b3_0 + - ukkonen=1.0.1=py312hd5eb7cc_5 + - unicodedata2=16.0.0=py312he06e257_1 + - unidecode=1.3.8=pyh29332c3_1 + - uri-template=1.3.0=pyhd8ed1ab_1 + - uriparser=0.9.8=h5a68840_0 + - urllib3=2.5.0=pyhd8ed1ab_0 + - validators=0.35.0=pyhd8ed1ab_0 + - vc=14.3=h2b53caa_32 + - vc14_runtime=14.44.35208=h818238b_32 + - vcomp14=14.44.35208=h818238b_32 + - virtualenv=20.35.3=pyhd8ed1ab_0 + - vs2015_runtime=14.44.35208=h38c0c73_32 + - wcwidth=0.2.14=pyhd8ed1ab_0 + - webcolors=24.11.1=pyhd8ed1ab_0 + - webencodings=0.5.1=pyhd8ed1ab_3 + - websocket-client=1.9.0=pyhd8ed1ab_0 + - wheel=0.45.1=pyhd8ed1ab_1 + - widgetsnbextension=4.0.14=pyhd8ed1ab_0 + - win_inet_pton=1.1.0=pyh7428d3b_8 + - winpty=0.4.3=4 + - wrapt=1.17.3=py312he06e257_1 + - xarray=2025.6.1=pyhd8ed1ab_1 + - xerces-c=3.2.5=he0c23c2_2 + - xlrd=2.0.2=pyhd8ed1ab_0 + - xorg-libice=1.1.2=h0e40799_0 + - xorg-libsm=1.2.6=h0e40799_0 + - xorg-libx11=1.8.12=hf48077a_0 + - xorg-libxau=1.0.12=h0e40799_0 + - xorg-libxdmcp=1.1.5=h0e40799_0 + - xorg-libxext=1.3.6=h0e40799_0 + - xorg-libxpm=3.5.17=h0e40799_1 + - xorg-libxt=1.3.1=h0e40799_0 + - xyzservices=2025.4.0=pyhd8ed1ab_0 + - yaml=0.2.5=h6a83c73_3 + - yarl=1.20.1=py312h31fea79_0 + - yte=1.8.1=pyha770c72_0 + - zeromq=4.3.5=h5bddc39_9 + - zict=3.0.0=pyhd8ed1ab_1 + - zipp=3.23.0=pyhd8ed1ab_0 + - zlib=1.3.1=h2466b09_2 + - zlib-ng=2.2.5=h1608b31_0 + - zstandard=0.25.0=py312he5662c2_0 + - zstd=1.5.7=hbeecb71_2 + - pip: + - gurobipy == 12.0.3 --hash=sha256:af18fd03d5dc3f6e5f590c372ad288b8430a6d88a5b5e66cfcd8432f86ee8650 + - ply == 3.11 --hash=sha256:096f9b8350b65ebd2fd1346b12452efe5b9607f7482813ffca50c22722a807ce + - pyomo == 6.9.5 --hash=sha256:60326f7d3143ee7d0f5c5c4a3cbf871b53e08cc6c2b0c9e6d25568880233472f + - tsam == 2.3.9 --hash=sha256:edcc4febb9e1dacc028bc819d710974ede8f563467c3d235a250f46416f93a1b diff --git a/rules/build_electricity.smk b/rules/build_electricity.smk index 5605e76d8e..40b9d8ac22 100755 --- a/rules/build_electricity.smk +++ b/rules/build_electricity.smk @@ -178,7 +178,7 @@ rule determine_availability_matrix_MD_UA: wdpa="data/WDPA.gpkg", wdpa_marine="data/WDPA_WDOECM_marine.gpkg", gebco=lambda w: ( - "data/bundle/gebco/GEBCO_2014_2D.nc" + "data/bundle/gebco/GEBCO_2023_2D.nc" if config_provider("renewable", w.technology)(w).get("max_depth") else [] ), @@ -243,7 +243,7 @@ rule determine_availability_matrix: ), gebco=ancient( lambda w: ( - "data/bundle/gebco/GEBCO_2014_2D.nc" + "data/bundle/gebco/GEBCO_2023_2D.nc" if ( config_provider("renewable", w.technology)(w).get("max_depth") or config_provider("renewable", w.technology)(w).get("min_depth") From ec35396da8745db64e8a319ed8dc93d323442d90 Mon Sep 17 00:00:00 2001 From: lmezilis Date: Wed, 22 Oct 2025 16:43:07 +0200 Subject: [PATCH 03/19] name and color corrections --- config/test/config.electricity.yaml | 275 ++++------------------------ 1 file changed, 37 insertions(+), 238 deletions(-) diff --git a/config/test/config.electricity.yaml b/config/test/config.electricity.yaml index ecc4c3fd2a..6048789b41 100644 --- a/config/test/config.electricity.yaml +++ b/config/test/config.electricity.yaml @@ -458,207 +458,69 @@ plotting: tech_colors: # wind - onwind: "#235ebc" - # onshore wind: "#235ebc" - # offwind: "#6895dd" - # offshore wind: "#6895dd" - offwind-ac: "#6895dd" - # offshore wind (AC): "#6895dd" - # offshore wind ac: "#6895dd" - offwind-dc: "#74c6f2" - # offshore wind (DC): "#74c6f2" - # offshore wind dc: "#74c6f2" - offwind-float: "#b5e2fa" - # offshore wind (Float): "#b5e2fa" - # offshore wind float: "#b5e2fa" + onwind: "#2D61A6" + onshore wind: "#2D61A6" + offwind: "#6895dd" + offshore wind: "#29A2CC" + offwind-ac: "#29A2CC" + offshore wind (AC): "#29A2CC" + offshore wind ac: "#29A2CC" + offwind-dc: "#29A2CC" + offshore wind (DC): "#29A2CC" + offshore wind dc: "#29A2CC" + offwind-float: "#52DAE5" + offshore wind (Float): "#52DAE5" + offshore wind float: "#52DAE5" # water - hydro: '#298c81' - hydro reservoir: '#298c81' - ror: '#3dbfb0' - run of river: '#3dbfb0' + hydro: '#FC6D8E' + hydro reservoir: '#FC6D8E' + ror: '#F2BDE4' + run of river: '#F2BDE4' hydroelectricity: '#298c81' - PHS: '#51dbcc' + PHS: '#CF1F33' hydro+PHS: "#08ad97" # solar - solar: "#f9d002" + solar: "#F2CE1B" solar PV: "#f9d002" - solar-hsat: "#fdb915" + solar-hsat: "#F2A30F" solar thermal: '#ffbf2b' + offsolar: '#F2620F' residential rural solar thermal: '#f1c069' services rural solar thermal: '#eabf61' residential urban decentral solar thermal: '#e5bc5a' services urban decentral solar thermal: '#dfb953' urban central solar thermal: '#d7b24c' solar rooftop: '#ffea80' - # gas - OCGT: '#e0986c' - OCGT marginal: '#e0986c' - OCGT-heat: '#e0986c' - gas boiler: '#db6a25' - gas boilers: '#db6a25' - gas boiler marginal: '#db6a25' - residential rural gas boiler: '#d4722e' - residential urban decentral gas boiler: '#cb7a36' - services rural gas boiler: '#c4813f' - services urban decentral gas boiler: '#ba8947' - urban central gas boiler: '#b0904f' - gas: '#e05b09' - fossil gas: '#e05b09' - natural gas: '#e05b09' - biogas to gas: '#e36311' - biogas to gas CC: '#e51245' - CCGT: '#a85522' - CCGT marginal: '#a85522' - allam: '#B98F76' - gas for industry co2 to atmosphere: '#692e0a' - gas for industry co2 to stored: '#8a3400' - gas for industry: '#853403' - gas for industry CC: '#692e0a' - gas pipeline: '#ebbca0' - gas pipeline new: '#a87c62' - # oil - oil: '#c9c9c9' - oil primary: '#d2d2d2' - oil refining: '#e6e6e6' - imported oil: '#a3a3a3' - oil boiler: '#adadad' - residential rural oil boiler: '#a9a9a9' - services rural oil boiler: '#a5a5a5' - residential urban decentral oil boiler: '#a1a1a1' - urban central oil boiler: '#9d9d9d' - services urban decentral oil boiler: '#999999' - agriculture machinery oil: '#949494' - shipping oil: "#808080" - land transport oil: '#afafaf' - # nuclear - Nuclear: '#ff8c00' - Nuclear marginal: '#ff8c00' - nuclear: '#ff8c00' - uranium: '#ff8c00' - # coal - Coal: '#545454' - coal: '#545454' - Coal marginal: '#545454' - coal for industry: '#343434' - solid: '#545454' - Lignite: '#826837' - lignite: '#826837' - Lignite marginal: '#826837' - # biomass - biogas: '#e3d37d' - biomass: '#baa741' - solid biomass: '#baa741' - municipal solid waste: '#91ba41' - solid biomass import: '#d5ca8d' - solid biomass transport: '#baa741' - solid biomass for industry: '#7a6d26' - solid biomass for industry CC: '#47411c' - solid biomass for industry co2 from atmosphere: '#736412' - solid biomass for industry co2 to stored: '#47411c' - urban central solid biomass CHP: '#9d9042' - urban central solid biomass CHP CC: '#6c5d28' - biomass boiler: '#8A9A5B' - residential rural biomass boiler: '#a1a066' - residential urban decentral biomass boiler: '#b0b87b' - services rural biomass boiler: '#c6cf98' - services urban decentral biomass boiler: '#dde5b5' - biomass to liquid: '#32CD32' - unsustainable solid biomass: '#998622' - unsustainable bioliquids: '#32CD32' - electrobiofuels: 'red' - BioSNG: '#123456' - solid biomass to hydrogen: '#654321' + # wave + wave-farshore: '#4CE766' + wave-nearshore: '#24961A' + wave-shallow: '#216406' # power transmission lines: '#6c9459' transmission lines: '#6c9459' electricity distribution grid: '#97ad8c' low voltage: '#97ad8c' # electricity demand - Electric load: '#110d63' - electric demand: '#110d63' - electricity: '#110d63' + Electric load: '#B4A7B2' + electric demand: '#B4A7B2' + electricity: '#B4A7B2' industry electricity: '#2d2a66' industry new electricity: '#2d2a66' agriculture electricity: '#494778' # battery + EVs battery: '#ace37f' battery storage: '#ace37f' - battery charger: '#88a75b' - battery discharger: '#5d4e29' + battery charger: '#F2762E' + battery discharger: '#5B224E' home battery: '#80c944' home battery storage: '#80c944' - home battery charger: '#5e8032' - home battery discharger: '#3c5221' + home battery charger: '#F2762E' + home battery discharger: '#5B224E' BEV charger: '#baf238' V2G: '#e5ffa8' land transport EV: '#baf238' land transport demand: '#38baf2' EV battery: '#baf238' - # hot water storage - water tanks: '#e69487' - residential rural water tanks: '#f7b7a3' - services rural water tanks: '#f3afa3' - residential urban decentral water tanks: '#f2b2a3' - services urban decentral water tanks: '#f1b4a4' - urban central water tanks: '#e9977d' - hot water storage: '#e69487' - hot water charging: '#e8998b' - urban central water tanks charger: '#b57a67' - residential rural water tanks charger: '#b4887c' - residential urban decentral water tanks charger: '#b39995' - services rural water tanks charger: '#b3abb0' - services urban decentral water tanks charger: '#b3becc' - hot water discharging: '#e99c8e' - urban central water tanks discharger: '#b9816e' - residential rural water tanks discharger: '#ba9685' - residential urban decentral water tanks discharger: '#baac9e' - services rural water tanks discharger: '#bbc2b8' - services urban decentral water tanks discharger: '#bdd8d3' - # heat demand - Heat load: '#cc1f1f' - heat: '#cc1f1f' - heat vent: '#aa3344' - heat demand: '#cc1f1f' - rural heat: '#ff5c5c' - residential rural heat: '#ff7c7c' - services rural heat: '#ff9c9c' - central heat: '#cc1f1f' - urban central heat: '#d15959' - urban central heat vent: '#a74747' - decentral heat: '#750606' - residential urban decentral heat: '#a33c3c' - services urban decentral heat: '#cc1f1f' - low-temperature heat for industry: '#8f2727' - process heat: '#ff0000' - agriculture heat: '#d9a5a5' - # heat supply - heat pumps: '#2fb537' - heat pump: '#2fb537' - air heat pump: '#36eb41' - residential urban decentral air heat pump: '#48f74f' - services urban decentral air heat pump: '#5af95d' - services rural air heat pump: '#5af95d' - urban central air heat pump: '#6cfb6b' - ground heat pump: '#2fb537' - residential rural ground heat pump: '#48f74f' - residential rural air heat pump: '#48f74f' - services rural ground heat pump: '#5af95d' - Ambient: '#98eb9d' - CHP: '#8a5751' - urban central gas CHP: '#8d5e56' - CHP CC: '#634643' - urban central gas CHP CC: '#6e4e4c' - CHP heat: '#8a5751' - CHP electric: '#8a5751' - district heating: '#e8beac' - resistive heater: '#d8f9b8' - residential rural resistive heater: '#bef5b5' - residential urban decentral resistive heater: '#b2f1a9' - services rural resistive heater: '#a5ed9d' - services urban decentral resistive heater: '#98e991' - urban central resistive heater: '#8cdf85' - retrofitting: '#8487e8' - building retrofitting: '#8487e8' # hydrogen H2 for industry: "#f073da" H2 for shipping: "#ebaee0" @@ -674,74 +536,11 @@ plotting: land transport fuel cell: '#6b3161' H2 pipeline: '#f081dc' H2 pipeline retrofitted: '#ba99b5' - H2 Fuel Cell: '#c251ae' - H2 fuel cell: '#c251ae' + H2 Fuel Cell: '#D0D98B' + H2 fuel cell: '#D0D98B' H2 turbine: '#991f83' - H2 Electrolysis: '#ff29d9' - H2 electrolysis: '#ff29d9' - # ammonia - NH3: '#46caf0' - ammonia: '#46caf0' - ammonia store: '#00ace0' - ammonia cracker: '#87d0e6' - Haber-Bosch: '#076987' - # syngas - Sabatier: '#9850ad' - methanation: '#c44ce6' - methane: '#c44ce6' - # synfuels - Fischer-Tropsch: '#25c49a' - liquid: '#25c49a' - kerosene for aviation: '#a1ffe6' - naphtha for industry: '#57ebc4' - methanol-to-kerosene: '#C98468' - methanol-to-olefins/aromatics: '#FFA07A' - Methanol steam reforming: '#FFBF00' - Methanol steam reforming CC: '#A2EA8A' - methanolisation: '#00FFBF' - biomass-to-methanol: '#EAD28A' - biomass-to-methanol CC: '#EADBAD' - allam methanol: '#B98F76' - CCGT methanol: '#B98F76' - CCGT methanol CC: '#B98F76' - OCGT methanol: '#B98F76' - methanol: '#FF7B00' - methanol transport: '#FF7B00' - shipping methanol: '#468c8b' - industry methanol: '#468c8b' - # co2 - CC: '#f29dae' - CCS: '#f29dae' - CO2 sequestration: '#f29dae' - DAC: '#ff5270' - co2 stored: '#f2385a' - co2 sequestered: '#f2682f' - co2: '#f29dae' - co2 vent: '#ffd4dc' - CO2 pipeline: '#f5627f' - # emissions - process emissions CC: '#000000' - process emissions: '#222222' - process emissions to stored: '#444444' - process emissions to atmosphere: '#888888' - oil emissions: '#aaaaaa' - shipping oil emissions: "#555555" - shipping methanol emissions: '#666666' - land transport oil emissions: '#777777' - agriculture machinery oil emissions: '#333333' - # other - shipping: '#03a2ff' - power-to-heat: '#2fb537' - power-to-gas: '#c44ce6' - power-to-H2: '#ff29d9' - power-to-liquid: '#25c49a' - gas-to-power/heat: '#ee8340' - waste: '#e3d37d' - other: '#000000' - geothermal: '#ba91b1' - geothermal heat: '#ba91b1' - geothermal district heat: '#d19D00' - geothermal organic rankine cycle: '#ffbf00' + H2 Electrolysis: '#36373A' + H2 electrolysis: '#36373A' AC: "#70af1d" AC-AC: "#70af1d" AC line: "#70af1d" @@ -750,7 +549,7 @@ plotting: DC: "#8a1caf" DC-DC: "#8a1caf" DC link: "#8a1caf" - load: "#dd2e23" + load: "#B4A7B2" waste CHP: '#e3d37d' waste CHP CC: '#e3d3ff' HVC to air: 'k' From a61a41baac3f124db8d327de60c656b675e2b2a3 Mon Sep 17 00:00:00 2001 From: lmezilis Date: Wed, 5 Nov 2025 12:05:37 +0100 Subject: [PATCH 04/19] Fix minor issues --- config/test/config.myopic.yaml | 169 ++++++++++-------------------- envs/environment.yaml | 2 +- scripts/prepare_sector_network.py | 2 +- scripts/solve_network.py | 16 +-- 4 files changed, 63 insertions(+), 126 deletions(-) diff --git a/config/test/config.myopic.yaml b/config/test/config.myopic.yaml index 0492edf0a6..ec4ef9039a 100644 --- a/config/test/config.myopic.yaml +++ b/config/test/config.myopic.yaml @@ -1,14 +1,12 @@ -# SPDX-FileCopyrightText: : 2017-2024 The PyPSA-Eur Authors + +# SPDX-FileCopyrightText: Contributors to PyPSA-Eur # # SPDX-License-Identifier: CC0-1.0 -# base_s_{clusters}_l{ll}_{opts}_{sector_opts}_{planning_horizons} -# snakemake -call results/greece_test_myopic/prenetworks-brownfield/base_s_44_lv1.5__Co2L0-1h-GtCO2_2050.nc --configfile config/test/config.myopic.yaml - -tutorial: false +tutorial: true run: - name: "greece_test_myopic" + name: "test-sector-myopic" disable_progressbar: true shared_resources: policy: false @@ -17,123 +15,91 @@ run: foresight: myopic scenario: - ll: - - v1.5 clusters: - - 45 - opts: # only relevant for PyPSA-Eur + - 5 + sector_opts: - '' - sector_opts: # this is where the main scenario settings are - - Co2L0-1h #-GtCO2 planning_horizons: + - 2030 + - 2040 - 2050 -countries: ['GR'] +countries: ['BE'] snapshots: - start: "2020-03-01" - end: "2020-03-08" - -# enable: -# prepare_links_p_nom: false -# retrieve_databundle: false -# retrieve_sector_databundle: true -# retrieve_cost_data: false -# build_cutout: false -# retrieve_cutout: false -# build_natura_raster: false -# retrieve_natura_raster: false -# custom_busmap: false + start: "2013-03-01" + end: "2013-03-08" sector: - central_heat_vent: false + central_heat_vent: true + solid_biomass_import: + enable: true + district_heating: + ates: + enable: true + ptes: + supplemental_heating: + enable: true + booster_heat_pump: true + heat_pump_sources: + urban central: + - air + - geothermal + - river_water + - sea_water + urban decentral: + - air + rural: + - air + - ground + hydrogen_turbine: false + regional_oil_demand: false + regional_co2_sequestration_potential: + enable: false + co2_spatial: false + co2_network: false + methanol: + methanol_to_power: + ocgt: false + biomass_to_methanol: false + ammonia: false + biomass_spatial: false + biomass_to_liquid: false + electrobiofuels: false electricity: - # BAU_mincapacities: - # solar: 0 - # solar-hsat: 0 - # onwind: 100000 - # offwind-ac: 0 - # offwind-dc: 100000 - # offwind-float: 100000 - - voltages: [200., 220., 300., 380., 400., 500., 750.] - base_network: osm-prebuilt - osm-prebuilt-version: 0.4 - gaslimit_enable: false - gaslimit: false - co2limit_enable: false - co2limit: 7.75e+7 - co2base: 1.487e+9 - - operational_reserve: - activate: false - epsilon_load: 0.02 - epsilon_vres: 0.02 - contingency: 4000 - - max_hours: - battery: 6 - H2: 168 extendable_carriers: Generator: [OCGT] StorageUnit: [battery] Store: [H2] Link: [H2 pipeline] - renewable_carriers: [solar, solar-hsat, onwind, offwind-ac, offwind-float] + renewable_carriers: [solar, solar-hsat, onwind, offwind-ac, offwind-dc, offwind-float] estimate_renewable_capacities: enable: false atlite: - default_cutout: greece_era5_2020_cutout + default_cutout: be-03-2013-era5 cutouts: - greece_era5_2020_cutout: + be-03-2013-era5: module: era5 x: [4., 15.] y: [46., 56.] time: ["2013-03-01", "2013-03-08"] renewable: - onwind: - cutout: greece_era5_2020_cutout offwind-ac: - cutout: greece_era5_2020_cutout max_depth: false offwind-dc: - cutout: greece_era5_2020_cutout max_depth: false offwind-float: - cutout: greece_era5_2020_cutout max_depth: false min_depth: false - solar: - cutout: greece_era5_2020_cutout - solar-hsat: - cutout: greece_era5_2020_cutout clustering: - focus_weights: false - simplify_network: - to_substations: false - remove_stubs: false - remove_stubs_across_borders: false - cluster_network: - algorithm: kmeans - # hac_features: - # - wnd100m - # - influx_direct - exclude_carriers: [] - consider_efficiency_classes: false - aggregation_strategies: - generators: - committable: any - ramp_limit_up: max - ramp_limit_down: max temporal: - resolution_elec: false - resolution_sector: false + resolution_sector: 24h industry: St_primary_fraction: @@ -141,35 +107,11 @@ industry: 2040: 0.5 2050: 0.4 -transmission_projects: - enable: false - include: - tyndp2020: false - nep: false - manual: false - skip: - - upgraded_lines - - upgraded_links - status: - - under_construction - - in_permitting - - confirmed - #- planned_not_yet_permitted - #- under_consideration - new_link_capacity: zero #keep or zero - -existing_capacities: - grouping_years_power: [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 - threshold_capacity: 10 - -costs: - year: 2050 - solving: solver: - name: gurobi - options: gurobi-default + name: highs + options: highs-simplex + mem: 4000 plotting: map: @@ -181,4 +123,7 @@ plotting: costs_threshold: 0.0000001 energy_max: energy_min: - energy_threshold: 0.000001 \ No newline at end of file + energy_threshold: 0.000001 + interactive_bus_balance: + bus_name_pattern: "*central heat*" + enable_heat_source_maps: true diff --git a/envs/environment.yaml b/envs/environment.yaml index a94b191113..67ad6d09f3 100644 --- a/envs/environment.yaml +++ b/envs/environment.yaml @@ -2,7 +2,7 @@ # # SPDX-License-Identifier: CC0-1.0 -name: pypsa-eur-mrel +name: pypsa-eur channels: - conda-forge - bioconda diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index 60cc59a613..73871f1351 100755 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -432,7 +432,7 @@ def update_wind_solar_costs( ] # for offshore wind, need to calculated connection costs - for connection in ["dc", "ac", "float", "fl"]: + for connection in ["dc", "ac", "float"]: tech = "offwind-" + connection landfall_length = landfall_lengths.get(tech, 0.0) if tech not in n.generators.carrier.values: diff --git a/scripts/solve_network.py b/scripts/solve_network.py index c77c7be1dd..c427858320 100644 --- a/scripts/solve_network.py +++ b/scripts/solve_network.py @@ -134,10 +134,10 @@ def add_land_use_constraint(n): ext_i = (n.generators.carrier == carrier) & ~n.generators.p_nom_extendable existing = ( n.generators.loc[ext_i, "p_nom"] - .groupby(n.generators.bus.map(n.buses.country)) + .groupby(n.generators.bus.map(n.buses.country)) #changed for the Greek run problem with csv files .sum() ) - existing.index += " " + carrier + "-" #+ snakemake.wildcards.planning_horizons + existing.index += " " + carrier + "-" + snakemake.wildcards.planning_horizons n.generators.loc[existing.index, "p_nom_max"] -= existing # check if existing capacities are larger than technical potential @@ -638,10 +638,6 @@ 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")) - # Debug output to check carrier names - print(f"Carriers in mincaps: {mincaps.index}") - print(f"Carriers in network: {ext_i.carrier.unique()}") - lhs = p_nom.groupby(ext_carrier_i).sum() rhs = mincaps[lhs.indexes["carrier"]].rename_axis("carrier") n.model.add_constraints(lhs >= rhs, name="bau_mincaps") @@ -944,11 +940,7 @@ def add_co2_atmosphere_constraint(n, snapshots): def extra_functionality(n, snapshots): config = n.config - opts = config.get('scenario', {}).get('opts', []) - - # Debugging output to verify opts are passed correctly - print(f"Scenario opts: {opts}") - + opts = config.get('scenario', {}).get('opts', []) constraints = config["solving"].get("constraints", {}) if constraints.get("BAU", False) and n.generators.p_nom_extendable.any(): @@ -1068,7 +1060,7 @@ def solve_network(n, config, params, solving, **kwargs): clusters="5", ll="v1.0", sector_opts="", - planning_horizons="2030", + # planning_horizons="2030", ) configure_logging(snakemake) set_scenario_config(snakemake) From d0f2d442858dd2d9407b5cd8168a3aecba1754a1 Mon Sep 17 00:00:00 2001 From: lmezilis Date: Wed, 5 Nov 2025 14:23:03 +0100 Subject: [PATCH 05/19] fix space --- config/test/config.electricity.yaml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/config/test/config.electricity.yaml b/config/test/config.electricity.yaml index 6048789b41..44300564d8 100644 --- a/config/test/config.electricity.yaml +++ b/config/test/config.electricity.yaml @@ -2,11 +2,8 @@ # # SPDX-License-Identifier: CC0-1.0 -# snakemake -call results/test/networks/base_s_38_elec_lv1.25_Co2L-24h.nc --configfile config/test/config.electricity.yaml - tutorial: false - run: name: "test" # use this to keep track of runs with different settings disable_progressbar: true @@ -39,8 +36,6 @@ enable: # custom_busmap: false drop_leap_day: true - - electricity: # BAU_mincapacities: # solar: 0 From 18dfe63318c5e41bc4ad946c1ab6e808705bfd0d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 5 Nov 2025 14:17:15 +0000 Subject: [PATCH 06/19] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- config/test/config.electricity.yaml | 4 ++-- rules/build_electricity.smk | 5 ++++- scripts/_helpers.py | 2 +- scripts/build_renewable_profiles.py | 6 +++++- scripts/plot_summary.py | 3 +-- scripts/prepare_sector_network.py | 7 ++----- scripts/solve_network.py | 7 +++---- 7 files changed, 18 insertions(+), 16 deletions(-) diff --git a/config/test/config.electricity.yaml b/config/test/config.electricity.yaml index 17d856de30..5e603b7b35 100644 --- a/config/test/config.electricity.yaml +++ b/config/test/config.electricity.yaml @@ -16,7 +16,7 @@ scenario: - lv1.25 clusters: - 38 - opts: + opts: - Co2L-24h countries: ['AL', 'AT', 'BA', 'BE', 'BG', 'CH', 'CZ', 'DE', 'DK', 'EE', 'ES', 'FI', 'FR', 'GB', 'GR', 'HR', 'HU', 'IE', 'IT', 'LT', 'LU', 'LV', 'ME', 'MK', 'NL', 'NO', 'PL', 'PT', 'RO', 'RS', 'SE', 'SI', 'SK'] @@ -65,7 +65,7 @@ electricity: H2: 168 extendable_carriers: - Generator: [solar, solar-hsat, offsolar, onwind, offwind-ac, offwind-float, wave-farshore, wave-nearshore, wave-shallow ] + Generator: [solar, solar-hsat, offsolar, onwind, offwind-ac, offwind-float, wave-farshore, wave-nearshore, wave-shallow] StorageUnit: [battery, H2] Store: [] Link: [H2 pipeline] diff --git a/rules/build_electricity.smk b/rules/build_electricity.smk index cc793a07e2..adce5cf9e3 100755 --- a/rules/build_electricity.smk +++ b/rules/build_electricity.smk @@ -341,13 +341,16 @@ rule determine_availability_matrix: if config["enable"].get("build_renewable_profiles", True): + rule build_renewable_profiles: params: snapshots=config_provider("snapshots"), drop_leap_day=config_provider("enable", "drop_leap_day"), renewable=config_provider("renewable"), input: - availability_matrix=resources("availability_matrix_{clusters}_{technology}.nc"), + availability_matrix=resources( + "availability_matrix_{clusters}_{technology}.nc" + ), offshore_shapes=resources("offshore_shapes.geojson"), regions=resources("regions_onshore_base_s_{clusters}.geojson"), cutout=lambda w: "cutouts/" diff --git a/scripts/_helpers.py b/scripts/_helpers.py index 42b23ee60e..2141b093b1 100644 --- a/scripts/_helpers.py +++ b/scripts/_helpers.py @@ -9,6 +9,7 @@ import os import re import time +import warnings from functools import partial, wraps from pathlib import Path from tempfile import NamedTemporaryFile @@ -32,7 +33,6 @@ wait_exponential, ) from tqdm import tqdm -import warnings # Suppress the 'highly fragmented' warning warnings.simplefilter("ignore", category=UserWarning) diff --git a/scripts/build_renewable_profiles.py b/scripts/build_renewable_profiles.py index 62f3788363..910e080520 100644 --- a/scripts/build_renewable_profiles.py +++ b/scripts/build_renewable_profiles.py @@ -154,7 +154,11 @@ regions = gpd.read_file(snakemake.input.distance_regions) # do not pull up, set_index does not work if geo dataframe is empty regions = regions.set_index("name").rename_axis("bus") - if snakemake.wildcards.technology.startswith("offwind") or snakemake.wildcards.technology.startswith("wave") or snakemake.wildcards.technology.startswith("offsolar"): + if ( + snakemake.wildcards.technology.startswith("offwind") + or snakemake.wildcards.technology.startswith("wave") + or snakemake.wildcards.technology.startswith("offsolar") + ): # for offshore regions, the shortest distance to the shoreline is used offshore_regions = availability.coords["bus"].values regions = regions.loc[offshore_regions] diff --git a/scripts/plot_summary.py b/scripts/plot_summary.py index 5cda47e689..f0fcf6bd01 100644 --- a/scripts/plot_summary.py +++ b/scripts/plot_summary.py @@ -117,8 +117,7 @@ def rename_techs(label): "solar thermal", "solar rooftop", "solar", - "floating solar" - "building retrofitting", + "floating solarbuilding retrofitting", "ground heat pump", "air heat pump", "heat pump", diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index 102774aa8b..a5b1539041 100755 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -506,7 +506,7 @@ def update_wind_solar_costs( n.generators.loc[n.generators.carrier == tech, "capital_cost"] = ( capital_cost.rename(index=lambda node: node + " " + tech) ) - + for connection in ["farshore", "nearshore", "shallow"]: tech = "wave-" + connection landfall_length = landfall_lengths.get(tech, 0.0) @@ -514,7 +514,6 @@ def update_wind_solar_costs( continue profile = snakemake.input["profile_wave-" + connection] with xr.open_dataset(profile) as ds: - # if-statement for compatibility with old profiles if "year" in ds.indexes: ds = ds.sel(year=ds.year.min(), drop=True) @@ -533,9 +532,7 @@ def update_wind_solar_costs( ) logger.info( - "Added connection cost of {:0.0f}-{:0.0f} Eur/MW/a to {}".format( - connection_cost.min(), connection_cost.max(), tech - ) + f"Added connection cost of {connection_cost.min():0.0f}-{connection_cost.max():0.0f} Eur/MW/a to {tech}" ) n.generators.loc[n.generators.carrier == tech, "capital_cost"] = ( diff --git a/scripts/solve_network.py b/scripts/solve_network.py index c162c3f411..ca31664521 100644 --- a/scripts/solve_network.py +++ b/scripts/solve_network.py @@ -166,7 +166,7 @@ def add_land_use_constraint(n: pypsa.Network, planning_horizons: str) -> None: "offsolar", "wave-shallow", "wave-nearshore", - "wave-farshore" + "wave-farshore", ]: ext_i = (n.generators.carrier == carrier) & ~n.generators.p_nom_extendable grouper = n.generators.loc[ext_i].index.str.replace( @@ -735,7 +735,6 @@ def add_BAU_constraints(n: pypsa.Network, config: dict) -> None: logger.info("BAU is done.") - # TODO: think about removing or make per country def add_SAFE_constraints(n, config): """ @@ -1194,9 +1193,9 @@ def extra_functionality( ``snakemake.config`` are expected to be attached to the network. """ config = n.config - opts = config.get('scenario', {}).get('opts', []) + opts = config.get("scenario", {}).get("opts", []) constraints = config["solving"].get("constraints", {}) - + if constraints.get("BAU", False) and n.generators.p_nom_extendable.any(): add_BAU_constraints(n, config) if constraints["SAFE"] and n.generators.p_nom_extendable.any(): From 51160e7f393eacac52e416eeee341e139ddf2ca1 Mon Sep 17 00:00:00 2001 From: lmezilis Date: Mon, 10 Nov 2025 12:50:29 +0100 Subject: [PATCH 07/19] update branch --- config/test/config.electricity.yaml | 164 ++-------------------------- rules/build_electricity.smk | 5 +- 2 files changed, 11 insertions(+), 158 deletions(-) diff --git a/config/test/config.electricity.yaml b/config/test/config.electricity.yaml index 783d070d2a..b4c453a755 100644 --- a/config/test/config.electricity.yaml +++ b/config/test/config.electricity.yaml @@ -16,7 +16,7 @@ scenario: - lv1.25 clusters: - 38 - opts: + opts: - Co2L-24h countries: ['AL', 'AT', 'BA', 'BE', 'BG', 'CH', 'CZ', 'DE', 'DK', 'EE', 'ES', 'FI', 'FR', 'GB', 'GR', 'HR', 'HU', 'IE', 'IT', 'LT', 'LU', 'LV', 'ME', 'MK', 'NL', 'NO', 'PL', 'PT', 'RO', 'RS', 'SE', 'SI', 'SK'] @@ -60,7 +60,7 @@ electricity: Generator: [solar, solar-hsat, offsolar, onwind, offwind-ac, offwind-float, wave-farshore, wave-nearshore, wave-shallow ] StorageUnit: [battery, H2] # battery, H2 Store: [] - Link: [H2 pipeline] # H2 pipeline + Link: [H2 pipeline] powerplants_filter: (DateOut >= 2023 or DateOut != DateOut) and not (Country == 'Germany' and Fueltype == 'Nuclear') custom_powerplants: false @@ -322,17 +322,6 @@ lines: activate: true max_line_rating: 1.3 -load: - interpolate_limit: 3 - time_shift_for_large_gaps: 1w - manual_adjustments: true # false - scaling_factor: 1.5 - fixed_year: false # false or year (e.g. 2013) - supplement_synthetic: true - distribution_key: - gdp: 0.6 - population: 0.4 - # docs in https://pypsa-eur.readthedocs.io/en/latest/configuration.html#transmission_projects transmission_projects: enable: false @@ -393,148 +382,9 @@ solving: BAU: false EQ: false solver: - name: gurobi - options: gurobi-default - -plotting: - map: - boundaries: [-11, 30, 34, 71] - color_geomap: - ocean: lightblue - land: white - projection: - name: "EqualEarth" - # See https://scitools.org.uk/cartopy/docs/latest/reference/projections.html for alternatives, for example: - # name: "LambertConformal" - # central_longitude: 10. - # central_latitude: 50. - # standard_parallels: [35, 65] - eu_node_location: - x: -5.5 - y: 46. - costs_max: 1000 - costs_threshold: 1 - energy_max: 20000 - energy_min: -20000 - energy_threshold: 50. - - nice_names: - - onwind: "Onshore Wind" - offwind-ac: "BF Offshore Wind" - offwind-dc: "Offshore Wind (DC)" - offwind-float: "FL Offshore Wind" - wave-farshore: "WEC Farshore" - wave-nearshore: "WEC Nearshore" - wave-shallow: "WEC Shallow" - solar: "Solar" - solar-hsat: "Solar-HSAT" - offsolar: "FL Solar" - PHS: "Pumped Hydro Storage" - hydro: "Reservoir & Dam" - battery: "Battery Storage" - H2: "Hydrogen Storage" - lines: "Transmission Lines" - ror: "Run of River" - load: "Load Shedding" - ac: "AC" - dc: "DC" - - tech_colors: - # wind - onwind: "#2D61A6" - onshore wind: "#2D61A6" - offwind: "#6895dd" - offshore wind: "#29A2CC" - offwind-ac: "#29A2CC" - offshore wind (AC): "#29A2CC" - offshore wind ac: "#29A2CC" - offwind-dc: "#29A2CC" - offshore wind (DC): "#29A2CC" - offshore wind dc: "#29A2CC" - offwind-float: "#52DAE5" - offshore wind (Float): "#52DAE5" - offshore wind float: "#52DAE5" - # water - hydro: '#FC6D8E' - hydro reservoir: '#FC6D8E' - ror: '#F2BDE4' - run of river: '#F2BDE4' - hydroelectricity: '#298c81' - PHS: '#CF1F33' - hydro+PHS: "#08ad97" - # solar - solar: "#F2CE1B" - solar PV: "#f9d002" - solar-hsat: "#F2A30F" - solar thermal: '#ffbf2b' - offsolar: '#F2620F' - residential rural solar thermal: '#f1c069' - services rural solar thermal: '#eabf61' - residential urban decentral solar thermal: '#e5bc5a' - services urban decentral solar thermal: '#dfb953' - urban central solar thermal: '#d7b24c' - solar rooftop: '#ffea80' - # wave - wave-farshore: '#4CE766' - wave-nearshore: '#24961A' - wave-shallow: '#216406' - # power transmission - lines: '#6c9459' - transmission lines: '#6c9459' - electricity distribution grid: '#97ad8c' - low voltage: '#97ad8c' - # electricity demand - Electric load: '#B4A7B2' - electric demand: '#B4A7B2' - electricity: '#B4A7B2' - industry electricity: '#2d2a66' - industry new electricity: '#2d2a66' - agriculture electricity: '#494778' - # battery + EVs - battery: '#ace37f' - battery storage: '#ace37f' - battery charger: '#F2762E' - battery discharger: '#5B224E' - home battery: '#80c944' - home battery storage: '#80c944' - home battery charger: '#F2762E' - home battery discharger: '#5B224E' - BEV charger: '#baf238' - V2G: '#e5ffa8' - land transport EV: '#baf238' - land transport demand: '#38baf2' - EV battery: '#baf238' - # hydrogen - H2 for industry: "#f073da" - H2 for shipping: "#ebaee0" - H2: '#bf13a0' - hydrogen: '#bf13a0' - retrofitted H2 boiler: '#e5a0d9' - SMR: '#870c71' - SMR CC: '#4f1745' - H2 liquefaction: '#d647bd' - hydrogen storage: '#bf13a0' - H2 Store: '#bf13a0' - H2 storage: '#bf13a0' - land transport fuel cell: '#6b3161' - H2 pipeline: '#f081dc' - H2 pipeline retrofitted: '#ba99b5' - H2 Fuel Cell: '#D0D98B' - H2 fuel cell: '#D0D98B' - H2 turbine: '#991f83' - H2 Electrolysis: '#36373A' - H2 electrolysis: '#36373A' - AC: "#70af1d" - AC-AC: "#70af1d" - AC line: "#70af1d" - links: "#8a1caf" - HVDC links: "#8a1caf" - DC: "#8a1caf" - DC-DC: "#8a1caf" - DC link: "#8a1caf" - load: "#B4A7B2" - waste CHP: '#e3d37d' - waste CHP CC: '#e3d3ff' - HVC to air: 'k' + name: highs + options: highs-simplex + check_objective: + enable: false + expected_value: 3.8120188094e+07 diff --git a/rules/build_electricity.smk b/rules/build_electricity.smk index cc793a07e2..adce5cf9e3 100755 --- a/rules/build_electricity.smk +++ b/rules/build_electricity.smk @@ -341,13 +341,16 @@ rule determine_availability_matrix: if config["enable"].get("build_renewable_profiles", True): + rule build_renewable_profiles: params: snapshots=config_provider("snapshots"), drop_leap_day=config_provider("enable", "drop_leap_day"), renewable=config_provider("renewable"), input: - availability_matrix=resources("availability_matrix_{clusters}_{technology}.nc"), + availability_matrix=resources( + "availability_matrix_{clusters}_{technology}.nc" + ), offshore_shapes=resources("offshore_shapes.geojson"), regions=resources("regions_onshore_base_s_{clusters}.geojson"), cutout=lambda w: "cutouts/" From 81ddb012538ae2e5234d219a03399eb6f725fcda Mon Sep 17 00:00:00 2001 From: Johannes HAMPP <42553970+euronion@users.noreply.github.com> Date: Fri, 7 Nov 2025 14:41:23 +0100 Subject: [PATCH 08/19] doc: Automatically update DAGs in documentation (#1880) * doc: Automatically update DAGs in documentation * doc: Update release notes * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * I find working with the CI always tricky * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * some adjustments --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: lkstrp --- .github/workflows/release.yaml | 93 ++++++++++++++++++++++++++++++++++ Makefile | 6 +++ doc/release_notes.rst | 2 + 3 files changed, 101 insertions(+) create mode 100644 .github/workflows/release.yaml diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000000..b05d3fed70 --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,93 @@ +name: Release + +on: + push: + tags: + - v*.*.* + +env: + PREPARATION_COMMIT: '[github-actions.ci] prepare release ${{ github.ref_name }}' + BASE_ENV: envs/environment.yaml + +jobs: + check-preparation: + name: Check if release is prepared + runs-on: ubuntu-latest + outputs: + prepared: ${{ steps.validate.outputs.prepared }} + steps: + - uses: actions/checkout@v5 + + - name: Validate commit message + id: validate + run: | + # Check if last commit is the expected commit message + COMMIT_MESSAGE=$(git log -1 --pretty=%B) + echo "Expected: '${{ env.PREPARATION_COMMIT }}'" + echo "Received: '$COMMIT_MESSAGE'" + + prepared="false" + if [[ "$COMMIT_MESSAGE" == "${{ env.PREPARATION_COMMIT }}" ]]; then + prepared="true" + fi + + echo "prepared=$prepared" >> $GITHUB_OUTPUT + + prepare-release: + name: Prepare release + needs: [check-preparation] + if: ${{ needs.check-preparation.outputs.prepared == 'false' }} + runs-on: ubuntu-latest + steps: + - name: Generate token for PyPSA Bot + id: generate-token + uses: actions/create-github-app-token@v2 + with: + app-id: ${{ vars.PYPSA_BOT_ID }} + private-key: ${{ secrets.PYPSA_BOT_PRIVATE_KEY }} + + - uses: actions/checkout@v5 + with: + fetch-depth: 0 + + - name: Find the branch for commit/ tag + run: | + branch=$(git branch -r --contains ${{ github.sha }} | grep -v 'HEAD' | head -n 1 | sed 's|origin/||' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') + echo "Branch found: $branch" + echo "BRANCH_NAME=$branch" >> $GITHUB_ENV + + - uses: actions/checkout@v5 + with: + fetch-depth: 0 + ref: ${{ env.BRANCH_NAME }} + token: ${{ steps.generate-token.outputs.token }} + + - name: Setup conda environment + uses: conda-incubator/setup-miniconda@v3 + with: + miniforge-version: latest + environment-file: ${{ env.BASE_ENV }} + activate-environment: pypsa-eur + channel-priority: strict + + # Start of preparation script + + - name: Update DAGs in documentation + run: | + conda run -n pypsa-eur make update-dags + + # End of preparation script + + - name: Remove previous tag + run: | + git tag -d ${{ github.ref_name }} + git push origin --delete ${{ github.ref_name }} + + - name: Commit changes + uses: stefanzweifel/git-auto-commit-action@v7 + with: + branch: ${{ env.BRANCH_NAME }} + commit_message: '${{ env.PREPARATION_COMMIT }}' + tagging_message: '${{ github.ref_name }}' # add tag again + push_options: '${{ github.ref_name }}' + add_options: '-u' # Never add untracked files diff --git a/Makefile b/Makefile index 2883c8262e..47672e7aa3 100755 --- a/Makefile +++ b/Makefile @@ -109,3 +109,9 @@ reset: rm -r ./.snakemake || true; \ echo "Reset completed." \ ) || echo "Reset cancelled." + +# Update the DAGs in the documentation +# use sed to remove everything up to 'Building DAG' outputs, e.g. from pulp/Gurobi before passing to dot +update-dags: + snakemake results/networks/base_s_128_elec_.nc -F --dag | sed -n "/digraph/,/}/p" | dot -Tpng -o doc/img/intro-workflow.png + snakemake --rulegraph -F | sed -n "/digraph/,/}/p" | dot -Tpng -o doc/img/workflow.png \ No newline at end of file diff --git a/doc/release_notes.rst b/doc/release_notes.rst index 76e62276fa..9b042fac8b 100644 --- a/doc/release_notes.rst +++ b/doc/release_notes.rst @@ -76,6 +76,8 @@ Upcoming Release * Misc: Empty folders that are automatically generated by ``snakemake`` have been added to the repository, e.g. ``resources/`` and ``results/``. The ``purge`` rule now removes their contents but keeps the folders (https://github.com/PyPSA/pypsa-eur/pull/1764). +* Misc: Automatically update the DAGs shown in the documentation (https://github.com/PyPSA/pypsa-eur/pull/1880). + PyPSA-Eur v2025.07.0 (11th July 2025) ===================================== From 2e68c83a6dad076c7607483cb3a6dedd76367c9d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 10 Nov 2025 13:37:10 +0000 Subject: [PATCH 09/19] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- config/test/config.electricity.yaml | 2 +- scripts/add_electricity.py | 21 ++++++++------------- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/config/test/config.electricity.yaml b/config/test/config.electricity.yaml index b4c453a755..ca776534dd 100644 --- a/config/test/config.electricity.yaml +++ b/config/test/config.electricity.yaml @@ -57,7 +57,7 @@ electricity: H2: 168 extendable_carriers: - Generator: [solar, solar-hsat, offsolar, onwind, offwind-ac, offwind-float, wave-farshore, wave-nearshore, wave-shallow ] + Generator: [solar, solar-hsat, offsolar, onwind, offwind-ac, offwind-float, wave-farshore, wave-nearshore, wave-shallow] StorageUnit: [battery, H2] # battery, H2 Store: [] Link: [H2 pipeline] diff --git a/scripts/add_electricity.py b/scripts/add_electricity.py index 8f24586981..bb178137f2 100755 --- a/scripts/add_electricity.py +++ b/scripts/add_electricity.py @@ -47,7 +47,9 @@ discharging. This leads to three investment variables for the energy capacity, charging and discharging capacity of the storage unit. """ + import warnings + from pandas.errors import PerformanceWarning # Suppress PerformanceWarning globally @@ -493,9 +495,7 @@ def attach_wind_and_solar( + connection_cost ) logger.info( - "Added connection cost of {:0.0f}-{:0.0f} Eur/MW/a to {}".format( - connection_cost.min(), connection_cost.max(), car - ) + f"Added connection cost of {connection_cost.min():0.0f}-{connection_cost.max():0.0f} Eur/MW/a to {car}" ) if car == "wave-farshore": distance = ds["average_distance"].to_pandas() @@ -514,9 +514,8 @@ def attach_wind_and_solar( + connection_cost ) logger.info( - "Added connection cost of {:0.0f}-{:0.0f} Eur/MW/a to {}".format( - connection_cost.min(), connection_cost.max(), car - )) + f"Added connection cost of {connection_cost.min():0.0f}-{connection_cost.max():0.0f} Eur/MW/a to {car}" + ) if car == "wave-nearshore": distance = ds["average_distance"].to_pandas() @@ -534,9 +533,7 @@ def attach_wind_and_solar( + connection_cost ) logger.info( - "Added connection cost of {:0.0f}-{:0.0f} Eur/MW/a to {}".format( - connection_cost.min(), connection_cost.max(), car - ) + f"Added connection cost of {connection_cost.min():0.0f}-{connection_cost.max():0.0f} Eur/MW/a to {car}" ) if car == "wave-shallow": distance = ds["average_distance"].to_pandas() @@ -554,9 +551,7 @@ def attach_wind_and_solar( + connection_cost ) logger.info( - "Added connection cost of {:0.0f}-{:0.0f} Eur/MW/a to {}".format( - connection_cost.min(), connection_cost.max(), car - ) + f"Added connection cost of {connection_cost.min():0.0f}-{connection_cost.max():0.0f} Eur/MW/a to {car}" ) else: @@ -1250,4 +1245,4 @@ def attach_stores( sanitize_locations(n) n.meta = dict(snakemake.config, **dict(wildcards=dict(snakemake.wildcards))) - n.export_to_netcdf(snakemake.output[0]) \ No newline at end of file + n.export_to_netcdf(snakemake.output[0]) From afcfe5dfa82ef310d3b3faef7e57a7dc3555f6d4 Mon Sep 17 00:00:00 2001 From: lmezilis Date: Mon, 10 Nov 2025 15:08:19 +0100 Subject: [PATCH 10/19] fix imports --- scripts/_helpers.py | 7 +--- scripts/add_electricity.py | 7 ---- scripts/plot_summary.py | 77 -------------------------------------- scripts/solve_network.py | 1 - 4 files changed, 1 insertion(+), 91 deletions(-) diff --git a/scripts/_helpers.py b/scripts/_helpers.py index 2141b093b1..e16ddd3bed 100644 --- a/scripts/_helpers.py +++ b/scripts/_helpers.py @@ -9,7 +9,6 @@ import os import re import time -import warnings from functools import partial, wraps from pathlib import Path from tempfile import NamedTemporaryFile @@ -34,10 +33,6 @@ ) from tqdm import tqdm -# Suppress the 'highly fragmented' warning -warnings.simplefilter("ignore", category=UserWarning) - - logger = logging.getLogger(__name__) REGION_COLS = ["geometry", "name", "x", "y", "country"] @@ -1135,4 +1130,4 @@ def load_costs(cost_file: str) -> pd.DataFrame: DataFrame containing the prepared cost data """ - return pd.read_csv(cost_file, index_col=0) + return pd.read_csv(cost_file, index_col=0) \ No newline at end of file diff --git a/scripts/add_electricity.py b/scripts/add_electricity.py index bb178137f2..4d2684738e 100755 --- a/scripts/add_electricity.py +++ b/scripts/add_electricity.py @@ -48,13 +48,6 @@ charging and discharging capacity of the storage unit. """ -import warnings - -from pandas.errors import PerformanceWarning - -# Suppress PerformanceWarning globally -warnings.simplefilter("ignore", category=PerformanceWarning) - import logging from collections.abc import Iterable from typing import Any diff --git a/scripts/plot_summary.py b/scripts/plot_summary.py index f0fcf6bd01..4c241833e8 100644 --- a/scripts/plot_summary.py +++ b/scripts/plot_summary.py @@ -19,83 +19,6 @@ # consolidate and rename -def rename_techs(label): - prefix_to_remove = [ - "residential ", - "services ", - "urban ", - "rural ", - "central ", - "decentral ", - ] - - rename_if_contains = [ - "CHP", - "gas boiler", - "biogas", - "solar thermal", - "air heat pump", - "ground heat pump", - "resistive heater", - "Fischer-Tropsch", - ] - - rename_if_contains_dict = { - "water tanks": "hot water storage", - "retrofitting": "building retrofitting", - # "H2 Electrolysis": "hydrogen storage", - # "H2 Fuel Cell": "hydrogen storage", - # "H2 pipeline": "hydrogen storage", - "battery": "battery storage", - "H2 for industry": "H2 for industry", - "land transport fuel cell": "land transport fuel cell", - "land transport oil": "land transport oil", - "oil shipping": "shipping oil", - # "CC": "CC" - } - - rename = { - "solar": "solar PV", - "Sabatier": "methanation", - "offwind": "offshore wind", - "offwind-ac": "offshore wind ", - "offwind-dc": "offshore wind (DC)", - "offwind-fl": "offshore floating wind", - "offwind-float": "offshore wind (Float)", - "offsolar": "floating solar", - "wave-farshore": "wave farshore", - "wave-nearshore": "wave nearshore", - "wave-shallow": "wave shallow", - "onwind": "onshore wind", - "ror": "hydroelectricity", - "hydro": "hydroelectricity", - "PHS": "hydroelectricity", - "NH3": "ammonia", - "co2 Store": "DAC", - "co2 stored": "CO2 sequestration", - "AC": "transmission lines", - "DC": "transmission lines", - "B2B": "transmission lines", - } - - for ptr in prefix_to_remove: - if label[: len(ptr)] == ptr: - label = label[len(ptr) :] - - for rif in rename_if_contains: - if rif in label: - label = rif - - for old, new in rename_if_contains_dict.items(): - if old in label: - label = new - - for old, new in rename.items(): - if old == label: - label = new - return label - - preferred_order = pd.Index( [ "transmission lines", diff --git a/scripts/solve_network.py b/scripts/solve_network.py index 0226730839..7337df1231 100644 --- a/scripts/solve_network.py +++ b/scripts/solve_network.py @@ -1193,7 +1193,6 @@ def extra_functionality( ``snakemake.config`` are expected to be attached to the network. """ config = n.config - opts = config.get("scenario", {}).get("opts", []) constraints = config["solving"].get("constraints", {}) if constraints.get("BAU", False) and n.generators.p_nom_extendable.any(): From a1d9014d9649a41922b1ddb7bca10508e1e864dd Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 10 Nov 2025 14:08:57 +0000 Subject: [PATCH 11/19] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- scripts/_helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/_helpers.py b/scripts/_helpers.py index e16ddd3bed..4a164c70fb 100644 --- a/scripts/_helpers.py +++ b/scripts/_helpers.py @@ -1130,4 +1130,4 @@ def load_costs(cost_file: str) -> pd.DataFrame: DataFrame containing the prepared cost data """ - return pd.read_csv(cost_file, index_col=0) \ No newline at end of file + return pd.read_csv(cost_file, index_col=0) From f4e8369a61d388b1c1e68873bcaa60daacd87bbf Mon Sep 17 00:00:00 2001 From: lmezilis Date: Tue, 25 Nov 2025 13:51:02 +0100 Subject: [PATCH 12/19] swich to 'converter' --- config/test/config.electricity.yaml | 6 +++--- scripts/build_renewable_profiles.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/config/test/config.electricity.yaml b/config/test/config.electricity.yaml index ca776534dd..acda338247 100644 --- a/config/test/config.electricity.yaml +++ b/config/test/config.electricity.yaml @@ -234,7 +234,7 @@ renewable: cutout: era5_2020_cutout #mrel_2020.cutout resource: method: wave - wec_type: Farshore_750kW + converter: Farshore_750kW capacity_per_sqkm: 50 #for the packing density the safe assumption is 10-20 MW/Km2, but with good assessment this is 20-50 MW/Km2 correction_factor: 1 corine: [44, 255] @@ -251,7 +251,7 @@ renewable: cutout: era5_2020_cutout resource: method: wave - wec_type: Nearshore_400kW + converter: Nearshore_400kW capacity_per_sqkm: 35 #for the packing density the safe assumption is 10-20 MW/Km2, but with good assessment this is 20-40 MW/Km2 correction_factor: 1 corine: [44, 255] @@ -267,7 +267,7 @@ renewable: cutout: era5_2020_cutout resource: method: wave - wec_type: Shallow_290kW + converter: Shallow_290kW capacity_per_sqkm: 30 #for the packing density the safe assumption is 10-20 MW/Km2, but with good assessment this is 20-40 MW/Km2 correction_factor: 1 corine: [44, 255] diff --git a/scripts/build_renewable_profiles.py b/scripts/build_renewable_profiles.py index 910e080520..c58e0b7e16 100644 --- a/scripts/build_renewable_profiles.py +++ b/scripts/build_renewable_profiles.py @@ -128,7 +128,7 @@ resource = params["resource"] # pv panel params / wind turbine params resource["show_progress"] = not noprogress - tech = next(t for t in ["panel", "turbine", "wec_type"] if t in resource) + tech = next(t for t in ["panel", "turbine", "converter"] if t in resource) models = resource[tech] if not isinstance(models, dict): models = {0: models} From 8476a69ffbe51fa718817510bcb95eb2f695eaa8 Mon Sep 17 00:00:00 2001 From: lmezilis Date: Tue, 13 Jan 2026 20:04:37 +0100 Subject: [PATCH 13/19] integrate generators electricity --- config/plotting.default.yaml | 13 +++++ config/test/config.electricity.yaml | 3 - scripts/add_electricity.py | 85 ++++------------------------- scripts/plot_summary.py | 5 +- scripts/prepare_sector_network.py | 6 +- scripts/solve_network.py | 3 +- 6 files changed, 30 insertions(+), 85 deletions(-) diff --git a/config/plotting.default.yaml b/config/plotting.default.yaml index 28dbd27d04..432746d48d 100644 --- a/config/plotting.default.yaml +++ b/config/plotting.default.yaml @@ -74,6 +74,10 @@ plotting: - offwind-dc - offwind-ac - offwind-float + - wave-shallow + - wave-nearshore + - wave-farshore + - offsolar - ror - hydro - PHS @@ -373,6 +377,10 @@ plotting: offwind-dc: "Offshore Wind (DC)" offwind-float: "Offshore Wind (Floating)" onwind: "Onshore Wind" + wave-shallow: "WEC Shallow" + wave-nearshore: "WEC Nearshore" + wave-farshore: "WEC Farshore" + offsolar: "Offshore Solar (Floating)" solar: "Solar" PHS: "Pumped Hydro Storage" hydro: "Reservoir & Dam" @@ -399,6 +407,10 @@ plotting: offwind-float: "#b5e2fa" offshore wind (Float): "#b5e2fa" offshore wind float: "#b5e2fa" + # wave + wave-shallow: "#216406" + wave-nearshore: "#24961a" + wave-farshore: "#4ce766" # water hydro: '#298c81' hydro reservoir: '#298c81' @@ -411,6 +423,7 @@ plotting: solar: "#f9d002" solar PV: "#f9d002" solar-hsat: "#fdb915" + offsolar: "#f2620f" solar thermal: '#ffbf2b' residential rural solar thermal: '#f1c069' services rural solar thermal: '#eabf61' diff --git a/config/test/config.electricity.yaml b/config/test/config.electricity.yaml index e660d43255..7dca58069d 100644 --- a/config/test/config.electricity.yaml +++ b/config/test/config.electricity.yaml @@ -30,9 +30,6 @@ enable: retrieve_cost_data: false build_cutout: false custom_busmap: false - build_renewable_profiles: true - # retrieve_cutout: true - # custom_busmap: false drop_leap_day: true electricity: diff --git a/scripts/add_electricity.py b/scripts/add_electricity.py index 4d2684738e..277dbba257 100755 --- a/scripts/add_electricity.py +++ b/scripts/add_electricity.py @@ -174,7 +174,6 @@ def sanitize_carriers(n, config): add_missing_carriers(n, c.df.carrier) carrier_i = n.carriers.index - print(n.carriers.index) nice_names = ( pd.Series(config["plotting"]["nice_names"]) .reindex(carrier_i) @@ -454,8 +453,9 @@ def attach_wind_and_solar( ds = ds.stack(bus_bin=["bus", "bin"]) supcar = car.split("-", 2)[0] - if supcar == "offwind": + if supcar == {"offwind", "wave", "offsolar"}: distance = ds["average_distance"].to_pandas() + distance.index = distance.index.map(flatten) submarine_cost = costs.at[car + "-connection-submarine", "capital_cost"] underground_cost = costs.at[ car + "-connection-underground", "capital_cost" @@ -466,87 +466,22 @@ def attach_wind_and_solar( # Take 'offwind-float' capital cost for 'float', and 'offwind' capital cost for the rest ('ac' and 'dc') midcar = car.split("-", 2)[1] - if midcar == "float": + if supcar == "offwind" and midcar != "float": + capital_cost = ( + costs.at["offwind", "capital_cost"] + + costs.at[car + "-station", "capital_cost"] + + connection_cost + ) + else: capital_cost = ( costs.at[car, "capital_cost"] + costs.at[car + "-station", "capital_cost"] + connection_cost ) - if car == "offsolar": - distance = ds["average_distance"].to_pandas() - submarine_cost = costs.at[car + "-connection-submarine", "capital_cost"] - underground_cost = costs.at[ - car + "-connection-underground", "capital_cost" - ] - connection_cost = line_length_factor * ( - distance * submarine_cost + landfall_length * underground_cost - ) - - capital_cost = ( - costs.at["offsolar", "capital_cost"] - + costs.at[car + "-station", "capital_cost"] - + connection_cost - ) - logger.info( - f"Added connection cost of {connection_cost.min():0.0f}-{connection_cost.max():0.0f} Eur/MW/a to {car}" - ) - if car == "wave-farshore": - distance = ds["average_distance"].to_pandas() - distance.index = distance.index.map(flatten) - submarine_cost = costs.at[car + "-connection-submarine", "capital_cost"] - underground_cost = costs.at[ - car + "-connection-underground", "capital_cost" - ] - connection_cost = line_length_factor * ( - distance * submarine_cost + landfall_length * underground_cost - ) - - capital_cost = ( - costs.at["wave-farshore", "capital_cost"] - + costs.at[car + "-station", "capital_cost"] - + connection_cost - ) logger.info( f"Added connection cost of {connection_cost.min():0.0f}-{connection_cost.max():0.0f} Eur/MW/a to {car}" ) - - if car == "wave-nearshore": - distance = ds["average_distance"].to_pandas() - submarine_cost = costs.at[car + "-connection-submarine", "capital_cost"] - underground_cost = costs.at[ - car + "-connection-underground", "capital_cost" - ] - connection_cost = line_length_factor * ( - distance * submarine_cost + landfall_length * underground_cost - ) - - capital_cost = ( - costs.at["wave-nearshore", "capital_cost"] - + costs.at[car + "-station", "capital_cost"] - + connection_cost - ) - logger.info( - f"Added connection cost of {connection_cost.min():0.0f}-{connection_cost.max():0.0f} Eur/MW/a to {car}" - ) - if car == "wave-shallow": - distance = ds["average_distance"].to_pandas() - submarine_cost = costs.at[car + "-connection-submarine", "capital_cost"] - underground_cost = costs.at[ - car + "-connection-underground", "capital_cost" - ] - connection_cost = line_length_factor * ( - distance * submarine_cost + landfall_length * underground_cost - ) - - capital_cost = ( - costs.at["wave-shallow", "capital_cost"] - + costs.at[car + "-station", "capital_cost"] - + connection_cost - ) - logger.info( - f"Added connection cost of {connection_cost.min():0.0f}-{connection_cost.max():0.0f} Eur/MW/a to {car}" - ) - + else: capital_cost = costs.at[car, "capital_cost"] diff --git a/scripts/plot_summary.py b/scripts/plot_summary.py index 4c241833e8..5359f01120 100644 --- a/scripts/plot_summary.py +++ b/scripts/plot_summary.py @@ -30,7 +30,7 @@ "biogas", "onshore wind", "offshore wind", - "offshore wind", + "offshore wind (AC)", "offshore wind (DC)", "offshore floating wind", "wave farshore", @@ -40,7 +40,8 @@ "solar thermal", "solar rooftop", "solar", - "floating solarbuilding retrofitting", + "floating solar", + "building retrofitting", "ground heat pump", "air heat pump", "heat pump", diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index f713857019..0f256e58cf 100755 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -486,15 +486,15 @@ def update_wind_solar_costs( # Take 'offwind-float' capital cost for 'float', and 'offwind' capital cost for the rest ('ac' and 'dc') midtech = tech.split("-", 2)[1] - if midtech == "float": + if tech == "offwind" and midtech != "float": capital_cost = ( - costs.at[tech, "capital_cost"] + costs.at["offwind", "capital_cost"] + costs.at[tech + "-station", "capital_cost"] + connection_cost ) else: capital_cost = ( - costs.at["offwind", "capital_cost"] + costs.at[tech, "capital_cost"] + costs.at[tech + "-station", "capital_cost"] + connection_cost ) diff --git a/scripts/solve_network.py b/scripts/solve_network.py index 4d09c82b04..94111e7e0b 100644 --- a/scripts/solve_network.py +++ b/scripts/solve_network.py @@ -729,7 +729,6 @@ def add_BAU_constraints(n: pypsa.Network, config: dict) -> None: lhs = p_nom.groupby(ext_carrier_i).sum() rhs = mincaps[lhs.indexes["carrier"]].rename_axis("carrier") n.model.add_constraints(lhs >= rhs, name="bau_mincaps") - logger.info("BAU is done.") # TODO: think about removing or make per country @@ -1192,7 +1191,7 @@ def extra_functionality( config = n.config constraints = config["solving"].get("constraints", {}) - if constraints.get("BAU", False) and n.generators.p_nom_extendable.any(): + if constraints["BAU"] and n.generators.p_nom_extendable.any(): add_BAU_constraints(n, config) if constraints["SAFE"] and n.generators.p_nom_extendable.any(): add_SAFE_constraints(n, config) From e0b6848675425e5a64081ffe8d1d172e4acd3a70 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 13 Jan 2026 19:05:00 +0000 Subject: [PATCH 14/19] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- scripts/add_electricity.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/add_electricity.py b/scripts/add_electricity.py index 277dbba257..958c705844 100755 --- a/scripts/add_electricity.py +++ b/scripts/add_electricity.py @@ -481,7 +481,7 @@ def attach_wind_and_solar( logger.info( f"Added connection cost of {connection_cost.min():0.0f}-{connection_cost.max():0.0f} Eur/MW/a to {car}" ) - + else: capital_cost = costs.at[car, "capital_cost"] From d18d83514fda7745916d3742c3917a47054660e3 Mon Sep 17 00:00:00 2001 From: lmezilis Date: Tue, 21 Apr 2026 14:32:49 +0200 Subject: [PATCH 15/19] envs correction --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index f348a6348e..b146ade77b 100644 --- a/.gitignore +++ b/.gitignore @@ -32,7 +32,6 @@ doc/_build /scripts/old /scripts/create_scenarios.py /config/create_scenarios.py -# /envs/win-64.lock.yaml config/config.yaml config/scenarios.yaml From a01c944bacc5055488f0aa6ae016638f77ec53f8 Mon Sep 17 00:00:00 2001 From: lmezilis Date: Tue, 21 Apr 2026 14:46:47 +0200 Subject: [PATCH 16/19] correcting config.electricitty --- config/test/config.electricity.yaml | 374 +++------------------------- 1 file changed, 41 insertions(+), 333 deletions(-) diff --git a/config/test/config.electricity.yaml b/config/test/config.electricity.yaml index 4f2bf4cb18..d655bb2384 100644 --- a/config/test/config.electricity.yaml +++ b/config/test/config.electricity.yaml @@ -3,385 +3,93 @@ # # SPDX-License-Identifier: CC0-1.0 -tutorial: false +tutorial: true + run: - name: "test" # use this to keep track of runs with different settings + name: "test-elec" # use this to keep track of runs with different settings disable_progressbar: true shared_resources: policy: false scenario: - ll: - - lv1.25 clusters: - - 38 + - 5 opts: - - Co2L-24h + - '' -countries: ['AL', 'AT', 'BA', 'BE', 'BG', 'CH', 'CZ', 'DE', 'DK', 'EE', 'ES', 'FI', 'FR', 'GB', 'GR', 'HR', 'HU', 'IE', 'IT', 'LT', 'LU', 'LV', 'ME', 'MK', 'NL', 'NO', 'PL', 'PT', 'RO', 'RS', 'SE', 'SI', 'SK'] +countries: ['BE'] snapshots: - start: "2020-01-01" - end: "2020-01-08" - -enable: - retrieve: auto - retrieve_databundle: false - retrieve_cost_data: false - build_cutout: false - custom_busmap: false - drop_leap_day: true + start: "2013-03-01" + end: "2013-03-08" electricity: - voltages: [200., 220., 300., 380., 400., 500., 750.] - base_network: osm-prebuilt - osm-prebuilt-version: 0.4 - gaslimit_enable: false - gaslimit: false - co2limit_enable: false - co2limit: 7.75e+7 - co2base: 1.487e+9 - - operational_reserve: - activate: false - epsilon_load: 0.02 - epsilon_vres: 0.02 - contingency: 4000 - - max_hours: - battery: 6 - H2: 168 + co2limit_enable: true + co2limit: 100.e+6 extendable_carriers: - Generator: [solar, solar-hsat, offsolar, onwind, offwind-ac, offwind-float, wave-farshore, wave-nearshore, wave-shallow] - StorageUnit: [battery, H2] # battery, H2 - Store: [] + Generator: [solar, solar-hsat, onwind, offwind-ac, offwind-dc, offwind-float, OCGT, CCGT, nuclear, wave-farshore, wave-nearshore, wave-shallow, offsolar] + StorageUnit: [battery] + Store: [H2] Link: [H2 pipeline] - powerplants_filter: (DateOut >= 2023 or DateOut != DateOut) and not (Country == 'Germany' and Fueltype == 'Nuclear') - custom_powerplants: false - everywhere_powerplants: [] - - conventional_carriers: [] - renewable_carriers: [solar, solar-hsat, offsolar, onwind, offwind-ac, offwind-float, wave-farshore, wave-nearshore, wave-shallow] - + renewable_carriers: [solar, solar-hsat, onwind, offwind-ac, offwind-dc, offwind-float, wave-farshore, wave-nearshore, wave-shallow, offsolar] estimate_renewable_capacities: enable: true - from_opsd: true - year: 2022 + from_powerplantmatching: true + year: 2020 expansion_limit: false technology_mapping: - Offshore: [offsolar, offwind-ac, offwind-float, wave-farshore, wave-nearshore, wave-shallow] - # Onshore: [onwind] - # PV: [solar, solar-hsat] - - autarky: - enable: false - by_country: false - + Offshore: offwind-ac + Onshore: onwind + PV: solar atlite: - default_cutout: era5_2020_cutout - nprocesses: 4 + default_cutout: be-03-2013-era5 cutouts: - era5_2020_cutout: + be-03-2013-era5: module: era5 - x: [-12., 42.] - y: [32., 73] - time: ["2020-01-01", "2021-01-01"] - # mrel_2020_cutout: - # module: cerra - # x: [-12., 42.] - # y: [32., 73] - # time: ["2020-01-01", "2021-01-01"] - # cerra_2020_cutout: - # module: mrel_wave - # x: [-12., 42.] - # y: [32., 73] - # time: ["2020-01-01", "2021-01-01"] - + x: [4., 15.] + y: [46., 56.] + time: ["2013-03-01", "2013-03-08"] renewable: - onwind: - cutout: era5_2020_cutout - resource: - method: wind - turbine: Vestas_V112_3MW - smooth: false - add_cutout_windspeed: true - capacity_per_sqkm: 3 - # correction_factor: 0.93 - corine: - grid_codes: [12, 13, 14, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 31, 32] - distance: 5000 - distance_grid_codes: [1, 2, 3, 4, 5, 6, 15, 16, 17, 33, 34, 35, 38, 42, 43] - luisa: false - # grid_codes: [1111, 1121, 1122, 1123, 1130, 1210, 1221, 1222, 1230, 1241, 1242] - # distance: 1000 - # distance_grid_codes: [1111, 1121, 1122, 1123, 1130, 1210, 1221, 1222, 1230, 1241, 1242] - natura: true - excluder_resolution: 100 - clip_p_max_pu: 1.e-2 offwind-ac: - cutout: era5_2020_cutout - resource: - method: wind - turbine: NREL_ReferenceTurbine_2020ATB_12MW_offshore - smooth: false - add_cutout_windspeed: true - capacity_per_sqkm: 7 - correction_factor: 0.8855 - corine: [44, 255] - luisa: false # [0, 5230] - natura: true - ship_threshold: 400 - max_depth: 60 - min_depth: 5 - max_shore_distance: 10000 # 10 Km - min_shore_distance: 3000 # 3 Km - excluder_resolution: 200 - clip_p_max_pu: 1.e-2 - landfall_length: 10 - offwind-float: #Floating offshore wind dc - cutout: era5_2020_cutout #cerra_2020_cutout - resource: - method: wind - turbine: NREL_ReferenceTurbine_2020ATB_15MW_offshore - smooth: false - add_cutout_windspeed: true - capacity_per_sqkm: 9 - correction_factor: 0.8855 - corine: [44, 255] - natura: true # area excluded if true - ship_threshold: 400 - max_depth: 250 - min_depth: 60 - max_shore_distance: 50000 # 50 Km - min_shore_distance: 10000 # 40 Km - excluder_resolution: 200 - potential: simple # or conservative - clip_p_max_pu: 1.e-2 - landfall_length: 10 - solar: - cutout: era5_2020_cutout - resource: - method: pv - panel: CSi - orientation: - slope: 35. - azimuth: 180. - capacity_per_sqkm: 1 - # correction_factor: 0.854337 - corine: - grid_codes: [12, 13, 14, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 31, 32] - distance: 3000 - distance_grid_codes: [1, 2, 3, 4, 5, 6, 15, 16, 17, 33, 34, 35, 38, 42, 43] - 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 - solar-hsat: - cutout: era5_2020_cutout - resource: - method: pv - panel: CSi - orientation: - slope: 35. - azimuth: 180. - tracking: horizontal - capacity_per_sqkm: 0.85 # 15% higher land usage acc. to NREL - corine: - grid_codes: [12, 13, 14, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 31, 32] - distance: 1000 - distance_grid_codes: [1, 2, 3, 4, 5, 6, 15, 16, 17, 33, 34, 35, 38, 42, 43] - 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 - offsolar: - cutout: era5_2020_cutout - resource: - method: pv - panel: CSi - orientation: - slope: 35. - azimuth: 180. - capacity_per_sqkm: 1.7 - # correction_factor: 0.854337 - corine: [44, 255] - natura: true - ship_threshold: 400 - max_depth: 150 - min_depth: 0 - excluder_resolution: 200 - clip_p_max_pu: 1.e-2 - hydro: - cutout: era5_2020_cutout - carriers: [ror, PHS, hydro] - PHS_max_hours: 6 - hydro_max_hours: "energy_capacity_totals_by_country" # one of energy_capacity_totals_by_country, estimate_by_large_installations or a float - flatten_dispatch: false - flatten_dispatch_buffer: 0.2 - clip_min_inflow: 1.0 - eia_norm_year: false - eia_correct_by_capacity: false - eia_approximate_missing: false + max_depth: false + offwind-dc: + max_depth: false + offwind-float: + max_depth: false + min_depth: false wave-farshore: - cutout: era5_2020_cutout #mrel_2020.cutout - resource: - method: wave - converter: Farshore_750kW - capacity_per_sqkm: 50 #for the packing density the safe assumption is 10-20 MW/Km2, but with good assessment this is 20-50 MW/Km2 - correction_factor: 1 - corine: [44, 255] - luisa: false # [0, 5230] - natura: false # area excluded if true - max_depth: 250 - min_depth: 80 - max_shore_distance: 100000 # 100 Km -# min_shore_distance: 40000 # 40 Km - excluder_resolution: 200 - # potential: simple # or conservative - clip_p_max_pu: 1.e-2 + max_depth: false + min_depth: false wave-nearshore: - cutout: era5_2020_cutout - resource: - method: wave - converter: Nearshore_400kW - capacity_per_sqkm: 35 #for the packing density the safe assumption is 10-20 MW/Km2, but with good assessment this is 20-40 MW/Km2 - correction_factor: 1 - corine: [44, 255] - luisa: false # [0, 5230] - natura: false # area excluded if true - max_depth: 100 - min_depth: 20 - max_shore_distance: 100000 # 100 Km - min_shore_distance: 10000 # 40 Km - # potential: simple # or conservative - clip_p_max_pu: 1.e-2 + max_depth: false + min_depth: false wave-shallow: - cutout: era5_2020_cutout - resource: - method: wave - converter: Shallow_290kW - capacity_per_sqkm: 30 #for the packing density the safe assumption is 10-20 MW/Km2, but with good assessment this is 20-40 MW/Km2 - correction_factor: 1 - corine: [44, 255] - luisa: false # [0, 5230] - natura: false # area excluded if true - max_depth: 20 - min_depth: 5 - max_shore_distance: 100000 # 100 Km - # potential: simple # or conservative - clip_p_max_pu: 1.e-2 - + max_depth: false + offsolar: + max_depth: false + min_depth: false clustering: - focus_weights: false - simplify_network: - to_substations: false - algorithm: kmeans # choose from: [hac, kmeans] - feature: solar+onwind-time - exclude_carriers: [] - remove_stubs: true - remove_stubs_across_borders: true - cluster_network: - algorithm: kmeans - feature: solar+onwind-time - exclude_carriers: [] - consider_efficiency_classes: false - aggregation_strategies: - generators: - committable: any - ramp_limit_up: max - ramp_limit_down: max + exclude_carriers: ["OCGT", "offwind-ac", "coal"] temporal: - resolution_elec: false - resolution_sector: false + resolution_elec: 24h lines: - types: - 200.: "Al/St 240/40 2-bundle 200.0" - 220.: "Al/St 240/40 2-bundle 220.0" - 300.: "Al/St 240/40 3-bundle 300.0" - 380.: "Al/St 240/40 4-bundle 380.0" - 400.: "Al/St 240/40 4-bundle 380.0" - 500.: "Al/St 240/40 4-bundle 380.0" - 750.: "Al/St 560/50 4-bundle 750.0" - s_max_pu: 0.7 - s_nom_max: .inf - max_extension: 20000 #MW - length_factor: 1 - reconnect_crimea: true - under_construction: 'keep' # 'zero': set capacity to zero, 'remove': remove, 'keep': with full capacity for lines in grid extract dynamic_line_rating: activate: true max_line_rating: 1.3 -# docs in https://pypsa-eur.readthedocs.io/en/latest/configuration.html#transmission_projects -transmission_projects: - enable: false - include: - tyndp2020: false - nep: false - manual: false - skip: - - upgraded_lines - - upgraded_links - status: - - under_construction - - in_permitting - - confirmed - #- planned_not_yet_permitted - #- under_consideration - new_link_capacity: zero #keep or zero - -costs: - year: 2030 - version: v0.9.2 - social_discountrate: 0.02 - fill_values: - FOM: 0 - VOM: 0.01 - efficiency: 1 - fuel: 0 - investment: 0 - lifetime: 25 - "CO2 intensity": 0 - "discount rate": 0.07 - # Marginal and capital costs can be overwritten - # capital_cost: - # onwind: 500 - marginal_cost: #VOM - solar: 0.01 - solar-hsat: 0.01 - onwind: 0.02 - offwind-ac: 0.02 - offwind-float: 0.02 - wave-farshore: 0.02 - wave-nearshore: 0.02 - wave-shallow: 0.02 - offwind: 1000 - # hydro: 0. - # H2: 0. - # electrolysis: 0. - # fuel cell: 0. - # battery: 0. - # battery inverter: 0. - emission_prices: - enable: false - co2: 0. - co2_monthly_prices: false solving: - constraints: - BAU: false - EQ: false solver: name: highs options: highs-default check_objective: enable: false - expected_value: 3.8120188094e+07 + expected_value: 3.8120188094e+07 \ No newline at end of file From 3837d2c210dece21230bc68a6b26704f4990c594 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 21 Apr 2026 12:47:09 +0000 Subject: [PATCH 17/19] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- config/test/config.electricity.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/test/config.electricity.yaml b/config/test/config.electricity.yaml index d655bb2384..08f91e70b8 100644 --- a/config/test/config.electricity.yaml +++ b/config/test/config.electricity.yaml @@ -92,4 +92,4 @@ solving: check_objective: enable: false - expected_value: 3.8120188094e+07 \ No newline at end of file + expected_value: 3.8120188094e+07 From 29e86e5eeb1cb843564856f7c070a4e481db9962 Mon Sep 17 00:00:00 2001 From: lmezilis Date: Tue, 21 Apr 2026 14:55:05 +0200 Subject: [PATCH 18/19] update pixi --- pixi.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pixi.toml b/pixi.toml index d59fc0e13e..dc95109684 100644 --- a/pixi.toml +++ b/pixi.toml @@ -43,7 +43,7 @@ sync-locks = """ """ [dependencies] -atlite = ">=0.3" +atlite = ">=0.3,!=0.5.0" bokeh = ">=3.8.0" cartopy = ">=0.25.0" copernicusmarine = ">=2.2.4" @@ -176,4 +176,4 @@ pytest = ">=8.4.2" [environments] doc = { features = ["doc"], no-default-feature = true } -test = ["test"] +test = ["test"] \ No newline at end of file From 98ebd44afa692e030e110d99aafed6d0fdae000b Mon Sep 17 00:00:00 2001 From: lmezilis Date: Tue, 21 Apr 2026 15:59:41 +0200 Subject: [PATCH 19/19] update sector_network --- scripts/prepare_sector_network.py | 32 ------------------------------- 1 file changed, 32 deletions(-) diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index 5b74831d32..605574d11c 100755 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -513,38 +513,6 @@ def update_wind_solar_costs( capital_cost.rename(index=lambda node: node + " " + tech) ) - for connection in ["farshore", "nearshore", "shallow"]: - tech = "wave-" + connection - landfall_length = landfall_lengths.get(tech, 0.0) - if tech not in n.generators.carrier.values: - continue - profile = snakemake.input["profile_wave-" + connection] - with xr.open_dataset(profile) as ds: - # if-statement for compatibility with old profiles - if "year" in ds.indexes: - ds = ds.sel(year=ds.year.min(), drop=True) - - distance = ds["average_distance"].to_pandas() - submarine_cost = costs.at[tech + "-connection-submarine", "fixed"] - underground_cost = costs.at[tech + "-connection-underground", "fixed"] - connection_cost = line_length_factor * ( - distance * submarine_cost + landfall_length * underground_cost - ) - - capital_cost = ( - costs.at["wave", "fixed"] - + costs.at[tech + "-station", "fixed"] - + connection_cost - ) - - logger.info( - f"Added connection cost of {connection_cost.min():0.0f}-{connection_cost.max():0.0f} Eur/MW/a to {tech}" - ) - - n.generators.loc[n.generators.carrier == tech, "capital_cost"] = ( - capital_cost.rename(index=lambda node: node + " " + tech) - ) - def add_carrier_buses( n: pypsa.Network,