External-region representations: generator-based imports and boundary-metered CPUC contracted resources - #810
Conversation
Ten rules in build_electricity.smk and add_sectors in build_sector.smk had no benchmark: directive, so snakemake recorded no wall time or max RSS for them. That included cluster_resources (the largest walltime budget in the config) and add_extra_components (the terminal rule for network-only builds), which are exactly the rules needed to tune Slurm resource requests. Benchmark paths mirror each rule's log path under BENCHMARKS, and carry every wildcard the rule takes so concurrent jobs of the same rule cannot collide. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
run_slurm.sh took a single hard-coded configfile and passed -A {cluster.account},
which Sherlock rejects (it has no accounts). It now takes a list of overlay
configs, runs each as its own `snakemake --cluster` invocation with bounded
concurrency, and reads each build target from config/weather_years/manifest.tsv.
slurm_submit.sh wraps sbatch because several rules compute mem_mb as a float
(e.g. (input.size // 150000) * attempt * 1.5) and `sbatch --mem 4500.0` is
rejected; it rounds up and floors at 2000 MB.
Note: the target must precede --configfile, which is nargs='+' and would
otherwise swallow it as a third config file.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The uv-managed CPython ships no CA bundle -- ssl.get_default_verify_paths() returns cafile=None -- so retrieve_cpuc_servm_load died with CERTIFICATE_VERIFY_FAILED. A curl reachability test does not catch this, because curl reads the system trust store that Python never consults. run_slurm.sh now derives SSL_CERT_FILE/REQUESTS_CA_BUNDLE from certifi and relies on sbatch --export=ALL to propagate them to every rule job. Also adds probe_2019.sbatch, the driver job for the resource-tuning probe. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
sacct splits what we need across two rows: the allocation row has JobName and ReqMem, the .batch row has MaxRSS. `sacct -X` shows MaxRSS blank, which is a quiet trap. This joins them on the base job id and aggregates per rule. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…dations Complements collect_benchmarks.sh: reads snakemake benchmark TSVs, resolves rule names against rules/*.smk, and prints recommended mem_mb (1.5x peak RSS) and walltime (3x peak runtime) per rule. Offered upstream in issue #808. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adds `electricity.imports.representation` (store | generator) and moves all
import/export construction into a new self-contained `external_regions` module.
* `store` (default) is the existing behaviour, byte-for-byte: a bottomless
Store per external zone and PRICED interface links.
* `generator` puts a priced, non-extendable import generator (carrier
`unspecified_imports`, carrying `imports.co2_emissions`) on the external bus,
leaves the interface link unpriced, and moves the CPUC contracted
out-of-state units BEHIND the boundary. Their deliveries then traverse a
carrier-`imports` link, so `opts/interchange.py` meters them against the
import volume cap and the interface capacity bounds them — which is the point.
`add_electricity` now splits the contracted-unit derivation from its
attachment: in `generator` mode it serializes the fully-derived bundle (unit
table, borrowed VRE profiles, cost table) to a new `remote_units_s{simpl}.pkl`
output that `add_extra_components` attaches at the external buses. Each unit is
placed behind the boundary zone whose state matches its plants, preferring the
zone with the largest inbound interface capacity, falling back (with a warning)
to the largest boundary zone overall.
Also deletes the dead `trim_network` path: the schema typed
`model_topology.trim` as a boolean while the code indexed `trim_topology["zone"]`,
so it could never run.
The California config switches to `generator` and drops the now-obsolete
`p_nom_extendable: any` clustering workaround — remote units no longer exist
inside the footprint at clustering time.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Extends test_interfaces.py with a toy one-zone footprint and a NARIS-shaped flowgate frame (two NV zones, one AZ zone, one outbound-only interface): * generator mode creates the external buses, a non-extendable import generator sized at the zone's inbound interface capacity, and UNPRICED interface links; store mode keeps the priced links and the bottomless Store; * the CO2 factor moves onto `unspecified_imports` in generator mode and stays on `imports` in store mode; * exports are byte-identical across representations, and nothing but export links injects into an export bus; * a toy contracted-unit bundle attaches at the state-matched external bus (AZ -> the AZ boundary zone), falls back with a warning when the unit's state has no direct interface (UT -> the largest inbound zone), and carries its borrowed VRE profile across. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…cement At county resolution the boundary zones are 'p'+FIPS ids, absent from the ReEDS BA membership table, so every contracted unit fell through to the largest-inbound fallback (all 49 CPUC units behind Clark County). Derive the state from the FIPS prefix instead; BA zones still use membership. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…chema; CA imports at 60 USD/MWh The costs code path has always accepted a float or a carrier name, but the schema enum only admitted the literal strings 'wholesale'/'carrier'/'float', so neither actually validated. California switches off the mislabeled 'wholesale' (retail-priced, #807) EIA series to a flat 60 USD/MWh. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
EIA coal/shipments/receipts has no data before 2008 (2007 returns only spillover from the inclusive end bound). Snapshot years <2008 crashed build_fuel_prices with AttributeError on the empty payload; 2007 would have silently produced all-NaN coal prices. Fetch 2008 prices instead and relabel the month-start index onto the snapshot year. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Same failure class as the coal clamp: EIA electric-power gas prices begin 2002-01, so weather years 2000-2001 got an empty (or spillover-only) payload -- wy2000 crashed in format_data and wy2001 would have KeyError'd at the snapshot filter. Shift years rather than replace to keep the inclusive-end spillover January collision-free. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ricing Third EIA data floor: retail-sales electricity prices begin 2001, so the exports 'wholesale' path in add_extra_components crashed for weather year 2000 on an empty payload. No index relabel needed -- _build_cost_timeseries already maps the data onto investment periods. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
@copilot resolve the merge conflicts in this pull request |
# Conflicts: # workflow/repo_data/config/config.california.yaml # workflow/report_benchmarks.py Co-authored-by: ktehranchi <83722342+ktehranchi@users.noreply.github.com>
Resolved by merging |
|
I haven't looked at the implementation, but I am assuming its good! My question is about supporting two different implementations for the same functionality (I believe its the same, but please correct me if it isn't!). Was there a reason the store/link combo didn't work well? The reason I implemented it like that (store/link rather than generator) was for emission accounting purposes (ie. easier to track emissions from different sectors). Im not against switching to just generators, but do think it would be good to standardize across the code base! |
|
@trevorb1 So the behavior of the two implementations is actually a little different. So for my model I needed to have a representation of external buses which have both generic imports generation AND have externally contracted resources (solar, wind, BESS). I also wanted to constrain the amount of total imports different out of state buses in-addition to the total imports from out of state buses. We could unify the two different implementations by placing a generic link+store for unspecified imports behind other regional buses which also have the contracted resources..... |
Stacked on #809 (targets its branch to avoid a duplicated diff; retarget to
developonce #809 merges). Related: #807.Motivation
Regionally scoped runs (California) had three overlapping representations of out-of-footprint supply:
trim_network— a pricedimport_{bus}generator at real boundary buses. Dead code: the schema typesmodel_topology.trimas boolean while the code indexestrim_topology['zone'], so it cannot run.add_elec_imports_exports— bottomless Store behind each external flowgate zone, with the import price on the interface links.add_electricity— invisible to the import volume cap and interface limits, and the cause of aconsensecrash incluster_network(non-extendable remote units sharing (bus, carrier) groups with extendable in-state units, withp_nom_max=infpolluting land-cap sums).What this does
New module
workflow/scripts/external_regions.py— the single home for external-region construction, selected by a new config keyelectricity.imports.representation:store(default): today's behavior, pure code motion — byte-identical construction, protecting existing western/usa configs.generator: the external zone is modeled as a place with generation rather than an infinite energy tank. A generic import Generator (carrierunspecified_imports, carrying the import emission factor) sized at the zone's inbound interface capacity and priced with the same machinery; interface links become unpriced transfer capacity; and the CPUC contracted units are attached behind the boundary at the external bus of the flowgate zone they physically sit in (state-matched viamembership.csv, largest-interface fallback with a warning). Their deliveries traverse carrier-importslinks, soopts/interchange.py(untouched) automatically meters them againstvolume_limit, bounded by interface capacity.Design notes (full reasoning in the module docstring):
{zone}_imports/{zone}_exportsbuses are load-bearing in generator mode: a shared bus would create a pure arbitrage loop (import generator selling into the export sink).add_electricitynow splits remote-contract handling into data derivation vs. network attachment; in generator mode it serializes the unit bundle (remote_units_s{simpl}.pkl) instead of attaching, which also fixes thecluster_networkconsense crash at the root — thep_nom_extendable: anyworkaround is removed from the CA config.trim_networkandmodel_topology.trimare deleted.Files
workflow/scripts/external_regions.py(new): both representations, pricing machinery (moved), zone mapping, publicadd_external_regions()dispatcher.workflow/scripts/add_extra_components.py: −442 lines (moved/deleted), calls the module.workflow/scripts/add_electricity.py: remote-contract split + bundle output, gated on the new param.workflow/rules/build_electricity.smk,workflow/schemas/config.schema.yaml,config.default.yaml,config.california.yaml,docs/source/configtables/electricity.csv.workflow/scripts/test/test_interfaces.py: +12 tests covering both representations, pricing placement, and the zone-mapping rules.Validation
glpk not installedsolver tests, none related.tests/static+tests/docs: 131 passed; DAG dry-run tests (including both California variants): 7 passed.Known behavioral change
With the CA config on
generator, contracted out-of-state deliveries now consume the 25% import volume headroom and are bounded by interface capacity — the intent of the design, but a real tightening relative to previous CA runs.🤖 Generated with Claude Code