diff --git a/Snakefile b/Snakefile index 5bca217a60..ec6d92a7a4 100644 --- a/Snakefile +++ b/Snakefile @@ -129,7 +129,7 @@ rule all: ( RESULTS + "maps/static/base_s_{clusters}_{opts}_{sector_opts}-h2_network_{planning_horizons}.pdf" - if config_provider("sector", "H2_network")(w) + if config_provider("transmission", "hydrogen", "enable")(w) else [] ), run=config["run"]["name"], @@ -139,7 +139,7 @@ rule all: ( RESULTS + "maps/static/base_s_{clusters}_{opts}_{sector_opts}-ch4_network_{planning_horizons}.pdf" - if config_provider("sector", "gas_network")(w) + if config_provider("transmission", "gas", "enable")(w) else [] ), run=config["run"]["name"], diff --git a/config/config.default.yaml b/config/config.default.yaml index 146429bbc1..e303c53379 100644 --- a/config/config.default.yaml +++ b/config/config.default.yaml @@ -112,7 +112,6 @@ electricity: - 400.0 - 500.0 - 750.0 - base_network: osm gaslimit_enable: false gaslimit: false co2limit_enable: false @@ -182,7 +181,101 @@ electricity: autarky: enable: false by_country: false - transmission_limit: vopt + +# docs in https://pypsa-eur.readthedocs.io/en/latest/configuration.html#transmission +transmission: + electricity: + enable: true + base_network: osm + transmission_limit: vopt + lines: + types: + 63.0: "94-AL1/15-ST1A 20.0" + 66.0: "94-AL1/15-ST1A 20.0" + 90.0: "184-AL1/30-ST1A 110.0" + 110.0: "184-AL1/30-ST1A 110.0" + 132.0: "243-AL1/39-ST1A 110.0" + 150.0: "243-AL1/39-ST1A 110.0" + 220.0: "Al/St 240/40 2-bundle 220.0" + 300.0: "Al/St 240/40 3-bundle 300.0" + 330.0: "Al/St 240/40 3-bundle 300.0" + 380.0: "Al/St 240/40 4-bundle 380.0" + 400.0: "Al/St 240/40 4-bundle 380.0" + 500.0: "Al/St 240/40 4-bundle 380.0" + 750.0: "Al/St 560/50 4-bundle 750.0" + s_max_pu: 0.7 + s_nom_max: .inf + max_extension: 20000 + length_factor: 1.25 + reconnect_crimea: true + under_construction: keep + dynamic_line_rating: + activate: false + cutout: default + correction_factor: 0.95 + max_voltage_difference: false + max_line_rating: false + links: + p_max_pu: 1.0 + p_min_pu: -1.0 + p_nom_max: .inf + max_extension: 30000 + length_factor: 1.25 + under_construction: keep + efficiency: + enable: true + efficiency_static: 0.98 + efficiency_per_1000km: 0.977 + transformers: + x: 0.1 + s_nom: 2000.0 + type: "" + projects: + enable: true + include: + tyndp2020: true + nep: true + manual: true + skip: + - upgraded_lines + - upgraded_links + status: + - under_construction + - in_permitting + - confirmed + new_link_capacity: zero + electricity_distribution: + enable: true + cost_factor: 1.0 + efficiency: + enable: true + efficiency_static: 0.97 + hydrogen: + enable: true + gabriel_filter_min_degree: 1 + max_offshore_haversine_distance: .inf + length_factor: 1.25 + cost_factor: 1 + retrofit: + enable: false + capacity_per_ch4: 0.6 + efficiency: + enable: true + efficiency_per_1000km: 1.0 + compression_per_1000km: 0.018 + carbon_dioxide: + enable: true + gabriel_filter_min_degree: 1 + max_offshore_haversine_distance: .inf + length_factor: 1.25 + cost_factor: 1 + gas: + enable: true + connectivity_upgrade: 1 + efficiency: + enable: true + efficiency_per_1000km: 1.0 + compression_per_1000km: 0.01 # docs in https://pypsa-eur.readthedocs.io/en/latest/configuration.html#atlite atlite: @@ -427,66 +520,6 @@ conventional: nuclear: p_max_pu: data/nuclear_p_max_pu.csv -# docs in https://pypsa-eur.readthedocs.io/en/latest/configuration.html#lines -lines: - types: - 63.0: "94-AL1/15-ST1A 20.0" - 66.0: "94-AL1/15-ST1A 20.0" - 90.0: "184-AL1/30-ST1A 110.0" - 110.0: "184-AL1/30-ST1A 110.0" - 132.0: "243-AL1/39-ST1A 110.0" - 150.0: "243-AL1/39-ST1A 110.0" - 220.0: "Al/St 240/40 2-bundle 220.0" - 300.0: "Al/St 240/40 3-bundle 300.0" - 330.0: "Al/St 240/40 3-bundle 300.0" - 380.0: "Al/St 240/40 4-bundle 380.0" - 400.0: "Al/St 240/40 4-bundle 380.0" - 500.0: "Al/St 240/40 4-bundle 380.0" - 750.0: "Al/St 560/50 4-bundle 750.0" - s_max_pu: 0.7 - s_nom_max: .inf - max_extension: 20000 - length_factor: 1.25 - reconnect_crimea: true - under_construction: keep - dynamic_line_rating: - activate: false - cutout: default - correction_factor: 0.95 - max_voltage_difference: false - max_line_rating: false - -# docs in https://pypsa-eur.readthedocs.io/en/latest/configuration.html#links -links: - p_max_pu: 1.0 - p_min_pu: -1.0 - p_nom_max: .inf - max_extension: 30000 - length_factor: 1.25 - under_construction: keep - -# docs in https://pypsa-eur.readthedocs.io/en/latest/configuration.html#transmission_projects -transmission_projects: - enable: true - include: - tyndp2020: true - nep: true - manual: true - skip: - - upgraded_lines - - upgraded_links - status: - - under_construction - - in_permitting - - confirmed - new_link_capacity: zero - -# docs in https://pypsa-eur.readthedocs.io/en/latest/configuration.html#transformers -transformers: - x: 0.1 - s_nom: 2000.0 - type: "" - # docs in https://pypsa-eur.readthedocs.io/en/latest/configuration.html#load load: fill_gaps: @@ -870,8 +903,6 @@ sector: co2_sequestration_cost: 30 co2_sequestration_lifetime: 50 co2_spatial: true - co2_network: true - co2_network_cost_factor: 1 cc_fraction: 0.9 hydrogen_underground_storage: true hydrogen_underground_storage_locations: @@ -900,33 +931,8 @@ sector: use_methanation_waste_heat: 0.25 use_fuel_cell_waste_heat: 1 use_electrolysis_waste_heat: 0.25 - electricity_transmission_grid: true - electricity_distribution_grid: true - electricity_distribution_grid_cost_factor: 1.0 - electricity_grid_connection: true - transmission_efficiency: - enable: - - DC - - H2 pipeline - - gas pipeline - - electricity distribution grid - DC: - efficiency_static: 0.98 - efficiency_per_1000km: 0.977 - H2 pipeline: - efficiency_per_1000km: 1 - compression_per_1000km: 0.018 - gas pipeline: - efficiency_per_1000km: 1 - compression_per_1000km: 0.01 - electricity distribution grid: - efficiency_static: 0.97 - H2_network: true - gas_network: true - H2_retrofit: false - H2_retrofit_capacity_per_CH4: 0.6 - gas_network_connectivity_upgrade: 1 - gas_distribution_grid: true + electricity_grid_connection_cost: true + gas_distribution_grid_cost: true gas_distribution_grid_cost_factor: 1.0 biomass_spatial: true biomass_transport: false diff --git a/config/examples/config.distribution-grid-experimental.yaml b/config/examples/config.distribution-grid-experimental.yaml index 06642fe5ae..3399f86093 100644 --- a/config/examples/config.distribution-grid-experimental.yaml +++ b/config/examples/config.distribution-grid-experimental.yaml @@ -45,10 +45,13 @@ countries: electricity: voltages: [63., 66., 90., 110., 132., 150., 220., 300., 330., 380., 400., 500., 750.] - base_network: osm -transmission_projects: - enable: false +transmission: + electricity: + enable: true + base_network: osm + projects: + enable: false data: osm: diff --git a/config/examples/config.entsoe-all.yaml b/config/examples/config.entsoe-all.yaml index 6f5627e6ee..2ca21bf5a9 100644 --- a/config/examples/config.entsoe-all.yaml +++ b/config/examples/config.entsoe-all.yaml @@ -31,5 +31,7 @@ electricity: co2limit: 9.59e+7 co2base: 1.918e+9 -lines: - reconnect_crimea: true +transmission: + electricity: + lines: + reconnect_crimea: true diff --git a/config/examples/config.iterative.yaml b/config/examples/config.iterative.yaml index 18076927e2..4a5a2a5a9e 100644 --- a/config/examples/config.iterative.yaml +++ b/config/examples/config.iterative.yaml @@ -28,7 +28,6 @@ snapshots: end: "2013-03-08" electricity: - extendable_carriers: Generator: [OCGT] StorageUnit: [battery] @@ -37,6 +36,15 @@ electricity: renewable_carriers: [solar, solar-hsat, onwind, offwind-ac, offwind-dc, offwind-float] +transmission: + hydrogen: + retrofit: + enable: true + carbon_dioxide: + enable: false + gas: + enable: true + atlite: default_cutout: be-03-2013-era5 cutouts: @@ -60,8 +68,6 @@ clustering: resolution_sector: 24h sector: - gas_network: true - H2_retrofit: true district_heating: ptes: supplemental_heating: @@ -85,7 +91,6 @@ sector: regional_co2_sequestration_potential: enable: false co2_spatial: false - co2_network: false methanol: methanol_to_power: ocgt: false diff --git a/config/examples/config.osm-release.yaml b/config/examples/config.osm-release.yaml index 47461fde41..2a7ff4699b 100644 --- a/config/examples/config.osm-release.yaml +++ b/config/examples/config.osm-release.yaml @@ -52,10 +52,13 @@ electricity: - 400.0 - 500.0 - 750.0 - base_network: osm -transmission_projects: - enable: false +transmission: + electricity: + enable: true + base_network: osm + projects: + enable: false data: osm: diff --git a/config/examples/config.validation.yaml b/config/examples/config.validation.yaml index d48b5295b4..2c45fa4954 100644 --- a/config/examples/config.validation.yaml +++ b/config/examples/config.validation.yaml @@ -61,12 +61,13 @@ conventional: biomass: p_max_pu: 0.65 -lines: - s_max_pu: 0.23 - under_construction: 'remove' - -links: - include_tyndp: false +transmission: + electricity: + lines: + s_max_pu: 0.23 + under_construction: 'remove' + projects: + enable: false costs: year: 2020 diff --git a/config/schema.default.json b/config/schema.default.json index 0468c33193..2c9cdde247 100644 --- a/config/schema.default.json +++ b/config/schema.default.json @@ -2063,16 +2063,6 @@ }, "type": "array" }, - "base_network": { - "default": "osm", - "description": "Specify the underlying base network, i.e. GridKit (based on ENTSO-E web map extract), OpenStreetMap (OSM), or TYNDP.", - "enum": [ - "entsoegridkit", - "osm", - "tyndp" - ], - "type": "string" - }, "gaslimit_enable": { "default": false, "description": "Add an overall absolute gas limit configured in `electricity: gaslimit`.", @@ -2337,10 +2327,71 @@ "type": "boolean" } } + } + } + }, + "ElectricityProjectsConfig": { + "description": "Configuration for `electricity.projects` settings.", + "properties": { + "enable": { + "default": true, + "description": "Whether to integrate this transmission projects or not.", + "type": "boolean" }, - "transmission_limit": { - "default": "vopt", - "description": "Limit on transmission expansion. The first part can be `v` (for setting a limit on line volume) or `c` (for setting a limit on line cost). The second part can be `opt` or a float bigger than one (e.g. 1.25). If `opt` is chosen line expansion is optimised according to its capital cost (where the choice `v` only considers overhead costs for HVDC transmission lines, while `c` uses more accurate costs distinguishing between overhead and underwater sections and including inverter pairs). The setting `v1.25` will limit the total volume of line expansion to 25% of currently installed capacities weighted by individual line lengths. The setting `c1.25` will allow to build a transmission network that costs no more than 25 % more than the current system.", + "include": { + "description": "Configuration for `electricity.projects.include` settings.", + "properties": { + "tyndp2020": { + "default": true, + "description": "Whether to integrate the TYNDP 2020 dataset.", + "type": "boolean" + }, + "nep": { + "default": true, + "description": "Whether to integrate the German network development plan dataset.", + "type": "boolean" + }, + "manual": { + "default": true, + "description": "Whether to integrate the manually added transmission projects. They are taken from the previously existing links_tyndp.csv file.", + "type": "boolean" + } + } + }, + "skip": { + "description": "Type of lines to skip from all transmission projects. Possible values are: `upgraded_lines`, `upgraded_links`, `new_lines`, `new_links`.", + "items": { + "type": "string" + }, + "type": "array" + }, + "status": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "additionalProperties": { + "items": { + "type": "string" + }, + "type": "array" + }, + "type": "object" + } + ], + "description": "Status to include into the model as list or as dict with name of project and status to include. Possible values for status are `under_construction`, `in_permitting`, `confirmed`, `planned_not_yet_permitted`, `under_consideration`." + }, + "new_link_capacity": { + "default": "zero", + "description": "Whether to set the new link capacity to the provided capacity or set it to zero.", + "enum": [ + "zero", + "keep" + ], "type": "string" } } @@ -2767,6 +2818,26 @@ "keep" ], "type": "string" + }, + "efficiency": { + "description": "Configuration for `transmission.electricity.links.efficiency` settings.", + "properties": { + "enable": { + "default": true, + "description": "Apply transmission efficiency for DC links.", + "type": "boolean" + }, + "efficiency_static": { + "default": 0.98, + "description": "Static DC transmission efficiency.", + "type": "number" + }, + "efficiency_per_1000km": { + "default": 0.977, + "description": "Distance-dependent DC efficiency factor per 1000 km.", + "type": "number" + } + } } } }, @@ -4698,16 +4769,6 @@ "description": "Add option to spatially resolve carrier representing stored carbon dioxide. This allows for more detailed modelling of CCUTS, e.g. regarding the capturing of industrial process emissions, usage as feedstock for electrofuels, transport of carbon dioxide, and geological sequestration sites.", "type": "boolean" }, - "co2_network": { - "default": true, - "description": "Add option for planning a new carbon dioxide transmission network.", - "type": "boolean" - }, - "co2_network_cost_factor": { - "default": 1, - "description": "The cost factor for the capital cost of the carbon dioxide transmission network.", - "type": "number" - }, "cc_fraction": { "default": 0.9, "description": "The default fraction of CO2 captured with post-combustion capture.", @@ -4829,97 +4890,14 @@ "description": "Add option for using waste heat of electrolysis in district heating networks.", "type": "number" }, - "electricity_transmission_grid": { - "default": true, - "description": "Switch for enabling/disabling the electricity transmission grid.", - "type": "boolean" - }, - "electricity_distribution_grid": { - "default": true, - "description": "Add a simplified representation of the exchange capacity between transmission and distribution grid level through a link.", - "type": "boolean" - }, - "electricity_distribution_grid_cost_factor": { - "default": 1.0, - "description": "Multiplies the investment cost of the electricity distribution grid.", - "type": "number" - }, - "electricity_grid_connection": { + "electricity_grid_connection_cost": { "default": true, "description": "Add the cost of electricity grid connection for onshore wind and solar.", "type": "boolean" }, - "transmission_efficiency": { - "description": "Configuration for `sector.transmission_efficiency` settings.", - "properties": { - "enable": { - "description": "Switch to select the carriers for which transmission efficiency is to be added. Carriers not listed assume lossless transmission.", - "items": { - "type": "string" - }, - "type": "array" - }, - "DC": { - "additionalProperties": { - "type": "number" - }, - "description": "DC transmission efficiency.", - "type": "object" - }, - "H2 pipeline": { - "additionalProperties": { - "type": "number" - }, - "description": "H2 pipeline transmission efficiency.", - "type": "object" - }, - "gas pipeline": { - "additionalProperties": { - "type": "number" - }, - "description": "Gas pipeline transmission efficiency.", - "type": "object" - }, - "electricity distribution grid": { - "additionalProperties": { - "type": "number" - }, - "description": "Electricity distribution grid efficiency.", - "type": "object" - } - } - }, - "H2_network": { - "default": true, - "description": "Add option for new hydrogen pipelines.", - "type": "boolean" - }, - "gas_network": { - "default": true, - "description": "Add existing natural gas infrastructure, incl. LNG terminals, production and entry-points. The existing gas network is added with a lossless transport model. A length-weighted `k-edge augmentation algorithm `_ can be run to add new candidate gas pipelines such that all regions of the model can be connected to the gas network. When activated, all the gas demands are regionally disaggregated as well.", - "markdownDescription": "Add existing natural gas infrastructure, incl. LNG terminals, production and entry-points. The existing gas network is added with a lossless transport model. A length-weighted [k-edge augmentation algorithm ](https://networkx.org/documentation/stable/reference/algorithms/generated/networkx.algorithms.connectivity.edge_augmentation.k_edge_augmentation.html#networkx.algorithms.connectivity.edge_augmentation.k_edge_augmentation) can be run to add new candidate gas pipelines such that all regions of the model can be connected to the gas network. When activated, all the gas demands are regionally disaggregated as well.", - "type": "boolean" - }, - "H2_retrofit": { - "default": false, - "description": "Add option for retrofiting existing pipelines to transport hydrogen.", - "type": "boolean" - }, - "H2_retrofit_capacity_per_CH4": { - "default": 0.6, - "description": "The ratio for H2 capacity per original CH4 capacity of retrofitted pipelines. The `European Hydrogen Backbone (April, 2020) p.15 `_ 60% of original natural gas capacity could be used in cost-optimal case as H2 capacity.", - "markdownDescription": "The ratio for H2 capacity per original CH4 capacity of retrofitted pipelines. The [European Hydrogen Backbone (April, 2020) p.15 ](https://gasforclimate2050.eu/wp-content/uploads/2020/07/2020_European-Hydrogen-Backbone_Report.pdf) 60% of original natural gas capacity could be used in cost-optimal case as H2 capacity.", - "type": "number" - }, - "gas_network_connectivity_upgrade": { - "default": 1, - "description": "The number of desired edge connectivity (k) in the length-weighted `k-edge augmentation algorithm `_ used for the gas network.", - "markdownDescription": "The number of desired edge connectivity (k) in the length-weighted [k-edge augmentation algorithm ](https://networkx.org/documentation/stable/reference/algorithms/generated/networkx.algorithms.connectivity.edge_augmentation.k_edge_augmentation.html#networkx.algorithms.connectivity.edge_augmentation.k_edge_augmentation) used for the gas network.", - "type": "number" - }, - "gas_distribution_grid": { + "gas_distribution_grid_cost": { "default": true, - "description": "Add a gas distribution grid.", + "description": "Add gas distribution grid costs to gas-consuming components.", "type": "boolean" }, "gas_distribution_grid_cost_factor": { @@ -5603,116 +5581,500 @@ } } }, - "TransmissionProjectsConfig": { - "description": "Configuration for `transmission_projects` settings.", + "TransmissionConfig": { + "description": "Configuration for `transmission` settings.", "properties": { - "enable": { - "default": true, - "description": "Whether to integrate this transmission projects or not.", - "type": "boolean" - }, - "include": { - "description": "Configuration for `transmission_projects.include` settings.", + "electricity": { + "description": "Configuration for electricity transmission grid.", "properties": { - "tyndp2020": { - "default": true, - "description": "Whether to integrate the TYNDP 2020 dataset.", - "type": "boolean" - }, - "nep": { + "enable": { "default": true, - "description": "Whether to integrate the German network development plan dataset.", + "description": "Switch for enabling/disabling the electricity transmission grid.", "type": "boolean" }, - "manual": { - "default": true, - "description": "Whether to integrate the manually added transmission projects. They are taken from the previously existing links_tyndp.csv file.", - "type": "boolean" - } - } - }, - "skip": { - "description": "Type of lines to skip from all transmission projects. Possible values are: `upgraded_lines`, `upgraded_links`, `new_lines`, `new_links`.", - "items": { - "type": "string" - }, - "type": "array" - }, - "status": { - "anyOf": [ - { - "items": { - "type": "string" - }, - "type": "array" + "base_network": { + "default": "osm", + "description": "Specify the underlying base network, i.e. GridKit (based on ENTSO-E web map extract), OpenStreetMap (OSM), or TYNDP.", + "enum": [ + "entsoegridkit", + "osm", + "tyndp" + ], + "type": "string" }, - { - "additionalProperties": { - "items": { - "type": "string" - }, - "type": "array" - }, - "type": "object" - } - ], - "description": "Status to include into the model as list or as dict with name of project and status to include. Possible values for status are `under_construction`, `in_permitting`, `confirmed`, `planned_not_yet_permitted`, `under_consideration`." - }, - "new_link_capacity": { - "default": "zero", - "description": "Whether to set the new link capacity to the provided capacity or set it to zero.", - "enum": [ - "zero", - "keep" - ], - "type": "string" - } - } - }, - "_AdjustmentConfig": { - "description": "Configuration for adjustment settings (factor/absolute)", - "properties": { - "factor": { - "anyOf": [ - { - "type": "boolean" + "transmission_limit": { + "default": "vopt", + "description": "Limit on transmission expansion. The first part can be `v` (for setting a limit on line volume) or `c` (for setting a limit on line cost). The second part can be `opt` or a float bigger than one (e.g. 1.25). If `opt` is chosen line expansion is optimised according to its capital cost (where the choice `v` only considers overhead costs for HVDC transmission lines, while `c` uses more accurate costs distinguishing between overhead and underwater sections and including inverter pairs). The setting `v1.25` will limit the total volume of line expansion to 25% of currently installed capacities weighted by individual line lengths. The setting `c1.25` will allow to build a transmission network that costs no more than 25 % more than the current system.", + "type": "string" }, - { - "additionalProperties": { - "additionalProperties": { + "lines": { + "description": "Configuration for `lines` settings.", + "properties": { + "types": { "additionalProperties": { - "anyOf": [ - { - "type": "number" - }, - { - "additionalProperties": { - "type": "number" - }, - "type": "object" - } - ] + "type": "string" }, + "description": "Specifies line types to assume for the different voltage levels of the ENTSO-E grid extraction. Should normally handle voltage levels 220, 300, and 380 kV.", "type": "object" }, - "type": "object" - }, - "type": "object" - } - ], - "default": false, - "description": "Multiply original value with given factor" - }, - "absolute": { - "anyOf": [ - { - "type": "boolean" - }, - { - "additionalProperties": { - "additionalProperties": { - "additionalProperties": { - "anyOf": [ + "s_max_pu": { + "default": 0.7, + "description": "Correction factor for line capacities (`s_nom`) to approximate N-1 security and reserve capacity for reactive power flows.", + "type": "number" + }, + "s_nom_max": { + "default": null, + "description": "Global upper limit for the maximum capacity of each extendable line (MW).", + "type": "number" + }, + "max_extension": { + "default": 20000, + "description": "Upper limit for the extended capacity of each extendable line (MW).", + "type": "number" + }, + "length_factor": { + "default": 1.25, + "description": "Correction factor to account for the fact that buses are *not* connected by lines through air-line distance.", + "type": "number" + }, + "reconnect_crimea": { + "default": true, + "description": "Whether to reconnect Crimea to the Ukrainian grid.", + "type": "boolean" + }, + "under_construction": { + "default": "keep", + "description": "Specifies how to handle lines which are currently under construction.", + "enum": [ + "zero", + "remove", + "keep" + ], + "type": "string" + }, + "dynamic_line_rating": { + "description": "Configuration for `lines.dynamic_line_rating` settings.", + "properties": { + "activate": { + "default": false, + "description": "Whether to take dynamic line rating into account.", + "type": "boolean" + }, + "cutout": { + "anyOf": [ + { + "type": "string" + }, + { + "items": { + "type": "string" + }, + "type": "array" + } + ], + "default": "default", + "description": "Specifies the weather data cutout file(s) to use." + }, + "correction_factor": { + "default": 0.95, + "description": "Factor to compensate for overestimation of wind speeds in hourly averaged wind data.", + "type": "number" + }, + "max_voltage_difference": { + "anyOf": [ + { + "type": "number" + }, + { + "const": false, + "type": "boolean" + } + ], + "default": false, + "description": "Maximum voltage angle difference in degrees or 'false' to disable." + }, + "max_line_rating": { + "anyOf": [ + { + "type": "number" + }, + { + "const": false, + "type": "boolean" + } + ], + "default": false, + "description": "Maximum line rating relative to nominal capacity without DLR, e.g. 1.3 or 'false' to disable." + } + } + } + } + }, + "links": { + "description": "Configuration for `links` settings.", + "properties": { + "p_max_pu": { + "default": 1.0, + "description": "Correction factor for link capacities `p_nom`.", + "type": "number" + }, + "p_min_pu": { + "default": -1.0, + "description": "Correction factor for link capacities `p_nom`.", + "type": "number" + }, + "p_nom_max": { + "default": null, + "description": "Global upper limit for the maximum capacity of each extendable DC link (MW).", + "type": "number" + }, + "max_extension": { + "default": 30000, + "description": "Upper limit for the extended capacity of each extendable DC link (MW).", + "type": "number" + }, + "length_factor": { + "default": 1.25, + "description": "Correction factor to account for the fact that buses are *not* connected by links through air-line distance.", + "type": "number" + }, + "under_construction": { + "default": "keep", + "description": "Specifies how to handle lines which are currently under construction.", + "enum": [ + "zero", + "remove", + "keep" + ], + "type": "string" + }, + "efficiency": { + "description": "Configuration for `transmission.electricity.links.efficiency` settings.", + "properties": { + "enable": { + "default": true, + "description": "Apply transmission efficiency for DC links.", + "type": "boolean" + }, + "efficiency_static": { + "default": 0.98, + "description": "Static DC transmission efficiency.", + "type": "number" + }, + "efficiency_per_1000km": { + "default": 0.977, + "description": "Distance-dependent DC efficiency factor per 1000 km.", + "type": "number" + } + } + } + } + }, + "transformers": { + "description": "Configuration for `transformers` settings.", + "properties": { + "x": { + "default": 0.1, + "description": "Series reactance in per unit (p.u.), using `s_nom` as base power of the transformer. Overwritten if `type` is specified.", + "type": "number" + }, + "s_nom": { + "default": 2000.0, + "description": "Limit of apparent power which can pass through branch (MVA). Overwritten if `type` is specified.", + "type": "number" + }, + "type": { + "default": "", + "description": "Specifies transformer types to assume for the transformers of the ENTSO-E grid extraction.", + "type": "string" + } + } + }, + "projects": { + "description": "Configuration for `electricity.projects` settings.", + "properties": { + "enable": { + "default": true, + "description": "Whether to integrate this transmission projects or not.", + "type": "boolean" + }, + "include": { + "description": "Configuration for `electricity.projects.include` settings.", + "properties": { + "tyndp2020": { + "default": true, + "description": "Whether to integrate the TYNDP 2020 dataset.", + "type": "boolean" + }, + "nep": { + "default": true, + "description": "Whether to integrate the German network development plan dataset.", + "type": "boolean" + }, + "manual": { + "default": true, + "description": "Whether to integrate the manually added transmission projects. They are taken from the previously existing links_tyndp.csv file.", + "type": "boolean" + } + } + }, + "skip": { + "description": "Type of lines to skip from all transmission projects. Possible values are: `upgraded_lines`, `upgraded_links`, `new_lines`, `new_links`.", + "items": { + "type": "string" + }, + "type": "array" + }, + "status": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "additionalProperties": { + "items": { + "type": "string" + }, + "type": "array" + }, + "type": "object" + } + ], + "description": "Status to include into the model as list or as dict with name of project and status to include. Possible values for status are `under_construction`, `in_permitting`, `confirmed`, `planned_not_yet_permitted`, `under_consideration`." + }, + "new_link_capacity": { + "default": "zero", + "description": "Whether to set the new link capacity to the provided capacity or set it to zero.", + "enum": [ + "zero", + "keep" + ], + "type": "string" + } + } + } + } + }, + "electricity_distribution": { + "properties": { + "enable": { + "default": true, + "description": "Add a simplified representation of the exchange capacity between transmission and distribution grid level through a link.", + "type": "boolean" + }, + "cost_factor": { + "default": 1.0, + "description": "Multiplies the investment cost of the electricity distribution grid.", + "type": "number" + }, + "efficiency": { + "description": "Configuration for `transmission.electricity_distribution.efficiency` settings.", + "properties": { + "enable": { + "default": true, + "description": "Apply electricity distribution efficiency.", + "type": "boolean" + }, + "efficiency_static": { + "default": 0.97, + "description": "Static electricity distribution efficiency.", + "type": "number" + } + } + } + }, + "description": "Configuration for simplified electricity distribution grid." + }, + "hydrogen": { + "properties": { + "enable": { + "default": true, + "description": "Enable transmission candidate generation for this carrier.", + "type": "boolean" + }, + "gabriel_filter_min_degree": { + "default": 1, + "description": "Minimum node degree target applied after Gabriel graph filtering. Set to >= 1 to activate Gabriel filtering with min-degree backfilling; 0 disables it.", + "minimum": 0, + "type": "integer" + }, + "max_offshore_haversine_distance": { + "default": null, + "description": "Maximum haversine distance in km for offshore transmission candidates. Candidate edges a total offshore length exceeding this threshold are excluded. Defaults to infinity (no limit).", + "exclusiveMinimum": 0, + "type": "number" + }, + "length_factor": { + "default": 1.25, + "description": "Multiplier applied to geometric corridor lengths.", + "exclusiveMinimum": 0, + "type": "number" + }, + "cost_factor": { + "default": 1, + "description": "Multiplier applied to the capital cost of transmission infrastructure.", + "exclusiveMinimum": 0, + "type": "number" + }, + "retrofit": { + "description": "Configuration for `transmission.hydrogen.retrofit` settings.", + "properties": { + "enable": { + "default": false, + "description": "Add option for retrofitting existing pipelines to transport hydrogen.", + "type": "boolean" + }, + "capacity_per_ch4": { + "default": 0.6, + "description": "The ratio for H2 capacity per original CH4 capacity of retrofitted pipelines. The `European Hydrogen Backbone (April, 2020) p.15 `_ 60% of original natural gas capacity could be used in cost-optimal case as H2 capacity.", + "markdownDescription": "The ratio for H2 capacity per original CH4 capacity of retrofitted pipelines. The [European Hydrogen Backbone (April, 2020) p.15 ](https://gasforclimate2050.eu/wp-content/uploads/2020/07/2020_European-Hydrogen-Backbone_Report.pdf) 60% of original natural gas capacity could be used in cost-optimal case as H2 capacity.", + "type": "number" + } + } + }, + "efficiency": { + "description": "Configuration for `transmission..efficiency` settings.", + "properties": { + "enable": { + "default": true, + "description": "Apply transmission efficiency for this carrier.", + "type": "boolean" + }, + "efficiency_per_1000km": { + "default": 1, + "description": "Distance-dependent efficiency factor per 1000 km.", + "type": "number" + }, + "compression_per_1000km": { + "default": 0, + "description": "Additional electricity consumption for compression per 1000 km.", + "type": "number" + } + } + } + }, + "description": "Configuration for hydrogen transmission candidates." + }, + "carbon_dioxide": { + "description": "Configuration for a single transmission carrier.", + "properties": { + "enable": { + "default": true, + "description": "Enable transmission candidate generation for this carrier.", + "type": "boolean" + }, + "gabriel_filter_min_degree": { + "default": 1, + "description": "Minimum node degree target applied after Gabriel graph filtering. Set to >= 1 to activate Gabriel filtering with min-degree backfilling; 0 disables it.", + "minimum": 0, + "type": "integer" + }, + "max_offshore_haversine_distance": { + "default": null, + "description": "Maximum haversine distance in km for offshore transmission candidates. Candidate edges a total offshore length exceeding this threshold are excluded. Defaults to infinity (no limit).", + "exclusiveMinimum": 0, + "type": "number" + }, + "length_factor": { + "default": 1.25, + "description": "Multiplier applied to geometric corridor lengths.", + "exclusiveMinimum": 0, + "type": "number" + }, + "cost_factor": { + "default": 1, + "description": "Multiplier applied to the capital cost of transmission infrastructure.", + "exclusiveMinimum": 0, + "type": "number" + } + } + }, + "gas": { + "properties": { + "enable": { + "default": true, + "description": "Add existing natural gas infrastructure, incl. LNG terminals, production and entry-points. A length-weighted `k-edge augmentation algorithm `_ can be run to add new candidate gas pipelines such that all regions of the model can be connected to the gas network. When activated, all the gas demands are regionally disaggregated as well.", + "markdownDescription": "Add existing natural gas infrastructure, incl. LNG terminals, production and entry-points. A length-weighted [k-edge augmentation algorithm ](https://networkx.org/documentation/stable/reference/algorithms/generated/networkx.algorithms.connectivity.edge_augmentation.k_edge_augmentation.html#networkx.algorithms.connectivity.edge_augmentation.k_edge_augmentation) can be run to add new candidate gas pipelines such that all regions of the model can be connected to the gas network. When activated, all the gas demands are regionally disaggregated as well.", + "type": "boolean" + }, + "connectivity_upgrade": { + "default": 1, + "description": "The number of desired edge connectivity (k) in the length-weighted `k-edge augmentation algorithm `_ used for the gas network.", + "markdownDescription": "The number of desired edge connectivity (k) in the length-weighted [k-edge augmentation algorithm ](https://networkx.org/documentation/stable/reference/algorithms/generated/networkx.algorithms.connectivity.edge_augmentation.k_edge_augmentation.html#networkx.algorithms.connectivity.edge_augmentation.k_edge_augmentation) used for the gas network.", + "type": "number" + }, + "efficiency": { + "description": "Configuration for `transmission..efficiency` settings.", + "properties": { + "enable": { + "default": true, + "description": "Apply transmission efficiency for this carrier.", + "type": "boolean" + }, + "efficiency_per_1000km": { + "default": 1, + "description": "Distance-dependent efficiency factor per 1000 km.", + "type": "number" + }, + "compression_per_1000km": { + "default": 0, + "description": "Additional electricity consumption for compression per 1000 km.", + "type": "number" + } + } + } + }, + "description": "Configuration for methane gas transmission candidates." + } + } + }, + "_AdjustmentConfig": { + "description": "Configuration for adjustment settings (factor/absolute)", + "properties": { + "factor": { + "anyOf": [ + { + "type": "boolean" + }, + { + "additionalProperties": { + "additionalProperties": { + "additionalProperties": { + "anyOf": [ + { + "type": "number" + }, + { + "additionalProperties": { + "type": "number" + }, + "type": "object" + } + ] + }, + "type": "object" + }, + "type": "object" + }, + "type": "object" + } + ], + "default": false, + "description": "Multiply original value with given factor" + }, + "absolute": { + "anyOf": [ + { + "type": "boolean" + }, + { + "additionalProperties": { + "additionalProperties": { + "additionalProperties": { + "anyOf": [ { "type": "number" }, @@ -6207,6 +6569,26 @@ } } }, + "_DCEfficiencyConfig": { + "description": "Configuration for `transmission.electricity.links.efficiency` settings.", + "properties": { + "enable": { + "default": true, + "description": "Apply transmission efficiency for DC links.", + "type": "boolean" + }, + "efficiency_static": { + "default": 0.98, + "description": "Static DC transmission efficiency.", + "type": "number" + }, + "efficiency_per_1000km": { + "default": 0.977, + "description": "Distance-dependent DC efficiency factor per 1000 km.", + "type": "number" + } + } + }, "_DataSourceConfig": { "description": "Configuration for a single data source.", "properties": { @@ -6380,6 +6762,21 @@ } } }, + "_ElectricityDistributionEfficiencyConfig": { + "description": "Configuration for `transmission.electricity_distribution.efficiency` settings.", + "properties": { + "enable": { + "default": true, + "description": "Apply electricity distribution efficiency.", + "type": "boolean" + }, + "efficiency_static": { + "default": 0.97, + "description": "Static electricity distribution efficiency.", + "type": "number" + } + } + }, "_EmissionPricesConfig": { "description": "Configuration for `costs.emission_prices` settings.", "properties": { @@ -6694,6 +7091,22 @@ } } }, + "_HydrogenRetrofitConfig": { + "description": "Configuration for `transmission.hydrogen.retrofit` settings.", + "properties": { + "enable": { + "default": false, + "description": "Add option for retrofitting existing pipelines to transport hydrogen.", + "type": "boolean" + }, + "capacity_per_ch4": { + "default": 0.6, + "description": "The ratio for H2 capacity per original CH4 capacity of retrofitted pipelines. The `European Hydrogen Backbone (April, 2020) p.15 `_ 60% of original natural gas capacity could be used in cost-optimal case as H2 capacity.", + "markdownDescription": "The ratio for H2 capacity per original CH4 capacity of retrofitted pipelines. The [European Hydrogen Backbone (April, 2020) p.15 ](https://gasforclimate2050.eu/wp-content/uploads/2020/07/2020_European-Hydrogen-Backbone_Report.pdf) 60% of original natural gas capacity could be used in cost-optimal case as H2 capacity.", + "type": "number" + } + } + }, "_ImportsConfig": { "description": "Configuration for `sector.imports` settings.", "properties": { @@ -6722,7 +7135,7 @@ } }, "_IncludeConfig": { - "description": "Configuration for `transmission_projects.include` settings.", + "description": "Configuration for `electricity.projects.include` settings.", "properties": { "tyndp2020": { "default": true, @@ -7604,505 +8017,927 @@ { "type": "string" }, - { - "items": { - "type": "string" + { + "items": { + "type": "string" + }, + "type": "array" + } + ], + "default": "default", + "description": "Specifies the weather data cutout file(s) to use." + }, + "resource": { + "description": "Configuration for solar resource settings.", + "properties": { + "method": { + "default": "pv", + "description": "A superordinate technology type.", + "type": "string" + }, + "panel": { + "anyOf": [ + { + "type": "string" + }, + { + "additionalProperties": { + "type": "string" + }, + "type": "object" + } + ], + "default": "CSi", + "description": "Specifies the solar panel technology and its characteristic attributes. Can be a string or a dictionary with years as keys which denote the year another panel model becomes available." + }, + "orientation": { + "additionalProperties": { + "type": "number" + }, + "description": "Panel orientation with slope and azimuth.", + "type": "object" + }, + "tracking": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Tracking type (e.g., 'horizontal')." + } + } + }, + "resource_classes": { + "default": 1, + "description": "Number of resource classes per clustered region.", + "type": "integer" + }, + "capacity_per_sqkm": { + "default": 5.1, + "description": "Allowable density of solar panel placement.", + "type": "number" + }, + "correction_factor": { + "default": 1.0, + "description": "A correction factor for the capacity factor (availability) time series.", + "type": "number" + }, + "corine": { + "anyOf": [ + { + "type": "boolean" + }, + { + "items": { + "type": "integer" + }, + "type": "array" + } + ], + "description": "Specifies areas according to CORINE Land Cover codes which are generally eligible for solar panel placement." + }, + "luisa": { + "anyOf": [ + { + "type": "boolean" + }, + { + "items": { + "type": "integer" + }, + "type": "array" + } + ], + "default": false, + "description": "Specifies areas according to the LUISA Base Map codes which are generally eligible for solar panel placement." + }, + "natura": { + "default": true, + "description": "Switch to exclude `Natura 2000 `_ natural protection areas. Area is excluded if `true`.", + "markdownDescription": "Switch to exclude [Natura 2000 ](https://en.wikipedia.org/wiki/Natura_2000) natural protection areas. Area is excluded if `true`.", + "type": "boolean" + }, + "excluder_resolution": { + "default": 100, + "description": "Resolution in meters on which to perform geographical eligibility analysis.", + "type": "number" + }, + "clip_p_max_pu": { + "default": 0.01, + "description": "To avoid too small values in the renewables` per-unit availability time series values below this threshold are set to zero.", + "type": "number" + } + } + }, + "_SolarResourceConfig": { + "description": "Configuration for solar resource settings.", + "properties": { + "method": { + "default": "pv", + "description": "A superordinate technology type.", + "type": "string" + }, + "panel": { + "anyOf": [ + { + "type": "string" + }, + { + "additionalProperties": { + "type": "string" + }, + "type": "object" + } + ], + "default": "CSi", + "description": "Specifies the solar panel technology and its characteristic attributes. Can be a string or a dictionary with years as keys which denote the year another panel model becomes available." + }, + "orientation": { + "additionalProperties": { + "type": "number" + }, + "description": "Panel orientation with slope and azimuth.", + "type": "object" + }, + "tracking": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Tracking type (e.g., 'horizontal')." + } + } + }, + "_SolidBiomassImportConfig": { + "description": "Configuration for `sector.solid_biomass_import` settings.", + "properties": { + "enable": { + "default": false, + "description": "Add option to include solid biomass imports.", + "type": "boolean" + }, + "price": { + "default": 54, + "description": "Price for importing solid biomass (currency/MWh).", + "type": "number" + }, + "max_amount": { + "default": 1390, + "description": "Maximum solid biomass import potential (TWh).", + "type": "number" + }, + "upstream_emissions_factor": { + "default": 0.1, + "description": "Upstream emissions of solid biomass imports.", + "type": "number" + } + } + }, + "_SolverConfig": { + "description": "Configuration for `solving.solver` settings.", + "properties": { + "name": { + "default": "gurobi", + "description": "Solver to use for optimisation problems in the workflow; e.g. clustering and linear optimal power flow.", + "type": "string" + }, + "options": { + "default": "gurobi-default", + "description": "Link to specific parameter settings.", + "type": "string" + } + } + }, + "_SolvingOptionsConfig": { + "description": "Configuration for `solving.options` settings.", + "properties": { + "clip_p_max_pu": { + "default": 0.01, + "description": "To avoid too small values in the renewables` per-unit availability time series values below this threshold are set to zero.", + "type": "number" + }, + "load_shedding": { + "description": "Configuration for `solving.options.load_shedding` settings.", + "properties": { + "enable": { + "default": false, + "description": "Enable load shedding by adding high-cost generators to avoid infeasibilities. Requires either all_carriers: true or at least one entry in carriers.", + "type": "boolean" + }, + "default_cost": { + "default": 100000, + "description": "The default cost for load-shedding in the unit of the bus carrier (e.g. EUR/MWh for electricity, EUR/t_CO2 for CO2). Must be positive.", + "exclusiveMinimum": 0, + "type": "number" + }, + "all_carriers": { + "default": true, + "description": "Switch to apply load shedding to all carriers. Otherwise, load shedding will be applied to listed carriers only.", + "type": "boolean" + }, + "carriers": { + "additionalProperties": { + "exclusiveMinimum": 0, + "type": "number" }, - "type": "array" + "default": {}, + "description": "Dictionary of carriers and their specific load shedding cost in the unit of the bus carrier (e.g. EUR/MWh for electricity, EUR/t_CO2 for CO2). If load shedding is enabled for all carriers, the default cost is assumed for non-listed carriers.", + "type": "object" } - ], - "default": "default", - "description": "Specifies the weather data cutout file(s) to use." + } }, - "resource": { - "description": "Configuration for solar resource settings.", + "load_sinks": { + "description": "Configuration for `solving.options.load_sinks` settings.", "properties": { - "method": { - "default": "pv", - "description": "A superordinate technology type.", - "type": "string" + "enable": { + "default": false, + "description": "Add load sinks by adding negative-cost, energy consuming generators to avoid infeasibilities by absorbing excess energy. Requires either all_carriers: true or at least one entry in carriers.", + "type": "boolean" }, - "panel": { - "anyOf": [ - { - "type": "string" - }, - { - "additionalProperties": { - "type": "string" - }, - "type": "object" - } - ], - "default": "CSi", - "description": "Specifies the solar panel technology and its characteristic attributes. Can be a string or a dictionary with years as keys which denote the year another panel model becomes available." + "default_cost": { + "default": 100000, + "description": "The default cost for load sinks in the unit of the bus carrier (e.g. EUR/MWh for electricity, EUR/t_CO2 for CO2). Must be positive.", + "exclusiveMinimum": 0, + "type": "number" }, - "orientation": { + "all_carriers": { + "default": false, + "description": "Switch to add load sinks for all carriers. Otherwise, load sinks will be added for listed carriers only.", + "type": "boolean" + }, + "carriers": { "additionalProperties": { + "exclusiveMinimum": 0, "type": "number" }, - "description": "Panel orientation with slope and azimuth.", + "default": {}, + "description": "Dictionary of carriers and their specific load sink cost in the unit of the bus carrier (e.g. EUR/MWh for electricity, EUR/t_CO2 for CO2). If load sinks are added for all carriers, the default cost is assumed for non-listed carriers.", "type": "object" - }, - "tracking": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "default": null, - "description": "Tracking type (e.g., 'horizontal')." } } }, - "resource_classes": { - "default": 1, - "description": "Number of resource classes per clustered region.", - "type": "integer" + "curtailment_mode": { + "default": false, + "description": "Fixes the dispatch profiles of generators with time-varying p_max_pu by setting `p_min_pu = p_max_pu` and adds an auxiliary curtailment generator (with negative sign to absorb excess power) at every AC bus. This can speed up the solving process as the curtailment decision is aggregated into a single generator per region. Defaults to `false`.", + "type": "boolean" }, - "capacity_per_sqkm": { - "default": 5.1, - "description": "Allowable density of solar panel placement.", - "type": "number" + "noisy_costs": { + "default": true, + "description": "Add random noise to marginal cost of generators by :math:`\\mathcal{U}(0.009,0,011)` and capital cost of lines and links by :math:`\\mathcal{U}(0.09,0,11)`.", + "type": "boolean" }, - "correction_factor": { - "default": 1.0, - "description": "A correction factor for the capacity factor (availability) time series.", - "type": "number" + "skip_iterations": { + "default": true, + "description": "Skip iterating, do not update impedances of branches. Defaults to true.", + "type": "boolean" }, - "corine": { + "rolling_horizon": { + "default": false, + "description": "Switch for rule `solve_operations_network` whether to optimize the network in a rolling horizon manner, where the snapshot range is split into slices of size `horizon` which are solved consecutively. This setting has currently no effect on sector-coupled networks.", + "type": "boolean" + }, + "seed": { + "default": 123, + "description": "Random seed for increased deterministic behaviour.", + "type": "integer" + }, + "custom_extra_functionality": { "anyOf": [ { - "type": "boolean" + "type": "string" }, { - "items": { - "type": "integer" - }, - "type": "array" + "type": "null" } ], - "description": "Specifies areas according to CORINE Land Cover codes which are generally eligible for solar panel placement." + "default": "../data/custom_extra_functionality.py", + "description": "Path to a Python file with custom extra functionality code to be injected into the solving rules of the workflow relative to `rules` directory." }, - "luisa": { + "io_api": { "anyOf": [ { - "type": "boolean" + "type": "string" }, { - "items": { - "type": "integer" - }, - "type": "array" + "type": "null" } ], + "default": null, + "description": "Passed to linopy and determines the API used to communicate with the solver. With the `'lp'` and `'mps'` options linopy passes a file to the solver; with the `'direct'` option (only supported for HIGHS and Gurobi) linopy uses an in-memory python API resulting in better performance." + }, + "track_iterations": { "default": false, - "description": "Specifies areas according to the LUISA Base Map codes which are generally eligible for solar panel placement." + "description": "Flag whether to store the intermediate branch capacities and objective function values are recorded for each iteration in `network.lines['s_nom_opt_X']` (where `X` labels the iteration)", + "type": "boolean" }, - "natura": { + "min_iterations": { + "default": 2, + "description": "Minimum number of solving iterations in between which resistance and reactence (`x/r`) are updated for branches according to `s_nom_opt` of the previous run.", + "type": "integer" + }, + "max_iterations": { + "default": 3, + "description": "Maximum number of solving iterations in between which resistance and reactence (`x/r`) are updated for branches according to `s_nom_opt` of the previous run.", + "type": "integer" + }, + "transmission_losses": { + "default": 2, + "description": "Add piecewise linear approximation of transmission losses based on n tangents. Defaults to 0, which means losses are ignored.", + "type": "integer" + }, + "linearized_unit_commitment": { "default": true, - "description": "Switch to exclude `Natura 2000 `_ natural protection areas. Area is excluded if `true`.", - "markdownDescription": "Switch to exclude [Natura 2000 ](https://en.wikipedia.org/wiki/Natura_2000) natural protection areas. Area is excluded if `true`.", + "description": "Whether to optimise using the linearized unit commitment formulation.", "type": "boolean" }, - "excluder_resolution": { - "default": 100, - "description": "Resolution in meters on which to perform geographical eligibility analysis.", - "type": "number" + "horizon": { + "default": 365, + "description": "Number of snapshots to consider in each iteration. Defaults to 100.", + "type": "integer" }, - "clip_p_max_pu": { - "default": 0.01, - "description": "To avoid too small values in the renewables` per-unit availability time series values below this threshold are set to zero.", - "type": "number" + "overlap": { + "default": 0, + "description": "Number of overlapping snapshots between consecutive iterations in rolling horizon optimization. Defaults to 0, which means no overlap.", + "type": "integer" + }, + "post_discretization": { + "description": "Configuration for `solving.options.post_discretization` settings.", + "properties": { + "enable": { + "default": false, + "description": "Switch to enable post-discretization of the network. Disabled by default.", + "type": "boolean" + }, + "line_unit_size": { + "default": 1700, + "description": "Discrete unit size of lines in MW.", + "type": "number" + }, + "line_threshold": { + "default": 0.3, + "description": "The threshold relative to the discrete line unit size beyond which to round up to the next unit.", + "type": "number" + }, + "link_unit_size": { + "additionalProperties": { + "type": "number" + }, + "description": "Discrete unit size of links in MW by carrier (given in dictionary style).", + "type": "object" + }, + "link_threshold": { + "additionalProperties": { + "type": "number" + }, + "description": "The threshold relative to the discrete link unit size beyond which to round up to the next unit by carrier (given in dictionary style).", + "type": "object" + }, + "fractional_last_unit_size": { + "default": false, + "description": "When true, links and lines can be built up to p_nom_max. When false, they can only be built up to a multiple of the unit size.", + "type": "boolean" + } + } + }, + "keep_files": { + "default": false, + "description": "Whether to keep LPs and MPS files after solving.", + "type": "boolean" + }, + "store_model": { + "default": false, + "description": "Store the linopy model to a NetCDF file after solving. Not supported with rolling_horizon. Not scenario-aware.", + "type": "boolean" + }, + "model_kwargs": { + "description": "Configuration for `solving.options.model_kwargs` settings.", + "properties": { + "solver_dir": { + "default": "", + "description": "Absolute path to the directory where linopy saves files.", + "type": "string" + } + } } } }, - "_SolarResourceConfig": { - "description": "Configuration for solar resource settings.", + "_TechnologyMappingConfig": { + "description": "Configuration for `electricity.estimate_renewable_capacities.technology_mapping` settings.", "properties": { - "method": { - "default": "pv", - "description": "A superordinate technology type.", + "Offshore": { + "default": "offwind-ac", + "description": "PyPSA-Eur carrier that is considered for existing offshore wind technology (IRENA, GEM).", "type": "string" }, - "panel": { + "Onshore": { + "default": "onwind", + "description": "PyPSA-Eur carrier that is considered for existing onshore wind capacities (IRENA, GEM).", + "type": "string" + }, + "PV": { + "default": "solar", + "description": "PyPSA-Eur carrier that is considered for existing solar PV capacities (IRENA, GEM).", + "type": "string" + } + } + }, + "_TemporalConfig": { + "description": "Configuration for `clustering.temporal` settings.", + "properties": { + "resolution_elec": { "anyOf": [ { - "type": "string" + "type": "boolean" }, { - "additionalProperties": { - "type": "string" - }, - "type": "object" + "type": "string" } ], - "default": "CSi", - "description": "Specifies the solar panel technology and its characteristic attributes. Can be a string or a dictionary with years as keys which denote the year another panel model becomes available." - }, - "orientation": { - "additionalProperties": { - "type": "number" - }, - "description": "Panel orientation with slope and azimuth.", - "type": "object" + "default": false, + "description": "Resample the time-resolution by averaging over every `n` snapshots in `prepare_network`. **Warning:** This option should currently only be used with electricity-only networks, not for sector-coupled networks." }, - "tracking": { + "resolution_sector": { "anyOf": [ { - "type": "string" + "type": "boolean" }, { - "type": "null" + "type": "string" } ], - "default": null, - "description": "Tracking type (e.g., 'horizontal')." + "default": false, + "description": "Resample the time-resolution by averaging over every `n` snapshots in `prepare_sector_network`." } } }, - "_SolidBiomassImportConfig": { - "description": "Configuration for `sector.solid_biomass_import` settings.", + "_TransmissionCarrierConfigElectricity": { + "description": "Configuration for electricity transmission grid.", "properties": { "enable": { - "default": false, - "description": "Add option to include solid biomass imports.", + "default": true, + "description": "Switch for enabling/disabling the electricity transmission grid.", "type": "boolean" }, - "price": { - "default": 54, - "description": "Price for importing solid biomass (currency/MWh).", - "type": "number" - }, - "max_amount": { - "default": 1390, - "description": "Maximum solid biomass import potential (TWh).", - "type": "number" - }, - "upstream_emissions_factor": { - "default": 0.1, - "description": "Upstream emissions of solid biomass imports.", - "type": "number" - } - } - }, - "_SolverConfig": { - "description": "Configuration for `solving.solver` settings.", - "properties": { - "name": { - "default": "gurobi", - "description": "Solver to use for optimisation problems in the workflow; e.g. clustering and linear optimal power flow.", + "base_network": { + "default": "osm", + "description": "Specify the underlying base network, i.e. GridKit (based on ENTSO-E web map extract), OpenStreetMap (OSM), or TYNDP.", + "enum": [ + "entsoegridkit", + "osm", + "tyndp" + ], "type": "string" }, - "options": { - "default": "gurobi-default", - "description": "Link to specific parameter settings.", + "transmission_limit": { + "default": "vopt", + "description": "Limit on transmission expansion. The first part can be `v` (for setting a limit on line volume) or `c` (for setting a limit on line cost). The second part can be `opt` or a float bigger than one (e.g. 1.25). If `opt` is chosen line expansion is optimised according to its capital cost (where the choice `v` only considers overhead costs for HVDC transmission lines, while `c` uses more accurate costs distinguishing between overhead and underwater sections and including inverter pairs). The setting `v1.25` will limit the total volume of line expansion to 25% of currently installed capacities weighted by individual line lengths. The setting `c1.25` will allow to build a transmission network that costs no more than 25 % more than the current system.", "type": "string" - } - } - }, - "_SolvingOptionsConfig": { - "description": "Configuration for `solving.options` settings.", - "properties": { - "clip_p_max_pu": { - "default": 0.01, - "description": "To avoid too small values in the renewables` per-unit availability time series values below this threshold are set to zero.", - "type": "number" }, - "load_shedding": { - "description": "Configuration for `solving.options.load_shedding` settings.", + "lines": { + "description": "Configuration for `lines` settings.", "properties": { - "enable": { - "default": false, - "description": "Enable load shedding by adding high-cost generators to avoid infeasibilities. Requires either all_carriers: true or at least one entry in carriers.", - "type": "boolean" + "types": { + "additionalProperties": { + "type": "string" + }, + "description": "Specifies line types to assume for the different voltage levels of the ENTSO-E grid extraction. Should normally handle voltage levels 220, 300, and 380 kV.", + "type": "object" }, - "default_cost": { - "default": 100000, - "description": "The default cost for load-shedding in the unit of the bus carrier (e.g. EUR/MWh for electricity, EUR/t_CO2 for CO2). Must be positive.", - "exclusiveMinimum": 0, + "s_max_pu": { + "default": 0.7, + "description": "Correction factor for line capacities (`s_nom`) to approximate N-1 security and reserve capacity for reactive power flows.", "type": "number" }, - "all_carriers": { + "s_nom_max": { + "default": null, + "description": "Global upper limit for the maximum capacity of each extendable line (MW).", + "type": "number" + }, + "max_extension": { + "default": 20000, + "description": "Upper limit for the extended capacity of each extendable line (MW).", + "type": "number" + }, + "length_factor": { + "default": 1.25, + "description": "Correction factor to account for the fact that buses are *not* connected by lines through air-line distance.", + "type": "number" + }, + "reconnect_crimea": { "default": true, - "description": "Switch to apply load shedding to all carriers. Otherwise, load shedding will be applied to listed carriers only.", + "description": "Whether to reconnect Crimea to the Ukrainian grid.", "type": "boolean" }, - "carriers": { - "additionalProperties": { - "exclusiveMinimum": 0, - "type": "number" - }, - "default": {}, - "description": "Dictionary of carriers and their specific load shedding cost in the unit of the bus carrier (e.g. EUR/MWh for electricity, EUR/t_CO2 for CO2). If load shedding is enabled for all carriers, the default cost is assumed for non-listed carriers.", - "type": "object" + "under_construction": { + "default": "keep", + "description": "Specifies how to handle lines which are currently under construction.", + "enum": [ + "zero", + "remove", + "keep" + ], + "type": "string" + }, + "dynamic_line_rating": { + "description": "Configuration for `lines.dynamic_line_rating` settings.", + "properties": { + "activate": { + "default": false, + "description": "Whether to take dynamic line rating into account.", + "type": "boolean" + }, + "cutout": { + "anyOf": [ + { + "type": "string" + }, + { + "items": { + "type": "string" + }, + "type": "array" + } + ], + "default": "default", + "description": "Specifies the weather data cutout file(s) to use." + }, + "correction_factor": { + "default": 0.95, + "description": "Factor to compensate for overestimation of wind speeds in hourly averaged wind data.", + "type": "number" + }, + "max_voltage_difference": { + "anyOf": [ + { + "type": "number" + }, + { + "const": false, + "type": "boolean" + } + ], + "default": false, + "description": "Maximum voltage angle difference in degrees or 'false' to disable." + }, + "max_line_rating": { + "anyOf": [ + { + "type": "number" + }, + { + "const": false, + "type": "boolean" + } + ], + "default": false, + "description": "Maximum line rating relative to nominal capacity without DLR, e.g. 1.3 or 'false' to disable." + } + } } } }, - "load_sinks": { - "description": "Configuration for `solving.options.load_sinks` settings.", + "links": { + "description": "Configuration for `links` settings.", "properties": { - "enable": { - "default": false, - "description": "Add load sinks by adding negative-cost, energy consuming generators to avoid infeasibilities by absorbing excess energy. Requires either all_carriers: true or at least one entry in carriers.", - "type": "boolean" + "p_max_pu": { + "default": 1.0, + "description": "Correction factor for link capacities `p_nom`.", + "type": "number" }, - "default_cost": { - "default": 100000, - "description": "The default cost for load sinks in the unit of the bus carrier (e.g. EUR/MWh for electricity, EUR/t_CO2 for CO2). Must be positive.", - "exclusiveMinimum": 0, + "p_min_pu": { + "default": -1.0, + "description": "Correction factor for link capacities `p_nom`.", "type": "number" }, - "all_carriers": { - "default": false, - "description": "Switch to add load sinks for all carriers. Otherwise, load sinks will be added for listed carriers only.", - "type": "boolean" + "p_nom_max": { + "default": null, + "description": "Global upper limit for the maximum capacity of each extendable DC link (MW).", + "type": "number" }, - "carriers": { - "additionalProperties": { - "exclusiveMinimum": 0, - "type": "number" - }, - "default": {}, - "description": "Dictionary of carriers and their specific load sink cost in the unit of the bus carrier (e.g. EUR/MWh for electricity, EUR/t_CO2 for CO2). If load sinks are added for all carriers, the default cost is assumed for non-listed carriers.", - "type": "object" + "max_extension": { + "default": 30000, + "description": "Upper limit for the extended capacity of each extendable DC link (MW).", + "type": "number" + }, + "length_factor": { + "default": 1.25, + "description": "Correction factor to account for the fact that buses are *not* connected by links through air-line distance.", + "type": "number" + }, + "under_construction": { + "default": "keep", + "description": "Specifies how to handle lines which are currently under construction.", + "enum": [ + "zero", + "remove", + "keep" + ], + "type": "string" + }, + "efficiency": { + "description": "Configuration for `transmission.electricity.links.efficiency` settings.", + "properties": { + "enable": { + "default": true, + "description": "Apply transmission efficiency for DC links.", + "type": "boolean" + }, + "efficiency_static": { + "default": 0.98, + "description": "Static DC transmission efficiency.", + "type": "number" + }, + "efficiency_per_1000km": { + "default": 0.977, + "description": "Distance-dependent DC efficiency factor per 1000 km.", + "type": "number" + } + } } } }, - "curtailment_mode": { - "default": false, - "description": "Fixes the dispatch profiles of generators with time-varying p_max_pu by setting `p_min_pu = p_max_pu` and adds an auxiliary curtailment generator (with negative sign to absorb excess power) at every AC bus. This can speed up the solving process as the curtailment decision is aggregated into a single generator per region. Defaults to `false`.", - "type": "boolean" - }, - "noisy_costs": { - "default": true, - "description": "Add random noise to marginal cost of generators by :math:`\\mathcal{U}(0.009,0,011)` and capital cost of lines and links by :math:`\\mathcal{U}(0.09,0,11)`.", - "type": "boolean" - }, - "skip_iterations": { - "default": true, - "description": "Skip iterating, do not update impedances of branches. Defaults to true.", - "type": "boolean" - }, - "rolling_horizon": { - "default": false, - "description": "Switch for rule `solve_operations_network` whether to optimize the network in a rolling horizon manner, where the snapshot range is split into slices of size `horizon` which are solved consecutively. This setting has currently no effect on sector-coupled networks.", - "type": "boolean" - }, - "seed": { - "default": 123, - "description": "Random seed for increased deterministic behaviour.", - "type": "integer" - }, - "custom_extra_functionality": { - "anyOf": [ - { + "transformers": { + "description": "Configuration for `transformers` settings.", + "properties": { + "x": { + "default": 0.1, + "description": "Series reactance in per unit (p.u.), using `s_nom` as base power of the transformer. Overwritten if `type` is specified.", + "type": "number" + }, + "s_nom": { + "default": 2000.0, + "description": "Limit of apparent power which can pass through branch (MVA). Overwritten if `type` is specified.", + "type": "number" + }, + "type": { + "default": "", + "description": "Specifies transformer types to assume for the transformers of the ENTSO-E grid extraction.", "type": "string" + } + } + }, + "projects": { + "description": "Configuration for `electricity.projects` settings.", + "properties": { + "enable": { + "default": true, + "description": "Whether to integrate this transmission projects or not.", + "type": "boolean" + }, + "include": { + "description": "Configuration for `electricity.projects.include` settings.", + "properties": { + "tyndp2020": { + "default": true, + "description": "Whether to integrate the TYNDP 2020 dataset.", + "type": "boolean" + }, + "nep": { + "default": true, + "description": "Whether to integrate the German network development plan dataset.", + "type": "boolean" + }, + "manual": { + "default": true, + "description": "Whether to integrate the manually added transmission projects. They are taken from the previously existing links_tyndp.csv file.", + "type": "boolean" + } + } + }, + "skip": { + "description": "Type of lines to skip from all transmission projects. Possible values are: `upgraded_lines`, `upgraded_links`, `new_lines`, `new_links`.", + "items": { + "type": "string" + }, + "type": "array" }, - { - "type": "null" - } - ], - "default": "../data/custom_extra_functionality.py", - "description": "Path to a Python file with custom extra functionality code to be injected into the solving rules of the workflow relative to `rules` directory." - }, - "io_api": { - "anyOf": [ - { - "type": "string" + "status": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "additionalProperties": { + "items": { + "type": "string" + }, + "type": "array" + }, + "type": "object" + } + ], + "description": "Status to include into the model as list or as dict with name of project and status to include. Possible values for status are `under_construction`, `in_permitting`, `confirmed`, `planned_not_yet_permitted`, `under_consideration`." }, - { - "type": "null" + "new_link_capacity": { + "default": "zero", + "description": "Whether to set the new link capacity to the provided capacity or set it to zero.", + "enum": [ + "zero", + "keep" + ], + "type": "string" } - ], - "default": null, - "description": "Passed to linopy and determines the API used to communicate with the solver. With the `'lp'` and `'mps'` options linopy passes a file to the solver; with the `'direct'` option (only supported for HIGHS and Gurobi) linopy uses an in-memory python API resulting in better performance." - }, - "track_iterations": { - "default": false, - "description": "Flag whether to store the intermediate branch capacities and objective function values are recorded for each iteration in `network.lines['s_nom_opt_X']` (where `X` labels the iteration)", - "type": "boolean" - }, - "min_iterations": { - "default": 2, - "description": "Minimum number of solving iterations in between which resistance and reactence (`x/r`) are updated for branches according to `s_nom_opt` of the previous run.", - "type": "integer" - }, - "max_iterations": { - "default": 3, - "description": "Maximum number of solving iterations in between which resistance and reactence (`x/r`) are updated for branches according to `s_nom_opt` of the previous run.", - "type": "integer" - }, - "transmission_losses": { - "default": 2, - "description": "Add piecewise linear approximation of transmission losses based on n tangents. Defaults to 0, which means losses are ignored.", - "type": "integer" - }, - "linearized_unit_commitment": { + } + } + } + }, + "_TransmissionCarrierConfigElectricityDistribution": { + "properties": { + "enable": { "default": true, - "description": "Whether to optimise using the linearized unit commitment formulation.", + "description": "Add a simplified representation of the exchange capacity between transmission and distribution grid level through a link.", "type": "boolean" }, - "horizon": { - "default": 365, - "description": "Number of snapshots to consider in each iteration. Defaults to 100.", - "type": "integer" - }, - "overlap": { - "default": 0, - "description": "Number of overlapping snapshots between consecutive iterations in rolling horizon optimization. Defaults to 0, which means no overlap.", - "type": "integer" + "cost_factor": { + "default": 1.0, + "description": "Multiplies the investment cost of the electricity distribution grid.", + "type": "number" }, - "post_discretization": { - "description": "Configuration for `solving.options.post_discretization` settings.", + "efficiency": { + "description": "Configuration for `transmission.electricity_distribution.efficiency` settings.", "properties": { "enable": { - "default": false, - "description": "Switch to enable post-discretization of the network. Disabled by default.", + "default": true, + "description": "Apply electricity distribution efficiency.", "type": "boolean" }, - "line_unit_size": { - "default": 1700, - "description": "Discrete unit size of lines in MW.", - "type": "number" - }, - "line_threshold": { - "default": 0.3, - "description": "The threshold relative to the discrete line unit size beyond which to round up to the next unit.", + "efficiency_static": { + "default": 0.97, + "description": "Static electricity distribution efficiency.", "type": "number" - }, - "link_unit_size": { - "additionalProperties": { - "type": "number" - }, - "description": "Discrete unit size of links in MW by carrier (given in dictionary style).", - "type": "object" - }, - "link_threshold": { - "additionalProperties": { - "type": "number" - }, - "description": "The threshold relative to the discrete link unit size beyond which to round up to the next unit by carrier (given in dictionary style).", - "type": "object" - }, - "fractional_last_unit_size": { - "default": false, - "description": "When true, links and lines can be built up to p_nom_max. When false, they can only be built up to a multiple of the unit size.", - "type": "boolean" } } - }, - "keep_files": { - "default": false, - "description": "Whether to keep LPs and MPS files after solving.", + } + } + }, + "_TransmissionCarrierConfigGas": { + "properties": { + "enable": { + "default": true, + "description": "Add existing natural gas infrastructure, incl. LNG terminals, production and entry-points. A length-weighted `k-edge augmentation algorithm `_ can be run to add new candidate gas pipelines such that all regions of the model can be connected to the gas network. When activated, all the gas demands are regionally disaggregated as well.", + "markdownDescription": "Add existing natural gas infrastructure, incl. LNG terminals, production and entry-points. A length-weighted [k-edge augmentation algorithm ](https://networkx.org/documentation/stable/reference/algorithms/generated/networkx.algorithms.connectivity.edge_augmentation.k_edge_augmentation.html#networkx.algorithms.connectivity.edge_augmentation.k_edge_augmentation) can be run to add new candidate gas pipelines such that all regions of the model can be connected to the gas network. When activated, all the gas demands are regionally disaggregated as well.", "type": "boolean" }, - "store_model": { - "default": false, - "description": "Store the linopy model to a NetCDF file after solving. Not supported with rolling_horizon. Not scenario-aware.", - "type": "boolean" + "connectivity_upgrade": { + "default": 1, + "description": "The number of desired edge connectivity (k) in the length-weighted `k-edge augmentation algorithm `_ used for the gas network.", + "markdownDescription": "The number of desired edge connectivity (k) in the length-weighted [k-edge augmentation algorithm ](https://networkx.org/documentation/stable/reference/algorithms/generated/networkx.algorithms.connectivity.edge_augmentation.k_edge_augmentation.html#networkx.algorithms.connectivity.edge_augmentation.k_edge_augmentation) used for the gas network.", + "type": "number" }, - "model_kwargs": { - "description": "Configuration for `solving.options.model_kwargs` settings.", + "efficiency": { + "description": "Configuration for `transmission..efficiency` settings.", "properties": { - "solver_dir": { - "default": "", - "description": "Absolute path to the directory where linopy saves files.", - "type": "string" + "enable": { + "default": true, + "description": "Apply transmission efficiency for this carrier.", + "type": "boolean" + }, + "efficiency_per_1000km": { + "default": 1, + "description": "Distance-dependent efficiency factor per 1000 km.", + "type": "number" + }, + "compression_per_1000km": { + "default": 0, + "description": "Additional electricity consumption for compression per 1000 km.", + "type": "number" } } } } }, - "_TechnologyMappingConfig": { - "description": "Configuration for `electricity.estimate_renewable_capacities.technology_mapping` settings.", + "_TransmissionCarrierConfigGeneral": { + "description": "Configuration for a single transmission carrier.", "properties": { - "Offshore": { - "default": "offwind-ac", - "description": "PyPSA-Eur carrier that is considered for existing offshore wind technology (IRENA, GEM).", - "type": "string" + "enable": { + "default": true, + "description": "Enable transmission candidate generation for this carrier.", + "type": "boolean" }, - "Onshore": { - "default": "onwind", - "description": "PyPSA-Eur carrier that is considered for existing onshore wind capacities (IRENA, GEM).", - "type": "string" + "gabriel_filter_min_degree": { + "default": 1, + "description": "Minimum node degree target applied after Gabriel graph filtering. Set to >= 1 to activate Gabriel filtering with min-degree backfilling; 0 disables it.", + "minimum": 0, + "type": "integer" }, - "PV": { - "default": "solar", - "description": "PyPSA-Eur carrier that is considered for existing solar PV capacities (IRENA, GEM).", - "type": "string" + "max_offshore_haversine_distance": { + "default": null, + "description": "Maximum haversine distance in km for offshore transmission candidates. Candidate edges a total offshore length exceeding this threshold are excluded. Defaults to infinity (no limit).", + "exclusiveMinimum": 0, + "type": "number" + }, + "length_factor": { + "default": 1.25, + "description": "Multiplier applied to geometric corridor lengths.", + "exclusiveMinimum": 0, + "type": "number" + }, + "cost_factor": { + "default": 1, + "description": "Multiplier applied to the capital cost of transmission infrastructure.", + "exclusiveMinimum": 0, + "type": "number" } } }, - "_TemporalConfig": { - "description": "Configuration for `clustering.temporal` settings.", + "_TransmissionCarrierConfigHydrogen": { "properties": { - "resolution_elec": { - "anyOf": [ - { + "enable": { + "default": true, + "description": "Enable transmission candidate generation for this carrier.", + "type": "boolean" + }, + "gabriel_filter_min_degree": { + "default": 1, + "description": "Minimum node degree target applied after Gabriel graph filtering. Set to >= 1 to activate Gabriel filtering with min-degree backfilling; 0 disables it.", + "minimum": 0, + "type": "integer" + }, + "max_offshore_haversine_distance": { + "default": null, + "description": "Maximum haversine distance in km for offshore transmission candidates. Candidate edges a total offshore length exceeding this threshold are excluded. Defaults to infinity (no limit).", + "exclusiveMinimum": 0, + "type": "number" + }, + "length_factor": { + "default": 1.25, + "description": "Multiplier applied to geometric corridor lengths.", + "exclusiveMinimum": 0, + "type": "number" + }, + "cost_factor": { + "default": 1, + "description": "Multiplier applied to the capital cost of transmission infrastructure.", + "exclusiveMinimum": 0, + "type": "number" + }, + "retrofit": { + "description": "Configuration for `transmission.hydrogen.retrofit` settings.", + "properties": { + "enable": { + "default": false, + "description": "Add option for retrofitting existing pipelines to transport hydrogen.", "type": "boolean" }, - { - "type": "string" + "capacity_per_ch4": { + "default": 0.6, + "description": "The ratio for H2 capacity per original CH4 capacity of retrofitted pipelines. The `European Hydrogen Backbone (April, 2020) p.15 `_ 60% of original natural gas capacity could be used in cost-optimal case as H2 capacity.", + "markdownDescription": "The ratio for H2 capacity per original CH4 capacity of retrofitted pipelines. The [European Hydrogen Backbone (April, 2020) p.15 ](https://gasforclimate2050.eu/wp-content/uploads/2020/07/2020_European-Hydrogen-Backbone_Report.pdf) 60% of original natural gas capacity could be used in cost-optimal case as H2 capacity.", + "type": "number" } - ], - "default": false, - "description": "Resample the time-resolution by averaging over every `n` snapshots in `prepare_network`. **Warning:** This option should currently only be used with electricity-only networks, not for sector-coupled networks." + } }, - "resolution_sector": { - "anyOf": [ - { + "efficiency": { + "description": "Configuration for `transmission..efficiency` settings.", + "properties": { + "enable": { + "default": true, + "description": "Apply transmission efficiency for this carrier.", "type": "boolean" }, - { - "type": "string" + "efficiency_per_1000km": { + "default": 1, + "description": "Distance-dependent efficiency factor per 1000 km.", + "type": "number" + }, + "compression_per_1000km": { + "default": 0, + "description": "Additional electricity consumption for compression per 1000 km.", + "type": "number" } - ], - "default": false, - "description": "Resample the time-resolution by averaging over every `n` snapshots in `prepare_sector_network`." - } - } - }, - "_TransmissionEfficiencyConfig": { - "description": "Configuration for `sector.transmission_efficiency` settings.", - "properties": { - "enable": { - "description": "Switch to select the carriers for which transmission efficiency is to be added. Carriers not listed assume lossless transmission.", - "items": { - "type": "string" - }, - "type": "array" - }, - "DC": { - "additionalProperties": { - "type": "number" - }, - "description": "DC transmission efficiency.", - "type": "object" - }, - "H2 pipeline": { - "additionalProperties": { - "type": "number" - }, - "description": "H2 pipeline transmission efficiency.", - "type": "object" + } + } + } + }, + "_TransmissionEfficiencyConfig": { + "description": "Configuration for `transmission..efficiency` settings.", + "properties": { + "enable": { + "default": true, + "description": "Apply transmission efficiency for this carrier.", + "type": "boolean" }, - "gas pipeline": { - "additionalProperties": { - "type": "number" - }, - "description": "Gas pipeline transmission efficiency.", - "type": "object" + "efficiency_per_1000km": { + "default": 1, + "description": "Distance-dependent efficiency factor per 1000 km.", + "type": "number" }, - "electricity distribution grid": { - "additionalProperties": { - "type": "number" - }, - "description": "Electricity distribution grid efficiency.", - "type": "object" + "compression_per_1000km": { + "default": 0, + "description": "Additional electricity consumption for compression per 1000 km.", + "type": "number" } } }, @@ -8486,16 +9321,6 @@ }, "type": "array" }, - "base_network": { - "default": "osm", - "description": "Specify the underlying base network, i.e. GridKit (based on ENTSO-E web map extract), OpenStreetMap (OSM), or TYNDP.", - "enum": [ - "entsoegridkit", - "osm", - "tyndp" - ], - "type": "string" - }, "gaslimit_enable": { "default": false, "description": "Add an overall absolute gas limit configured in `electricity: gaslimit`.", @@ -8707,64 +9532,509 @@ "description": "Renewable capacities are based on existing capacities reported by IRENA (IRENASTAT) for the specified year.", "type": "integer" }, - "expansion_limit": { - "anyOf": [ - { - "type": "number" - }, - { + "expansion_limit": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "boolean" + } + ], + "default": false, + "description": "Artificially limit maximum IRENA capacities to a factor. For example, an `expansion_limit: 1.1` means 110% of capacities. If false are chosen, the estimated renewable potentials determine by the workflow are used." + }, + "technology_mapping": { + "description": "Configuration for `electricity.estimate_renewable_capacities.technology_mapping` settings.", + "properties": { + "Offshore": { + "default": "offwind-ac", + "description": "PyPSA-Eur carrier that is considered for existing offshore wind technology (IRENA, GEM).", + "type": "string" + }, + "Onshore": { + "default": "onwind", + "description": "PyPSA-Eur carrier that is considered for existing onshore wind capacities (IRENA, GEM).", + "type": "string" + }, + "PV": { + "default": "solar", + "description": "PyPSA-Eur carrier that is considered for existing solar PV capacities (IRENA, GEM).", + "type": "string" + } + } + } + } + }, + "estimate_battery_capacities": { + "default": false, + "description": "Enable estimation of existing battery storage capacities.", + "type": "boolean" + }, + "autarky": { + "description": "Configuration for `electricity.autarky` settings.", + "properties": { + "enable": { + "default": false, + "description": "Require each node to be autarkic by removing all lines and links.", + "type": "boolean" + }, + "by_country": { + "default": false, + "description": "Require each country to be autarkic by removing all cross-border lines and links. `electricity: autarky` must be enabled.", + "type": "boolean" + } + } + } + } + }, + "transmission": { + "description": "Configuration for `transmission` settings.", + "properties": { + "electricity": { + "description": "Configuration for electricity transmission grid.", + "properties": { + "enable": { + "default": true, + "description": "Switch for enabling/disabling the electricity transmission grid.", + "type": "boolean" + }, + "base_network": { + "default": "osm", + "description": "Specify the underlying base network, i.e. GridKit (based on ENTSO-E web map extract), OpenStreetMap (OSM), or TYNDP.", + "enum": [ + "entsoegridkit", + "osm", + "tyndp" + ], + "type": "string" + }, + "transmission_limit": { + "default": "vopt", + "description": "Limit on transmission expansion. The first part can be `v` (for setting a limit on line volume) or `c` (for setting a limit on line cost). The second part can be `opt` or a float bigger than one (e.g. 1.25). If `opt` is chosen line expansion is optimised according to its capital cost (where the choice `v` only considers overhead costs for HVDC transmission lines, while `c` uses more accurate costs distinguishing between overhead and underwater sections and including inverter pairs). The setting `v1.25` will limit the total volume of line expansion to 25% of currently installed capacities weighted by individual line lengths. The setting `c1.25` will allow to build a transmission network that costs no more than 25 % more than the current system.", + "type": "string" + }, + "lines": { + "description": "Configuration for `lines` settings.", + "properties": { + "types": { + "additionalProperties": { + "type": "string" + }, + "description": "Specifies line types to assume for the different voltage levels of the ENTSO-E grid extraction. Should normally handle voltage levels 220, 300, and 380 kV.", + "type": "object" + }, + "s_max_pu": { + "default": 0.7, + "description": "Correction factor for line capacities (`s_nom`) to approximate N-1 security and reserve capacity for reactive power flows.", + "type": "number" + }, + "s_nom_max": { + "default": null, + "description": "Global upper limit for the maximum capacity of each extendable line (MW).", + "type": "number" + }, + "max_extension": { + "default": 20000, + "description": "Upper limit for the extended capacity of each extendable line (MW).", + "type": "number" + }, + "length_factor": { + "default": 1.25, + "description": "Correction factor to account for the fact that buses are *not* connected by lines through air-line distance.", + "type": "number" + }, + "reconnect_crimea": { + "default": true, + "description": "Whether to reconnect Crimea to the Ukrainian grid.", + "type": "boolean" + }, + "under_construction": { + "default": "keep", + "description": "Specifies how to handle lines which are currently under construction.", + "enum": [ + "zero", + "remove", + "keep" + ], + "type": "string" + }, + "dynamic_line_rating": { + "description": "Configuration for `lines.dynamic_line_rating` settings.", + "properties": { + "activate": { + "default": false, + "description": "Whether to take dynamic line rating into account.", + "type": "boolean" + }, + "cutout": { + "anyOf": [ + { + "type": "string" + }, + { + "items": { + "type": "string" + }, + "type": "array" + } + ], + "default": "default", + "description": "Specifies the weather data cutout file(s) to use." + }, + "correction_factor": { + "default": 0.95, + "description": "Factor to compensate for overestimation of wind speeds in hourly averaged wind data.", + "type": "number" + }, + "max_voltage_difference": { + "anyOf": [ + { + "type": "number" + }, + { + "const": false, + "type": "boolean" + } + ], + "default": false, + "description": "Maximum voltage angle difference in degrees or 'false' to disable." + }, + "max_line_rating": { + "anyOf": [ + { + "type": "number" + }, + { + "const": false, + "type": "boolean" + } + ], + "default": false, + "description": "Maximum line rating relative to nominal capacity without DLR, e.g. 1.3 or 'false' to disable." + } + } + } + } + }, + "links": { + "description": "Configuration for `links` settings.", + "properties": { + "p_max_pu": { + "default": 1.0, + "description": "Correction factor for link capacities `p_nom`.", + "type": "number" + }, + "p_min_pu": { + "default": -1.0, + "description": "Correction factor for link capacities `p_nom`.", + "type": "number" + }, + "p_nom_max": { + "default": null, + "description": "Global upper limit for the maximum capacity of each extendable DC link (MW).", + "type": "number" + }, + "max_extension": { + "default": 30000, + "description": "Upper limit for the extended capacity of each extendable DC link (MW).", + "type": "number" + }, + "length_factor": { + "default": 1.25, + "description": "Correction factor to account for the fact that buses are *not* connected by links through air-line distance.", + "type": "number" + }, + "under_construction": { + "default": "keep", + "description": "Specifies how to handle lines which are currently under construction.", + "enum": [ + "zero", + "remove", + "keep" + ], + "type": "string" + }, + "efficiency": { + "description": "Configuration for `transmission.electricity.links.efficiency` settings.", + "properties": { + "enable": { + "default": true, + "description": "Apply transmission efficiency for DC links.", + "type": "boolean" + }, + "efficiency_static": { + "default": 0.98, + "description": "Static DC transmission efficiency.", + "type": "number" + }, + "efficiency_per_1000km": { + "default": 0.977, + "description": "Distance-dependent DC efficiency factor per 1000 km.", + "type": "number" + } + } + } + } + }, + "transformers": { + "description": "Configuration for `transformers` settings.", + "properties": { + "x": { + "default": 0.1, + "description": "Series reactance in per unit (p.u.), using `s_nom` as base power of the transformer. Overwritten if `type` is specified.", + "type": "number" + }, + "s_nom": { + "default": 2000.0, + "description": "Limit of apparent power which can pass through branch (MVA). Overwritten if `type` is specified.", + "type": "number" + }, + "type": { + "default": "", + "description": "Specifies transformer types to assume for the transformers of the ENTSO-E grid extraction.", + "type": "string" + } + } + }, + "projects": { + "description": "Configuration for `electricity.projects` settings.", + "properties": { + "enable": { + "default": true, + "description": "Whether to integrate this transmission projects or not.", + "type": "boolean" + }, + "include": { + "description": "Configuration for `electricity.projects.include` settings.", + "properties": { + "tyndp2020": { + "default": true, + "description": "Whether to integrate the TYNDP 2020 dataset.", + "type": "boolean" + }, + "nep": { + "default": true, + "description": "Whether to integrate the German network development plan dataset.", + "type": "boolean" + }, + "manual": { + "default": true, + "description": "Whether to integrate the manually added transmission projects. They are taken from the previously existing links_tyndp.csv file.", + "type": "boolean" + } + } + }, + "skip": { + "description": "Type of lines to skip from all transmission projects. Possible values are: `upgraded_lines`, `upgraded_links`, `new_lines`, `new_links`.", + "items": { + "type": "string" + }, + "type": "array" + }, + "status": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "additionalProperties": { + "items": { + "type": "string" + }, + "type": "array" + }, + "type": "object" + } + ], + "description": "Status to include into the model as list or as dict with name of project and status to include. Possible values for status are `under_construction`, `in_permitting`, `confirmed`, `planned_not_yet_permitted`, `under_consideration`." + }, + "new_link_capacity": { + "default": "zero", + "description": "Whether to set the new link capacity to the provided capacity or set it to zero.", + "enum": [ + "zero", + "keep" + ], + "type": "string" + } + } + } + } + }, + "electricity_distribution": { + "properties": { + "enable": { + "default": true, + "description": "Add a simplified representation of the exchange capacity between transmission and distribution grid level through a link.", + "type": "boolean" + }, + "cost_factor": { + "default": 1.0, + "description": "Multiplies the investment cost of the electricity distribution grid.", + "type": "number" + }, + "efficiency": { + "description": "Configuration for `transmission.electricity_distribution.efficiency` settings.", + "properties": { + "enable": { + "default": true, + "description": "Apply electricity distribution efficiency.", + "type": "boolean" + }, + "efficiency_static": { + "default": 0.97, + "description": "Static electricity distribution efficiency.", + "type": "number" + } + } + } + }, + "description": "Configuration for simplified electricity distribution grid." + }, + "hydrogen": { + "properties": { + "enable": { + "default": true, + "description": "Enable transmission candidate generation for this carrier.", + "type": "boolean" + }, + "gabriel_filter_min_degree": { + "default": 1, + "description": "Minimum node degree target applied after Gabriel graph filtering. Set to >= 1 to activate Gabriel filtering with min-degree backfilling; 0 disables it.", + "minimum": 0, + "type": "integer" + }, + "max_offshore_haversine_distance": { + "default": null, + "description": "Maximum haversine distance in km for offshore transmission candidates. Candidate edges a total offshore length exceeding this threshold are excluded. Defaults to infinity (no limit).", + "exclusiveMinimum": 0, + "type": "number" + }, + "length_factor": { + "default": 1.25, + "description": "Multiplier applied to geometric corridor lengths.", + "exclusiveMinimum": 0, + "type": "number" + }, + "cost_factor": { + "default": 1, + "description": "Multiplier applied to the capital cost of transmission infrastructure.", + "exclusiveMinimum": 0, + "type": "number" + }, + "retrofit": { + "description": "Configuration for `transmission.hydrogen.retrofit` settings.", + "properties": { + "enable": { + "default": false, + "description": "Add option for retrofitting existing pipelines to transport hydrogen.", "type": "boolean" + }, + "capacity_per_ch4": { + "default": 0.6, + "description": "The ratio for H2 capacity per original CH4 capacity of retrofitted pipelines. The `European Hydrogen Backbone (April, 2020) p.15 `_ 60% of original natural gas capacity could be used in cost-optimal case as H2 capacity.", + "markdownDescription": "The ratio for H2 capacity per original CH4 capacity of retrofitted pipelines. The [European Hydrogen Backbone (April, 2020) p.15 ](https://gasforclimate2050.eu/wp-content/uploads/2020/07/2020_European-Hydrogen-Backbone_Report.pdf) 60% of original natural gas capacity could be used in cost-optimal case as H2 capacity.", + "type": "number" } - ], - "default": false, - "description": "Artificially limit maximum IRENA capacities to a factor. For example, an `expansion_limit: 1.1` means 110% of capacities. If false are chosen, the estimated renewable potentials determine by the workflow are used." + } }, - "technology_mapping": { - "description": "Configuration for `electricity.estimate_renewable_capacities.technology_mapping` settings.", + "efficiency": { + "description": "Configuration for `transmission..efficiency` settings.", "properties": { - "Offshore": { - "default": "offwind-ac", - "description": "PyPSA-Eur carrier that is considered for existing offshore wind technology (IRENA, GEM).", - "type": "string" + "enable": { + "default": true, + "description": "Apply transmission efficiency for this carrier.", + "type": "boolean" }, - "Onshore": { - "default": "onwind", - "description": "PyPSA-Eur carrier that is considered for existing onshore wind capacities (IRENA, GEM).", - "type": "string" + "efficiency_per_1000km": { + "default": 1, + "description": "Distance-dependent efficiency factor per 1000 km.", + "type": "number" }, - "PV": { - "default": "solar", - "description": "PyPSA-Eur carrier that is considered for existing solar PV capacities (IRENA, GEM).", - "type": "string" + "compression_per_1000km": { + "default": 0, + "description": "Additional electricity consumption for compression per 1000 km.", + "type": "number" } } } - } - }, - "estimate_battery_capacities": { - "default": false, - "description": "Enable estimation of existing battery storage capacities.", - "type": "boolean" + }, + "description": "Configuration for hydrogen transmission candidates." }, - "autarky": { - "description": "Configuration for `electricity.autarky` settings.", + "carbon_dioxide": { + "description": "Configuration for a single transmission carrier.", "properties": { "enable": { - "default": false, - "description": "Require each node to be autarkic by removing all lines and links.", + "default": true, + "description": "Enable transmission candidate generation for this carrier.", "type": "boolean" }, - "by_country": { - "default": false, - "description": "Require each country to be autarkic by removing all cross-border lines and links. `electricity: autarky` must be enabled.", - "type": "boolean" + "gabriel_filter_min_degree": { + "default": 1, + "description": "Minimum node degree target applied after Gabriel graph filtering. Set to >= 1 to activate Gabriel filtering with min-degree backfilling; 0 disables it.", + "minimum": 0, + "type": "integer" + }, + "max_offshore_haversine_distance": { + "default": null, + "description": "Maximum haversine distance in km for offshore transmission candidates. Candidate edges a total offshore length exceeding this threshold are excluded. Defaults to infinity (no limit).", + "exclusiveMinimum": 0, + "type": "number" + }, + "length_factor": { + "default": 1.25, + "description": "Multiplier applied to geometric corridor lengths.", + "exclusiveMinimum": 0, + "type": "number" + }, + "cost_factor": { + "default": 1, + "description": "Multiplier applied to the capital cost of transmission infrastructure.", + "exclusiveMinimum": 0, + "type": "number" } } }, - "transmission_limit": { - "default": "vopt", - "description": "Limit on transmission expansion. The first part can be `v` (for setting a limit on line volume) or `c` (for setting a limit on line cost). The second part can be `opt` or a float bigger than one (e.g. 1.25). If `opt` is chosen line expansion is optimised according to its capital cost (where the choice `v` only considers overhead costs for HVDC transmission lines, while `c` uses more accurate costs distinguishing between overhead and underwater sections and including inverter pairs). The setting `v1.25` will limit the total volume of line expansion to 25% of currently installed capacities weighted by individual line lengths. The setting `c1.25` will allow to build a transmission network that costs no more than 25 % more than the current system.", - "type": "string" + "gas": { + "properties": { + "enable": { + "default": true, + "description": "Add existing natural gas infrastructure, incl. LNG terminals, production and entry-points. A length-weighted `k-edge augmentation algorithm `_ can be run to add new candidate gas pipelines such that all regions of the model can be connected to the gas network. When activated, all the gas demands are regionally disaggregated as well.", + "markdownDescription": "Add existing natural gas infrastructure, incl. LNG terminals, production and entry-points. A length-weighted [k-edge augmentation algorithm ](https://networkx.org/documentation/stable/reference/algorithms/generated/networkx.algorithms.connectivity.edge_augmentation.k_edge_augmentation.html#networkx.algorithms.connectivity.edge_augmentation.k_edge_augmentation) can be run to add new candidate gas pipelines such that all regions of the model can be connected to the gas network. When activated, all the gas demands are regionally disaggregated as well.", + "type": "boolean" + }, + "connectivity_upgrade": { + "default": 1, + "description": "The number of desired edge connectivity (k) in the length-weighted `k-edge augmentation algorithm `_ used for the gas network.", + "markdownDescription": "The number of desired edge connectivity (k) in the length-weighted [k-edge augmentation algorithm ](https://networkx.org/documentation/stable/reference/algorithms/generated/networkx.algorithms.connectivity.edge_augmentation.k_edge_augmentation.html#networkx.algorithms.connectivity.edge_augmentation.k_edge_augmentation) used for the gas network.", + "type": "number" + }, + "efficiency": { + "description": "Configuration for `transmission..efficiency` settings.", + "properties": { + "enable": { + "default": true, + "description": "Apply transmission efficiency for this carrier.", + "type": "boolean" + }, + "efficiency_per_1000km": { + "default": 1, + "description": "Distance-dependent efficiency factor per 1000 km.", + "type": "number" + }, + "compression_per_1000km": { + "default": 0, + "description": "Additional electricity consumption for compression per 1000 km.", + "type": "number" + } + } + } + }, + "description": "Configuration for methane gas transmission candidates." } } }, @@ -9955,331 +11225,102 @@ "description": "Specifies the types of hydro power plants to build per-unit availability time series for. 'ror' stands for run-of-river plants, 'PHS' represents pumped-hydro storage, and 'hydro' stands for hydroelectric dams.", "items": { "type": "string" - }, - "type": "array" - }, - "PHS_max_hours": { - "default": 6, - "description": "Maximum state of charge capacity of the pumped-hydro storage (PHS) in terms of hours at full output capacity `p_nom`. Cf. `PyPSA documentation `_.", - "markdownDescription": "Maximum state of charge capacity of the pumped-hydro storage (PHS) in terms of hours at full output capacity `p_nom`. Cf. [PyPSA documentation ](https://pypsa.readthedocs.io/en/latest/components.html#storage-unit).", - "type": "number" - }, - "hydro_max_hours": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "number" - } - ], - "default": "energy_capacity_totals_by_country", - "description": "Maximum state of charge capacity of the pumped-hydro storage (PHS) in terms of hours at full output capacity `p_nom` or heuristically determined. Cf. `PyPSA documentation `_.", - "markdownDescription": "Maximum state of charge capacity of the pumped-hydro storage (PHS) in terms of hours at full output capacity `p_nom` or heuristically determined. Cf. [PyPSA documentation ](https://pypsa.readthedocs.io/en/latest/components.html#storage-unit)." - }, - "flatten_dispatch": { - "default": false, - "description": "Consider an upper limit for the hydro dispatch. The limit is given by the average capacity factor plus the buffer given in `flatten_dispatch_buffer`.", - "type": "boolean" - }, - "flatten_dispatch_buffer": { - "default": 0.2, - "description": "If `flatten_dispatch` is true, specify the value added above the average capacity factor.", - "type": "number" - }, - "clip_min_inflow": { - "default": 1.0, - "description": "To avoid too small values in the inflow time series, values below this threshold (MW) are set to zero.", - "type": "number" - }, - "eia_norm_year": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "integer" - } - ], - "default": false, - "description": "To specify a specific year by which hydro inflow is normed that deviates from the snapshots' year." - }, - "eia_correct_by_capacity": { - "default": false, - "description": "Correct EIA annual hydro generation data by installed capacity.", - "type": "boolean" - }, - "eia_approximate_missing": { - "default": false, - "description": "Approximate hydro generation data for years not included in EIA dataset through a regression based on annual runoff.", - "type": "boolean" - } - } - } - } - }, - "conventional": { - "additionalProperties": true, - "description": "Configuration for `conventional` settings.", - "properties": { - "unit_commitment": { - "default": false, - "description": "Allow the overwrite of ramp_limit_up, ramp_limit_start_up, ramp_limit_shut_down, p_min_pu, min_up_time, min_down_time, and start_up_cost of conventional generators. Refer to the CSV file 'unit_commitment.csv'.", - "type": "boolean" - }, - "dynamic_fuel_price": { - "default": false, - "description": "Consider the monthly fluctuating fuel prices for each conventional generator. Refer to the CSV file 'data/validation/monthly_fuel_price.csv'.", - "type": "boolean" - }, - "fuel_price_rolling_window": { - "default": 6, - "description": "Monthly rolling mean window for fossil fuel prices smoothing.", - "minimum": 1, - "type": "integer" - }, - "nuclear": { - "additionalProperties": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "number" - } - ] - }, - "description": "For any carrier/technology overwrite attributes as listed below.", - "type": "object" - } - } - }, - "lines": { - "description": "Configuration for `lines` settings.", - "properties": { - "types": { - "additionalProperties": { - "type": "string" - }, - "description": "Specifies line types to assume for the different voltage levels of the ENTSO-E grid extraction. Should normally handle voltage levels 220, 300, and 380 kV.", - "type": "object" - }, - "s_max_pu": { - "default": 0.7, - "description": "Correction factor for line capacities (`s_nom`) to approximate N-1 security and reserve capacity for reactive power flows.", - "type": "number" - }, - "s_nom_max": { - "default": null, - "description": "Global upper limit for the maximum capacity of each extendable line (MW).", - "type": "number" - }, - "max_extension": { - "default": 20000, - "description": "Upper limit for the extended capacity of each extendable line (MW).", - "type": "number" - }, - "length_factor": { - "default": 1.25, - "description": "Correction factor to account for the fact that buses are *not* connected by lines through air-line distance.", - "type": "number" - }, - "reconnect_crimea": { - "default": true, - "description": "Whether to reconnect Crimea to the Ukrainian grid.", - "type": "boolean" - }, - "under_construction": { - "default": "keep", - "description": "Specifies how to handle lines which are currently under construction.", - "enum": [ - "zero", - "remove", - "keep" - ], - "type": "string" - }, - "dynamic_line_rating": { - "description": "Configuration for `lines.dynamic_line_rating` settings.", - "properties": { - "activate": { - "default": false, - "description": "Whether to take dynamic line rating into account.", - "type": "boolean" - }, - "cutout": { - "anyOf": [ - { - "type": "string" - }, - { - "items": { - "type": "string" - }, - "type": "array" - } - ], - "default": "default", - "description": "Specifies the weather data cutout file(s) to use." + }, + "type": "array" }, - "correction_factor": { - "default": 0.95, - "description": "Factor to compensate for overestimation of wind speeds in hourly averaged wind data.", + "PHS_max_hours": { + "default": 6, + "description": "Maximum state of charge capacity of the pumped-hydro storage (PHS) in terms of hours at full output capacity `p_nom`. Cf. `PyPSA documentation `_.", + "markdownDescription": "Maximum state of charge capacity of the pumped-hydro storage (PHS) in terms of hours at full output capacity `p_nom`. Cf. [PyPSA documentation ](https://pypsa.readthedocs.io/en/latest/components.html#storage-unit).", "type": "number" }, - "max_voltage_difference": { + "hydro_max_hours": { "anyOf": [ { - "type": "number" + "type": "string" }, { - "const": false, - "type": "boolean" + "type": "number" } ], + "default": "energy_capacity_totals_by_country", + "description": "Maximum state of charge capacity of the pumped-hydro storage (PHS) in terms of hours at full output capacity `p_nom` or heuristically determined. Cf. `PyPSA documentation `_.", + "markdownDescription": "Maximum state of charge capacity of the pumped-hydro storage (PHS) in terms of hours at full output capacity `p_nom` or heuristically determined. Cf. [PyPSA documentation ](https://pypsa.readthedocs.io/en/latest/components.html#storage-unit)." + }, + "flatten_dispatch": { "default": false, - "description": "Maximum voltage angle difference in degrees or 'false' to disable." + "description": "Consider an upper limit for the hydro dispatch. The limit is given by the average capacity factor plus the buffer given in `flatten_dispatch_buffer`.", + "type": "boolean" }, - "max_line_rating": { + "flatten_dispatch_buffer": { + "default": 0.2, + "description": "If `flatten_dispatch` is true, specify the value added above the average capacity factor.", + "type": "number" + }, + "clip_min_inflow": { + "default": 1.0, + "description": "To avoid too small values in the inflow time series, values below this threshold (MW) are set to zero.", + "type": "number" + }, + "eia_norm_year": { "anyOf": [ { - "type": "number" + "type": "boolean" }, { - "const": false, - "type": "boolean" + "type": "integer" } ], "default": false, - "description": "Maximum line rating relative to nominal capacity without DLR, e.g. 1.3 or 'false' to disable." - } - } - } - } - }, - "links": { - "description": "Configuration for `links` settings.", - "properties": { - "p_max_pu": { - "default": 1.0, - "description": "Correction factor for link capacities `p_nom`.", - "type": "number" - }, - "p_min_pu": { - "default": -1.0, - "description": "Correction factor for link capacities `p_nom`.", - "type": "number" - }, - "p_nom_max": { - "default": null, - "description": "Global upper limit for the maximum capacity of each extendable DC link (MW).", - "type": "number" - }, - "max_extension": { - "default": 30000, - "description": "Upper limit for the extended capacity of each extendable DC link (MW).", - "type": "number" - }, - "length_factor": { - "default": 1.25, - "description": "Correction factor to account for the fact that buses are *not* connected by links through air-line distance.", - "type": "number" - }, - "under_construction": { - "default": "keep", - "description": "Specifies how to handle lines which are currently under construction.", - "enum": [ - "zero", - "remove", - "keep" - ], - "type": "string" - } - } - }, - "transmission_projects": { - "description": "Configuration for `transmission_projects` settings.", - "properties": { - "enable": { - "default": true, - "description": "Whether to integrate this transmission projects or not.", - "type": "boolean" - }, - "include": { - "description": "Configuration for `transmission_projects.include` settings.", - "properties": { - "tyndp2020": { - "default": true, - "description": "Whether to integrate the TYNDP 2020 dataset.", - "type": "boolean" + "description": "To specify a specific year by which hydro inflow is normed that deviates from the snapshots' year." }, - "nep": { - "default": true, - "description": "Whether to integrate the German network development plan dataset.", + "eia_correct_by_capacity": { + "default": false, + "description": "Correct EIA annual hydro generation data by installed capacity.", "type": "boolean" }, - "manual": { - "default": true, - "description": "Whether to integrate the manually added transmission projects. They are taken from the previously existing links_tyndp.csv file.", + "eia_approximate_missing": { + "default": false, + "description": "Approximate hydro generation data for years not included in EIA dataset through a regression based on annual runoff.", "type": "boolean" } } - }, - "skip": { - "description": "Type of lines to skip from all transmission projects. Possible values are: `upgraded_lines`, `upgraded_links`, `new_lines`, `new_links`.", - "items": { - "type": "string" - }, - "type": "array" - }, - "status": { - "anyOf": [ - { - "items": { - "type": "string" - }, - "type": "array" - }, - { - "additionalProperties": { - "items": { - "type": "string" - }, - "type": "array" - }, - "type": "object" - } - ], - "description": "Status to include into the model as list or as dict with name of project and status to include. Possible values for status are `under_construction`, `in_permitting`, `confirmed`, `planned_not_yet_permitted`, `under_consideration`." - }, - "new_link_capacity": { - "default": "zero", - "description": "Whether to set the new link capacity to the provided capacity or set it to zero.", - "enum": [ - "zero", - "keep" - ], - "type": "string" } } }, - "transformers": { - "description": "Configuration for `transformers` settings.", + "conventional": { + "additionalProperties": true, + "description": "Configuration for `conventional` settings.", "properties": { - "x": { - "default": 0.1, - "description": "Series reactance in per unit (p.u.), using `s_nom` as base power of the transformer. Overwritten if `type` is specified.", - "type": "number" + "unit_commitment": { + "default": false, + "description": "Allow the overwrite of ramp_limit_up, ramp_limit_start_up, ramp_limit_shut_down, p_min_pu, min_up_time, min_down_time, and start_up_cost of conventional generators. Refer to the CSV file 'unit_commitment.csv'.", + "type": "boolean" }, - "s_nom": { - "default": 2000.0, - "description": "Limit of apparent power which can pass through branch (MVA). Overwritten if `type` is specified.", - "type": "number" + "dynamic_fuel_price": { + "default": false, + "description": "Consider the monthly fluctuating fuel prices for each conventional generator. Refer to the CSV file 'data/validation/monthly_fuel_price.csv'.", + "type": "boolean" }, - "type": { - "default": "", - "description": "Specifies transformer types to assume for the transformers of the ENTSO-E grid extraction.", - "type": "string" + "fuel_price_rolling_window": { + "default": 6, + "description": "Monthly rolling mean window for fossil fuel prices smoothing.", + "minimum": 1, + "type": "integer" + }, + "nuclear": { + "additionalProperties": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "number" + } + ] + }, + "description": "For any carrier/technology overwrite attributes as listed below.", + "type": "object" } } }, @@ -11142,16 +12183,6 @@ "description": "Add option to spatially resolve carrier representing stored carbon dioxide. This allows for more detailed modelling of CCUTS, e.g. regarding the capturing of industrial process emissions, usage as feedstock for electrofuels, transport of carbon dioxide, and geological sequestration sites.", "type": "boolean" }, - "co2_network": { - "default": true, - "description": "Add option for planning a new carbon dioxide transmission network.", - "type": "boolean" - }, - "co2_network_cost_factor": { - "default": 1, - "description": "The cost factor for the capital cost of the carbon dioxide transmission network.", - "type": "number" - }, "cc_fraction": { "default": 0.9, "description": "The default fraction of CO2 captured with post-combustion capture.", @@ -11273,97 +12304,14 @@ "description": "Add option for using waste heat of electrolysis in district heating networks.", "type": "number" }, - "electricity_transmission_grid": { - "default": true, - "description": "Switch for enabling/disabling the electricity transmission grid.", - "type": "boolean" - }, - "electricity_distribution_grid": { - "default": true, - "description": "Add a simplified representation of the exchange capacity between transmission and distribution grid level through a link.", - "type": "boolean" - }, - "electricity_distribution_grid_cost_factor": { - "default": 1.0, - "description": "Multiplies the investment cost of the electricity distribution grid.", - "type": "number" - }, - "electricity_grid_connection": { + "electricity_grid_connection_cost": { "default": true, "description": "Add the cost of electricity grid connection for onshore wind and solar.", "type": "boolean" }, - "transmission_efficiency": { - "description": "Configuration for `sector.transmission_efficiency` settings.", - "properties": { - "enable": { - "description": "Switch to select the carriers for which transmission efficiency is to be added. Carriers not listed assume lossless transmission.", - "items": { - "type": "string" - }, - "type": "array" - }, - "DC": { - "additionalProperties": { - "type": "number" - }, - "description": "DC transmission efficiency.", - "type": "object" - }, - "H2 pipeline": { - "additionalProperties": { - "type": "number" - }, - "description": "H2 pipeline transmission efficiency.", - "type": "object" - }, - "gas pipeline": { - "additionalProperties": { - "type": "number" - }, - "description": "Gas pipeline transmission efficiency.", - "type": "object" - }, - "electricity distribution grid": { - "additionalProperties": { - "type": "number" - }, - "description": "Electricity distribution grid efficiency.", - "type": "object" - } - } - }, - "H2_network": { - "default": true, - "description": "Add option for new hydrogen pipelines.", - "type": "boolean" - }, - "gas_network": { - "default": true, - "description": "Add existing natural gas infrastructure, incl. LNG terminals, production and entry-points. The existing gas network is added with a lossless transport model. A length-weighted `k-edge augmentation algorithm `_ can be run to add new candidate gas pipelines such that all regions of the model can be connected to the gas network. When activated, all the gas demands are regionally disaggregated as well.", - "markdownDescription": "Add existing natural gas infrastructure, incl. LNG terminals, production and entry-points. The existing gas network is added with a lossless transport model. A length-weighted [k-edge augmentation algorithm ](https://networkx.org/documentation/stable/reference/algorithms/generated/networkx.algorithms.connectivity.edge_augmentation.k_edge_augmentation.html#networkx.algorithms.connectivity.edge_augmentation.k_edge_augmentation) can be run to add new candidate gas pipelines such that all regions of the model can be connected to the gas network. When activated, all the gas demands are regionally disaggregated as well.", - "type": "boolean" - }, - "H2_retrofit": { - "default": false, - "description": "Add option for retrofiting existing pipelines to transport hydrogen.", - "type": "boolean" - }, - "H2_retrofit_capacity_per_CH4": { - "default": 0.6, - "description": "The ratio for H2 capacity per original CH4 capacity of retrofitted pipelines. The `European Hydrogen Backbone (April, 2020) p.15 `_ 60% of original natural gas capacity could be used in cost-optimal case as H2 capacity.", - "markdownDescription": "The ratio for H2 capacity per original CH4 capacity of retrofitted pipelines. The [European Hydrogen Backbone (April, 2020) p.15 ](https://gasforclimate2050.eu/wp-content/uploads/2020/07/2020_European-Hydrogen-Backbone_Report.pdf) 60% of original natural gas capacity could be used in cost-optimal case as H2 capacity.", - "type": "number" - }, - "gas_network_connectivity_upgrade": { - "default": 1, - "description": "The number of desired edge connectivity (k) in the length-weighted `k-edge augmentation algorithm `_ used for the gas network.", - "markdownDescription": "The number of desired edge connectivity (k) in the length-weighted [k-edge augmentation algorithm ](https://networkx.org/documentation/stable/reference/algorithms/generated/networkx.algorithms.connectivity.edge_augmentation.k_edge_augmentation.html#networkx.algorithms.connectivity.edge_augmentation.k_edge_augmentation) used for the gas network.", - "type": "number" - }, - "gas_distribution_grid": { + "gas_distribution_grid_cost": { "default": true, - "description": "Add a gas distribution grid.", + "description": "Add gas distribution grid costs to gas-consuming components.", "type": "boolean" }, "gas_distribution_grid_cost_factor": { diff --git a/config/test/config.electricity.yaml b/config/test/config.electricity.yaml index 7e13435c5f..ba4e93bf2d 100644 --- a/config/test/config.electricity.yaml +++ b/config/test/config.electricity.yaml @@ -68,10 +68,12 @@ clustering: temporal: resolution_elec: 24h -lines: - dynamic_line_rating: - activate: true - max_line_rating: 1.3 +transmission: + electricity: + lines: + dynamic_line_rating: + activate: true + max_line_rating: 1.3 solving: diff --git a/config/test/config.myopic.yaml b/config/test/config.myopic.yaml index e76df5a395..0dc89b2718 100644 --- a/config/test/config.myopic.yaml +++ b/config/test/config.myopic.yaml @@ -56,7 +56,6 @@ sector: regional_co2_sequestration_potential: enable: false co2_spatial: false - co2_network: false methanol: methanol_to_power: ocgt: false @@ -78,6 +77,10 @@ electricity: estimate_renewable_capacities: enable: false +transmission: + carbon_dioxide: + enable: false + atlite: default_cutout: be-03-2013-era5 cutouts: diff --git a/config/test/config.overnight.yaml b/config/test/config.overnight.yaml index f2ba5209e5..b83fbb8a01 100644 --- a/config/test/config.overnight.yaml +++ b/config/test/config.overnight.yaml @@ -36,6 +36,15 @@ electricity: renewable_carriers: [solar, solar-hsat, onwind, offwind-ac, offwind-dc, offwind-float] +transmission: + hydrogen: + retrofit: + enable: true + carbon_dioxide: + enable: false + gas: + enable: true + atlite: default_cutout: be-03-2013-era5 cutouts: @@ -59,8 +68,6 @@ clustering: resolution_sector: 24h sector: - gas_network: true - H2_retrofit: true residential_heat: dsm: enable: true @@ -96,7 +103,6 @@ sector: regional_co2_sequestration_potential: enable: false co2_spatial: false - co2_network: false methanol: methanol_to_power: ocgt: false diff --git a/config/test/config.perfect.yaml b/config/test/config.perfect.yaml index 16a5199ad3..1659766f30 100644 --- a/config/test/config.perfect.yaml +++ b/config/test/config.perfect.yaml @@ -39,6 +39,10 @@ electricity: renewable_carriers: [solar, solar-hsat, onwind, offwind-ac, offwind-dc, offwind-float] +transmission: + carbon_dioxide: + enable: false + sector: min_part_load_fischer_tropsch: 0 min_part_load_methanolisation: 0 @@ -68,7 +72,6 @@ sector: regional_co2_sequestration_potential: enable: false co2_spatial: false - co2_network: false methanol: methanol_to_power: ocgt: false diff --git a/config/test/config.tyndp.yaml b/config/test/config.tyndp.yaml index 7d3cc73073..4fffe83c79 100644 --- a/config/test/config.tyndp.yaml +++ b/config/test/config.tyndp.yaml @@ -22,9 +22,6 @@ snapshots: end: "2013-03-08" electricity: - base_network: tyndp - transmission_limit: v1.0 - extendable_carriers: Generator: [OCGT] StorageUnit: [battery] @@ -33,6 +30,19 @@ electricity: renewable_carriers: [solar, solar-hsat, onwind, offwind-ac, offwind-dc, offwind-float] +transmission: + electricity: + base_network: tyndp + transmission_limit: v1.0 + links: + p_min_pu: 0 + projects: + enable: true + include: + tyndp2020: false + nep: false + manual: false + atlite: default_cutout: europe-2013-03-sarah3-era5 cutouts: @@ -48,22 +58,6 @@ renewable: max_depth: false min_depth: false -links: - p_min_pu: 0 - -transmission_projects: - include: - tyndp2020: false - nep: false - manual: false - -sector: - transmission_efficiency: - enable: - - H2 pipeline - - gas pipeline - - electricity distribution grid - clustering: mode: administrative administrative: diff --git a/doc/configuration.rst b/doc/configuration.rst index ae4bfb2cdc..e5852c045f 100644 --- a/doc/configuration.rst +++ b/doc/configuration.rst @@ -288,6 +288,54 @@ Switches for some rules and optional features. :start-at: electricity: :end-before: # docs +.. _transmission_cf: + +``transmission`` +================ + +Configure transmission related settings for all carriers. + +For non electric carriers, the transmission candidates are built based on +Delaunay triangulation and optional Gabriel filtering. The minimum degree +specifies the minimum connectedness of the candidate graph. + +.. _transmission_projects_cf: + +``electricity.projects`` +------------------------ + +Allows to define additional transmission projects that will be added to the +base network, e.g., from the TYNDP 2020 dataset. The projects are read in +from the CSV files in the subfolder of ``data/transmission_projects/``. New +transmission projects, e.g. from TYNDP 2024, can be added in a new subfolder +of transmission projects, e.g. ``data/transmission_projects/tyndp2024`` while +extending the list of projects in ``transmission.electricity.projects.include`` +in the configuration. The CSV files in the project folder should have the same +columns as the CSV files in the template folder +``data/transmission_projects/template``. + +.. jsonschema:: ../config/schema.default.json#/$defs/TransmissionProjectsConfig + :lift_description: + :hide_key: /**/additionalProperties + +**YAML Syntax** + +.. literalinclude:: ../config/config.default.yaml + :language: yaml + :start-at: projects: + :end-before: hydrogen: + +.. jsonschema:: ../config/schema.default.json#/$defs/TransmissionConfig + :lift_description: + :hide_key: /**/additionalProperties + +**YAML Syntax** + +.. literalinclude:: ../config/config.default.yaml + :language: yaml + :start-at: transmission: + :end-before: # docs + .. _atlite_cf: ``atlite`` @@ -426,72 +474,6 @@ overwrite the existing values. :start-at: conventional: :end-before: # docs -.. _lines_cf: - -``lines`` -========= - -.. jsonschema:: ../config/schema.default.json#/$defs/LinesConfig - :lift_description: - :hide_key: /**/additionalProperties - -**YAML Syntax** - -.. literalinclude:: ../config/config.default.yaml - :language: yaml - :start-at: lines: - :end-before: # docs - -.. _links_cf: - -``links`` -============= - -.. jsonschema:: ../config/schema.default.json#/$defs/LinksConfig - :lift_description: - :hide_key: /**/additionalProperties - -**YAML Syntax** - -.. literalinclude:: ../config/config.default.yaml - :language: yaml - :start-at: links: - :end-before: # docs - -.. _transmission_projects_cf: - -``transmission_projects`` -========================= - -Allows to define additional transmission projects that will be added to the base network, e.g., from the TYNDP 2020 dataset. The projects are read in from the CSV files in the subfolder of ``data/transmission_projects/``. New transmission projects, e.g. from TYNDP 2024, can be added in a new subfolder of transmission projects, e.g. ``data/transmission_projects/tyndp2024`` while extending the list of ``transmission_projects`` in the ``config.yaml`` by ``tyndp2024``. The CSV files in the project folder should have the same columns as the CSV files in the template folder ``data/transmission_projects/template``. - -.. jsonschema:: ../config/schema.default.json#/$defs/TransmissionProjectsConfig - :lift_description: - :hide_key: /**/additionalProperties - -**YAML Syntax** - -.. literalinclude:: ../config/config.default.yaml - :language: yaml - :start-at: transmission_projects: - :end-before: # docs - -.. _transformers_cf: - -``transformers`` -================ - -.. jsonschema:: ../config/schema.default.json#/$defs/TransformersConfig - :lift_description: - :hide_key: /**/additionalProperties - -**YAML Syntax** - -.. literalinclude:: ../config/config.default.yaml - :language: yaml - :start-at: transformers: - :end-before: # docs - .. _load_cf: ``load`` diff --git a/doc/preparation.rst b/doc/preparation.rst index a6cce4d5bb..7a29fab66b 100644 --- a/doc/preparation.rst +++ b/doc/preparation.rst @@ -79,20 +79,20 @@ Rule ``build_natura`` .. automodule:: build_natura -Rule ``build_transmission_projects`` +Rule ``build_electricity_transmission_projects`` ==================================== -.. automodule:: build_transmission_projects +.. automodule:: build_electricity_transmission_projects Rule ``build_line_rating`` ================================= .. automodule:: build_line_rating -Rule ``add_transmission_projects_and_dlr`` +Rule ``add_electricity_transmission_projects_and_dlr`` =========================================== -.. automodule:: add_transmission_projects_and_dlr +.. automodule:: add_electricity_transmission_projects_and_dlr Rule ``build_bidding_zones`` ============================= diff --git a/doc/release_notes.rst b/doc/release_notes.rst index c707e83566..f912d677d5 100644 --- a/doc/release_notes.rst +++ b/doc/release_notes.rst @@ -22,6 +22,26 @@ Release Notes * feat: Improve the config validation to cover scenario management (https://github.com/PyPSA/pypsa-eur/pull/2155). +* feat: Add new rule :mod:`build_transmission_topology` to generate greenfield pipeline investment candidates + (H2, CO2) using Delaunay triangulation with optional Gabriel graph filtering and minimum-degree backfilling, + replacing the former AC-topology-based approach. A ``max_offshore_haversine_distance`` setting per carrier + limits offshore corridor lengths (https://github.com/PyPSA/pypsa-eur/pull/2153). + +* refactor: Rename ``transmission_projects`` to ``transmission.electricity.projects`` and + ``gas_distribution_grid`` to ``gas_distribution_grid_cost`` to better reflect their semantics + (https://github.com/PyPSA/pypsa-eur/pull/2153). Rename `sector.electricity_grid_connection` to `sector.electricity_grid_connection_cost` + and `gas_distribution_grid` to `gas_distribution_grid_cost`. + +**Breaking changes** + +* Consolidate all carrier-specific transmission settings under a new top-level ``transmission`` config + key with sub-keys per carrier (``electricity``, ``hydrogen``, ``carbon_dioxide``, ``gas``, + ``electricity_distribution``), replacing the previously scattered ``lines``, ``links``, ``transformers``, + ``transmission_projects``, ``base_network``, ``transmission_limit``, and sector-level keys such as + ``H2_network``, ``gas_network``, ``co2_network``, ``transmission_efficiency``, ``H2_retrofit``, and + ``gas_distribution_grid``. Carrier efficiencies, H2 retrofit settings, and distribution grid costs are + now nested under the respective carrier sub-key (https://github.com/PyPSA/pypsa-eur/pull/2153). + PyPSA-Eur v2026.02.0 (18th February 2026) ========================================= diff --git a/doc/tutorial.rst b/doc/tutorial.rst index a80a63f202..2a6f18c271 100644 --- a/doc/tutorial.rst +++ b/doc/tutorial.rst @@ -172,12 +172,12 @@ This triggers a workflow of multiple preceding jobs that depend on each rule's i 17[label = "retrieve_jrc_ardeco", color = "0.06 0.6 0.85", style="rounded"]; 18[label = "cluster_network\nclusters: 6", color = "0.12 0.6 0.85", style="rounded"]; 19[label = "simplify_network", color = "0.33 0.6 0.85", style="rounded"]; - 20[label = "add_transmission_projects_and_dlr", color = "0.46 0.6 0.85", style="rounded"]; + 20[label = "add_electricity_transmission_projects_and_dlr", color = "0.46 0.6 0.85", style="rounded"]; 21[label = "base_network", color = "0.00 0.6 0.85", style="rounded"]; 22[label = "retrieve_osm_prebuilt", color = "0.41 0.6 0.85", style="rounded"]; 23[label = "build_line_rating", color = "0.57 0.6 0.85", style="rounded"]; 24[label = "retrieve_cutout\ncutout: be-03-2013-era5", color = "0.18 0.6 0.85", style="rounded"]; - 25[label = "build_transmission_projects", color = "0.04 0.6 0.85", style="rounded"]; + 25[label = "build_electricity_transmission_projects", color = "0.04 0.6 0.85", style="rounded"]; 26[label = "build_electricity_demand_base", color = "0.61 0.6 0.85", style="rounded"]; 27[label = "build_electricity_demand", color = "0.16 0.6 0.85", style="rounded"]; 28[label = "retrieve_electricity_demand", color = "0.21 0.6 0.85", style="rounded"]; @@ -307,36 +307,36 @@ In the terminal, this will show up as a list of jobs to be run: Building DAG of jobs... Job stats: - job count - ------------------------------------- ------- - add_electricity 1 - add_transmission_projects_and_dlr 1 - base_network 1 - build_electricity_demand 1 - build_electricity_demand_base 1 - build_line_rating 1 - build_osm_boundaries 4 - build_powerplants 1 - build_renewable_profiles 6 - build_shapes 1 - build_ship_raster 1 - build_transmission_projects 1 - cluster_network 1 - determine_availability_matrix 6 - prepare_network 1 - retrieve_cost_data 1 - retrieve_databundle 1 - retrieve_eez 1 - retrieve_electricity_demand 1 - retrieve_jrc_ardeco 1 - retrieve_nuts_2021_shapes 1 - retrieve_osm_boundaries 4 - retrieve_osm_prebuilt 1 - retrieve_ship_raster 1 - retrieve_synthetic_electricity_demand 1 - simplify_network 1 - solve_network 1 - total 43 + job count + ------------------------------------- ------- + add_electricity 1 + add_electricity_transmission_projects_and_dlr 1 + base_network 1 + build_electricity_demand 1 + build_electricity_demand_base 1 + build_line_rating 1 + build_osm_boundaries 4 + build_powerplants 1 + build_renewable_profiles 6 + build_shapes 1 + build_ship_raster 1 + build_electricity_transmission_projects 1 + cluster_network 1 + determine_availability_matrix 6 + prepare_network 1 + retrieve_cost_data 1 + retrieve_databundle 1 + retrieve_eez 1 + retrieve_electricity_demand 1 + retrieve_jrc_ardeco 1 + retrieve_nuts_2021_shapes 1 + retrieve_osm_boundaries 4 + retrieve_osm_prebuilt 1 + retrieve_ship_raster 1 + retrieve_synthetic_electricity_demand 1 + simplify_network 1 + solve_network 1 + total 43 ``snakemake`` then runs these jobs in the correct order. diff --git a/doc/tutorial_sector.rst b/doc/tutorial_sector.rst index 7044198716..a1b923d750 100644 --- a/doc/tutorial_sector.rst +++ b/doc/tutorial_sector.rst @@ -68,7 +68,7 @@ which were already included in the electricity-only tutorial: job count ------------------------------------------------ ------- add_electricity 1 - add_transmission_projects_and_dlr 1 + add_electricity_transmission_projects_and_dlr 1 all 1 base_network 1 build_ammonia_production 1 @@ -105,7 +105,7 @@ which were already included in the electricity-only tutorial: build_ship_raster 1 build_shipping_demand 1 build_temperature_profiles 1 - build_transmission_projects 1 + build_electricity_transmission_projects 1 build_transport_demand 1 cluster_gas_network 1 cluster_network 1 @@ -186,10 +186,10 @@ successfully. 22[label = "retrieve_jrc_ardeco", color = "0.22 0.6 0.85", style="rounded"]; 23[label = "cluster_network\nclusters: 5", color = "0.39 0.6 0.85", style="rounded"]; 24[label = "simplify_network", color = "0.13 0.6 0.85", style="rounded"]; - 25[label = "add_transmission_projects_and_dlr", color = "0.44 0.6 0.85", style="rounded"]; + 25[label = "add_electricity_transmission_projects_and_dlr", color = "0.44 0.6 0.85", style="rounded"]; 26[label = "base_network", color = "0.62 0.6 0.85", style="rounded"]; 27[label = "retrieve_osm_prebuilt", color = "0.52 0.6 0.85", style="rounded"]; - 28[label = "build_transmission_projects", color = "0.32 0.6 0.85", style="rounded"]; + 28[label = "build_electricity_transmission_projects", color = "0.32 0.6 0.85", style="rounded"]; 29[label = "build_electricity_demand_base", color = "0.47 0.6 0.85", style="rounded"]; 30[label = "build_electricity_demand", color = "0.34 0.6 0.85", style="rounded"]; 31[label = "retrieve_electricity_demand", color = "0.61 0.6 0.85", style="rounded"]; @@ -560,10 +560,10 @@ workflow: 23[label = "retrieve_jrc_ardeco", color = "0.29 0.6 0.85", style="rounded"]; 24[label = "cluster_network\nclusters: 5", color = "0.30 0.6 0.85", style="rounded"]; 25[label = "simplify_network", color = "0.02 0.6 0.85", style="rounded"]; - 26[label = "add_transmission_projects_and_dlr", color = "0.60 0.6 0.85", style="rounded"]; + 26[label = "add_electricity_transmission_projects_and_dlr", color = "0.60 0.6 0.85", style="rounded"]; 27[label = "base_network", color = "0.30 0.6 0.85", style="rounded"]; 28[label = "retrieve_osm_prebuilt", color = "0.43 0.6 0.85", style="rounded"]; - 29[label = "build_transmission_projects", color = "0.51 0.6 0.85", style="rounded"]; + 29[label = "build_electricity_transmission_projects", color = "0.51 0.6 0.85", style="rounded"]; 30[label = "build_electricity_demand_base", color = "0.64 0.6 0.85", style="rounded"]; 31[label = "build_electricity_demand", color = "0.16 0.6 0.85", style="rounded"]; 32[label = "retrieve_electricity_demand", color = "0.33 0.6 0.85", style="rounded"]; diff --git a/rules/build_electricity.smk b/rules/build_electricity.smk index bb6c4d270b..b207740514 100755 --- a/rules/build_electricity.smk +++ b/rules/build_electricity.smk @@ -60,7 +60,7 @@ rule build_powerplants: def input_base_network(w): - base_network = config_provider("electricity", "base_network")(w) + base_network = config_provider("transmission", "electricity", "base_network")(w) source = config_provider("data", "osm", "source")(w) components = {"buses", "lines", "links", "converters", "transformers"} if (base_network == "osm") and (source == "archive"): @@ -100,9 +100,9 @@ rule base_network: countries=config_provider("countries"), snapshots=config_provider("snapshots"), drop_leap_day=config_provider("enable", "drop_leap_day"), - lines=config_provider("lines"), - links=config_provider("links"), - transformers=config_provider("transformers"), + lines=config_provider("transmission", "electricity", "lines"), + links=config_provider("transmission", "electricity", "links"), + transformers=config_provider("transmission", "electricity", "transformers"), clustering=config_provider("clustering", "mode"), admin_levels=config_provider("clustering", "administrative"), message: @@ -477,7 +477,10 @@ rule build_line_rating: input: base_network=resources("networks/base.nc"), cutout=lambda w: input_cutout( - w, config_provider("lines", "dynamic_line_rating", "cutout")(w) + w, + config_provider( + "transmission", "electricity", "lines", "dynamic_line_rating", "cutout" + )(w), ), output: output=resources("dlr.nc"), @@ -497,77 +500,87 @@ rule build_line_rating: scripts("build_line_rating.py") -rule build_transmission_projects: +rule build_electricity_transmission_projects: input: base_network=resources("networks/base.nc"), offshore_shapes=resources("offshore_shapes.geojson"), europe_shape=resources("europe_shape.geojson"), transmission_projects=lambda w: [ "data/transmission_projects/" + name - for name, include in config_provider("transmission_projects", "include")( - w - ).items() + for name, include in config_provider( + "transmission", "electricity", "projects", "include" + )(w).items() if include ], output: - new_lines=resources("transmission_projects/new_lines.csv"), - new_links=resources("transmission_projects/new_links.csv"), - adjust_lines=resources("transmission_projects/adjust_lines.csv"), - adjust_links=resources("transmission_projects/adjust_links.csv"), - new_buses=resources("transmission_projects/new_buses.csv"), + new_lines=resources("transmission/electricity_projects/new_lines.csv"), + new_links=resources("transmission/electricity_projects/new_links.csv"), + adjust_lines=resources("transmission/electricity_projects/adjust_lines.csv"), + adjust_links=resources("transmission/electricity_projects/adjust_links.csv"), + new_buses=resources("transmission/electricity_projects/new_buses.csv"), log: - logs("build_transmission_projects.log"), + logs("build_electricity_transmission_projects.log"), benchmark: - benchmarks("build_transmission_projects") + benchmarks("build_electricity_transmission_projects") threads: 1 resources: mem_mb=4000, params: - transmission_projects=config_provider("transmission_projects"), - line_factor=config_provider("lines", "length_factor"), - s_max_pu=config_provider("lines", "s_max_pu"), + transmission_projects=config_provider("transmission", "electricity", "projects"), + line_factor=config_provider( + "transmission", "electricity", "lines", "length_factor" + ), + s_max_pu=config_provider("transmission", "electricity", "lines", "s_max_pu"), message: - "Building transmission projects" + "Building electricity transmission projects" script: - scripts("build_transmission_projects.py") + scripts("build_electricity_transmission_projects.py") -rule add_transmission_projects_and_dlr: +rule add_electricity_transmission_projects_and_dlr: input: network=resources("networks/base.nc"), dlr=lambda w: ( resources("dlr.nc") - if config_provider("lines", "dynamic_line_rating", "activate")(w) + if config_provider( + "transmission", + "electricity", + "lines", + "dynamic_line_rating", + "activate", + )(w) else [] ), transmission_projects=lambda w: ( [ - resources("transmission_projects/new_buses.csv"), - resources("transmission_projects/new_lines.csv"), - resources("transmission_projects/new_links.csv"), - resources("transmission_projects/adjust_lines.csv"), - resources("transmission_projects/adjust_links.csv"), + resources("transmission/electricity_projects/new_buses.csv"), + resources("transmission/electricity_projects/new_lines.csv"), + resources("transmission/electricity_projects/new_links.csv"), + resources("transmission/electricity_projects/adjust_lines.csv"), + resources("transmission/electricity_projects/adjust_links.csv"), ] - if config_provider("transmission_projects", "enable")(w) + if config_provider("transmission", "electricity", "projects", "enable")(w) else [] ), output: network=resources("networks/base_extended.nc"), log: - logs("add_transmission_projects_and_dlr.log"), + logs("add_electricity_transmission_projects_and_dlr.log"), benchmark: - benchmarks("add_transmission_projects_and_dlr") + benchmarks("add_electricity_transmission_projects_and_dlr") threads: 1 resources: mem_mb=4000, params: - transmission_projects=config_provider("transmission_projects"), - dlr=config_provider("lines", "dynamic_line_rating"), - s_max_pu=config_provider("lines", "s_max_pu"), + transmission_projects=config_provider("transmission", "electricity", "projects"), + dlr=config_provider( + "transmission", "electricity", "lines", "dynamic_line_rating" + ), + s_max_pu=config_provider("transmission", "electricity", "lines", "s_max_pu"), message: "Adding transmission projects and dynamic line ratings" script: - scripts("add_transmission_projects_and_dlr.py") + scripts("add_electricity_transmission_projects_and_dlr.py") def input_class_regions(w): @@ -677,8 +690,12 @@ rule simplify_network: aggregation_strategies=config_provider( "clustering", "aggregation_strategies", default={} ), - p_max_pu=config_provider("links", "p_max_pu", default=1.0), - p_min_pu=config_provider("links", "p_min_pu", default=-1.0), + p_max_pu=config_provider( + "transmission", "electricity", "links", "p_max_pu", default=1.0 + ), + p_min_pu=config_provider( + "transmission", "electricity", "links", "p_min_pu", default=-1.0 + ), message: "Simplifying network" script: @@ -694,11 +711,11 @@ def input_custom_busmap(w): mode = config_provider("clustering", "mode", default="busmap")(w) if mode == "custom_busmap": - base_network = config_provider("electricity", "base_network")(w) + base_network = config_provider("transmission", "electricity", "base_network")(w) custom_busmap = f"data/busmaps/base_s_{w.clusters}_{base_network}.csv" if mode == "custom_busshapes": - base_network = config_provider("electricity", "base_network")(w) + base_network = config_provider("transmission", "electricity", "base_network")(w) custom_busshapes = f"data/busshapes/base_s_{w.clusters}_{base_network}.geojson" return { @@ -753,7 +770,9 @@ rule cluster_network: "electricity", "conventional_carriers", default=[] ), max_hours=config_provider("electricity", "max_hours"), - length_factor=config_provider("lines", "length_factor"), + length_factor=config_provider( + "transmission", "electricity", "lines", "length_factor" + ), cluster_mode=config_provider("clustering", "mode"), copperplate_regions=config_provider("clustering", "copperplate_regions"), message: @@ -817,8 +836,12 @@ rule add_electricity: resources: mem_mb=10000, params: - line_length_factor=config_provider("lines", "length_factor"), - link_length_factor=config_provider("links", "length_factor"), + line_length_factor=config_provider( + "transmission", "electricity", "lines", "length_factor" + ), + link_length_factor=config_provider( + "transmission", "electricity", "links", "length_factor" + ), scaling_factor=config_provider("load", "scaling_factor"), countries=config_provider("countries"), snapshots=config_provider("snapshots"), @@ -860,8 +883,8 @@ rule prepare_network: mem_mb=4000, params: time_resolution=config_provider("clustering", "temporal", "resolution_elec"), - links=config_provider("links"), - lines=config_provider("lines"), + links=config_provider("transmission", "electricity", "links"), + lines=config_provider("transmission", "electricity", "lines"), co2base=config_provider("electricity", "co2base"), co2limit_enable=config_provider("electricity", "co2limit_enable", default=False), co2limit=config_provider("electricity", "co2limit"), @@ -871,7 +894,9 @@ rule prepare_network: adjustments=config_provider("adjustments", "electricity"), autarky=config_provider("electricity", "autarky", default={}), drop_leap_day=config_provider("enable", "drop_leap_day"), - transmission_limit=config_provider("electricity", "transmission_limit"), + transmission_limit=config_provider( + "transmission", "electricity", "transmission_limit" + ), message: "Preparing network for model with {wildcards.clusters} clusters and options {wildcards.opts}" script: @@ -958,7 +983,7 @@ rule build_osm_network: params: countries=config_provider("countries"), voltages=config_provider("electricity", "voltages"), - line_types=config_provider("lines", "types"), + line_types=config_provider("transmission", "electricity", "lines", "types"), under_construction=config_provider("osm_network_release", "under_construction"), remove_after=config_provider("osm_network_release", "remove_after"), message: diff --git a/rules/build_sector.smk b/rules/build_sector.smk index 59b6540fea..d9b736b69d 100755 --- a/rules/build_sector.smk +++ b/rules/build_sector.smk @@ -2,6 +2,8 @@ # # SPDX-License-Identifier: MIT +import math + rule build_population_layouts: input: @@ -146,6 +148,50 @@ rule cluster_gas_network: scripts("cluster_gas_network.py") +rule build_transmission_delaunay_graph: + input: + network=resources("networks/base_s_{clusters}.nc"), + offshore_shapes=resources("offshore_shapes.geojson"), + output: + delaunay_graph=resources("transmission/delaunay_graph_{clusters}.geojson"), + log: + logs("build_transmission_delaunay_graph_{clusters}.log"), + benchmark: + benchmarks("build_transmission_delaunay_graph/{clusters}") + resources: + mem_mb=4000, + message: + "Building Delaunay triangulation graph for {wildcards.clusters} clusters" + script: + scripts("build_transmission_delaunay_graph.py") + + +rule build_transmission_topology: + input: + delaunay_graph=resources("transmission/delaunay_graph_{clusters}.geojson"), + output: + candidates=resources( + "transmission/candidates_{clusters}_min_{min_degree}_maxoffdist_{max_offdist}_km.geojson" + ), + log: + logs( + "build_transmission_topology_{clusters}_min_{min_degree}_maxoffdist_{max_offdist}.log" + ), + benchmark: + benchmarks( + "build_transmission_topology/{clusters}_min_{min_degree}_maxoffdist_{max_offdist}" + ) + resources: + mem_mb=2000, + params: + min_degree=lambda w: int(w.min_degree), + max_offdist=lambda w: float(w.max_offdist), + message: + "Building transmission topology for {wildcards.clusters} clusters (min_degree={wildcards.min_degree}, max_offdist={wildcards.max_offdist} km)" + script: + scripts("build_transmission_topology.py") + + rule build_daily_heat_demand: input: pop_layout=resources("pop_layout_total.nc"), @@ -1562,10 +1608,32 @@ def input_heat_source_power(w): } +def input_transmission_candidates(w): + """Generate transmission candidate file inputs for enabled transmission carriers.""" + candidates = {} + for carrier in ("hydrogen", "carbon_dioxide"): + carrier_cfg = config["transmission"][carrier] + if not carrier_cfg["enable"]: + continue + key = f"{carrier}_transmission_candidates" + min_degree = carrier_cfg["gabriel_filter_min_degree"] + max_offdist = carrier_cfg["max_offshore_haversine_distance"] + if min_degree > 0 or max_offdist < float("inf"): + candidates[key] = resources( + f"transmission/candidates_{{clusters}}_min_{min_degree}_maxoffdist_{max_offdist}_km.geojson" + ) + else: + candidates[key] = resources( + "transmission/delaunay_graph_{clusters}.geojson" + ) + return candidates + + rule prepare_sector_network: input: unpack(input_profile_offwind), unpack(input_heat_source_power), + unpack(input_transmission_candidates), **rules.cluster_gas_network.output, **rules.build_gas_input_locations.output, snapshot_weightings=resources( @@ -1714,9 +1782,7 @@ rule prepare_sector_network: sector=config_provider("sector"), industry=config_provider("industry"), renewable=config_provider("renewable"), - lines=config_provider("lines"), pypsa_eur=config_provider("pypsa_eur"), - length_factor=config_provider("lines", "length_factor"), planning_horizons=config_provider("scenario", "planning_horizons"), countries=config_provider("countries"), adjustments=config_provider("adjustments", "sector"), @@ -1737,6 +1803,7 @@ rule prepare_sector_network: temperature_limited_stores=config_provider( "sector", "district_heating", "temperature_limited_stores" ), + transmission=config_provider("transmission"), message: "Preparing integrated sector-coupled energy network for {wildcards.clusters} clusters, {wildcards.planning_horizons} planning horizon, {wildcards.opts} electric options and {wildcards.sector_opts} sector options" script: diff --git a/rules/development.smk b/rules/development.smk index 88bad702a1..809270a0f1 100644 --- a/rules/development.smk +++ b/rules/development.smk @@ -26,9 +26,9 @@ rule base_network_incumbent: countries=config_provider("countries"), snapshots=config_provider("snapshots"), drop_leap_day=config_provider("enable", "drop_leap_day"), - lines=config_provider("lines"), - links=config_provider("links"), - transformers=config_provider("transformers"), + lines=config_provider("transmission", "electricity", "lines"), + links=config_provider("transmission", "electricity", "links"), + transformers=config_provider("transmission", "electricity", "transformers"), clustering=config_provider("clustering", "mode"), admin_levels=config_provider("clustering", "administrative"), message: @@ -54,7 +54,7 @@ rule make_network_comparison: mem_mb=2000, params: countries=config_provider("countries"), - base_network=config_provider("electricity", "base_network"), + base_network=config_provider("transmission", "electricity", "base_network"), compare_to_version=config_provider( "osm_network_release", "compare_to", "version" ), @@ -85,7 +85,7 @@ rule prepare_osm_network_release: resources: mem_mb=1000, params: - line_types=config["lines"]["types"], + line_types=config["transmission"]["electricity"]["lines"]["types"], release_version=config_provider("osm_network_release", "release_version"), include_polygons=True, export=True, @@ -108,7 +108,7 @@ rule map_incumbent: resources: mem_mb=1000, params: - line_types=config["lines"]["types"], + line_types=config["transmission"]["electricity"]["lines"]["types"], release_version="Incumbent", include_polygons=False, export=False, diff --git a/rules/postprocess.smk b/rules/postprocess.smk index eeb9e5a26c..b2c92b0814 100644 --- a/rules/postprocess.smk +++ b/rules/postprocess.smk @@ -62,7 +62,9 @@ if config["foresight"] != "perfect": mem_mb=10000, params: plotting=config_provider("plotting"), - transmission_limit=config_provider("electricity", "transmission_limit"), + transmission_limit=config_provider( + "transmission", "electricity", "transmission_limit" + ), message: "Plotting power network for {wildcards.clusters} clusters, {wildcards.opts} electric options, {wildcards.sector_opts} sector options and {wildcards.planning_horizons} planning horizons" script: diff --git a/rules/solve_myopic.smk b/rules/solve_myopic.smk index edc8c59a94..738b8e58a3 100644 --- a/rules/solve_myopic.smk +++ b/rules/solve_myopic.smk @@ -45,6 +45,7 @@ rule add_existing_baseyear: costs=config_provider("costs"), heat_pump_sources=config_provider("sector", "heat_pump_sources"), energy_totals_year=config_provider("energy", "energy_totals_year"), + transmission=config_provider("transmission"), message: "Adding existing infrastructure for base year for {wildcards.clusters} clusters, {wildcards.planning_horizons} planning horizons, {wildcards.opts} electric options and {wildcards.sector_opts} sector options" script: @@ -82,10 +83,7 @@ rule add_brownfield: resources: mem_mb=10000, params: - H2_retrofit=config_provider("sector", "H2_retrofit"), - H2_retrofit_capacity_per_CH4=config_provider( - "sector", "H2_retrofit_capacity_per_CH4" - ), + transmission=config_provider("transmission"), threshold_capacity=config_provider("existing_capacities", "threshold_capacity"), snapshots=config_provider("snapshots"), drop_leap_day=config_provider("enable", "drop_leap_day"), @@ -141,6 +139,7 @@ rule solve_sector_network_myopic: params: solving=config_provider("solving"), foresight=config_provider("foresight"), + transmission=config_provider("transmission"), co2_sequestration_potential=config_provider( "sector", "co2_sequestration_potential", default=200 ), diff --git a/rules/solve_overnight.smk b/rules/solve_overnight.smk index 0733312440..3e58eb827c 100644 --- a/rules/solve_overnight.smk +++ b/rules/solve_overnight.smk @@ -40,6 +40,7 @@ rule solve_sector_network: params: solving=config_provider("solving"), foresight=config_provider("foresight"), + transmission=config_provider("transmission"), co2_sequestration_potential=config_provider( "sector", "co2_sequestration_potential", default=200 ), diff --git a/rules/solve_perfect.smk b/rules/solve_perfect.smk index be21e6ffcc..389fc6f1b0 100644 --- a/rules/solve_perfect.smk +++ b/rules/solve_perfect.smk @@ -45,6 +45,7 @@ rule add_existing_baseyear: costs=config_provider("costs"), heat_pump_sources=config_provider("sector", "heat_pump_sources"), energy_totals_year=config_provider("energy", "energy_totals_year"), + transmission=config_provider("transmission"), message: "Adding existing infrastructure for base year for {wildcards.clusters} clusters, {wildcards.planning_horizons} planning horizons, {wildcards.opts} electric options and {wildcards.sector_opts} sector options" script: @@ -125,6 +126,7 @@ rule solve_sector_network_perfect: params: solving=config_provider("solving"), foresight=config_provider("foresight"), + transmission=config_provider("transmission"), sector=config_provider("sector"), planning_horizons=config_provider("scenario", "planning_horizons"), co2_sequestration_potential=config_provider( diff --git a/scripts/_helpers.py b/scripts/_helpers.py index 4c74e4b718..16ebaded04 100644 --- a/scripts/_helpers.py +++ b/scripts/_helpers.py @@ -750,7 +750,7 @@ def update_config_from_wildcards(config, w, inplace=True): for o in opts: if o.startswith("lv") or o.startswith("lc"): - config["electricity"]["transmission_limit"] = o[1:] + config["transmission"]["electricity"]["transmission_limit"] = o[1:] break if w.get("sector_opts"): @@ -792,10 +792,10 @@ def update_config_from_wildcards(config, w, inplace=True): config["clustering"]["temporal"]["resolution_sector"] = nhours if "decentral" in opts: - config["sector"]["electricity_transmission_grid"] = False + config["transmission"]["electricity"]["enable"] = False if "noH2network" in opts: - config["sector"]["H2_network"] = False + config["transmission"]["hydrogen"] = False if "nowasteheat" in opts: config["sector"]["use_fischer_tropsch_waste_heat"] = False @@ -810,9 +810,9 @@ def update_config_from_wildcards(config, w, inplace=True): dg_enable, dg_factor = find_opt(opts, "dist") if dg_enable: - config["sector"]["electricity_distribution_grid"] = True + config["transmission"]["electricity_distribution"]["enable"] = True if dg_factor is not None: - config["sector"]["electricity_distribution_grid_cost_factor"] = ( + config["transmission"]["electricity_distribution"]["cost_factor"] = ( dg_factor ) @@ -821,8 +821,12 @@ def update_config_from_wildcards(config, w, inplace=True): _, maxext = find_opt(opts, "linemaxext") if maxext is not None: - config["lines"]["max_extension"] = maxext * 1e3 - config["links"]["max_extension"] = maxext * 1e3 + config["transmission"]["electricity"]["lines"]["max_extension"] = ( + maxext * 1e3 + ) + config["transmission"]["electricity"]["links"]["max_extension"] = ( + maxext * 1e3 + ) _, co2l_value = find_opt(opts, "Co2L") if co2l_value is not None: diff --git a/scripts/add_brownfield.py b/scripts/add_brownfield.py index d1a28bb45b..233412d2ae 100644 --- a/scripts/add_brownfield.py +++ b/scripts/add_brownfield.py @@ -30,8 +30,7 @@ def add_brownfield( n, n_p, year, - h2_retrofit=False, - h2_retrofit_capacity_per_ch4=None, + cf_transmission=None, capacity_threshold=None, ): """ @@ -45,14 +44,16 @@ def add_brownfield( Previous network to get brownfield from year : int Planning year - h2_retrofit : bool - Whether to allow hydrogen pipeline retrofitting - h2_retrofit_capacity_per_ch4 : float - Ratio of hydrogen to methane capacity for pipeline retrofitting + cf_transmission : dict + Transmission configuration containing hydrogen retrofit settings capacity_threshold : float Threshold for removing assets with low capacity """ logger.info(f"Preparing brownfield for the year {year}") + hydrogen_retrofit = cf_transmission["hydrogen"]["retrofit"]["enable"] + hydrogen_retrofit_capacity_per_ch4 = cf_transmission["hydrogen"]["retrofit"][ + "capacity_per_ch4" + ] # electric transmission grid set optimised capacities of previous as minimum n.lines.s_nom_min = n_p.lines.s_nom_opt @@ -127,7 +128,7 @@ def add_brownfield( n._import_series_from_df(c.dynamic[tattr], c.name, tattr) # deal with gas network - if h2_retrofit: + if hydrogen_retrofit: # subtract the already retrofitted from the maximum capacity h2_retrofitted_fixed_i = n.links[ (n.links.carrier == "H2 pipeline retrofitted") @@ -161,7 +162,7 @@ def add_brownfield( pipe_capacity = n.links.loc[gas_pipes_i, "p_nom"] fr = "H2 pipeline retrofitted" to = "gas pipeline" - CH4_per_H2 = 1 / h2_retrofit_capacity_per_ch4 + CH4_per_H2 = 1 / hydrogen_retrofit_capacity_per_ch4 already_retrofitted.index = already_retrofitted.index.str.replace(fr, to) remaining_capacity = ( pipe_capacity @@ -381,8 +382,7 @@ def update_dynamic_ptes_capacity( n, n_p, year, - h2_retrofit=snakemake.params.H2_retrofit, - h2_retrofit_capacity_per_ch4=snakemake.params.H2_retrofit_capacity_per_CH4, + cf_transmission=snakemake.params.transmission, capacity_threshold=snakemake.params.threshold_capacity, ) diff --git a/scripts/add_electricity.py b/scripts/add_electricity.py index 84fd226dfd..79a06bd93a 100755 --- a/scripts/add_electricity.py +++ b/scripts/add_electricity.py @@ -14,8 +14,9 @@ ``networks/base_s_{clusters}_elec.nc``. It includes: - today's transmission topology and transfer capacities (optionally including - lines which are under construction according to the config settings ``lines: - under_construction`` and ``links: under_construction``), + lines which are under construction according to the config settings + ``transmission: lines: under_construction`` and + ``links: under_construction``), - today's thermal and hydro power generation capacities (for the technologies listed in the config setting ``electricity: conventional_carriers``), and - today's load time-series (upsampled in a top-down approach according to diff --git a/scripts/add_transmission_projects_and_dlr.py b/scripts/add_electricity_transmission_projects_and_dlr.py similarity index 97% rename from scripts/add_transmission_projects_and_dlr.py rename to scripts/add_electricity_transmission_projects_and_dlr.py index 234dacf69d..36a1b0ac51 100644 --- a/scripts/add_transmission_projects_and_dlr.py +++ b/scripts/add_electricity_transmission_projects_and_dlr.py @@ -73,7 +73,7 @@ def attach_line_rating( if "snakemake" not in globals(): from scripts._helpers import mock_snakemake - snakemake = mock_snakemake("add_transmission_projects_and_dlr") + snakemake = mock_snakemake("add_electricity_transmission_projects_and_dlr") configure_logging(snakemake) # pylint: disable=E0606 set_scenario_config(snakemake) diff --git a/scripts/add_existing_baseyear.py b/scripts/add_existing_baseyear.py index 7e03eccb9d..bf8d3d27b1 100644 --- a/scripts/add_existing_baseyear.py +++ b/scripts/add_existing_baseyear.py @@ -779,15 +779,16 @@ def add_heating_capacities_installed_before_baseyear( update_config_from_wildcards(snakemake.config, snakemake.wildcards) options = snakemake.params.sector - renewable_carriers = snakemake.params.carriers - baseyear = snakemake.params.baseyear + cf_transmission = snakemake.params.transmission n = pypsa.Network(snakemake.input.network) # define spatial resolution of carriers - spatial = define_spatial(n.buses[n.buses.carrier == "AC"].index, options) + spatial = define_spatial( + n.buses[n.buses.carrier == "AC"].index, options, cf_transmission + ) add_build_year_to_new_assets(n, baseyear) costs = load_costs(snakemake.input.costs) @@ -833,7 +834,9 @@ def add_heating_capacities_installed_before_baseyear( capacity_threshold=snakemake.params.existing_capacities[ "threshold_capacity" ], - use_electricity_distribution_grid=options["electricity_distribution_grid"], + use_electricity_distribution_grid=cf_transmission[ + "electricity_distribution" + ]["enable"], ) # Set defaults for missing missing values diff --git a/scripts/base_network.py b/scripts/base_network.py index 327a14df21..e3602e261d 100644 --- a/scripts/base_network.py +++ b/scripts/base_network.py @@ -302,12 +302,12 @@ def _reconnect_crimea(lines): def _set_electrical_parameters_lines_eg(lines, config): v_noms = config["electricity"]["voltages"] - linetypes = config["lines"]["types"] + linetypes = config["transmission"]["electricity"]["lines"]["types"] for v_nom in v_noms: lines.loc[lines["v_nom"] == v_nom, "type"] = linetypes[v_nom] - lines["s_max_pu"] = config["lines"]["s_max_pu"] + lines["s_max_pu"] = config["transmission"]["electricity"]["lines"]["s_max_pu"] return lines @@ -318,7 +318,9 @@ def _set_electrical_parameters_lines_raw(lines, config): return lines v_noms = config["electricity"]["voltages"] - linetypes = _get_linetypes_config(config["lines"]["types"], v_noms) + linetypes = _get_linetypes_config( + config["transmission"]["electricity"]["lines"]["types"], v_noms + ) lines["carrier"] = "AC" lines["dc"] = False @@ -327,7 +329,7 @@ def _set_electrical_parameters_lines_raw(lines, config): lambda x: _get_linetype_by_voltage(x, linetypes) ) - lines["s_max_pu"] = config["lines"]["s_max_pu"] + lines["s_max_pu"] = config["transmission"]["electricity"]["lines"]["s_max_pu"] return lines @@ -345,8 +347,8 @@ def _set_electrical_parameters_links_eg(links, config, links_p_nom): if links.empty: return links - p_max_pu = config["links"].get("p_max_pu", 1.0) - p_min_pu = config["links"].get("p_min_pu", -p_max_pu) + p_max_pu = config["transmission"]["electricity"]["links"].get("p_max_pu", 1.0) + p_min_pu = config["transmission"]["electricity"]["links"].get("p_min_pu", -p_max_pu) links["p_max_pu"] = p_max_pu links["p_min_pu"] = p_min_pu @@ -378,8 +380,8 @@ def _set_electrical_parameters_links_raw(links, config): if links.empty: return links - p_max_pu = config["links"].get("p_max_pu", 1.0) - p_min_pu = config["links"].get("p_min_pu", -p_max_pu) + p_max_pu = config["transmission"]["electricity"]["links"].get("p_max_pu", 1.0) + p_min_pu = config["transmission"]["electricity"]["links"].get("p_min_pu", -p_max_pu) links["p_max_pu"] = p_max_pu links["p_min_pu"] = p_min_pu links["carrier"] = "DC" @@ -389,8 +391,8 @@ def _set_electrical_parameters_links_raw(links, config): def _set_electrical_parameters_converters(converters, config): - p_max_pu = config["links"].get("p_max_pu", 1.0) - p_min_pu = config["links"].get("p_min_pu", -p_max_pu) + p_max_pu = config["transmission"]["electricity"]["links"].get("p_max_pu", 1.0) + p_min_pu = config["transmission"]["electricity"]["links"].get("p_min_pu", -p_max_pu) converters["p_max_pu"] = p_max_pu converters["p_min_pu"] = p_min_pu @@ -407,7 +409,7 @@ def _set_electrical_parameters_converters(converters, config): def _set_electrical_parameters_transformers(transformers, config): - config = config["transformers"] + config = config["transmission"]["electricity"]["transformers"] ## Add transformer parameters transformers["x"] = config.get("x", 0.1) @@ -616,7 +618,9 @@ def _set_links_underwater_fraction(n, offshore_shapes): def _adjust_capacities_of_under_construction_branches(n, config): - lines_mode = config["lines"].get("under_construction", "undef") + lines_mode = config["transmission"]["electricity"]["lines"].get( + "under_construction", "undef" + ) if lines_mode == "zero": n.lines.loc[n.lines.under_construction, "num_parallel"] = 0.0 n.lines.loc[n.lines.under_construction, "s_nom"] = 0.0 @@ -624,17 +628,19 @@ def _adjust_capacities_of_under_construction_branches(n, config): n.remove("Line", n.lines.index[n.lines.under_construction]) elif lines_mode != "keep": logger.warning( - "Unrecognized configuration for `lines: under_construction` = `{}`. Keeping under construction lines." + "Unrecognized configuration for `transmission: lines: under_construction` = `{}`. Keeping under construction lines." ) - links_mode = config["links"].get("under_construction", "undef") + links_mode = config["transmission"]["electricity"]["links"].get( + "under_construction", "undef" + ) if links_mode == "zero": n.links.loc[n.links.under_construction, "p_nom"] = 0.0 elif links_mode == "remove": n.remove("Link", n.links.index[n.links.under_construction]) elif links_mode != "keep": logger.warning( - "Unrecognized configuration for `links: under_construction` = `{}`. Keeping under construction links." + "Unrecognized configuration for `transmission: links: under_construction` = `{}`. Keeping under construction links." ) if lines_mode == "remove" or links_mode == "remove": @@ -674,7 +680,7 @@ def base_network( parameter_corrections, config, ): - base_network = config["electricity"].get("base_network") + base_network = config["transmission"]["electricity"].get("base_network") osm_version = config["data"]["osm"]["version"] assert base_network in {"entsoegridkit", "osm", "tyndp"}, ( f"base_network must be either 'entsoegridkit', 'osm' or 'tyndp', but got '{base_network}'" @@ -701,9 +707,9 @@ def base_network( converters = _load_converters_from_eg(buses, converters) # Optionally reconnect Crimea - if (config["lines"].get("reconnect_crimea", True)) & ( - "UA" in config["countries"] - ): + if ( + config["transmission"]["electricity"]["lines"].get("reconnect_crimea", True) + ) & ("UA" in config["countries"]): lines = _reconnect_crimea(lines) # Set electrical parameters of lines and links @@ -742,7 +748,7 @@ def base_network( n.add("Link", converters.index, **converters) _set_lines_s_nom_from_linetypes(n) - if config["electricity"].get("base_network") == "entsoegridkit": + if config["transmission"]["electricity"].get("base_network") == "entsoegridkit": _apply_parameter_corrections(n, parameter_corrections) n = _remove_unconnected_components(n) diff --git a/scripts/build_transmission_projects.py b/scripts/build_electricity_transmission_projects.py similarity index 96% rename from scripts/build_transmission_projects.py rename to scripts/build_electricity_transmission_projects.py index e5c540c710..35f7550949 100644 --- a/scripts/build_transmission_projects.py +++ b/scripts/build_electricity_transmission_projects.py @@ -18,11 +18,11 @@ Outputs ------- -- ``transmission_projects/new_lines.csv``: New project lines to be added to the network. This includes new lines and upgraded lines. -- ``transmission_projects/new_links.csv``: New project links to be added to the network. This includes new links and upgraded links. -- ``transmission_projects/adjust_lines.csv``: For lines which are upgraded, the decommissioning year of the existing line is adjusted to the build year of the upgraded line. -- ``transmission_projects/adjust_links.csv``: For links which are upgraded, the decommissioning year of the existing link is adjusted to the build year of the upgraded link. -- ``transmission_projects/new_buses.csv``: For some links, we have to add new buses (e.g. North Sea Wind Power Hub). +- ``transmission/electricity_projects/new_lines.csv``: New project lines to be added to the network. This includes new lines and upgraded lines. +- ``transmission/electricity_projects/new_links.csv``: New project links to be added to the network. This includes new links and upgraded links. +- ``transmission/electricity_projects/adjust_lines.csv``: For lines which are upgraded, the decommissioning year of the existing line is adjusted to the build year of the upgraded line. +- ``transmission/electricity_projects/adjust_links.csv``: For links which are upgraded, the decommissioning year of the existing link is adjusted to the build year of the upgraded link. +- ``transmission/electricity_projects/new_buses.csv``: For some links, we have to add new buses (e.g. North Sea Wind Power Hub). """ import logging @@ -467,7 +467,7 @@ def fill_length_from_geometry(line, line_factor=1.2): if "snakemake" not in globals(): from scripts._helpers import mock_snakemake - snakemake = mock_snakemake("build_transmission_projects") + snakemake = mock_snakemake("build_electricity_transmission_projects") configure_logging(snakemake) set_scenario_config(snakemake) diff --git a/scripts/build_transmission_delaunay_graph.py b/scripts/build_transmission_delaunay_graph.py new file mode 100644 index 0000000000..50d49310a9 --- /dev/null +++ b/scripts/build_transmission_delaunay_graph.py @@ -0,0 +1,316 @@ +# SPDX-FileCopyrightText: Contributors to PyPSA-Eur +# +# SPDX-License-Identifier: MIT + +""" +Build the Delaunay triangulation graph for transmission corridor candidates. + +Description +----------- +Creates the full Delaunay edge table from clustered network bus coordinates, +enriched with Gabriel edge flags and offshore underwater fractions. + +The output GeoDataFrame is consumed by build_transmission_topology to filter +candidate edges per carrier configuration. + +Outputs +------- +GeoJSON file in WGS84 (EPSG:4326) format: + +``delaunay_graph`` + Full Delaunay edge table with columns: + + - ``name``: Canonical undirected edge identifier ``"bus0 -> bus1"``. + - ``bus0``: Canonically ordered first bus id (lexicographic order). + - ``bus1``: Canonically ordered second bus id. + - ``source``: Integer index of the first bus. + - ``target``: Integer index of the second bus. + - ``length``: Great-circle edge length in km. + - ``gabriel_edge``: ``True`` if the edge satisfies the Gabriel empty + circle criterion. + - ``underwater_fraction``: Fraction of edge length over offshore regions. + - ``geometry``: LineString geometry in ``EPSG:4326``. +""" + +import logging + +import geopandas as gpd +import numpy as np +import pandas as pd +import pypsa +from numpy.typing import NDArray +from pypsa.geo import haversine_pts +from scipy.spatial import Delaunay, QhullError +from shapely.geometry import LineString + +from scripts._helpers import configure_logging, set_scenario_config + +logger = logging.getLogger(__name__) + +GEO_CRS = "EPSG:4326" +DISTANCE_CRS = "EPSG:3035" + + +def delaunay_edges(coords_meter: NDArray[np.float64]) -> list[tuple[int, int]]: + """ + Build sorted unique undirected edges from Delaunay simplices. + + Parameters + ---------- + coords_meter : NDArray[np.float64] + Node coordinates in a metric CRS with shape ``(n_nodes, 2)``. + + Returns + ------- + list[tuple[int, int]] + Sorted list of unique undirected edges represented as index pairs + ``(u, v)`` with ``u < v``. + + Raises + ------ + ValueError + If fewer than three points are provided. + """ + if len(coords_meter) < 3: + raise ValueError("Need at least 3 points to compute Delaunay triangulation.") + + try: + tri = Delaunay(coords_meter) + except QhullError: + # Robust fallback for nearly degenerate point sets. + tri = Delaunay(coords_meter, qhull_options="QJ") + + edges: set[tuple[int, int]] = set() + for simplex in tri.simplices: + for i, j in ((0, 1), (1, 2), (0, 2)): + edges.add(tuple(sorted((int(simplex[i]), int(simplex[j]))))) + return sorted(edges) + + +def pairwise_sq_dists(coords: NDArray[np.float64]) -> NDArray[np.float64]: + """ + Compute the squared Euclidean distance matrix. + + Parameters + ---------- + coords : NDArray[np.float64] + Node coordinates in Euclidean space with shape ``(n_nodes, n_dims)``. + + Returns + ------- + NDArray[np.float64] + Dense matrix ``D`` with shape ``(n_nodes, n_nodes)`` where + ``D[i, j] = ||coords[i] - coords[j]||^2``. + """ + norms = np.sum(coords * coords, axis=1) + sq = norms[:, None] + norms[None, :] - 2.0 * (coords @ coords.T) + return np.maximum(sq, 0.0) + + +def classify_gabriel_edges( + edges: NDArray[np.int64], + coords_meter: NDArray[np.float64], + eps: float = 1e-9, + chunk_size: int = 1024, +) -> NDArray[np.bool_]: + """ + Classify candidate edges as Gabriel or non-Gabriel. + + Parameters + ---------- + edges : NDArray[np.int64] + Candidate undirected edges as node index pairs with shape ``(n_edges, 2)``. + coords_meter : NDArray[np.float64] + Node coordinates in a metric CRS with shape ``(n_nodes, 2)``. + eps : float, default 1e-9 + Numerical tolerance used in the Gabriel emptiness test. + chunk_size : int, default 1024 + Batch size used to limit temporary memory during vectorized checks. + + Returns + ------- + NDArray[np.bool_] + Boolean mask of shape ``(n_edges,)`` where ``True`` marks Gabriel edges. + """ + sq_dists = pairwise_sq_dists(coords_meter) + edge_count = edges.shape[0] + is_gabriel = np.ones(edge_count, dtype=bool) + + for start in range(0, edge_count, chunk_size): + end = min(start + chunk_size, edge_count) + uv = edges[start:end] + u = uv[:, 0] + v = uv[:, 1] + dij2 = sq_dists[u, v] + + # A point k invalidates edge (u, v) when dik^2 + djk^2 < dij^2. + inside = (sq_dists[u, :] + sq_dists[v, :]) < (dij2[:, None] - eps) + row = np.arange(end - start) + inside[row, u] = False + inside[row, v] = False + is_gabriel[start:end] = ~inside.any(axis=1) + + return is_gabriel + + +def get_bus_coordinates( + n: pypsa.Network, +) -> tuple[pd.Index, NDArray[np.float64], NDArray[np.float64]]: + """ + Extract bus ids and coordinates in geographic and metric CRS. + + Parameters + ---------- + n : pypsa.Network + Input network containing bus coordinate columns ``x`` and ``y``. + + Returns + ------- + tuple[pd.Index, NDArray[np.float64], NDArray[np.float64]] + Tuple ``(bus_ids, coords_geo, coords_meter)`` where ``coords_geo`` is in + ``EPSG:4326`` and ``coords_meter`` is in ``EPSG:3035``. + + Raises + ------ + ValueError + If fewer than two buses with valid coordinates are available. + """ + buses = n.buses.copy() + buses = buses.dropna(subset=["x", "y"]) + + if len(buses) < 2: + raise ValueError("Need at least 2 buses with coordinates to build corridors.") + + points_geo = gpd.points_from_xy(buses["x"], buses["y"], crs=GEO_CRS) + points_meter = gpd.GeoSeries(points_geo, crs=GEO_CRS).to_crs(DISTANCE_CRS) + + coords_geo = np.column_stack( + (np.asarray(points_geo.x), np.asarray(points_geo.y)) + ).astype(float) + coords_meter = np.column_stack( + (np.asarray(points_meter.x), np.asarray(points_meter.y)) + ).astype(float) + + return buses.index, coords_geo, coords_meter + + +def delaunay_triangulation( + bus_ids: pd.Index, + coords_geo: NDArray[np.float64], + coords_meter: NDArray[np.float64], +) -> gpd.GeoDataFrame: + """ + Build the full Delaunay edge table with geometry and metadata. + + Parameters + ---------- + bus_ids : pd.Index + Bus identifiers aligned with the coordinate arrays. + coords_geo : NDArray[np.float64] + Geographic coordinates (longitude, latitude) in ``EPSG:4326``. + coords_meter : NDArray[np.float64] + Projected coordinates in ``EPSG:3035`` used for metric calculations. + + Returns + ------- + gpd.GeoDataFrame + Delaunay edge table including integer node endpoints, normalized bus + names, canonical edge ``name``, ``length`` (haversine distance, km), + ``gabriel_edge`` flag, and LineString geometry. + """ + + edges = delaunay_edges(coords_meter) + edges_arr = np.array(edges, dtype=np.int64) + u = edges_arr[:, 0] + v = edges_arr[:, 1] + + logger.info("Compute Delaunay triangulation with %s edges.", len(edges)) + lengths = haversine_pts(coords_geo[u], coords_geo[v]) + gabriel_flags = classify_gabriel_edges(edges_arr, coords_meter) + edge_geoms = [LineString([coords_geo[i], coords_geo[j]]) for i, j in edges] + + bus0 = bus_ids.take(u).astype(str).to_numpy() + bus1 = bus_ids.take(v).astype(str).to_numpy() + + # Keep undirected edge naming compatible with topology naming in + # prepare_sector_network.create_network_topology. + swap_mask = bus0 > bus1 + bus0_norm = np.where(swap_mask, bus1, bus0) + bus1_norm = np.where(swap_mask, bus0, bus1) + connector = " -> " + edge_names = np.char.add( + np.char.add(bus0_norm, connector), + bus1_norm, + ) + + edges_gdf = gpd.GeoDataFrame( + { + "source": u, + "target": v, + "bus0": bus0_norm, + "bus1": bus1_norm, + "name": edge_names, + "length": lengths, + "gabriel_edge": gabriel_flags, + "geometry": edge_geoms, + }, + geometry="geometry", + crs=GEO_CRS, + ) + return edges_gdf + + +def add_underwater_fraction( + edges: gpd.GeoDataFrame, + offshore_shapes: gpd.GeoDataFrame, +) -> gpd.GeoDataFrame: + """ + Compute the fraction of each edge's length that lies within offshore regions. + + Parameters + ---------- + edges : gpd.GeoDataFrame + Edge table with LineString geometry in ``EPSG:4326``. + offshore_shapes : gpd.GeoDataFrame + Offshore region polygons used to determine submarine sections. + + Returns + ------- + gpd.GeoDataFrame + ``edges`` with an added ``underwater_fraction`` column in ``[0, 1]``. + """ + offshore_union = offshore_shapes.to_crs(DISTANCE_CRS).union_all() + geoms = edges.geometry.to_crs(DISTANCE_CRS) + edges["underwater_fraction"] = ( + (geoms.intersection(offshore_union).length / geoms.length).fillna(0.0).round(2) + ) + return edges + + +if __name__ == "__main__": + is_mock_run = "snakemake" not in globals() + + if is_mock_run: + from scripts._helpers import mock_snakemake + + snakemake = mock_snakemake( + "build_transmission_delaunay_graph", + clusters="50", + ) + + configure_logging(snakemake) + set_scenario_config(snakemake) + + n = pypsa.Network(snakemake.input.network) + offshore_shapes = gpd.read_file(snakemake.input.offshore_shapes) + + bus_ids, coords_geo, coords_meter = get_bus_coordinates(n) + delaunay_graph = delaunay_triangulation( + bus_ids=bus_ids, + coords_geo=coords_geo, + coords_meter=coords_meter, + ) + + add_underwater_fraction(delaunay_graph, offshore_shapes) + + delaunay_graph.to_file(snakemake.output.delaunay_graph) diff --git a/scripts/build_transmission_topology.py b/scripts/build_transmission_topology.py new file mode 100644 index 0000000000..e9de383726 --- /dev/null +++ b/scripts/build_transmission_topology.py @@ -0,0 +1,236 @@ +# SPDX-FileCopyrightText: Contributors to PyPSA-Eur +# +# SPDX-License-Identifier: MIT + +""" +Filter the Delaunay graph to transmission topology candidates. + +Description +----------- +Reads the full Delaunay edge table produced by build_transmission_delaunay_graph +and applies Gabriel graph filtering and min-degree backfilling to produce a +candidate edge set for one (min_degree, max_offshore_haversine_distance) pair. + +Outputs +------- +GeoJSON file in WGS84 (EPSG:4326) format: + +``candidates`` + Selected candidate corridor table (subset of Delaunay edges) with columns: + + - ``name`` + - ``bus0`` + - ``bus1`` + - ``length`` + - ``gabriel_edge`` + - ``underwater_fraction`` + - ``geometry`` +""" + +import logging + +import geopandas as gpd +import numpy as np + +from scripts._helpers import configure_logging, set_scenario_config + +logger = logging.getLogger(__name__) + +COLS_EDGES = [ + "name", + "bus0", + "bus1", + "length", + "gabriel_edge", + "geometry", + "underwater_fraction", +] + + +def enforce_min_degree( + delaunay_graph: gpd.GeoDataFrame, + node_count: int, + min_degree: int, +) -> gpd.GeoDataFrame: + """ + Add shortest Delaunay edges to satisfy a minimum node degree. + + Iterates over all Delaunay edges in ascending order of length and adds + edges to the selected set until every node reaches the requested minimum + degree, or until no further Delaunay edges are available for that node. + + Parameters + ---------- + delaunay_graph : gpd.GeoDataFrame + Full Delaunay triangulation edge table. + Must contain integer columns ``source``, ``target``, and ``length``, + with canonical ordering ``source < target`` and a boolean + ``gabriel_edge`` column. + node_count : int + Total number of nodes in the graph. Used to size degree arrays. + min_degree : int + Minimum number of edges required per node. Values <= 0 disable + Gabriel filtering and return the full ``delaunay_graph``. + + Returns + ------- + gpd.GeoDataFrame + Edge GeoDataFrame with the same schema as ``delaunay_graph``. + For ``min_degree > 0``, starts from Gabriel edges and extends with any + backfill edges needed to meet the degree constraint. + Nodes whose maximum possible Delaunay degree is below ``min_degree`` + are handled gracefully with a warning. + + Warns + ----- + Logs a warning if the minimum degree cannot be fully satisfied, either + because a node has too few Delaunay neighbours (unreachable) or because + the greedy backfill did not converge (unmet after backfill). + """ + if min_degree <= 0: + logger.info( + "Gabriel filtering disabled via min_degree=%s; returning full Delaunay graph.", + min_degree, + ) + return delaunay_graph.copy() + + import pandas as pd + + result = delaunay_graph[delaunay_graph["gabriel_edge"]].copy() + all_sorted = delaunay_graph.sort_values("length").reset_index(drop=True) + source_arr = all_sorted["source"].to_numpy(dtype=int) + target_arr = all_sorted["target"].to_numpy(dtype=int) + + max_possible_degree = np.bincount( + np.concatenate([source_arr, target_arr]), minlength=node_count + ) + + sel_source = result["source"].to_numpy(dtype=int) + sel_target = result["target"].to_numpy(dtype=int) + edge_set: set[tuple[int, int]] = set(zip(sel_source, sel_target)) + degrees = np.bincount( + np.concatenate([sel_source, sel_target]), minlength=node_count + ) + + deficits = np.maximum(0, min_degree - degrees) + remaining_deficits = int(deficits.sum()) + additions_idx: list[int] = [] + + for idx, (u, v) in enumerate(zip(source_arr, target_arr)): + if remaining_deficits == 0: + break + if (u, v) in edge_set: + continue + if deficits[u] <= 0 and deficits[v] <= 0: + continue + edge_set.add((u, v)) + additions_idx.append(idx) + if deficits[u] > 0: + deficits[u] -= 1 + remaining_deficits -= 1 + if deficits[v] > 0: + deficits[v] -= 1 + remaining_deficits -= 1 + + if additions_idx: + to_add = all_sorted.iloc[additions_idx] + result = gpd.GeoDataFrame( + pd.concat([result, to_add], ignore_index=True), + geometry="geometry", + crs=delaunay_graph.crs, + ) + + result_source = result["source"].to_numpy(dtype=int) + result_target = result["target"].to_numpy(dtype=int) + achieved_degree = np.bincount( + np.concatenate([result_source, result_target]), minlength=node_count + ) + target_degree = np.minimum(min_degree, max_possible_degree) + unreachable_mask = max_possible_degree < min_degree + unmet_mask = achieved_degree < target_degree + + if np.any(unreachable_mask) or np.any(unmet_mask): + logger.warning( + "Min-degree not fully met: requested=%s, unreachable=%s/%s, unmet_after_backfill=%s/%s.", + min_degree, + int(unreachable_mask.sum()), + node_count, + int(unmet_mask.sum()), + node_count, + ) + + return result + + +def filter_by_max_offshore_haversine_distance( + delaunay_graph: gpd.GeoDataFrame, + max_offdist: float = float("inf"), +) -> gpd.GeoDataFrame: + """ + Filter Delaunay edges by maximum offshore haversine distance. + + Parameters + ---------- + delaunay_graph : gpd.GeoDataFrame + Full Delaunay edge table. + max_offdist : float, default inf + Maximum allowed offshore edge length in km, computed as + ``underwater_fraction * length``. ``inf`` disables this filter. + + Returns + ------- + gpd.GeoDataFrame + Delaunay edge table after applying offshore-distance filtering. + """ + if np.isfinite(max_offdist): + offshore_length = ( + delaunay_graph["length"] * delaunay_graph["underwater_fraction"] + ) + return delaunay_graph[offshore_length <= max_offdist].copy() + return delaunay_graph.copy() + + +if __name__ == "__main__": + is_mock_run = "snakemake" not in globals() + + if is_mock_run: + from scripts._helpers import mock_snakemake + + snakemake = mock_snakemake( + "build_transmission_topology", + clusters="50", + min_degree=1, + max_offdist="inf", + configfiles=["config/config.200.yaml"], + ) + + configure_logging(snakemake) + set_scenario_config(snakemake) + + min_degree = snakemake.params["min_degree"] + max_offdist = snakemake.params["max_offdist"] + + delaunay_graph = gpd.read_file(snakemake.input.delaunay_graph) + + source_arr = delaunay_graph["source"].to_numpy(dtype=int) + target_arr = delaunay_graph["target"].to_numpy(dtype=int) + node_count = int(max(source_arr.max(), target_arr.max())) + 1 + + logger.info( + "Filtering topology: min_degree=%s, max_offdist=%s.", + min_degree, + max_offdist, + ) + + candidate_pool = filter_by_max_offshore_haversine_distance( + delaunay_graph=delaunay_graph, + max_offdist=max_offdist, + ) + + selected_edges = enforce_min_degree( + delaunay_graph=candidate_pool, + node_count=node_count, + min_degree=min_degree, + )[COLS_EDGES] + + selected_edges.to_file(snakemake.output.candidates) diff --git a/scripts/lib/validation/config/_schema.py b/scripts/lib/validation/config/_schema.py index 5adb8665f0..471656bb5a 100644 --- a/scripts/lib/validation/config/_schema.py +++ b/scripts/lib/validation/config/_schema.py @@ -23,8 +23,6 @@ from scripts.lib.validation.config.existing_capacities import ExistingCapacitiesConfig from scripts.lib.validation.config.foresight import ForesightConfig from scripts.lib.validation.config.industry import IndustryConfig -from scripts.lib.validation.config.lines import LinesConfig -from scripts.lib.validation.config.links import LinksConfig from scripts.lib.validation.config.load import LoadConfig from scripts.lib.validation.config.overpass_api import OverpassApiConfig from scripts.lib.validation.config.pypsa_eur import PypsaEurConfig @@ -35,10 +33,7 @@ from scripts.lib.validation.config.snapshots import SnapshotsConfig from scripts.lib.validation.config.solar_thermal import SolarThermalConfig from scripts.lib.validation.config.solving import SolvingConfig -from scripts.lib.validation.config.transformers import TransformersConfig -from scripts.lib.validation.config.transmission_projects import ( - TransmissionProjectsConfig, -) +from scripts.lib.validation.config.transmission import TransmissionConfig class LoggingConfig(ConfigModel): @@ -132,6 +127,10 @@ class ConfigSchema(BaseModel): default_factory=ElectricityConfig, description="Electricity sector configuration.", ) + transmission: TransmissionConfig = Field( + default_factory=TransmissionConfig, + description="Transmission candidate configuration.", + ) atlite: AtliteConfig = Field( default_factory=AtliteConfig, description="Atlite cutout configuration for weather data.", @@ -144,22 +143,6 @@ class ConfigSchema(BaseModel): default_factory=ConventionalConfig, description="Conventional power plants configuration.", ) - lines: LinesConfig = Field( - default_factory=LinesConfig, - description="Transmission lines configuration.", - ) - links: LinksConfig = Field( - default_factory=LinksConfig, - description="HVDC links configuration.", - ) - transmission_projects: TransmissionProjectsConfig = Field( - default_factory=TransmissionProjectsConfig, - description="Transmission projects configuration.", - ) - transformers: TransformersConfig = Field( - default_factory=TransformersConfig, - description="Transformers configuration.", - ) load: LoadConfig = Field( default_factory=LoadConfig, description="Electrical load configuration.", diff --git a/scripts/lib/validation/config/electricity.py b/scripts/lib/validation/config/electricity.py index d343cb06cd..8c92e554c6 100644 --- a/scripts/lib/validation/config/electricity.py +++ b/scripts/lib/validation/config/electricity.py @@ -8,8 +8,6 @@ See docs in https://pypsa-eur.readthedocs.io/en/latest/configuration.html#electricity """ -from typing import Literal - from pydantic import BaseModel, ConfigDict, Field from scripts.lib.validation.config._base import ConfigModel @@ -171,10 +169,6 @@ class ElectricityConfig(BaseModel): default_factory=lambda: [220.0, 300.0, 330.0, 380.0, 400.0, 500.0, 750.0], description="Voltage levels to consider.", ) - base_network: Literal["entsoegridkit", "osm", "tyndp"] = Field( - "osm", - description="Specify the underlying base network, i.e. GridKit (based on ENTSO-E web map extract), OpenStreetMap (OSM), or TYNDP.", - ) gaslimit_enable: bool = Field( False, description="Add an overall absolute gas limit configured in `electricity: gaslimit`.", @@ -257,9 +251,5 @@ class ElectricityConfig(BaseModel): default_factory=_AutarkyConfig, description="Autarky configuration.", ) - transmission_limit: str = Field( - "vopt", - description="Limit on transmission expansion. The first part can be `v` (for setting a limit on line volume) or `c` (for setting a limit on line cost). The second part can be `opt` or a float bigger than one (e.g. 1.25). If `opt` is chosen line expansion is optimised according to its capital cost (where the choice `v` only considers overhead costs for HVDC transmission lines, while `c` uses more accurate costs distinguishing between overhead and underwater sections and including inverter pairs). The setting `v1.25` will limit the total volume of line expansion to 25% of currently installed capacities weighted by individual line lengths. The setting `c1.25` will allow to build a transmission network that costs no more than 25 % more than the current system.", - ) model_config = ConfigDict(populate_by_name=True) diff --git a/scripts/lib/validation/config/lines.py b/scripts/lib/validation/config/lines.py deleted file mode 100644 index 7ef71cc85c..0000000000 --- a/scripts/lib/validation/config/lines.py +++ /dev/null @@ -1,91 +0,0 @@ -# SPDX-FileCopyrightText: Contributors to PyPSA-Eur -# -# SPDX-License-Identifier: MIT - -""" -Lines configuration. - -See docs in https://pypsa-eur.readthedocs.io/en/latest/configuration.html#lines -""" - -from typing import Literal - -from pydantic import BaseModel, Field - -from scripts.lib.validation.config._base import ConfigModel - - -class _DynamicLineRatingConfig(ConfigModel): - """Configuration for `lines.dynamic_line_rating` settings.""" - - activate: bool = Field( - False, - description="Whether to take dynamic line rating into account.", - ) - cutout: str | list[str] = Field( - "default", - description="Specifies the weather data cutout file(s) to use.", - ) - correction_factor: float = Field( - 0.95, - description="Factor to compensate for overestimation of wind speeds in hourly averaged wind data.", - ) - max_voltage_difference: float | Literal[False] = Field( - False, - description="Maximum voltage angle difference in degrees or 'false' to disable.", - ) - max_line_rating: float | Literal[False] = Field( - False, - description="Maximum line rating relative to nominal capacity without DLR, e.g. 1.3 or 'false' to disable.", - ) - - -class LinesConfig(BaseModel): - """Configuration for `lines` settings.""" - - types: dict[float, str] = Field( - default_factory=lambda: { - 63.0: "94-AL1/15-ST1A 20.0", - 66.0: "94-AL1/15-ST1A 20.0", - 90.0: "184-AL1/30-ST1A 110.0", - 110.0: "184-AL1/30-ST1A 110.0", - 132.0: "243-AL1/39-ST1A 110.0", - 150.0: "243-AL1/39-ST1A 110.0", - 220.0: "Al/St 240/40 2-bundle 220.0", - 300.0: "Al/St 240/40 3-bundle 300.0", - 330.0: "Al/St 240/40 3-bundle 300.0", - 380.0: "Al/St 240/40 4-bundle 380.0", - 400.0: "Al/St 240/40 4-bundle 380.0", - 500.0: "Al/St 240/40 4-bundle 380.0", - 750.0: "Al/St 560/50 4-bundle 750.0", - }, - description="Specifies line types to assume for the different voltage levels of the ENTSO-E grid extraction. Should normally handle voltage levels 220, 300, and 380 kV.", - ) - s_max_pu: float = Field( - 0.7, - description="Correction factor for line capacities (`s_nom`) to approximate N-1 security and reserve capacity for reactive power flows.", - ) - s_nom_max: float = Field( - float("inf"), - description="Global upper limit for the maximum capacity of each extendable line (MW).", - ) - max_extension: float = Field( - 20000, - description="Upper limit for the extended capacity of each extendable line (MW).", - ) - length_factor: float = Field( - 1.25, - description="Correction factor to account for the fact that buses are *not* connected by lines through air-line distance.", - ) - reconnect_crimea: bool = Field( - True, - description="Whether to reconnect Crimea to the Ukrainian grid.", - ) - under_construction: Literal["zero", "remove", "keep"] = Field( - "keep", - description="Specifies how to handle lines which are currently under construction.", - ) - dynamic_line_rating: _DynamicLineRatingConfig = Field( - default_factory=_DynamicLineRatingConfig, - description="Configuration for dynamic line rating.", - ) diff --git a/scripts/lib/validation/config/links.py b/scripts/lib/validation/config/links.py deleted file mode 100644 index a994c20af6..0000000000 --- a/scripts/lib/validation/config/links.py +++ /dev/null @@ -1,44 +0,0 @@ -# SPDX-FileCopyrightText: Contributors to PyPSA-Eur -# -# SPDX-License-Identifier: MIT - -""" -Links configuration. - -See docs in https://pypsa-eur.readthedocs.io/en/latest/configuration.html#links -""" - -from typing import Literal - -from pydantic import Field - -from scripts.lib.validation.config._base import ConfigModel - - -class LinksConfig(ConfigModel): - """Configuration for `links` settings.""" - - p_max_pu: float = Field( - 1.0, - description="Correction factor for link capacities `p_nom`.", - ) - p_min_pu: float = Field( - -1.0, - description="Correction factor for link capacities `p_nom`.", - ) - p_nom_max: float = Field( - float("inf"), - description="Global upper limit for the maximum capacity of each extendable DC link (MW).", - ) - max_extension: float = Field( - 30000, - description="Upper limit for the extended capacity of each extendable DC link (MW).", - ) - length_factor: float = Field( - 1.25, - description="Correction factor to account for the fact that buses are *not* connected by links through air-line distance.", - ) - under_construction: Literal["zero", "remove", "keep"] = Field( - "keep", - description="Specifies how to handle lines which are currently under construction.", - ) diff --git a/scripts/lib/validation/config/sector.py b/scripts/lib/validation/config/sector.py index 5801f7fcbe..a546d05d1c 100644 --- a/scripts/lib/validation/config/sector.py +++ b/scripts/lib/validation/config/sector.py @@ -10,7 +10,7 @@ from typing import Any -from pydantic import BaseModel, ConfigDict, Field +from pydantic import BaseModel, Field from scripts.lib.validation.config._base import ConfigModel @@ -224,50 +224,6 @@ class _MethanolConfig(BaseModel): ) -class _TransmissionEfficiencyConfig(BaseModel): - """Configuration for `sector.transmission_efficiency` settings.""" - - enable: list[str] = Field( - default_factory=lambda: [ - "DC", - "H2 pipeline", - "gas pipeline", - "electricity distribution grid", - ], - description="Switch to select the carriers for which transmission efficiency is to be added. Carriers not listed assume lossless transmission.", - ) - DC: dict[str, float] = Field( - default_factory=lambda: { - "efficiency_static": 0.98, - "efficiency_per_1000km": 0.977, - }, - description="DC transmission efficiency.", - ) - H2_pipeline: dict[str, float] = Field( - default_factory=lambda: { - "efficiency_per_1000km": 1, - "compression_per_1000km": 0.018, - }, - alias="H2 pipeline", - description="H2 pipeline transmission efficiency.", - ) - gas_pipeline: dict[str, float] = Field( - default_factory=lambda: { - "efficiency_per_1000km": 1, - "compression_per_1000km": 0.01, - }, - alias="gas pipeline", - description="Gas pipeline transmission efficiency.", - ) - electricity_distribution_grid: dict[str, float] = Field( - default_factory=lambda: {"efficiency_static": 0.97}, - alias="electricity distribution grid", - description="Electricity distribution grid efficiency.", - ) - - model_config = ConfigDict(populate_by_name=True) - - class _LimitMaxGrowthConfig(BaseModel): """Configuration for `sector.limit_max_growth` settings.""" @@ -740,14 +696,6 @@ class SectorConfig(BaseModel): True, description="Add option to spatially resolve carrier representing stored carbon dioxide. This allows for more detailed modelling of CCUTS, e.g. regarding the capturing of industrial process emissions, usage as feedstock for electrofuels, transport of carbon dioxide, and geological sequestration sites.", ) - co2_network: bool = Field( - True, - description="Add option for planning a new carbon dioxide transmission network.", - ) - co2_network_cost_factor: float = Field( - 1, - description="The cost factor for the capital cost of the carbon dioxide transmission network.", - ) cc_fraction: float = Field( 0.9, description="The default fraction of CO2 captured with post-combustion capture.", @@ -807,47 +755,13 @@ class SectorConfig(BaseModel): description="Add option for using waste heat of electrolysis in district heating networks.", ) - electricity_transmission_grid: bool = Field( - True, - description="Switch for enabling/disabling the electricity transmission grid.", - ) - electricity_distribution_grid: bool = Field( - True, - description="Add a simplified representation of the exchange capacity between transmission and distribution grid level through a link.", - ) - electricity_distribution_grid_cost_factor: float = Field( - 1.0, - description="Multiplies the investment cost of the electricity distribution grid.", - ) - electricity_grid_connection: bool = Field( + electricity_grid_connection_cost: bool = Field( True, description="Add the cost of electricity grid connection for onshore wind and solar.", ) - - transmission_efficiency: _TransmissionEfficiencyConfig = Field( - default_factory=_TransmissionEfficiencyConfig, - description="Transmission efficiency configuration.", - ) - - H2_network: bool = Field(True, description="Add option for new hydrogen pipelines.") - gas_network: bool = Field( + gas_distribution_grid_cost: bool = Field( True, - description="Add existing natural gas infrastructure, incl. LNG terminals, production and entry-points. The existing gas network is added with a lossless transport model. A length-weighted `k-edge augmentation algorithm `_ can be run to add new candidate gas pipelines such that all regions of the model can be connected to the gas network. When activated, all the gas demands are regionally disaggregated as well.", - ) - H2_retrofit: bool = Field( - False, - description="Add option for retrofiting existing pipelines to transport hydrogen.", - ) - H2_retrofit_capacity_per_CH4: float = Field( - 0.6, - description="The ratio for H2 capacity per original CH4 capacity of retrofitted pipelines. The `European Hydrogen Backbone (April, 2020) p.15 `_ 60% of original natural gas capacity could be used in cost-optimal case as H2 capacity.", - ) - gas_network_connectivity_upgrade: float = Field( - 1, - description="The number of desired edge connectivity (k) in the length-weighted `k-edge augmentation algorithm `_ used for the gas network.", - ) - gas_distribution_grid: bool = Field( - True, description="Add a gas distribution grid." + description="Add gas distribution grid costs to gas-consuming components.", ) gas_distribution_grid_cost_factor: float = Field( 1.0, diff --git a/scripts/lib/validation/config/transformers.py b/scripts/lib/validation/config/transformers.py deleted file mode 100644 index 207981250f..0000000000 --- a/scripts/lib/validation/config/transformers.py +++ /dev/null @@ -1,30 +0,0 @@ -# SPDX-FileCopyrightText: Contributors to PyPSA-Eur -# -# SPDX-License-Identifier: MIT - -""" -Transformers configuration. - -See docs in https://pypsa-eur.readthedocs.io/en/latest/configuration.html#transformers -""" - -from pydantic import Field - -from scripts.lib.validation.config._base import ConfigModel - - -class TransformersConfig(ConfigModel): - """Configuration for `transformers` settings.""" - - x: float = Field( - 0.1, - description="Series reactance in per unit (p.u.), using `s_nom` as base power of the transformer. Overwritten if `type` is specified.", - ) - s_nom: float = Field( - 2000.0, - description="Limit of apparent power which can pass through branch (MVA). Overwritten if `type` is specified.", - ) - type: str = Field( - "", - description="Specifies transformer types to assume for the transformers of the ENTSO-E grid extraction.", - ) diff --git a/scripts/lib/validation/config/transmission.py b/scripts/lib/validation/config/transmission.py new file mode 100644 index 0000000000..5b1b8dee39 --- /dev/null +++ b/scripts/lib/validation/config/transmission.py @@ -0,0 +1,375 @@ +# SPDX-FileCopyrightText: Contributors to PyPSA-Eur +# +# SPDX-License-Identifier: MIT + +""" +Transmission candidate configuration. + +See docs in https://pypsa-eur.readthedocs.io/en/latest/configuration.html#transmission +""" + +from typing import Literal + +from pydantic import BaseModel, Field + +from scripts.lib.validation.config._base import ConfigModel + + +class _DynamicLineRatingConfig(ConfigModel): + """Configuration for `lines.dynamic_line_rating` settings.""" + + activate: bool = Field( + False, + description="Whether to take dynamic line rating into account.", + ) + cutout: str | list[str] = Field( + "default", + description="Specifies the weather data cutout file(s) to use.", + ) + correction_factor: float = Field( + 0.95, + description="Factor to compensate for overestimation of wind speeds in hourly averaged wind data.", + ) + max_voltage_difference: float | Literal[False] = Field( + False, + description="Maximum voltage angle difference in degrees or 'false' to disable.", + ) + max_line_rating: float | Literal[False] = Field( + False, + description="Maximum line rating relative to nominal capacity without DLR, e.g. 1.3 or 'false' to disable.", + ) + + +class LinesConfig(BaseModel): + """Configuration for `lines` settings.""" + + types: dict[float, str] = Field( + default_factory=lambda: { + 63.0: "94-AL1/15-ST1A 20.0", + 66.0: "94-AL1/15-ST1A 20.0", + 90.0: "184-AL1/30-ST1A 110.0", + 110.0: "184-AL1/30-ST1A 110.0", + 132.0: "243-AL1/39-ST1A 110.0", + 150.0: "243-AL1/39-ST1A 110.0", + 220.0: "Al/St 240/40 2-bundle 220.0", + 300.0: "Al/St 240/40 3-bundle 300.0", + 330.0: "Al/St 240/40 3-bundle 300.0", + 380.0: "Al/St 240/40 4-bundle 380.0", + 400.0: "Al/St 240/40 4-bundle 380.0", + 500.0: "Al/St 240/40 4-bundle 380.0", + 750.0: "Al/St 560/50 4-bundle 750.0", + }, + description="Specifies line types to assume for the different voltage levels of the ENTSO-E grid extraction. Should normally handle voltage levels 220, 300, and 380 kV.", + ) + s_max_pu: float = Field( + 0.7, + description="Correction factor for line capacities (`s_nom`) to approximate N-1 security and reserve capacity for reactive power flows.", + ) + s_nom_max: float = Field( + float("inf"), + description="Global upper limit for the maximum capacity of each extendable line (MW).", + ) + max_extension: float = Field( + 20000, + description="Upper limit for the extended capacity of each extendable line (MW).", + ) + length_factor: float = Field( + 1.25, + description="Correction factor to account for the fact that buses are *not* connected by lines through air-line distance.", + ) + reconnect_crimea: bool = Field( + True, + description="Whether to reconnect Crimea to the Ukrainian grid.", + ) + under_construction: Literal["zero", "remove", "keep"] = Field( + "keep", + description="Specifies how to handle lines which are currently under construction.", + ) + dynamic_line_rating: _DynamicLineRatingConfig = Field( + default_factory=_DynamicLineRatingConfig, + description="Configuration for dynamic line rating.", + ) + + +class LinksConfig(ConfigModel): + """Configuration for `links` settings.""" + + class _DCEfficiencyConfig(BaseModel): + """Configuration for `transmission.electricity.links.efficiency` settings.""" + + enable: bool = Field( + True, + description="Apply transmission efficiency for DC links.", + ) + efficiency_static: float = Field( + 0.98, + description="Static DC transmission efficiency.", + ) + efficiency_per_1000km: float = Field( + 0.977, + description="Distance-dependent DC efficiency factor per 1000 km.", + ) + + p_max_pu: float = Field( + 1.0, + description="Correction factor for link capacities `p_nom`.", + ) + p_min_pu: float = Field( + -1.0, + description="Correction factor for link capacities `p_nom`.", + ) + p_nom_max: float = Field( + float("inf"), + description="Global upper limit for the maximum capacity of each extendable DC link (MW).", + ) + max_extension: float = Field( + 30000, + description="Upper limit for the extended capacity of each extendable DC link (MW).", + ) + length_factor: float = Field( + 1.25, + description="Correction factor to account for the fact that buses are *not* connected by links through air-line distance.", + ) + under_construction: Literal["zero", "remove", "keep"] = Field( + "keep", + description="Specifies how to handle lines which are currently under construction.", + ) + efficiency: _DCEfficiencyConfig = Field( + default_factory=_DCEfficiencyConfig, + description="DC transmission efficiency configuration.", + ) + + +class TransformersConfig(ConfigModel): + """Configuration for `transformers` settings.""" + + x: float = Field( + 0.1, + description="Series reactance in per unit (p.u.), using `s_nom` as base power of the transformer. Overwritten if `type` is specified.", + ) + s_nom: float = Field( + 2000.0, + description="Limit of apparent power which can pass through branch (MVA). Overwritten if `type` is specified.", + ) + type: str = Field( + "", + description="Specifies transformer types to assume for the transformers of the ENTSO-E grid extraction.", + ) + + +class _IncludeConfig(ConfigModel): + """Configuration for `electricity.projects.include` settings.""" + + tyndp2020: bool = Field( + True, + description="Whether to integrate the TYNDP 2020 dataset.", + ) + nep: bool = Field( + True, + description="Whether to integrate the German network development plan dataset.", + ) + manual: bool = Field( + True, + description="Whether to integrate the manually added transmission projects. They are taken from the previously existing links_tyndp.csv file.", + ) + + +class ElectricityProjectsConfig(BaseModel): + """Configuration for `electricity.projects` settings.""" + + enable: bool = Field( + True, + description="Whether to integrate this transmission projects or not.", + ) + include: _IncludeConfig = Field( + default_factory=_IncludeConfig, + description="Name of the transmission projects. They should be unique and have to be provided in the `data/transmission_projects` folder.", + ) + skip: list[str] = Field( + default_factory=lambda: ["upgraded_lines", "upgraded_links"], + description="Type of lines to skip from all transmission projects. Possible values are: `upgraded_lines`, `upgraded_links`, `new_lines`, `new_links`.", + ) + status: list[str] | dict[str, list[str]] = Field( + default_factory=lambda: ["under_construction", "in_permitting", "confirmed"], + description="Status to include into the model as list or as dict with name of project and status to include. Possible values for status are `under_construction`, `in_permitting`, `confirmed`, `planned_not_yet_permitted`, `under_consideration`.", + ) + new_link_capacity: Literal["zero", "keep"] = Field( + "zero", + description="Whether to set the new link capacity to the provided capacity or set it to zero.", + ) + + +class _TransmissionCarrierConfigGeneral(BaseModel): + """Configuration for a single transmission carrier.""" + + enable: bool = Field( + True, + description="Enable transmission candidate generation for this carrier.", + ) + gabriel_filter_min_degree: int = Field( + 1, + ge=0, + description="Minimum node degree target applied after Gabriel graph filtering. Set to >= 1 to activate Gabriel filtering with min-degree backfilling; 0 disables it.", + ) + max_offshore_haversine_distance: float = Field( + float("inf"), + gt=0, + description="Maximum haversine distance in km for offshore transmission candidates. Candidate edges a total offshore length exceeding this threshold are excluded. Defaults to infinity (no limit).", + ) + length_factor: float = Field( + 1.25, + gt=0, + description="Multiplier applied to geometric corridor lengths.", + ) + cost_factor: float = Field( + 1, + gt=0, + description="Multiplier applied to the capital cost of transmission infrastructure.", + ) + + +class _TransmissionEfficiencyConfig(BaseModel): + """Configuration for `transmission..efficiency` settings.""" + + enable: bool = Field( + True, + description="Apply transmission efficiency for this carrier.", + ) + efficiency_per_1000km: float = Field( + 1, + description="Distance-dependent efficiency factor per 1000 km.", + ) + compression_per_1000km: float = Field( + 0, + description="Additional electricity consumption for compression per 1000 km.", + ) + + +class _HydrogenRetrofitConfig(BaseModel): + """Configuration for `transmission.hydrogen.retrofit` settings.""" + + enable: bool = Field( + False, + description="Add option for retrofitting existing pipelines to transport hydrogen.", + ) + capacity_per_ch4: float = Field( + 0.6, + description="The ratio for H2 capacity per original CH4 capacity of retrofitted pipelines. The `European Hydrogen Backbone (April, 2020) p.15 `_ 60% of original natural gas capacity could be used in cost-optimal case as H2 capacity.", + ) + + +class _TransmissionCarrierConfigElectricity(BaseModel): + """Configuration for electricity transmission grid.""" + + enable: bool = Field( + True, + description="Switch for enabling/disabling the electricity transmission grid.", + ) + base_network: Literal["entsoegridkit", "osm", "tyndp"] = Field( + "osm", + description="Specify the underlying base network, i.e. GridKit (based on ENTSO-E web map extract), OpenStreetMap (OSM), or TYNDP.", + ) + transmission_limit: str = Field( + "vopt", + description="Limit on transmission expansion. The first part can be `v` (for setting a limit on line volume) or `c` (for setting a limit on line cost). The second part can be `opt` or a float bigger than one (e.g. 1.25). If `opt` is chosen line expansion is optimised according to its capital cost (where the choice `v` only considers overhead costs for HVDC transmission lines, while `c` uses more accurate costs distinguishing between overhead and underwater sections and including inverter pairs). The setting `v1.25` will limit the total volume of line expansion to 25% of currently installed capacities weighted by individual line lengths. The setting `c1.25` will allow to build a transmission network that costs no more than 25 % more than the current system.", + ) + lines: LinesConfig = Field( + default_factory=LinesConfig, + description="Transmission lines configuration.", + ) + links: LinksConfig = Field( + default_factory=LinksConfig, + description="HVDC links configuration.", + ) + transformers: TransformersConfig = Field( + default_factory=TransformersConfig, + description="Transformers configuration.", + ) + projects: ElectricityProjectsConfig = Field( + default_factory=ElectricityProjectsConfig, + description="Electricity transmission projects configuration.", + ) + + +class _TransmissionCarrierConfigGas(BaseModel): + enable: bool = Field( + True, + description="Add existing natural gas infrastructure, incl. LNG terminals, production and entry-points. A length-weighted `k-edge augmentation algorithm `_ can be run to add new candidate gas pipelines such that all regions of the model can be connected to the gas network. When activated, all the gas demands are regionally disaggregated as well.", + ) + connectivity_upgrade: float = Field( + 1, + description="The number of desired edge connectivity (k) in the length-weighted `k-edge augmentation algorithm `_ used for the gas network.", + ) + efficiency: _TransmissionEfficiencyConfig = Field( + default_factory=lambda: _TransmissionEfficiencyConfig( + efficiency_per_1000km=1, + compression_per_1000km=0.01, + ), + description="Methane gas pipeline transmission efficiency.", + ) + + +class _TransmissionCarrierConfigElectricityDistribution(BaseModel): + class _ElectricityDistributionEfficiencyConfig(BaseModel): + """Configuration for `transmission.electricity_distribution.efficiency` settings.""" + + enable: bool = Field( + True, + description="Apply electricity distribution efficiency.", + ) + efficiency_static: float = Field( + 0.97, + description="Static electricity distribution efficiency.", + ) + + enable: bool = Field( + True, + description="Add a simplified representation of the exchange capacity between transmission and distribution grid level through a link.", + ) + cost_factor: float = Field( + 1.0, + description="Multiplies the investment cost of the electricity distribution grid.", + ) + efficiency: _ElectricityDistributionEfficiencyConfig = Field( + default_factory=_ElectricityDistributionEfficiencyConfig, + description="Electricity distribution efficiency configuration.", + ) + + +class _TransmissionCarrierConfigHydrogen(_TransmissionCarrierConfigGeneral): + retrofit: _HydrogenRetrofitConfig = Field( + default_factory=_HydrogenRetrofitConfig, + description="Hydrogen pipeline retrofit configuration.", + ) + efficiency: _TransmissionEfficiencyConfig = Field( + default_factory=lambda: _TransmissionEfficiencyConfig( + efficiency_per_1000km=1, + compression_per_1000km=0.018, + ), + description="Hydrogen pipeline transmission efficiency.", + ) + + +class TransmissionConfig(BaseModel): + """Configuration for `transmission` settings.""" + + electricity: _TransmissionCarrierConfigElectricity = Field( + default_factory=_TransmissionCarrierConfigElectricity, + description="Configuration for electricity transmission grid.", + ) + electricity_distribution: _TransmissionCarrierConfigElectricityDistribution = Field( + default_factory=_TransmissionCarrierConfigElectricityDistribution, + description="Configuration for simplified electricity distribution grid.", + ) + hydrogen: _TransmissionCarrierConfigHydrogen = Field( + default_factory=_TransmissionCarrierConfigHydrogen, + description="Configuration for hydrogen transmission candidates.", + ) + carbon_dioxide: _TransmissionCarrierConfigGeneral = Field( + default_factory=_TransmissionCarrierConfigGeneral, + description="Configuration for carbon dioxide transmission candidates.", + ) + gas: _TransmissionCarrierConfigGas = Field( + default_factory=_TransmissionCarrierConfigGas, + description="Configuration for methane gas transmission candidates.", + ) diff --git a/scripts/lib/validation/config/transmission_projects.py b/scripts/lib/validation/config/transmission_projects.py deleted file mode 100644 index e48a790a98..0000000000 --- a/scripts/lib/validation/config/transmission_projects.py +++ /dev/null @@ -1,57 +0,0 @@ -# SPDX-FileCopyrightText: Contributors to PyPSA-Eur -# -# SPDX-License-Identifier: MIT - -""" -Transmission projects configuration. - -See docs in https://pypsa-eur.readthedocs.io/en/latest/configuration.html#transmission-projects -""" - -from typing import Literal - -from pydantic import BaseModel, Field - -from scripts.lib.validation.config._base import ConfigModel - - -class _IncludeConfig(ConfigModel): - """Configuration for `transmission_projects.include` settings.""" - - tyndp2020: bool = Field( - True, - description="Whether to integrate the TYNDP 2020 dataset.", - ) - nep: bool = Field( - True, - description="Whether to integrate the German network development plan dataset.", - ) - manual: bool = Field( - True, - description="Whether to integrate the manually added transmission projects. They are taken from the previously existing links_tyndp.csv file.", - ) - - -class TransmissionProjectsConfig(BaseModel): - """Configuration for `transmission_projects` settings.""" - - enable: bool = Field( - True, - description="Whether to integrate this transmission projects or not.", - ) - include: _IncludeConfig = Field( - default_factory=_IncludeConfig, - description="Name of the transmission projects. They should be unique and have to be provided in the `data/transmission_projects` folder.", - ) - skip: list[str] = Field( - default_factory=lambda: ["upgraded_lines", "upgraded_links"], - description="Type of lines to skip from all transmission projects. Possible values are: `upgraded_lines`, `upgraded_links`, `new_lines`, `new_links`.", - ) - status: list[str] | dict[str, list[str]] = Field( - default_factory=lambda: ["under_construction", "in_permitting", "confirmed"], - description="Status to include into the model as list or as dict with name of project and status to include. Possible values for status are `under_construction`, `in_permitting`, `confirmed`, `planned_not_yet_permitted`, `under_consideration`.", - ) - new_link_capacity: Literal["zero", "keep"] = Field( - "zero", - description="Whether to set the new link capacity to the provided capacity or set it to zero.", - ) diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index d9434ef76e..b832e39e5f 100755 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -11,6 +11,7 @@ from itertools import product from types import SimpleNamespace +import geopandas as gpd import networkx as nx import numpy as np import pandas as pd @@ -50,7 +51,7 @@ logger = logging.getLogger(__name__) -def define_spatial(nodes, options): +def define_spatial(nodes, options, cf_transmission): """ Namespace for spatial. @@ -109,7 +110,7 @@ def define_spatial(nodes, options): spatial.gas = SimpleNamespace() - if options["gas_network"]: + if cf_transmission["gas"]["enable"]: spatial.gas.nodes = nodes + " gas" spatial.gas.locations = nodes spatial.gas.biogas = nodes + " biogas" @@ -127,7 +128,7 @@ def define_spatial(nodes, options): spatial.gas.biogas_to_gas_cc = nodes + " biogas to gas CC" else: spatial.gas.biogas_to_gas_cc = ["EU biogas to gas CC"] - if options.get("co2_spatial", options["co2_network"]): + if options.get("co2_spatial", cf_transmission["carbon_dioxide"]["enable"]): spatial.gas.industry_cc = nodes + " gas for industry CC" else: spatial.gas.industry_cc = ["gas for industry CC"] @@ -871,7 +872,13 @@ def add_co2_tracking( ) -def add_co2_network(n, costs, co2_network_cost_factor=1.0): +def add_co2_network( + n, + carbon_dioxide_transmission_candidates, + costs, + co2_transmission_cost_factor=1.0, + co2_transmission_length_factor=1.0, +): """ Add CO2 transport network to the PyPSA network. @@ -883,12 +890,17 @@ def add_co2_network(n, costs, co2_network_cost_factor=1.0): ---------- n : pypsa.Network The PyPSA network container object + carbon_dioxide_transmission_candidates : str + Path to GeoJSON file containing CO2 pipeline candidates costs : pd.DataFrame Cost assumptions for different technologies. Must contain entries for 'CO2 pipeline' and 'CO2 submarine pipeline' with 'capital_cost' and 'lifetime' columns - co2_network_cost_factor : float, optional + co2_transmission_cost_factor : float, optional Factor to scale the capital costs of the CO2 network, default 1.0 + co2_transmission_length_factor : float, optional + Factor to scale transmission lengths read from topology candidates, + default 1.0 Returns ------- @@ -902,32 +914,43 @@ def add_co2_network(n, costs, co2_network_cost_factor=1.0): created using the create_network_topology helper function. """ logger.info("Adding CO2 network.") - co2_links = create_network_topology(n, "CO2 pipeline ") + co2_links = gpd.read_file(carbon_dioxide_transmission_candidates).copy() + + if "name" not in co2_links.columns: + co2_links["name"] = co2_links["bus0"] + " -> " + co2_links["bus1"] + + if not co2_links.empty and not co2_links.iloc[0]["bus0"].endswith(" co2 stored"): + co2_links[["bus0", "bus1"]] = co2_links[["bus0", "bus1"]] + " co2 stored" + + co2_links = co2_links.set_index("name") + co2_links.index = "CO2 pipeline " + co2_links.index if "underwater_fraction" not in co2_links.columns: co2_links["underwater_fraction"] = 0.0 + scaled_length = co2_links["length"] * co2_transmission_length_factor + cost_onshore = ( (1 - co2_links.underwater_fraction) * costs.at["CO2 pipeline", "capital_cost"] - * co2_links.length + * scaled_length ) cost_submarine = ( co2_links.underwater_fraction * costs.at["CO2 submarine pipeline", "capital_cost"] - * co2_links.length + * scaled_length ) capital_cost = cost_onshore + cost_submarine - capital_cost *= co2_network_cost_factor + capital_cost *= co2_transmission_cost_factor n.add( "Link", co2_links.index, - bus0=co2_links.bus0.values + " co2 stored", - bus1=co2_links.bus1.values + " co2 stored", + bus0=co2_links["bus0"].values, + bus1=co2_links["bus1"].values, p_min_pu=-1, p_nom_extendable=True, - length=co2_links.length.values, + length=scaled_length.values, capital_cost=capital_cost.values, carrier="CO2 pipeline", lifetime=costs.at["CO2 pipeline", "lifetime"], @@ -1502,7 +1525,7 @@ def add_ammonia( def insert_electricity_distribution_grid( n: pypsa.Network, costs: pd.DataFrame, - options: dict, + cf_transmission: dict, pop_layout: pd.DataFrame, solar_rooftop_potentials_fn: str, ) -> None: @@ -1521,10 +1544,9 @@ def insert_electricity_distribution_grid( Technology cost assumptions with technologies as index and cost parameters as columns, including 'fixed' costs, 'lifetime', and component-specific parameters like 'efficiency' - options : dict - Configuration options containing at least: - - transmission_efficiency: dict with distribution grid parameters - - marginal_cost_storage: float for storage operation costs + cf_transmission : dict + Transmission configuration containing at least: + - electricity_distribution.efficiency: distribution grid loss parameters pop_layout : pd.DataFrame Population data per node with at least: - 'total' column containing population in thousands @@ -1575,13 +1597,10 @@ def insert_electricity_distribution_grid( # deduct distribution losses from electricity demand as these are included in total load # https://nbviewer.org/github/Open-Power-System-Data/datapackage_timeseries/blob/2020-10-06/main.ipynb - if ( - efficiency := options["transmission_efficiency"] - .get("electricity distribution grid", {}) - .get("efficiency_static") - ) and "electricity distribution grid" in options["transmission_efficiency"][ - "enable" - ]: + distribution_efficiency = cf_transmission["electricity_distribution"]["efficiency"] + if distribution_efficiency["enable"] and ( + efficiency := distribution_efficiency["efficiency_static"] + ): logger.info( f"Deducting distribution losses from electricity demand: {np.around(100 * (1 - efficiency), decimals=2)}%" ) @@ -1678,7 +1697,7 @@ def insert_electricity_distribution_grid( ) -def insert_gas_distribution_costs( +def insert_gas_distribution_cost( n: pypsa.Network, costs: pd.DataFrame, options: dict, @@ -1737,7 +1756,7 @@ def insert_gas_distribution_costs( n.links.loc[mchp, "capital_cost"] += capital_cost -def add_electricity_grid_connection(n, costs): +def add_electricity_grid_connection_cost(n, costs): carriers = ["onwind", "solar", "solar-hsat"] gens = n.generators.index[n.generators.carrier.isin(carriers)] @@ -1752,11 +1771,13 @@ def add_h2_gas_infrastructure( costs, pop_layout, h2_cavern_file, + hydrogen_transmission_candidates, cavern_types, clustered_gas_network_file, gas_input_nodes, spatial, options, + cf_transmission, ): """ Add hydrogen and gas infrastructure to the network. @@ -1771,6 +1792,8 @@ def add_h2_gas_infrastructure( Population layout with index of locations/nodes h2_cavern_file : str Path to CSV file containing hydrogen cavern storage potentials + hydrogen_transmission_candidates : str + Path to GeoJSON file containing hydrogen pipeline candidates cavern_types : list List of underground storage types to consider clustered_gas_network_file : str, optional @@ -1785,17 +1808,14 @@ def add_h2_gas_infrastructure( - hydrogen_fuel_cell : bool - hydrogen_turbine : bool - hydrogen_underground_storage : bool - - gas_network : bool - - H2_retrofit : bool - - H2_network : bool - methanation : bool - coal_cc : bool - SMR_cc : bool - SMR : bool - min_part_load_methanation : float - cc_fraction : float - logger : logging.Logger, optional - Logger for output messages. If None, no logging is performed. + cf_transmission : dict + Dictionary of configuration options for transmission infrastructure. Returns ------- @@ -1812,6 +1832,8 @@ def add_h2_gas_infrastructure( """ # Set defaults options = options or {} + hydrogen_retrofit = cf_transmission["hydrogen"]["retrofit"] + gas_connectivity_upgrade = cf_transmission["gas"]["connectivity_upgrade"] logger.info("Add hydrogen storage") @@ -1919,10 +1941,10 @@ def add_h2_gas_infrastructure( lifetime=costs.at[tech, "lifetime"], ) - if options["H2_retrofit"]: + if hydrogen_retrofit["enable"]: gas_pipes = pd.read_csv(clustered_gas_network_file, index_col=0) - if options["gas_network"]: + if cf_transmission["gas"]["enable"]: logger.info( "Add natural gas infrastructure, incl. LNG terminals, production, storage and entry-points." ) @@ -1938,7 +1960,7 @@ def add_h2_gas_infrastructure( gas_pipes = pd.read_csv(clustered_gas_network_file, index_col=0) - if options["H2_retrofit"]: + if hydrogen_retrofit["enable"]: gas_pipes["p_nom_max"] = gas_pipes.p_nom gas_pipes["p_nom_min"] = 0.0 # 0.1 EUR/MWkm/a to prefer decommissioning to address degeneracy @@ -2019,7 +2041,7 @@ def add_h2_gas_infrastructure( ) # apply k_edge_augmentation weighted by length of complement edges - k_edge = options["gas_network_connectivity_upgrade"] + k_edge = gas_connectivity_upgrade if augmentation := list( k_edge_augmentation(G, k_edge, avail=complement_edges.values) ): @@ -2046,7 +2068,7 @@ def add_h2_gas_infrastructure( lifetime=costs.at["CH4 (g) pipeline", "lifetime"], ) - if options["H2_retrofit"]: + if hydrogen_retrofit["enable"]: logger.info("Add retrofitting options of existing CH4 pipes to H2 pipes.") fr = "gas pipeline" @@ -2056,39 +2078,52 @@ def add_h2_gas_infrastructure( n.add( "Link", h2_pipes.index, - bus0=h2_pipes.bus0 + " H2", - bus1=h2_pipes.bus1 + " H2", + bus0=h2_pipes["bus0"] + " H2", + bus1=h2_pipes["bus1"] + " H2", p_min_pu=-1.0, # allow that all H2 retrofit pipelines can be used in both directions - p_nom_max=h2_pipes.p_nom * options["H2_retrofit_capacity_per_CH4"], + p_nom_max=h2_pipes["p_nom"] * hydrogen_retrofit["capacity_per_ch4"], p_nom_extendable=True, - length=h2_pipes.length, + length=h2_pipes["length"], capital_cost=costs.at["H2 (g) pipeline repurposed", "capital_cost"] - * h2_pipes.length, - tags=h2_pipes.name, + * h2_pipes["length"], + tags=h2_pipes["name"], carrier="H2 pipeline retrofitted", lifetime=costs.at["H2 (g) pipeline repurposed", "lifetime"], ) - if options["H2_network"]: + # TODO: implement offshore costs, using 1.5-2.0x multiplier + if cf_transmission["hydrogen"]["enable"]: logger.info("Add options for new hydrogen pipelines.") - h2_pipes = create_network_topology( - n, "H2 pipeline ", carriers=["DC", "gas pipeline"] - ) - h2_buses_loc = n.buses.query("carrier == 'H2'").location # noqa: F841 - h2_pipes = h2_pipes.query("bus0 in @h2_buses_loc and bus1 in @h2_buses_loc") + h2_pipes = gpd.read_file(hydrogen_transmission_candidates).copy() + if "name" not in h2_pipes.columns: + h2_pipes["name"] = h2_pipes["bus0"] + " -> " + h2_pipes["bus1"] + + if not h2_pipes.empty and not h2_pipes.iloc[0]["bus0"].endswith(" H2"): + h2_pipes[["bus0", "bus1"]] = h2_pipes[["bus0", "bus1"]] + " H2" + + h2_pipes = h2_pipes.set_index("name") + h2_pipes.index = "H2 pipeline " + h2_pipes.index + + h2_bus_ids = n.buses.index[n.buses.carrier == "H2"] + h2_pipes = h2_pipes[ + h2_pipes["bus0"].isin(h2_bus_ids) & h2_pipes["bus1"].isin(h2_bus_ids) + ] - # TODO Add efficiency losses n.add( "Link", h2_pipes.index, - bus0=h2_pipes.bus0.values + " H2", - bus1=h2_pipes.bus1.values + " H2", + bus0=h2_pipes["bus0"].values, + bus1=h2_pipes["bus1"].values, p_min_pu=-1, p_nom_extendable=True, - length=h2_pipes.length.values, + length=( + h2_pipes["length"].values * cf_transmission["hydrogen"]["length_factor"] + ), capital_cost=costs.at["H2 (g) pipeline", "capital_cost"] - * h2_pipes.length.values, + * h2_pipes["length"].values + * cf_transmission["hydrogen"]["length_factor"] + * cf_transmission["hydrogen"]["cost_factor"], carrier="H2 pipeline", lifetime=costs.at["H2 (g) pipeline", "lifetime"], ) @@ -3748,6 +3783,7 @@ def add_biomass( options, spatial, cf_industry, + cf_transmission, pop_layout, biomass_potentials_file, biomass_transport_costs_file=None, @@ -3778,6 +3814,8 @@ def add_biomass( Object containing spatial information about different carriers (gas, biomass, etc.) cf_industry : dict Dictionary containing industrial sector configuration + cf_transmission : dict + Dictionary of configuration options for transmission infrastructure. pop_layout : pd.DataFrame DataFrame containing population layout information biomass_potentials_file : str @@ -3809,7 +3847,7 @@ def add_biomass( biomass_potentials = pd.read_csv(biomass_potentials_file, index_col=0) * nyears # need to aggregate potentials if gas not nodally resolved - if options["gas_network"]: + if cf_transmission["gas"]["enable"]: biogas_potentials_spatial = biomass_potentials["biogas"].rename( index=lambda x: x + " biogas" ) @@ -4477,6 +4515,7 @@ def add_industry( options: dict, spatial: SimpleNamespace, cf_industry: dict, + cf_transmission: dict, investment_year: int, ): """ @@ -4508,6 +4547,8 @@ def add_industry( (biomass, gas, oil, methanol, etc.) cf_industry : dict Industry-specific configuration parameters + cf_transmission : dict + Dictionary of configuration options for transmission infrastructure. investment_year : int Year for which investment costs should be considered HeatSystem : Enum @@ -4641,7 +4682,7 @@ def add_industry( gas_demand = industrial_demand.loc[nodes, "methane"] / nhours - if options["gas_network"]: + if cf_transmission["gas"]["enable"]: spatial_gas_demand = gas_demand.rename(index=lambda x: x + " gas for industry") else: spatial_gas_demand = gas_demand.sum() @@ -5017,7 +5058,7 @@ def add_industry( unit="t_co2", ) - if options["co2_spatial"] or options["co2_network"]: + if options["co2_spatial"] or cf_transmission["carbon_dioxide"]["enable"]: p_set = ( -industrial_demand.loc[nodes, "process emission"].rename( index=lambda x: x + " process emissions" @@ -6241,7 +6282,7 @@ def add_import_options( snakemake = mock_snakemake( "prepare_sector_network", opts="", - clusters="10", + clusters="50", sector_opts="", planning_horizons="2050", ) @@ -6252,6 +6293,7 @@ def add_import_options( options = snakemake.params.sector cf_industry = snakemake.params.industry + cf_transmission = snakemake.params.transmission ext_carriers = snakemake.params.electricity.get("extendable_carriers", dict()) investment_year = int(snakemake.wildcards.planning_horizons) @@ -6293,7 +6335,7 @@ def add_import_options( year = int(snakemake.params["energy_totals_year"]) heating_efficiencies = pd.read_csv(fn, index_col=[1, 0]).loc[year] - spatial = define_spatial(pop_layout.index, options) + spatial = define_spatial(pop_layout.index, options, cf_transmission) if snakemake.params.foresight in ["myopic", "perfect"]: add_lifetime_wind_solar(n, costs) @@ -6340,11 +6382,17 @@ def add_import_options( costs=costs, pop_layout=pop_layout, h2_cavern_file=snakemake.input.h2_cavern, + hydrogen_transmission_candidates=( + snakemake.input.hydrogen_transmission_candidates + if hasattr(snakemake.input, "hydrogen_transmission_candidates") + else None + ), cavern_types=snakemake.params.sector["hydrogen_underground_storage_locations"], clustered_gas_network_file=snakemake.input.clustered_gas_network, gas_input_nodes=gas_input_nodes, spatial=spatial, options=options, + cf_transmission=cf_transmission, ) # Hydrogen already implemented in add_h2_gas_infrastructure @@ -6427,6 +6475,7 @@ def add_import_options( options=options, spatial=spatial, cf_industry=cf_industry, + cf_transmission=cf_transmission, pop_layout=pop_layout, biomass_potentials_file=snakemake.input.biomass_potentials, biomass_transport_costs_file=snakemake.input.biomass_transport_costs, @@ -6449,6 +6498,7 @@ def add_import_options( options=options, spatial=spatial, cf_industry=cf_industry, + cf_transmission=cf_transmission, investment_year=investment_year, ) @@ -6491,18 +6541,22 @@ def add_import_options( if options["dac"]: add_dac(n, costs) - if not options["electricity_transmission_grid"]: + if not cf_transmission["electricity"]["enable"]: decentral(n) - if not options["H2_network"]: + if not cf_transmission["hydrogen"]["enable"]: remove_h2_network(n) - if options["co2_network"]: + if cf_transmission["carbon_dioxide"]["enable"]: add_co2_network( - n, - costs, - co2_network_cost_factor=snakemake.config["sector"][ - "co2_network_cost_factor" + n=n, + carbon_dioxide_transmission_candidates=snakemake.input.carbon_dioxide_transmission_candidates, + costs=costs, + co2_transmission_cost_factor=cf_transmission["carbon_dioxide"][ + "cost_factor" + ], + co2_transmission_length_factor=cf_transmission["carbon_dioxide"][ + "length_factor" ], ) @@ -6542,13 +6596,17 @@ def add_import_options( limit, ) - maxext = snakemake.params["lines"]["max_extension"] + maxext = cf_transmission["electricity"]["lines"]["max_extension"] if maxext is not None: limit_individual_line_extension(n, maxext) - if options["electricity_distribution_grid"]: + if cf_transmission["electricity_distribution"]["enable"]: insert_electricity_distribution_grid( - n, costs, options, pop_layout, snakemake.input.solar_rooftop_potentials + n, + costs, + cf_transmission, + pop_layout, + snakemake.input.solar_rooftop_potentials, ) if options["enhanced_geothermal"].get("enable", False): @@ -6566,15 +6624,25 @@ def add_import_options( if options["imports"]["enable"]: add_import_options(n, costs, options, gas_input_nodes) - if options["gas_distribution_grid"]: - insert_gas_distribution_costs(n, costs, options=options) + if options["gas_distribution_grid_cost"]: + insert_gas_distribution_cost(n, costs, options=options) - if options["electricity_grid_connection"]: - add_electricity_grid_connection(n, costs) + if options["electricity_grid_connection_cost"]: + add_electricity_grid_connection_cost(n, costs) + + transmission_efficiency_map = { + "DC": cf_transmission["electricity"]["links"]["efficiency"], + "H2 pipeline": cf_transmission["hydrogen"]["efficiency"], + "gas pipeline": cf_transmission["gas"]["efficiency"], + "electricity distribution grid": cf_transmission["electricity_distribution"][ + "efficiency" + ], + } - for k, v in options["transmission_efficiency"].items(): - if k in options["transmission_efficiency"]["enable"]: - lossy_bidirectional_links(n, k, v) + for carrier, efficiency_config in transmission_efficiency_map.items(): + if efficiency_config["enable"]: + losses = {k: v for k, v in efficiency_config.items() if k != "enable"} + lossy_bidirectional_links(n, carrier, losses) # Workaround: Remove lines with conflicting (and unrealistic) properties # cf. https://github.com/PyPSA/pypsa-eur/issues/444 diff --git a/scripts/simplify_network.py b/scripts/simplify_network.py index 6ba67f0700..fc94c4c941 100644 --- a/scripts/simplify_network.py +++ b/scripts/simplify_network.py @@ -432,7 +432,9 @@ def remove_converters(n: pypsa.Network) -> pypsa.Network: Nyears = n.snapshot_weightings.objective.sum() / 8760 buses_prev, lines_prev, links_prev = len(n.buses), len(n.lines), len(n.links) - linetype_380 = snakemake.config["lines"]["types"][380] + linetype_380 = snakemake.config["transmission"]["electricity"]["lines"]["types"][ + 380 + ] n, trafo_map = simplify_network_to_380(n, linetype_380) busmaps = [trafo_map] diff --git a/scripts/solve_network.py b/scripts/solve_network.py index 4b6429b47c..59132e3fa3 100644 --- a/scripts/solve_network.py +++ b/scripts/solve_network.py @@ -1137,7 +1137,9 @@ def add_pipe_retrofit_constraint(n): p_nom = n.model["Link-p_nom"] - CH4_per_H2 = 1 / n.config["sector"]["H2_retrofit_capacity_per_CH4"] + CH4_per_H2 = ( + 1 / n.params["transmission"]["hydrogen"]["retrofit"]["capacity_per_ch4"] + ) lhs = p_nom.loc[gas_pipes_i] + CH4_per_H2 * p_nom.loc[h2_retrofitted_i] rhs = n.links.p_nom[gas_pipes_i] if not PYPSA_V1: diff --git a/test/test_base_network.py b/test/test_base_network.py index 905c51faea..5131f3b41e 100644 --- a/test/test_base_network.py +++ b/test/test_base_network.py @@ -82,7 +82,11 @@ def test_get_linetype_by_voltage(config): line_type_list = [] for v_nom in v_nom_list: - line_type_list.append(_get_linetype_by_voltage(v_nom, config["lines"]["types"])) + line_type_list.append( + _get_linetype_by_voltage( + v_nom, config["transmission"]["electricity"]["lines"]["types"] + ) + ) assert len(line_type_list) == len(reference_list) assert all([x == y for x, y in zip(line_type_list, reference_list)])