Translate the new-format custom constraints, with relaxation and expansion-limit constraints - #139
Open
nick-gorman wants to merge 18 commits into
Open
Translate the new-format custom constraints, with relaxation and expansion-limit constraints#139nick-gorman wants to merge 18 commits into
nick-gorman wants to merge 18 commits into
Conversation
Each RHS value becomes one constraint instance per investment period (and per timeslice where tagged), with date_from resolved as the value active at the start of each period. LHS terms keep generator and battery names even though those components aren't translated yet — pypsa_build skips-and-logs missing components, and the logging silences itself once generator translation lands. The generators/batteries parameters are accepted now as the future name-mapping hook. Constraint-relaxation expansion options become dummy generators on bus_for_custom_constraint_gens, with their LHS terms filtered per period by build_year so a generator built for 2040 can't relax a 2030 constraint instance — an improvement over the old single-constraint formulation, which couldn't express this. Extendable links and relaxation generators get "_expansion_limit" constraints capping total p_nom at the option's allowed expansion; the suffix keeps their names clear of the parent constraints'. Duplicate input rows and RHS rows without LHS pairs raise rather than collapse silently; the "load" term type maps to Load/p, which pypsa_build logs as unimplemented and skips, matching the old path. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
#126 changed the expansion tables to allow blank key cells as wildcards, changed _prepare_expansion_costs to take the enabled element ids, and made expansion links carry forward/reverse per unit of max(forward, reverse). The cherry-picked module still read unresolved options, capped paths at their forward capacity and imported a helper #126 deleted. The expansion-limit cap is now max(forward, reverse), re-resolved for the paths that have expansion links with the network module's own helpers, and constraint relaxations resolve their options and costs against the constraints in the model so blank-id defaults apply to every constraint. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests.
... and 2 files with indirect coverage changes 🚀 New features to boost your workflow:
|
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…raint docstrings _keep_rows_for_enabled_elements is shared by the network and constraints translators, but "enabled elements" only described the network caller — the constraints side passes constraint_ids. Renamed to _keep_rows_for_expansion_ids and reworded the docstrings so each caller's selection is described where the decision is made. The constraint helpers' I/O examples now show full tables, including the blank-cell wildcard forms the options and costs accept. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A relaxation generator's p_nom was always subtracted from the parent constraint's LHS, which loosens a "<=" but tightens a ">=". The sign now follows the constraint's direction (-1.0 for "<=", +1.0 for ">=") so building relaxation capacity always loosens the constraint; an option on an "=" is rejected, since no single-signed term can loosen an equality. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QXjsJKX7ANSJxVyJbszLYW
… periods The translator raised on duplicate rows, missing directions, unpaired constraints and relaxed equalities -- all properties of the input tables, so they're now declared as schema rules the translator trusts without re-checking. What's genuinely per-run remains: after date_from resolution a (constraint, period) can end up with terms on one side only, so those periods are dropped from both sides with an INFO line each way. Relaxation-generator terms derive from the post-drop RHS so they can't re-create one-sided periods. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QXjsJKX7ANSJxVyJbszLYW
…nerators Every option has a cost in every investment period (the costs schema's coverage rule), so joining options to costs gives exactly one generator per option and period. The test fixture violated that rule -- SWQLD1 had a 2026 cost only -- which hid the second per-period generator from every relaxation expectation; it now carries schema-valid costs and the tests exercise both SWQLD1_exp_2026 and SWQLD1_exp_2028. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QXjsJKX7ANSJxVyJbszLYW
Terms naming components not in the model were described as skipped with a log line downstream -- a silent per-term drop. The custom_constraints_lhs schema now ties each term_type's variable_name to the table it must exist in, so the translator and pypsa_build take the names as given. The templater TODO points at the rule as the end state for its IASR ID lookup. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QXjsJKX7ANSJxVyJbszLYW
A blank timeslice isn't "binds at every snapshot": it's the constraint's fallback row, binding only at the snapshots none of its named-timeslice rows cover, matching the path-limits convention. Resolving timeslices to snapshots stays pypsa_build's job; this module passes them through. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QXjsJKX7ANSJxVyJbszLYW
Set-membership probes like "SWQLD1_exp_2026 not in variable_name" pass for many wrong outputs. The relaxation-disabled, late-date_from and missing-link tests now compare whole expected frames, and the negative log case covers every INFO drop line the module can emit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QXjsJKX7ANSJxVyJbszLYW
allowed_expansion rode through the relaxation-generator pipeline without any step using it, only to be read off at the end. The orchestrator now passes the resolved options' caps directly to _create_expansion_limit_constraints, the rez_transmission_expansion gate moves into _resolve_relaxation_options where the decision is made, and _finalise_generators disappears. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QXjsJKX7ANSJxVyJbszLYW
The module docstring and the orchestrator's docstring had become two accounts of the same pipeline, with fixes having to land in both. The orchestrator docstring now carries the lot -- the translation steps, the relaxation and expansion-limit behaviour, the schema rules the pipeline trusts without re-checking, and the blank-timeslice fallback semantics -- next to the I/O example, and the module docstring is deleted. Accuracy fixes folded in along the way: the one-sided period drop names both of its causes (date_from coverage and model scope), the flow-path cap is stated as max(forward, reverse), link_flow expansion is described as replacing the path_id term with per-link terms, and the rez_transmission_expansion gate, per-option-per-period generators and annuitised costs are now stated. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QXjsJKX7ANSJxVyJbszLYW
The side-by-side table pairs and the compressed links one-liner in the constraints I/O examples saved vertical space at the cost of scanning -- every input table now sits sequentially in the same tabular format as the rest of the examples. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QXjsJKX7ANSJxVyJbszLYW
…trator Dropping a term whose component isn't in the model left the constraint applied with a weakened LHS -- plausible-looking and silently wrong. The translator now raises when any LHS term fails to resolve to a model component (load terms, which pypsa_build doesn't implement, raise too), and resolves every term through an isp_name -> name mapping supplied by new generators and storage inputs, so new entrant units expand to their per-build-year components the same way link terms expand over a path's links. The generator/storage frame contract (isp_name + name, mirroring links) is assumed ahead of the translators that will produce it, for review by the team building that side. The orchestrator is renamed _translate_custom_constraints (the constraints aren't network-specific any more) and split into three block producers -- translate the user-authored tables, create the constraint relaxations, create the expansion limits -- assembled and finalised once, so future producers of constraint blocks (e.g. new entrant build limits) have a seam to append into before relaxation resolves. The reusable pieces' docstrings now state their source-agnostic contracts rather than relaxation-specific vocabulary. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QXjsJKX7ANSJxVyJbszLYW
The blanket raise on load terms made the real constraint set untranslatable -- the baseline CNSW-SNW South GPG constraint carries date_from-varying load terms. Demand is exogenous (p_set on the Load components pypsa_build attaches per demand node), so a load term is a data term, not a variable term: it now resolves through the standard isp_name -> name mapping to the "load_<bus>" component with attribute p_set, ready for pypsa_build to fold coefficient x demand into the constraint. pypsa_build owes that implementation and should raise, not skip, until it lands. Validation is against a new demand_nodes input rather than all buses, because REZ buses carry no demand and which buses do follows the regional granularity -- a load term for a sub-region the granularity aggregates away raises through the same not-in-model check as link terms. Also labels the missing-component error's tuple structure as (constraint_id, variable_name). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QXjsJKX7ANSJxVyJbszLYW
…l_name The mapping's inherited isp_name/name columns said where the data came from, not what the mapping means, so the constraints module now names both sides for their role. The custom_constraints_lhs schema also sets out the convention those names imply: a variable_name refers to a whole element - a new entrant across all its build years, a path across its existing and expansion links - never a per-build-year component. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0182yKiTKCwGoVPs5hToavKn
…trings Condensed column=value examples made the reader reconstruct the table shape in their head, so the I/O Example convention now requires full tables (with abridged column/row sets for otherwise large ones) and the constraints module's remaining condensed examples follow it. The expansion example also gains a new entrant term, showing the per-build-year fan-out next to the link and existing-unit cases. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0182yKiTKCwGoVPs5hToavKn
…nches Coverage showed four untested paths: the all-empty assembly branch, calendar year period starts, the unmapped-term_type drift guard, and the duplicate RHS name check. The last needed the one realistic collision - a relaxable constraint sharing its ID with an expandable path, giving both the same expansion-limit cap row - which the new test pins down. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0182yKiTKCwGoVPs5hToavKn
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Translate the new-format custom constraints, with relaxation and expansion-limit constraints