Skip to content

Add regional interconnection costs - #138

Draft
Max-Vanatta wants to merge 18 commits into
mainfrom
int_supply_curve_regional
Draft

Add regional interconnection costs#138
Max-Vanatta wants to merge 18 commits into
mainfrom
int_supply_curve_regional

Conversation

@Max-Vanatta

@Max-Vanatta Max-Vanatta commented Jul 8, 2026

Copy link
Copy Markdown

Summary

Replaces the flat intra-zone point-of-interconnection (POI) / network-reinforcement adder (GSw_TransIntraCost) with an optional increasing-cost supply curve. Successive amounts of new capacity built in a zone pay a higher marginal reinforcement cost, applied to all technologies through the existing INV_POI / eq_POI_cap mechanism.

While this is referred to as the POI in the code currently, this is purely the scaffolding that was used to implement the inclusion. POI in reV is separate from the reinforcement costs (POI is not the network costs). In this implementation, the POI from reV is still included but the reinforcement assumptions previously in reV are stripped out and replaced by the binned costs.

The bin structure reuses the VRE resource-supply-curve pattern (rtscbin as the indexer rather than rscbin): each bin has an incremental capacity width and a per-bin cost. The flat cost is the degenerate one-bin case, so numpoibins = 1 (the default) reproduces current behavior exactly — the binned method is fully opt-in.

Method documentation: inputs/transmission/poi_supply_curve.md.

Technical details

Implementation notes

  • Input processing (reeds/input_processing/): make_poi_supply_curve.py (new) builds the zonal cost bins from raw_interconnection_TSC_data.csv via optimal capacity-weighted least-squares segmentation. transmission.py::write_poi_supply_curve writes poi_supply_curve.csv (flat GSw_TransIntraCost when numpoibins=1, otherwise the binned curve with an unlimited GSw_POIUpperCost backstop bin). raw_interconnection_TSC_data.csv is the single source for every spatial resolution (like the hashed transmission cost files) — no per-zone-set fallback; a run whose regions it does not cover fails loudly (matching the transmission cost/distance validation).
  • GAMS: INV_POI(r,t)INV_POI(r,rtscbin,t); new set rtscbin, params cost_poi_bin/cap_poi_bin/poi_bin_feas, and eq_POI_binlim (incremental bin-width cap) in b_inputs.gms/c_model.gms. Objective (d_objective.gms), var-fixing (5_varfix.gms), and reporting (report.gms, report_params.csv) updated to carry the rtscbin index. The single zonal curve applies to every technology (the free existing capacity poi_cap_init and the non-generator POI terms — spur, converter, LCC — attach here too).
  • Double-counting avoided: when the curve is active (numpoibins != 1 and GSw_TransIntraCost > 0), reeds/io.py drops the reinforcement cost already embedded in the VRE resource supply curves (all regions, since coverage is enforced), so it is not counted twice. Retained unchanged when numpoibins = 1.

Additional changes

  • reeds/reedsplots.py + postprocessing/single_case_plots.py: plotting helper for the POI supply curve.
  • inputs/transmission/README.md: provenance for the raw input file; dollaryear.csv entry added (raw_interconnection_TSC_data.csv, USD2024). The per-zone-set poi_supply_curve_{GSw_ZoneSet}.csv placeholder files and their runfiles.csv copy entry were removed — the curve is built at run time from the single raw source.

Switches added/removed/changed

Switch Change Description
numpoibins added Number of POI cost bins. 1 (default) = legacy flat GSw_TransIntraCost; >1 = binned curve; 0 = defualt (one bin per raw segment).
GSw_POIUpperCost added [USD2004/kW] cost of the unlimited backstop bin above the finite binned capacities; only active when numpoibins != 1.
GSw_TransIntraCost unchanged Now also serves as the numpoibins = 1 flat-cost value.

Known incompatibilities

  • numpoibins != 1 (binned or defualt curve) requires GSw_TransIntraCost > 0; the curve is gated on that switch, so the combination numpoibins != 1 with GSw_TransIntraCost = 0 is rejected with a ValueError in runreeds.py rather than silently charging no reinforcement.
  • numpoibins = 1 (the default) reproduces the legacy flat-cost behavior exactly.

Relevant sources or documentation

  • Interconnection cost data from the TSC model with still to be PR-ed interconnection optimization. TODO: Add in the exact links and docs (noted in inputs/transmission/README.md).

Validation, testing, and comparison report(s)

  • Pending. This is a model + data change, so full-US reference and full-US decarb comparison reports are required and will be added before merge.
  • Test-case configs are included for exercising the feature at ERCOT resolution: cases_transmission_test.csv (ERCOT_default = flat legacy, ERCOT_0 = near-zero flat, ERCOT_regional = binned curve).

Checklist for author

Details to double-check

  • Charge code provided to reviewers
  • Included comparison reports for appropriate test cases (pending)
  • Documentation updated (inputs/transmission/README.md, inputs/transmission/poi_supply_curve.md, docs/source/model_documentation.md)
  • If input data added/modified:
    • Dollar year recorded (dollaryear.csv, USD2024) and converted to 2004$ for GAMS
    • Units are specified
    • Preprocessing steps documented/committed to ReEDS_Input_Processing (TSC-side; to confirm)
    • New large data files handled with .h5 instead of .csv (only raw_interconnection_TSC_data.csv is added, which is small; the per-zone-set curve files were removed in favor of the single raw source)
  • Code formatting standardized
  • Reusable functions used where possible instead of copy/pasted code

General information to guide review

  • Zero impact on results of default case (numpoibins=1 default)
  • No large data file(s) added/modified (only the small raw_interconnection_TSC_data.csv)
  • No substantive impact on runtime for full-US reference case
  • No change to package requirements
  • No change to process flow (runreeds.py gains one compatibility check: numpoibins != 1 requires GSw_TransIntraCost > 0)

Did you use LLM tools (chatbot or copilot) in the preparation of this PR? If so, describe how

Yes. Claude Code (Anthropic) was used across several sessions as a pair-programming and drafting assistant, with the author directing the design and reviewing/testing all changes performed by the LLM. Specifically, this was involved in:

  • Feature implementation: planning and scaffolding the conversion of the flat INV_POI adder into a binned supply curve — GAMS sets/params/equations (b_inputs.gms, c_model.gms, d_objective.gms), make_poi_supply_curve.py, the transmission.py writer, and the objective/report/var-fix wiring.
  • Hardening & hygiene: the USD2024→2004$ dollar-year conversion via get_inflatable()/dollaryear.csv; replacing most silent-default sw.get() switch lookups with direct indexing (keeping .get() only where switches are re-read from pre-feature runs); relocating make_poi_supply_curve.py into reeds/input_processing/; renaming the mislabeled capacity_MWcapacity_GW column; consolidating and restyling the documentation; and diff-hygiene cleanup.
  • Review response: a multi-agent code review of the branch drove several fixes — gating the double-count reinforcement drop on numpoibins != 1 (so defualt mode doesn't double-count), a runreeds.py guard rejecting numpoibins != 1 with GSw_TransIntraCost = 0, consolidating the curves to the single raw_interconnection_TSC_data.csv source with fail-loud region-coverage validation (removing the per-zone-set placeholder files), and the sw.get() backward-compat for postprocessing on older runs.

All LLM-assisted changes were reviewed, directed, and tested by the author.

Note: parts of this PR — including the summary of LLM usage above — were themselves drafted by Claude Code (from the branch diff and prior session history) and reviewed/revised by the author.

Max-Vanatta and others added 15 commits May 31, 2026 21:15
…ariant)

Derived from int_supply_cruve_sub by stripping the wind (wind-ons)
reinforcement limit, leaving the zonal / technology-agnostic increasing-cost
POI reinforcement supply curve and its regional-curve fixes intact.

Removed:
- GAMS: INV_WPOI variable, eq_WPOI_cap / eq_WPOI_binlim / eq_WPOI_link
  (c_model.gms), cap_wpoi_bin / wpoi_sc_dat load + subset clip (b_inputs.gms),
  INV_WPOI var-fixing (5_varfix.gms), wind_poi_capacity_bin report
  (report.gms, report_params.csv)
- Python: transmission.py::write_wind_poi_supply_curve + its call;
  runreeds.py GSw_WindReinf/numpoibins compatibility guard
- Switches/data: GSw_WindReinf (cases.csv), ERCOT_Wind test config
  (cases_transmission_test.csv), wind-ons_nodal_supply_curve.csv +
  its dollaryear.csv / README entries
- Docs: wind sections in poi_supply_curve.md

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… doc/test fixes

Behavioral:
- io.py: gate the double-count reinforcement drop on numpoibins != 1 (was > 1),
  so the native curve (numpoibins=0) no longer double-counts VRE reinforcement
- runreeds.py: raise when numpoibins != 1 with GSw_TransIntraCost == 0 (the
  curve is gated on GSw_TransIntraCost, so that combo silently charged nothing)

Quick/safe:
- cases_transmission_test.csv: run the ERCOT_default/ERCOT_0 baselines (ignore=0),
  fix ERCOT_defualt->ERCOT_default typo, add trailing newline
- model_documentation.md: reinforcement drop covers wind/solar/CSP/geothermal
- reedsplots.py: label POI curve y-axis [2004$/kW] (values are deflated)
- PR_description.md: typos, test-case list, document the new compatibility guard

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Per review direction:
- transmission.py: raw_interconnection_TSC_data.csv is now the single source of
  the POI reinforcement curves for every spatial resolution (like the hashed
  transmission cost files). Removed the per-zone-set fallback; if the raw file is
  missing or does not cover every model region when numpoibins != 1, raise
  (mirroring the transmission cost/distance / ITL validation)
- Deleted the committed placeholder poi_supply_curve_{zoneset}.csv files
  (~30.6k lines) and the runfiles.csv copy entry; write_poi_supply_curve writes
  inputs_case/poi_supply_curve.csv itself
- dollaryear.csv: drop the now-unused poi_supply_curve.csv entry (raw file stays
  USD2024)
- io.py: read numpoibins/GSw_TransIntraCost via sw.get() with legacy defaults so
  postprocessing (input plots, reeds_to_rev) doesn't AttributeError on
  pre-feature runs
- Docs (README, poi_supply_curve.md, model_documentation.md,
  make_poi_supply_curve.py, PR_description.md) updated for the raw-only source

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…notes

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Remove reedsplots.plot_poi_supply_curve and its single_case_plots.py driver
block; update PR text to match.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@patrickbrown4
patrickbrown4 self-requested a review July 8, 2026 22:51

@patrickbrown4 patrickbrown4 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot and sorry for the late followup (and the nitpicky comments). I haven't gotten through the whole thing yet, but adding some notes now. Let's discuss how to coordinate the work on getting distances out of the TSC results.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before merging, remove this one and add a test for the new method to cases_test.csv

Comment thread reeds/core/setup/b_inputs.gms Outdated
* rtscbin.csv is a single column (one bin label per line, no header), so $ondelim is neither
* needed nor allowed here (the set's dimension is the universe, which $ondelim cannot infer):
* a plain $include pastes the labels straight into the set body.
set rtscbin "POI / network-reinforcement supply curve bins (full-reV; loaded from data)"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For readability, can you use a name that's more visually distinct from tscbin and rscbin? Maybe icbin (for "interconnection cost bin") or poibin (for "point of interconnection (capacity) bin")?

Since the comments refer to them as POI bins (and the variable is already INV_POI), poibin seems best. (Understanding it conflicts with the reV usage of the term, but the reV component is really just a substation upgrade cost, so should probably be renamed)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer icbin (and added that to the next commits) due to the reV discussion, but also because it feels like these costs expand past poi only.

That being said, the rest of the POI constraints would then need to be renamed if we really try to separate the concepts.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • The PR text says this file uses zonehashes like transmission cost, but it does not. I can't remember if we discussed; zonehashes are preferred but either format is ok at this point. If zone names are used instead of hashes, the file should be moved to the associated inputs/zones folder. (See note elsewhere on how a given zone name can be used differently across GSw_ZoneSets.)
    • If they are indexed by zonehash, add a reader function that pulls the data for the specified GSw_ZoneSet
  • (nitpick) "raw" and "data" don't say much, and it can be helpful to keep filenames short when possible; could just call it "interconnection_cost_{scenario}.csv"
    • The upgrade curve also depends on tech assumptions (like "is reconductoring with advanced conductors allowed" and "what's the maximum voltage for new lines allowed in a region"), so I expect we're going to have multiple scenarios (at least with/without reconductoring). To support that, you could use a new switch like GSw_TransUpgradeMethod (see the pb/reconductor for an example of how it's used for different reconductoring scenarios)
  • Round to fewer significant figures (should be ok with 2 decimals for GW and 0 for the rest)

Comment thread reeds/core/setup/b_inputs.gms Outdated
Comment thread reeds/core/setup/b_inputs.gms Outdated
Comment thread reeds/input_processing/transmission.py Outdated
Comment thread reeds/input_processing/transmission.py
Comment thread reeds/core/setup/c_model.gms Outdated
Comment on lines +2004 to +2010
* This is a PRICE-ONLY mechanism: new generation is charged a per-MW reinforcement cost off the
* supply curve and NO deliverable intra-zone transmission capacity is created. INV_POI never
* enters the power balance, the transmission flow limits, or CAPTRAN; it relieves no flow. The
* accounting quantity is sized to the zone's interconnected generation capacity (CAP/ilr, AC)
* solely so the curve price can be levied on it -- it is a cost basis, not transmission capability.
* A single zonal reinforcement curve covers all technologies (the free existing capacity
* poi_cap_init and the non-generator POI terms -- spur, converter, LCC -- attach here too).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure about this new emphasis on a price-only mechanism. We require INV_POI to be expanded to expand the generation capacity, and we include interconnection miles in the output transmission reporting. It's not interzonal but it's still transmission, and the docs frame these as two different types of transmission (intrazonal + interzonal), not that one is "price-only".

This conversation is helpful though because it reminds me that we need to include the data and processing to get reinforcement TW-miles into the outputs. (Sorry, I think I said I would do some of the below in the TSC repo and forgot about it.) (These changes aren't required when we drop the reinforcement costs/distances for county resolution because the distances still get counted as interzonal; here, we need to do it because otherwise there's nothing to replace the reinforcement distances that are dropped.) That involves:

  • TSC
    • Add line miles to each line of upgradelog.csv
    • When determining the convex hull, sum and record these parameters for each bin:
      • miles and MW-miles of reconductoring (by voltage would be great but ok to save for next round)
      • miles and MW-miles of new circuits (")
      • Nice to have (for unrelated supply-curve projects) but ok to skip for first round since not currently used in ReEDS:
        • MW and number of substation upgrades (")
        • MW and number of new transformers (by high-side voltage would be great, but again, ok to save for next round)
    • Decide how to extend for the unlimited top bin
    • Write that data from TSC for use in ReEDS (see here for the analogous part of the ITL upgrade supply curve)
  • ReEDS
    • For parity with current features we would need two things in the core optimization:
      • cost per bin (already done)
      • Line miles by bin, as a replacement for distance_reinforcement(i,r,rscbin) in eq_transmission_investment_max, assuming we want to keep supporting limited-transmission scenarios via the GSw_TransInvMaxLongTerm switch
    • In output-processing, include the TW-miles of reinforcements in the outputs
      • That's messy because we currently have two almost-identical functions for it:
      • Brian is working on removing the bokeh versions so they only live in one place. So we can focus on the earlier steps for now in case that's done by the time we get to it.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Completely agreed, this emphasis was more of a caution than focus. With the other branch we will make that maps the upgradelog (or other method) then it should be completely defined.

Comment thread inputs/transmission/poi_supply_curve.md Outdated
Comment thread docs/source/model_documentation.md Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants