Add regional interconnection costs - #138
Conversation
…pply curves to compare the implementation.
…leaning up the formatting
…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
left a comment
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Before merging, remove this one and add a test for the new method to cases_test.csv
| * 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)" |
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
- 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/zonesfolder. (See note elsewhere on how a given zone name can be used differently acrossGSw_ZoneSets.)- If they are indexed by zonehash, add a reader function that pulls the data for the specified
GSw_ZoneSet
- If they are indexed by zonehash, add a reader function that pulls the data for the specified
- (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)
- 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
- Round to fewer significant figures (should be ok with 2 decimals for GW and 0 for the rest)
| * 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). |
There was a problem hiding this comment.
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)
- Add line miles to each line of
- 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)ineq_transmission_investment_max, assuming we want to keep supporting limited-transmission scenarios via theGSw_TransInvMaxLongTermswitch
- 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:
- pre_spur in bokeh
- calc_reinforcement_spur_capacity_miles in
reeds/results.py
- 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.
- That's messy because we currently have two almost-identical functions for it:
- For parity with current features we would need two things in the core optimization:
There was a problem hiding this comment.
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.
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 existingINV_POI/eq_POI_capmechanism.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 (
rtscbinas the indexer rather thanrscbin): each bin has an incremental capacity width and a per-bin cost. The flat cost is the degenerate one-bin case, sonumpoibins = 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
reeds/input_processing/):make_poi_supply_curve.py(new) builds the zonal cost bins fromraw_interconnection_TSC_data.csvvia optimal capacity-weighted least-squares segmentation.transmission.py::write_poi_supply_curvewritespoi_supply_curve.csv(flatGSw_TransIntraCostwhennumpoibins=1, otherwise the binned curve with an unlimitedGSw_POIUpperCostbackstop bin).raw_interconnection_TSC_data.csvis 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).INV_POI(r,t)→INV_POI(r,rtscbin,t); new setrtscbin, paramscost_poi_bin/cap_poi_bin/poi_bin_feas, andeq_POI_binlim(incremental bin-width cap) inb_inputs.gms/c_model.gms. Objective (d_objective.gms), var-fixing (5_varfix.gms), and reporting (report.gms,report_params.csv) updated to carry thertscbinindex. The single zonal curve applies to every technology (the free existing capacitypoi_cap_initand the non-generator POI terms — spur, converter, LCC — attach here too).numpoibins != 1andGSw_TransIntraCost > 0),reeds/io.pydrops 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 whennumpoibins = 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.csventry added (raw_interconnection_TSC_data.csv, USD2024). The per-zone-setpoi_supply_curve_{GSw_ZoneSet}.csvplaceholder files and theirrunfiles.csvcopy entry were removed — the curve is built at run time from the single raw source.Switches added/removed/changed
numpoibins1(default) = legacy flatGSw_TransIntraCost;>1= binned curve;0= defualt (one bin per raw segment).GSw_POIUpperCostnumpoibins != 1.GSw_TransIntraCostnumpoibins = 1flat-cost value.Known incompatibilities
numpoibins != 1(binned or defualt curve) requiresGSw_TransIntraCost > 0; the curve is gated on that switch, so the combinationnumpoibins != 1withGSw_TransIntraCost = 0is rejected with aValueErrorinrunreeds.pyrather than silently charging no reinforcement.numpoibins = 1(the default) reproduces the legacy flat-cost behavior exactly.Relevant sources or documentation
inputs/transmission/README.md).Validation, testing, and comparison report(s)
cases_transmission_test.csv(ERCOT_default= flat legacy,ERCOT_0= near-zero flat,ERCOT_regional= binned curve).Checklist for author
Details to double-check
inputs/transmission/README.md,inputs/transmission/poi_supply_curve.md,docs/source/model_documentation.md)dollaryear.csv, USD2024) and converted to 2004$ for GAMSraw_interconnection_TSC_data.csvis added, which is small; the per-zone-set curve files were removed in favor of the single raw source)General information to guide review
numpoibins=1default)raw_interconnection_TSC_data.csv)runreeds.pygains one compatibility check:numpoibins != 1requiresGSw_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:
INV_POIadder into a binned supply curve — GAMS sets/params/equations (b_inputs.gms,c_model.gms,d_objective.gms),make_poi_supply_curve.py, thetransmission.pywriter, and the objective/report/var-fix wiring.get_inflatable()/dollaryear.csv; replacing most silent-defaultsw.get()switch lookups with direct indexing (keeping.get()only where switches are re-read from pre-feature runs); relocatingmake_poi_supply_curve.pyintoreeds/input_processing/; renaming the mislabeledcapacity_MW→capacity_GWcolumn; consolidating and restyling the documentation; and diff-hygiene cleanup.numpoibins != 1(so defualt mode doesn't double-count), arunreeds.pyguard rejectingnumpoibins != 1withGSw_TransIntraCost = 0, consolidating the curves to the singleraw_interconnection_TSC_data.csvsource with fail-loud region-coverage validation (removing the per-zone-set placeholder files), and thesw.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.