Skip to content

Conversation

@FabianHofmann
Copy link
Contributor

@FabianHofmann FabianHofmann commented Sep 22, 2025

Streamlined and restructured workflow

This PR refactors the workflow in order to streamline and unify optimization approaches and configuration settings. here are some highlights of the PR, otherwise no one will have the motivation to look at this large change. I suggest before going into the code, read this here first, even though it is long...

Highlights

  • No more wildcards excepts for {horizon}; all moved explicit to config entries
  • No more cryptic filenames like elec_s_37_lv1.25_3H_2030.nc
  • Processing of network follows the logic of base.nc -> clustered.nc -> composed_{horizon}.nc -> solved_{horizon}.nc
  • Results are unified and made independent of horizons (csvs/nodal_costs.csv, csvs/capacities.csv ...)
  • compose rule is the entry point for green-field, brown-field, perfect foresight model for both sector-coupled and electricity-only.
  • Electricity-only models now run with myopic an perfect foresight
  • Make and plot summary now works for all combinations of models (elec/sector) and foresights

and now a more descriptive summary

Overview

  • The pr restructures the Snakemake workflow of PyPSA-Eur into a leaner pipeline (base → simplified → clustered → composed → solved) and standardises filenames so scenario information now lives in configuration instead of wildcards.
  • All rules that previously emitted *_s_{clusters} or ..._{planning_horizons} artefacts have been renamed; the new naming scheme makes horizons explicit via {horizon} and removes cluster suffixes.
  • Post-processing rules were refactored to read the new solved network filenames and now operate uniformly across all foresight modes, so the map and summary targets remain identical for overnight, myopic, and perfect runs for both elec-only and sector-coupled.

Workflow Changes

  1. Stage progression – The workflow now moves strictly through networks/base.ncnetworks/simplified.ncnetworks/clustered.ncnetworks/composed_{horizon}.ncRESULTS/networks/solved_{horizon}.nc. Old intermediate targets such as networks/base_s_{clusters}_{opts}_{sector_opts}_{planning_horizons}.nc no longer exist.
  2. Unified compositionrules/compose.smk encapsulates what used to be multiple prepare_*, add_*, and brownfield rules; it assembles everything needed for a given horizon and handles the myopic/perfect brownfield inputs.
  3. Single solve rulerules/solve.smk contains one solve_network rule; electricity-only and sector-coupled cases are distinguished by config, not by separate rule files (solve_* smks were deleted).
  4. Collection targetsrules/collect.smk now checks four milestones: clustered networks, composed networks, solved networks, and plotting; {horizon} replaces {planning_horizons} wildcards in file names.
  5. Scenario-aware confignavigate_config, get_full_config, and get_config (all in rules/common.smk) cache fully merged configs per wildcard set. Any new rules should obtain parameters through config_provider(...) or get_config(w) to stay scenario compatible.
  6. Warm-start logic – Myopic runs read the previous RESULTS/networks/solved_{prev}.nc, while perfect foresight reuses the prior networks/composed_{prev}.nc. Overnight runs must supply a single planning horizon.
  7. Post-process harmonizationrules/postprocess.smk now reads RESULTS/networks/solved_{horizon}.nc for all foresight modes, so every map (power_network_{horizon}.pdf, h2_network_{horizon}.pdf, {carrier}_balance_map_{horizon}.pdf, etc.) and CSV summary is generated with the same naming scheme regardless of foresight setting.

Stage Notes

  • Base & shapesrules/build_electricity.smk writes regions_onshore_base.geojson/regions_offshore_base.geojson together with networks/base.nc. Administrative shapes and OSM/TYNDP inputs remain unchanged.
  • Simplified assetssimplify_network now emits networks/simplified.nc, regions_onshore_simplified.geojson, regions_offshore_simplified.geojson, and busmap_simplified.csv. build_electricity_demand_base consumes those files and produces electricity_demand_simplified.nc. process_cost_data reads per-horizon costs_{horizon}.csv.
  • Clusteringcluster_network takes networks/simplified.nc plus busmap_simplified.csv and emits networks/clustered.nc, regions_onshore.geojson, regions_offshore.geojson, busmap.csv, and linemap.csv. Cluster counts are configured, not embedded in filenames.
  • Compose assets – Most intermediate files on inputs drop the _s_{clusters} suffix; ie. population layouts collapse to pop_layout.csv and pop_layout_simplified.csv; Gas locations become gas_input_locations.geojson/gas_input_locations_simplified.csv; Powerplant list becomes powerplants.csv etc.
  • Compositioncompose_network (one rule) flattens all previous add_existing_baseyear, add_brownfield, prepare_perfect_*, etc. It automatically wires the previous-horizon inputs depending on foresight and provides the final pre-solve network (networks/composed_{horizon}.nc).
  • Solvingsolve_network reads networks/composed_{horizon}.nc, writes RESULTS/networks/solved_{horizon}.nc, and stores solver/memory/python logs under RESULTS/logs/solve_network/. Custom extra functionality and solver settings are driven purely by config.
  • Post-processing – Preview maps are maps/base_network.pdf and maps/clustered_network.pdf; solved outputs are RESULTS/maps/power_network_{horizon}.pdf, .../h2_network_{horizon}.pdf, .../ch4_network_{horizon}.pdf, and .../{carrier}_balance_map_{horizon}.pdf. make_summary and plot_summary operate on RESULTS/networks/solved_{horizon}.nc regardless of foresight.

Config Changes

  • Planning horizons are now top-level – Set planning_horizons directly under the root of your config (config/config.default.yaml:34-35). The old scenario.planning_horizons entry is ignored, and the workflow expects config["planning_horizons"] to exist even when scenarios are disabled. Configuration bundles such as config/test/config.scenarios.yaml:17-20 already follow this format.
  • Global scenario block removed – The legacy scenario: section (with clusters, opts, sector_opts, etc.) is no longer part of config.default.yaml. Scenario sweeps should now be described via run.scenarios plus the dedicated scenario YAML file; individual dimensions (e.g. clusters) are configured directly under their respective sections. If you keep a scenario block in a local config it will simply be ignored.
  • CO₂ budget fields were restructuredco2_budget now specifies an emissions_scope, a values interpretation flag, and nested upper/lower dictionaries with their own enable switches (config/config.default.yaml:90-113). Update custom configs accordingly if you previously listed plain year-value pairs.
  • Transmission capacity caps gained explicit “extension” keyslines uses s_nom_max/s_nom_max_extension, and links uses p_nom_max/p_nom_max_extension (config/config.default.yaml:320-355). Rename any overrides that still refer to max_extension so the new limits are applied.
  • Selective carrier exclusion moved into configurationelectricity.exclude_carriers (config/config.default.yaml:115-162) lets you strip carriers during clustering; custom busmap logic should now read that list instead of hard-coding exclusions.
  • Existing capacity toggles are explicit – The brownfield logic key is existing_capacities.enabled (config/config.default.yaml:479-486). Set it to true when you want compose_network to import historical assets; otherwise the new workflow assumes a greenfield build.

Script Changes

  • scripts/compose_network.py combines function calls from multiple preparatory scripts (add_electricity.py, add_existing_baseyear.py, add_brownfield.py, prepare_network.py, prepare_sector_network.py, prepare_perfect_foresight.py). At a later stage a clearer packaging structure should be used here.
  • Preparatory scripts are no longer executed – there main sections was integrated into compose_network.py.
  • Solving entry points are harmonizedscripts/solve_network.py is the only solver script called from Snakemake; solve_operations_network.py (operations-only runs) is no longer referenced by the workflow.
  • Summary generation moved into scripts/make_summary.py – the script now loads all horizons via pypsa.NetworkCollection for overnight, myopic, and perfect runs, so the dedicated helpers scripts/make_summary_perfect.py and scripts/make_global_summary.py were deleted. Any custom tooling should invoke make_summary.py and read the unified CSV outputs.

File Name Mapping

Old target New target Notes
networks/base_s.nc networks/simplified.nc Paired with regions_onshore_simplified.geojson, regions_offshore_simplified.geojson, and busmap_simplified.csv.
regions_onshore_base_s_{clusters}.geojson / regions_offshore_base_s_{clusters}.geojson regions_onshore.geojson / regions_offshore.geojson Produced by cluster_network; no {clusters} wildcard in filename.
busmap_base_s_{clusters}.csv / linemap_base_s_{clusters}.csv busmap.csv / linemap.csv Same rule; clusters now implicit in config.
powerplants_s_{clusters}.csv powerplants_s.csv Generated after clustering; uses networks/clustered.nc.
electricity_demand_base_s.nc electricity_demand_simplified.nc Created from simplified assets.
availability_matrix_{clusters}_{technology}.nc availability_matrix_{technology}.nc Includes MD/UA variants.
profile_{clusters}_{technology}.nc profile_{technology}.nc regions_by_class_* follow the same pattern.
pop_layout_base_s_{clusters}.csv / pop_layout_base_s.csv pop_layout.csv / pop_layout_simplified.csv Solar rooftop potentials drop cluster suffixes too.
gas_input_locations_s_{clusters}.geojson gas_input_locations.geojson Simplified CSV also loses the suffix.
costs_{planning_horizons}.csv / _processed.csv costs_{horizon}.csv / _processed.csv collect.smk expands over {horizon}.
networks/base_s_{clusters}_{opts}_{sector_opts}_{planning_horizons}.nc (+ _brownfield*) networks/composed_{horizon}.nc Handles all foresight modes.
RESULTS/networks/base_s_{clusters}_{opts}_{sector_opts}_{planning_horizons}.nc RESULTS/networks/solved_{horizon}.nc Solver logs renamed accordingly.
maps/power-network.pdf / maps/power-network-s-{clusters}.pdf maps/base_network.pdf / maps/clustered_network.pdf Cluster preview plotting renamed.
RESULTS/maps/base_s_{clusters}_{opts}_{sector_opts}-costs-all_{planning_horizons}.pdf RESULTS/maps/power_network_{horizon}.pdf Same pattern for hydrogen, methane, and balance maps.

Migration Checklist

  1. Update rule dependencies – Replace any references to *_base_s* or *_base_s_{clusters}_* targets with the new filenames; consume networks/composed_{horizon}.nc/RESULTS/networks/solved_{horizon}.nc instead of the legacy base_s_* artefacts.
  2. Add custom inputs and outputs from prepare_ and add_ snakemake rules to the compose rule in compose.smk and update to the new wildcard convention (only keep {horizon} wildcard)
  3. Apply custom implementations in legacy scripts (add_.py/prepare_.py) to the corresponding sections in compose_network.py. Make sure to import necessary functions from the legacy script. The section in compose_network.py reference the old scripts to clearly indicate where to add the changes.
  4. Adjust custom scripts – Update plotting, analysis, or data-export scripts to read the {horizon}-based filenames and the new CSV outputs from make_summary.
  5. Warm-start expectations – Myopic extensions should request RESULTS/networks/solved_{prev}.nc; perfect foresight helpers should import the previous networks/composed_{prev}.nc. Do not craft filenames manually.

Checklist

  • I tested my contribution locally and it works as intended.
  • Code and workflow changes are sufficiently documented.
  • Changed dependencies are added to envs/environment.yaml.
  • Changes in configuration options are added in config/config.default.yaml.
  • Changes in configuration options are documented in doc/configtables/*.csv.
  • Sources of newly added data are documented in doc/data_sources.rst.
  • A release note doc/release_notes.rst is added.

FabianHofmann and others added 15 commits July 14, 2025 09:41
- Add test_streamlined.smk with 4-step workflow (base → clustered → composed → solved)
- Support all three foresight modes: overnight, myopic, perfect
- Add robust horizon handling for single values and lists
- Update test configs with proper temporal and clustering sections
- Add comprehensive test configurations for all foresight modes
- Add implementation plan and documentation

Features:
- Config-driven approach (no wildcard expansion)
- Dynamic input resolution based on foresight and horizons
- Sequential dependencies for myopic/perfect foresight
- Unified rule structure across all modes
- Robust parameter handling with defaults

Tested workflows:
- Overnight: 6 jobs, single horizon optimization
- Myopic: 10 jobs, sequential horizon optimization with brownfield
- Perfect: 8 jobs, multi-period simultaneous optimization

Refs: GitHub Discussion #1529
- Add Status Update section documenting completed proof of concept
- Update timeline to reflect completed Phase 0 (POC) and current focus
- Document validated implementation patterns from testing experience
- Add robust parameter handling and dynamic input resolution examples
- Update success criteria to distinguish completed vs remaining goals
- Reorganize open questions to focus on remaining implementation details
- Document created test files and artifacts

Key achievements validated:
- 4-step workflow architecture working across all foresight modes
- Config-driven approach with temporal section replacing scenario wildcards
- Dynamic dependencies with proper sequential chaining
- Robust error handling for missing config sections
- Flexible horizon handling for single values and lists

Next phase: Implement actual scripts with real PyPSA network operations
- Create scripts/compose_network.py that combines all network building steps
  in one big main section without additional function definitions
- Implement network concatenation logic for perfect foresight mode
- Add brownfield constraints for myopic optimization
- Create production rules/compose.smk with dynamic dependencies
- Update Snakefile to include compose rules when streamlined_workflow is enabled
- Add temporal configuration section to default config
- Create example configuration file demonstrating the new workflow

The new workflow follows the 4-step structure:
base → clustered → composed_{horizon} → solved_{horizon}

All configuration is now driven by config sections rather than wildcards,
simplifying the workflow and making it more maintainable.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
CRITICAL FIXES:
- Fix snapshot handling bug: prevent overwriting multi-period snapshots in perfect foresight
- Fix function signature error in load_and_aggregate_powerplants call
- Remove resource path conflict by using existing path provider system
- Simplify overly complex perfect foresight concatenation logic
- Fix parameter access patterns throughout the script
- Improve error handling for missing config sections

RESOURCE PATH UPDATES:
- Update all resource references to use existing path patterns with {clusters} wildcards
- Remove custom resources() function that conflicted with existing system
- Align file paths with existing workflow conventions

ROBUSTNESS IMPROVEMENTS:
- Add proper error handling for missing inputs
- Simplify carbon budget handling to avoid complex logic failures
- Use safer parameter access patterns with fallbacks

These fixes address the most serious issues that would have prevented
the workflow from functioning correctly.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
- Fix critical wildcard mismatch: resource paths used {clusters} but rules didn't have this wildcard
- Get cluster count from config and substitute into resource paths
- Add missing cluster_network rule to generate clustered.nc input
- Fix all resource path references to use config-derived cluster values
- Ensure proper dependency chain: base → clustered → composed → solved

This resolves the fundamental mismatch between wildcard-based resource
paths and the new config-driven workflow approach.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
STANDARDIZATION IMPROVEMENTS:
- Replace all direct config access with config_provider function calls
- Ensure consistent use of resources() function from existing path provider
- Follow PyPSA-EUR conventions for parameter handling in Snakemake rules
- Use proper wildcard handling patterns from existing codebase

KEY CHANGES:
- get_compose_inputs: Use config_provider for all config value retrieval
- cluster_network rule: Convert all params to use config_provider
- compose_network rule: Standardize all parameter definitions
- solve_network rule: Use config_provider for solver configuration
- Collection rules: Use config_provider for run name retrieval
- Validation rule: Standardize parameter access patterns

This ensures the streamlined workflow follows the same patterns and
conventions as the existing PyPSA-EUR workflow implementation.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
@coroa
Copy link
Member

coroa commented Sep 22, 2025

Thanks for starting this. Had only a short look so far. I guess it is too early for reviewing. How do you think we can best support?! I have opinions about many of the "critical open questions".

@FabianHofmann
Copy link
Contributor Author

You are totally right, it is too early for reviewing. I guess at this stage, it is best to think about whether I can proceed with this approach or if there is potential red flags, which speak against this strategy in the first place. But perhaps I spend more time on refining this and make a proper report for you guys what are this wider implications.

@FabianHofmann
Copy link
Contributor Author

I have opinions about many of the "critical open questions".

I must admit that the section Critical Unresolved Questions in the streamline document might be outdated and partially resolved (and perhaps even incomplete), so don't give it too much attention. but I would still be happy to hear any high level thoughts and critical things that I potentially miss!

@FabianHofmann FabianHofmann self-assigned this Sep 22, 2025
FabianHofmann and others added 10 commits September 25, 2025 13:43
…ed workflow

- Rename set_line_nom_max to cap_transmission_capacity with clearer parameter names
  - s_nom_max_set → line_max (AC line capacity limit)
  - p_nom_max_set → link_max (DC link capacity limit)
  - s_nom_max_ext → line_max_extension (AC line extension limit)
  - p_nom_max_ext → link_max_extension (DC link extension limit)
- Port cap_transmission_capacity to compose_network.py for network composition
- Port enforce_autarky to solve_network.py for pre-solve constraints
- Add cap_transmission_capacity config section to config.default.yaml
- Remove .get() calls in favor of strict dictionary access for better error messages
- All functionality now config-based instead of wildcard-based

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
- Add adjust_renewable_capacity_limits() function to compose_network.py
  for brownfield scenarios in myopic foresight mode
- Function subtracts existing renewable capacities from previous horizons
  from p_nom_max values in current horizon
- Uses renewable_carriers list from config (not hardcoded)
- Remove redundant add_land_use_constraint() from solve_network.py
  for myopic mode (functionality now in compose step)
- Keep add_land_use_constraint() for perfect foresight (different logic)
- Add comprehensive test suite with 8 test cases covering:
  * Basic capacity subtraction
  * Multiple existing generators
  * Cases where existing exceeds potential
  * Negative value clipping
  * All renewable carriers
  * Edge cases (no existing, non-renewables)
  * Custom carrier lists
- Update compose.smk to pass renewable_carriers as param

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
FabianHofmann and others added 15 commits November 3, 2025 10:05
Remove InputFiles dataclass and 60-line manual mapping code. Use
snakemake.input directly in all functions. Reduces code by 94 lines
while maintaining full functionality across all workflows.
The old expression ('transmission_expansion') is no longer in the config file and therefore fails to overwrite the 'transmission_limit' if it's used in the 'opts'
* Add option to set minimum unit dispatch for electrolysis

* electrolysis p_min_pu documentation
Bumps the github-actions group with 2 updates: [actions/upload-artifact](https://github.com/actions/upload-artifact) and [actions/download-artifact](https://github.com/actions/download-artifact).


Updates `actions/upload-artifact` from 4 to 5
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](actions/upload-artifact@v4...v5)

Updates `actions/download-artifact` from 5 to 6
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](actions/download-artifact@v5...v6)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: actions/download-artifact
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
# Conflicts:
#	.gitignore
#	Makefile
#	Snakefile
#	config/test/config.myopic.yaml
#	config/test/config.overnight.yaml
#	config/test/config.perfect.yaml
#	doc/release_notes.rst
#	envs/linux-64.lock.yaml
#	envs/osx-64.lock.yaml
#	envs/osx-arm64.lock.yaml
#	envs/win-64.lock.yaml
#	rules/build_electricity.smk
#	rules/build_sector.smk
#	rules/solve_myopic.smk
#	rules/solve_perfect.smk
#	scripts/_helpers.py
#	scripts/add_electricity.py
#	scripts/make_summary_perfect.py
#	scripts/prepare_sector_network.py
Remove unnecessary wildcards (opts, clusters, sector_opts) from mock_snakemake.
Standardize planning_horizons to horizon parameter with numeric values.
Apply strict indexing for required params, keep .get() for optional config.
@FabianHofmann FabianHofmann marked this pull request as ready for review November 24, 2025 18:18
@FabianHofmann FabianHofmann requested a review from fneum November 24, 2025 18:18
…ctor

- Replace separate resolution_elec and resolution_sector settings with
  single clustering.temporal.resolution key
- Update compose_network.py and _helpers.py to use unified key
- Update all test configs and documentation
- Remove electricity.co2limit_enable, co2limit, co2base config options
- Remove Co2L opts wildcard parsing from _helpers.py
- Migrate test configs to use co2_budget with upper/lower bounds
- Update release notes with comprehensive PR #1838 breaking changes
- Update documentation (electricity.csv, opts.csv)
- Delete opts.csv and sector-opts.csv (wildcards now config-driven)
- Remove Co2L and cb* wildcard parsing from sector_opts in _helpers.py
- Update release notes to document cb* wildcard removal
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants