Translate the new-format network tables to buses and links#126
Open
nick-gorman wants to merge 6 commits into
Open
Translate the new-format network tables to buses and links#126nick-gorman wants to merge 6 commits into
nick-gorman wants to merge 6 commits into
Conversation
network_geography becomes buses (REZ buses dropped when rezs is attached_to_parent_node), and the path/limit/expansion tables become links. The winter_reference limit is the link's static p_nom — winter is the calendar's default season — with the other timeslices' forward and reverse limits emitted as per-unit values in a link_timeslice_limits table for pypsa_build to expand into p_max_pu/p_min_pu series (per-unit values can exceed 1.0 when a season's limit tops winter's). Asymmetric reverse limits land in p_min_pu rather than separate links. Expansion options become one extendable link per (path, investment period) with annuitised capital costs, gated by transmission_expansion / rez_transmission_expansion according to whether the path connects a REZ. Paths with no capacity data take rez_to_sub_region_transmission_default_limit, replicating the existing REZ behaviour. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests.
🚀 New features to boost your workflow:
|
Pilots the blank-key-as-wildcard convention on the three sparse network tables (limits, expansion options, expansion costs) as a reference implementation for wider adoption: _resolve_wildcards in translator/helpers.py does the resolution until schema validation lands, with runtime guards in network.py standing in for the option-pairing rule. Drops that are designed selection (constraint_relaxation routing, non-investment-period cost years) are no longer logged as data loss. Links now take p_nom as the larger of their two directional capacities so every per-unit limit sits in [-1, 1], and all real limits (including blank-timeslice fallbacks) move into link_timeslice_limits under the coverage contract (#123); the static link attributes are inert defaults. rez_to_sub_region_transmission_default_limit becomes transmission_default_limit -- the default now applies to any path without limit data, not just REZ connections. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The options/costs pair now has all its empty/populated combinations pinned -- including options-without-costs, which the schema declares invalid but which silently builds no expansion links until schema validation lands -- plus the no-expansion-study config (both flags off), which resolves wildcards against an empty allowed set. The two cost-wildcard behaviours documented in _prepare_expansion_costs but previously untested are exercised: a concrete-year row overriding a blank-year static row (also the one spot where wildcard-expanded int years concat with CSV-parsed float years before the int cast), and a blank expansion_id acting as a table-wide default cost. The limits drop log gets its firing case (the negative already existed), and the calendar-year NotImplementedError closes the last uncovered line in network.py. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…rk-to-buses-links
The name was a leftover from the winter-reference design, where the frame really did hold the link-level static limit; it now holds the per-direction maximum capacities that _extract_max_capacities produces, so the old name misdescribed it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ndings _resolve_wildcards previously dropped out-of-set values itself, logging or staying silent per an expected_drops escape hatch, which conflated designed config-driven selection with bad input data. Callers now filter their designed selections out first (logged at INFO) and the resolver raises on anything left outside the allowed set — for limits a typo'd path_id halts the run, while an options/costs typo is indistinguishable from a constraint group or disabled element, so the log line is its only trace. Also acts on the PR #126 review: the forward/reverse pairing guard counts a blank expansion_option as its own label (nunique dropna=False) so a blank/named mismatch raises; the expansion_option schema description no longer claims the column is informational-only; p_nom collapses to a single groupby max; annuitisation is vectorised; and name-list test assertions are replaced with full-frame comparisons. Expansion cost years are opaque labels matched against the config's investment periods — year_type only governs how time is chunked — so the calendar-year NotImplementedError guard is gone. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.
This PR does two things: it adds the translator that turns the new-format network tables into PyPSA buses and links, and it uses those tables to pilot a "wildcard" convention for sparse inputs — implemented end-to-end so there is a concrete example to react to before deciding whether to adopt it more broadly.
The translation
network.pyconsumesnetwork_geography,network_transmission_paths,network_transmission_path_limits,network_expansion_optionsandnetwork_transmission_path_expansion_costs, and produces:Flow paths and REZ connections run through one pipeline — both are just paths.
A link's
p_nomis the larger of its forward and reverse capacities; every directional, per-demand-condition limit is expressed per unit of that in alink_timeslice_limitstable (forward limits asp_max_pu, reverse asp_min_pu), which pypsa_build later expands into per-snapshot series:Design choices worth knowing before reading the diff:
p_nomas the max of both directions keeps every per-unit limit in [-1, 1], so no timeslice can credit a link above its physical rating.p_min_pu/p_max_puon each link are inert defaults (0.0/1.0); the real limits live entirely inlink_timeslice_limitsunder a coverage contract (Translator timeslice implementation notes (decisions from staging PR #121) #123) — every snapshot is covered by a named-timeslice row or a blank-timeslice fallback.p_nom0 and are individually unbounded; the expansion-limit custom constraints (ispypsa.translator.constraints) cap the total built across a path's links. Asymmetric options setp_max_pu/p_min_puin the option's forward/reverse proportion.The wildcard proposal (feedback wanted)
The three sparse tables (limits, expansion options, expansion costs) accept blank key cells as wildcards, resolved most-specific-wins, with blank value cells taking a schema-declared
nan_fill:_resolve_wildcardsintranslator/helpers.pyis the generic resolver; the three schema files document the convention (wildcard keys,*_resolve_unambiguouslyand coverage rules,nan_fill). The validation rules are declarative-only for now — two runtime guards innetwork.pystand in for the option-pairing rule until schema validation lands.This is deliberately a reference implementation. The same convention could apply to most sparse ISPyPSA tables (build costs, fuel prices, generator properties); the question for review is whether it earns its keep as a uniform way to express defaults and sparse overrides, or whether it's over-engineering that will be hard to apply consistently. Either way, concrete code seemed a better basis for that call than an abstract proposal.
Scope note
The translation code is dark — nothing calls it until the orchestrator PR, so there's no change to production behaviour. This PR is stacked on #129 (the
transmission_default_limitrename) and targets that branch as its base; once #129 merges it retargets to main.🤖 Generated with Claude Code