Streamline workflow - #1838
Conversation
- 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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
- 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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
|
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". |
|
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. |
I must admit that the section |
…y network creation
…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 <noreply@anthropic.com>
- 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 <noreply@anthropic.com>
- replaces fixed existing_capacities.baseyear (broke sector-perfect: 2030 EV vintage removed, infeasible) - align gas-bus locations when phase-outs leave nodes without plants - drop dead existing_capacities.enabled leftovers from docs and test configs - repair stale unit tests (moved imports, new attach_load signature)
- align docs with actual rule/file names (solve_networks, maps/static, onshore_regions) - fix broken update-dags task for snakemake 9 syntax - restore offwind-ac/dc/float carriers in electricity test config - fix pylint E0606 blocking CI in solve_operations_network.py
- Drop unused simplified_busmap input/read in cluster_network - Cluster electricity demand via busmap_cluster_network (correct resolution) - Write sanitized cluster busmap to disk - Collect base->clustered busmap.csv in cluster_networks target
- process_cost_data: drop unused planning_horizon - prepare_sector_network: remove duplicate define_spatial call (matches master)
adjust_renewable_capacity_limits now includes the sector-only 'solar rooftop' carrier and runs on the myopic first horizon too, so existing capacity is subtracted from extendable p_nom_max everywhere. Also: gate sector heat/solar-thermal time_aggregation inputs on sector.enabled; document overnight cost-year, co2_budget units, H2 pipeline retrofit losses, and perfect-on-PyPSA-1.0 in migration/notes; ignore benchmark/.
# Conflicts: # doc/configuration.md
Replace the dual elec/sector grammars with a single path: averaging, segmentation and representative are now mutually-exclusive integer keys under clustering.temporal, parsed by get_temporal_resolution. Drop the duplicate elec aggregators, dead apply_time_segmentation_perfect and the time_segmentation block.
- `pixi run sync-locks` - `pixi run generate-config`
|
@fneum I still need to do some small things (delete dead code, sanitizing little stuff). The workflow now reproduces the exact same results as master for all test cases (objective assertion is in place). Plots produce nicely and things run very smooth. so I have the feeling this is good to go soon. I'll ping you next week likely when I am fully happy |
Restore dict-guard for per-horizon emission prices, skip relative co2 budget when no bound resolves, fix carbon-budget plot header parsing. Delete unreachable perfect-foresight helpers, dead sector add_co2limit, and unused resource_dir threading.
|
@FabianHofmann I'd have some time to review this in the next few weeks. Should we give this a final push over the finish line? |
You are a hero. Let me merge the latest master then this week and go through it one more time. But the last time I looked I was quite happy. |
|
Nice! We can also do a co-working session if you want. |
I'll see if I can do some preparation tomorrow. but lets meet anyway next week with or without the merge on the todo list. |
# Conflicts: # config/schema.default.json # doc/release_notes.md # rules/build_sector.smk # rules/common.smk # scripts/build_energy_totals.py # scripts/lib/validation/config/clustering.py # scripts/prepare_network.py # scripts/prepare_perfect_foresight.py # scripts/prepare_sector_network.py
- Restore extendable StorageUnit/Store attachment in prepare_sector_network (lost in the refactor; utility-scale batteries were missing) - Guard gas boiler retrofit constraint and pandas-3 fixes in plot_summary and build_biomass_potentials - Allow scalar co2_budget bounds in the config schema - Re-benchmark test objectives against master CI
s_nom_max back to .inf and s_nom_max_extension to 20000; the test configs no longer need to override them.
| latest_year = 2021 | ||
| idees_rename = {"GR": "EL", "GB": "UK"} | ||
| year = max(min(latest_year, int(snakemake.wildcards.planning_horizons)), 1990) # noqa: F841 | ||
| year = max(min(latest_year, int(snakemake.wildcards.horizon)), 1990) # noqa: F841 |
| n = pypsa.Network(snakemake.input.network) | ||
| nyears = n.snapshot_weightings.generators.sum() / 8760.0 | ||
| planning_horizon = str(snakemake.wildcards.planning_horizons) | ||
| planning_horizon = str(snakemake.wildcards.horizon) |
|
We are wondering from the open-tyndp side, whether it makes sense to do a trial run soft-fork merge with us to see what we need to do and to test the instructions. Let's have a small chat about this @FabianHofmann @lisazeyen |
|
We should definitely do a release before merging this. |
Closes #1916. (supersedes)
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
base.nc->clustered.nc->composed_{horizon}.nc->solved_{horizon}.nccsvs/nodal_costs.csv,csvs/capacities.csv...)composerule is the entry point for green-field, brown-field, perfect foresight model for both sector-coupled and electricity-only.and now a more descriptive summary
Overview
base → simplified → clustered → composed → solved) and standardises filenames so scenario information now lives in configuration instead of wildcards.*_s_{clusters}or..._{planning_horizons}artefacts have been renamed; the new naming scheme makes horizons explicit via{horizon}and removes cluster suffixes.Workflow Changes
networks/base.nc→networks/simplified.nc→networks/clustered.nc→networks/composed_{horizon}.nc→RESULTS/networks/solved_{horizon}.nc. Old intermediate targets such asnetworks/base_s_{clusters}_{opts}_{sector_opts}_{planning_horizons}.ncno longer exist.rules/compose.smkencapsulates what used to be multipleprepare_*,add_*, and brownfield rules; it assembles everything needed for a givenhorizonand handles the myopic/perfect brownfield inputs.rules/solve.smkcontains onesolve_networkrule; electricity-only and sector-coupled cases are distinguished by config, not by separate rule files (solve_*smks were deleted).rules/collect.smknow checks four milestones: clustered networks, composed networks, solved networks, and plotting;{horizon}replaces{planning_horizons}wildcards in file names.navigate_config,get_full_config, andget_config(all inrules/common.smk) cache fully merged configs per wildcard set. Any new rules should obtain parameters throughconfig_provider(...)orget_config(w)to stay scenario compatible.RESULTS/networks/solved_{prev}.nc, while perfect foresight reuses the priornetworks/composed_{prev}.nc. Overnight runs must supply a single planning horizon.rules/postprocess.smknow readsRESULTS/networks/solved_{horizon}.ncfor all foresight modes, so every map (power_network_{horizon}.pdf,h2_network_{horizon}.pdf,balance_map_{carrier}_{horizon}.pdf, etc.) and CSV summary is generated with the same naming scheme regardless of foresight setting.Stage Notes
rules/build_electricity.smkwritesonshore_regions_base.geojson/offshore_regions_base.geojsontogether withnetworks/base.nc. Administrative shapes and OSM/TYNDP inputs remain unchanged.simplify_networknow emitsnetworks/simplified.nc,onshore_regions_simplified.geojson,offshore_regions_simplified.geojson, andbusmap_simplify_network.csv.build_electricity_demand_baseconsumes those files and produceselectricity_demand_simplified.nc.process_cost_datareads per-horizoncosts_{horizon}.csv.cluster_networktakesnetworks/simplified.ncplusbusmap_simplify_network.csvand emitsnetworks/clustered.nc,onshore_regions.geojson,offshore_regions.geojson,busmap_cluster_network.csv(chained to the finalbusmap.csvviachain_busmaps), andlinemap_cluster_network.csv. Cluster counts are configured, not embedded in filenames._s_{clusters}suffix; ie. population layouts collapse topop_layout.csvandpop_layout_simplified.csv; Gas locations becomegas_input_locations.geojson/gas_input_locations_simplified.csv; Powerplant list becomespowerplants.csvetc.compose_network(one rule) flattens all previousadd_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).solve_networkreadsnetworks/composed_{horizon}.nc, writesRESULTS/networks/solved_{horizon}.nc, and stores solver/memory/python logs underRESULTS/logs/solve_network/. Custom extra functionality and solver settings are driven purely by config.maps/base_network.pdfandmaps/clustered_network.pdf; solved outputs live underRESULTS/maps/static/(power_network_{horizon}.pdf,h2_network_{horizon}.pdf,ch4_network_{horizon}.pdf,balance_map_{carrier}_{horizon}.pdf) with interactive HTML equivalents underRESULTS/maps/interactive/.make_summaryandplot_summaryoperate onRESULTS/networks/solved_{horizon}.ncregardless of foresight.Config Changes
planning_horizonsdirectly under the root of your config (config/config.default.yaml:34-35). The oldscenario.planning_horizonsentry is ignored, and the workflow expectsconfig["planning_horizons"]to exist even when scenarios are disabled. Configuration bundles such asconfig/test/config.scenarios.yaml:17-20already follow this format.scenarioblock removed – The legacyscenario:section (withclusters,opts,sector_opts, etc.) is no longer part ofconfig.default.yaml. Scenario sweeps should now be described viarun.scenariosplus the dedicated scenario YAML file; individual dimensions (e.g. clusters) are configured directly under their respective sections. If you keep ascenarioblock in a local config it will simply be ignored.co2_budgetnow specifies anemissions_scope, arelativeflag (values interpreted relative to a 1990 baseline whentrue, absolute whenfalse), and nestedupper/lowerdictionaries of year-value pairs (config/config.default.yaml). Update custom configs accordingly if you previously listed plain year-value pairs.linesusess_nom_max/s_nom_max_extension, andlinksusesp_nom_max/p_nom_max_extension(config/config.default.yaml:320-355). Rename any overrides that still refer tomax_extensionso the new limits are applied.electricity.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.compose_networkimports historical assets at the first planning horizon formyopicandperfectforesight;overnightruns assume a greenfield build. The base year for existing capacities is the first entry ofplanning_horizons(as on master), so there is no separate toggle or base-year key underexisting_capacities.existing_capacities.phase_outsas a list of{carriers, countries, year}rules. They cap conventional asset lifetimes and apply to both generators (electricity-only) and links (sector-coupled) in perfect foresight. Defaults reproduce the previous behaviour; edit or empty the list to customise.Script Changes
scripts/compose_network.pycombines 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.compose_network.py.scripts/solve_network.pyis the only solver script called from Snakemake;solve_operations_network.py(operations-only runs) is no longer referenced by the workflow.scripts/co2_budget.pyprovides the helpers for the restructuredco2_budgetconfiguration (per-horizon bounds and budgets).build_clustered_solar_rooftop_potentials.pywas renamed tobuild_solar_rooftop_potentials.py.scripts/make_summary.py– the script now loads all horizons viapypsa.NetworkCollectionfor overnight, myopic, and perfect runs, so the dedicated helpersscripts/make_summary_perfect.pyandscripts/make_global_summary.pywere deleted. Any custom tooling should invokemake_summary.pyand read the unified CSV outputs.File Name Mapping
networks/base_s.ncnetworks/simplified.nconshore_regions_simplified.geojson,offshore_regions_simplified.geojson, andbusmap_simplify_network.csv.regions_onshore_base_s_{clusters}.geojson/regions_offshore_base_s_{clusters}.geojsononshore_regions.geojson/offshore_regions.geojsoncluster_network; no{clusters}wildcard in filename.busmap_base_s_{clusters}.csv/linemap_base_s_{clusters}.csvbusmap.csv/linemap_cluster_network.csvbusmap.csvchained viachain_busmaps; clusters now implicit in config.powerplants_s_{clusters}.csvpowerplants.csvnetworks/clustered.nc.electricity_demand_base_s.ncelectricity_demand_simplified.ncavailability_matrix_{clusters}_{technology}.ncavailability_matrix_{technology}.ncprofile_{clusters}_{technology}.ncprofile_{technology}.ncregions_by_class_*follow the same pattern.pop_layout_base_s_{clusters}.csv/pop_layout_base_s.csvpop_layout.csv/pop_layout_simplified.csvgas_input_locations_s_{clusters}.geojsongas_input_locations.geojsoncosts_{planning_horizons}.csv/_processed.csvcosts_{horizon}.csv/_processed.csvcollect.smkexpands over{horizon}.networks/base_s_{clusters}_{opts}_{sector_opts}_{planning_horizons}.nc(+_brownfield*)networks/composed_{horizon}.ncRESULTS/networks/base_s_{clusters}_{opts}_{sector_opts}_{planning_horizons}.ncRESULTS/networks/solved_{horizon}.ncmaps/power-network.pdf/maps/power-network-s-{clusters}.pdfmaps/base_network.pdf/maps/clustered_network.pdfRESULTS/maps/base_s_{clusters}_{opts}_{sector_opts}-costs-all_{planning_horizons}.pdfRESULTS/maps/static/power_network_{horizon}.pdfTODO
existing_capacities.phase_outs; generalized to generators + links, applied in perfect foresight)add_brownfield; perfect via generalized twin-repoint inconcatenate_network_with_previous)set_investment_periodsfrom code (related to a previous bug in pypsa)cluster_electricity_demandrule →electricity_demand.nc;attach_loadno longer maps simplified→clustered)rules/solve.smkas opt-insolve_operations_network(solved_{horizon}.nc→operations_{horizon}.nc); rolling-horizon dispatch decoupled via newsolving.operationsblock, enabling capacity expansion followed by rolling dispatch.rule overviewin foresight.rst tomodules overview(look for similar places)Checklist
envs/environment.yaml.config/config.default.yaml.doc/release_notes.rstis added.