Skip to content

docs(pypsa): a committable unit built in a later period starts from off, and the big M follows pypsa for an infinite build cap - #817

Merged
FabianHofmann merged 18 commits into
mainfrom
docs/pypsa-commitment-edges
Oct 2, 2026
Merged

FabianHofmann merged 18 commits into
mainfrom
docs/pypsa-commitment-edges

Conversation

@FabianHofmann

@FabianHofmann FabianHofmann commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Prompt: Continue PyPSA parity for energy-models/mathspec: implement step 4 of the plan in tracking issue #783. Step 4 covers the parity items the language can already state. Run it as an ultracode workflow with at most 4 agents, one PR per group.

Note

The following content was generated by AI.

A committable unit built in a later period now starts from off, so it pays its start-up cost. The big M states PyPSA's rule for an infinite build cap. A start-up or shut-down ramp alone now also triggers the came_in_running refusal.

Part of #783 (step 4, group 2). Stacks on #787, which stacks on #786. Base: docs/pypsa-fixed-modular.

Method, gate output, PyPSA cites, alternatives

PyPSA cites are on master 02bdcbba.

Late-opening committable unit. PyPSA builds status - status.shift(1).fillna(0) (optimize/constraints.py:297). The status is masked by active (variables.py:71-95), so at the first snapshot a unit stands in, the previous status is zero. The file's shift(Generator_status) was absent there. So the com-transition-start-up row was not built, and the unit started for free. Generator_previous_status (and the Link and Process ones) now has a case opening_late: zero where position(snapshot) > 0 AND NOT shift(Generator_active, …). The old cases move into a new expression, Generator_status_carried_over. One block with both cases is refused at load, because the loader cannot tell position(snapshot) == 0 and a neighbour predicate apart. A block with one case may read a neighbour. The alternative was a data-prep boolean Generator_opens_late with two assumptions per component, as StorageUnit_opens_late has. That costs three parameters and six assumptions, and adds no guarantee.

Rung 64 (rung_64_commitment_edges.py) is new. It has two periods, a committable generator, link and process built in 2030 with start-up costs 100, 70 and 40, and a committable extendable unit with p_nom_max = inf. PyPSA solves it to 9245.0. I put the rung in before the change. Then I took PyPSA's model, set the start-up rows of the three late units at the first 2030 snapshot to >= -1, and solved again: 9035.0. That model is what the file stated before, and it is 210 lower, the sum of the three start-up costs. The repository runs no solver, so no test here can show this failure.

Big M with an infinite p_nom_max (components/components.py:1050-1121). M is p_nom_max × max_t p_max_pu where the cap is finite and positive. Otherwise it is committable_big_m × max_t p_max_pu, and without that keyword 10 × max(peak total load, largest finite p_nom, largest finite p_nom_max) of the component, or 1e6. Generator_big_m, Link_big_m and Process_big_m now state this rule, still as data prep. In rung 64 the inferred M is 3000 and the unit builds 70. With committable_big_m=50, PyPSA solves to 11745.0 and builds 50, so M can cap output. The page says this.

First-snapshot ramp from a start-up or shut-down ramp alone (constraints.py:1052-1053, mask at :1142, :1160). The file already built these rows (rung 28). The three {c}_came_in_running_unless_committable assumptions, and the Generator one in pypsa_linearized_uc.yaml, now also apply to ramp_limit_start_up and ramp_limit_shut_down. Without up_time_before, PyPSA builds the same capped first-snapshot row for these units (:1097-1100, :1116-1118, PyPSA/PyPSA#1943). The Refusals row and the rung 28 prose say so. The Refusals row also names the bound PyPSA gives with only a shut-down ramp, p >= (ramp_limit_shut_down - 1) * p_nom (:1146-1148, :1153-1158). This is the seventh #783 refusal; #816 leaves it to this PR, because the widened assumption covers it. No rung covers this: #783 says "no rung until PyPSA chooses".

Gates, in this order on the final tree:

  • pixi run python -m tools.pypsa_split split examples/pypsa and pixi run python -m tools.gallery: regenerated the fragments and the pages.
  • uv run --script examples/references/pypsa/reference.py: recorded rung_64_commitment_edges (objective 9245.0). No other record changed.
  • uv run --script examples/references/pypsa/reference.py --check: every rung solves to its record.
  • pixi run lint: pass.
  • pixi run ci: pass (2626 tests, docs-build, compile-tex 55 documents).

Not done. The PyPSA line cites outside the blocks above are not re-anchored; #806 does that. The cites I touched are on 02bdcbba. I did not add a guard test for the new case: the suite runs no solver, and a test that reads the case structure would test the implementation. Nothing needed language work.

@FBumann FBumann added the docs Documentation pages, guides, reference and README label Oct 1, 2026
@read-the-docs-community

read-the-docs-community Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

…-edges

# Conflicts:
#	CHANGELOG.md
#	docs/examples/pypsa.md
#	docs/examples/pypsa/generator_ramping.md
#	docs/examples/pypsa/link.md
#	docs/examples/pypsa/link_ramping.md
#	docs/examples/pypsa/process_ramping.md
#	docs/examples/pypsa_linearized_uc.md
#	examples/pypsa.yaml
#	examples/pypsa/generator_ramping.yaml
#	examples/pypsa/link_ramping.yaml
#	examples/pypsa/process_ramping.yaml
#	examples/pypsa_linearized_uc.yaml
#	examples/references/pypsa/references.json
#	tests/test_pypsa_references.py
@FabianHofmann
FabianHofmann merged commit 1926341 into main Oct 2, 2026
6 checks passed
FabianHofmann added a commit that referenced this pull request Oct 2, 2026
…ep steps, and a capital cost says where fom_cost and overnight_cost enter (#816)

* docs(pypsa): the pypsa references are recorded against pypsa master

* docs(pypsa): add the changelog line

* docs(pypsa): a fixed modular committable unit gets only its per-module commitment rows, as in pypsa master

* docs(pypsa): add the changelog line

* test(pypsa): read the masks with the exclusivity evaluator, not eval

* docs(pypsa): the pypsa page lists seven more refusals and eight data-prep steps, and a capital cost says where fom_cost and overnight_cost enter

Data prep section: n 20, median 19, over25 4.

* docs(pypsa): add the changelog line

* docs(pypsa): the shut-down-ramp refusal moves to #817, which assumes the case
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs Documentation pages, guides, reference and README

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants