Skip to content

Update interzonal transmission costs/distances and use directly calculated values#95

Open
patrickbrown4 wants to merge 49 commits into
mainfrom
pb/transcost
Open

Update interzonal transmission costs/distances and use directly calculated values#95
patrickbrown4 wants to merge 49 commits into
mainfrom
pb/transcost

Conversation

@patrickbrown4
Copy link
Copy Markdown
Contributor

@patrickbrown4 patrickbrown4 commented May 15, 2026

Summary

In the same vein as 2003, this PR:

Technical details

Implementation notes

Least-cost paths

There are a few updates to the interzonal least-cost path procedure:

  • The interzonal transmission costs have been out of sync with interconnection costs since 1836. This PR brings them in sync, so both are now using costs from the MISO 2025 Transmission Cost Estimation Guide (parent, description, data)
    • A new pair of data files from MISO, inputs/transmission/conductor_{ac or dc}.csv, provide lookup tables for converting from the assumed kV to MW capacity
  • We used to assume 500 kV costs for all interzonal greenfield lines, regardless of location or scale (so tiny counties with low-voltage lines got to use 500 kV costs, which are cheaper on a USD/MW basis than lower-voltage lines). Here, we instead use the maximum AC voltage currently connecting each pair of zones, with a 138 kV floor. So interfaces that are crossed by higher-voltage lines get lower USD/MW expansion than interfaces crossed by lower-voltage lines.

Spatial flexibility

  • The old future-capacity scenarios (which were specific to z134) have been removed. The scenarios used in NTP have been maintained (planned_lines-NTP_{MT or P2P}.csv), but now specify individual from/to endpoints
  • Similarly, planned additions are now specified as individual endpoints
    • TransWest Express and SunZia have been added; see the descriptions in inputs/transmission/README.md
    • The offshore zone backbones are in inputs/transmission/newlinks_offshore_backbone.csv
    • The offshore zone radial connections are in inputs/zones/{GSw_ZoneSet}/newlinks_offshore_radial.csv
      • These need to be specified for each GSw_ZoneSet because the user might want to control which zone they make landfall in, particularly at county resolution
      • Offshore zones and county resolution are now compatible (they were not before)
  • We keep the old single-bin transmission upgrade supply curve (TSC) file for greenfield 500 kV with GSw_ZoneSet=z134 as an example of the required file format when using TSC, but will eventually remove it when we add a full supply curve
    • The greenfield costs are put into this format automatically in transmission.py

Additional changes

  • Features that we no longer need with the new format have been removed:
    • inputs/shapefiles/transmission_endpoints (now in inputs/zones/{GSw_ZoneSet}/zonehash.csv)
    • Transmission aggregation functionality in copy_files.py and aggregate_regions.py
    • Zone aggregation functionality in reeds.io.get_zonemap()
  • transmission.py has been reorganized to make it importable
  • min_co2_spurline_miles (= 20): moved from hard-coded in transmission.py to scalars.csv

Switches added/removed/changed

  • GSw_TranScen: Allowable options changed to none (default, meaning only interfaces with existing capacity can be expanded), NTP_MT, or NTP_P2P

Issues resolved

Known incompatibilities

  • WECC_county is failing because we're missing the cost/distance for the newly added SunZia line at that resolution. I'll ask the reV team for it the next time we run more paths (it's more efficient to do in a batch), but since the WECC_county case was already failing the last time I checked (in PR 2003), I'm not sure it's worth delaying this PR for it. But let me know if you disagree and I can ask them to run it now.

Validation, testing, and comparison report(s)

Here's some background info and plots of the new costs: 20260515 - transmission costs distances.pptx

I'll add compare reports to that deck as they come in.

Checklist for author

Details to double-check

  • Make sure SunZia and TransWestExpress show up in the solution
  • Update documentation text and figures as laid out in Calculate greenfield transmission costs for all zone geometries directly #37
  • Decide on MISO default (rename from 'default' to 'miso') or ACSR by default for new conductors
  • Open issue on updating environment
  • Charge code provided to reviewers
  • Included comparison reports for appropriate test cases
  • Documentation updated if necessary
  • If input data added/modified:
    • Dollar year recorded and converted to 2004$ for GAMS
    • Units are specified
    • Preprocessing steps have been documented and committed to ReEDS_Input_Processing
    • New large data files handled with .h5 instead of .csv <- No, because we will add to the costs/distances as new zone geometries are added
  • 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
  • No large data file(s) added/modified
  • No substantive impact on runtime for full-US reference case
  • No substantive impact on folder size for full-US reference case
  • No change to process flow (runreeds.py, reeds/core/solve/solve.py)
  • No change to code organization
  • No change to package requirements (environment.yml or Project.toml)

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

No

…; only define co2_routes for r,rr pairs with nonzero distance
…ap() (no longer need to modify zone map in aggregate_regions.py)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Removed because the versions that match the interzonal paths are in the zonehash.csv files; these no longer match

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Included so we can avoid adding more 'MW' or '$/MWmile' columns to the already-large transmission_cost_distance.csv; instead we just look it up from here using the voltage column

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The "name" column doesn't actually matter but including it here in case it's useful for backwards compatibility

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

not directly related but was deferred earlier and it's on the list for #37

Comment thread inputs/zones/z69/b2b.csv
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

bugfix

Comment thread reeds/io.py
return offshore_zones


def get_zonemap(case=None, exclude_water_areas=False, crs='ESRI:102008', **kwargs):
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Updated to use the new zone node lat/lons and avoid need for county/mixed-specific processing

Comment thread cases.csv
GSw_TransSquiggliness,Transmission distance multiplier to approximate the squigglier paths followed by actual lines (only applied to NEW lines),float,1,
GSw_TransUpgradeMethod,Method for calculating AC transmission upgrade costs,500kv,500kv,
GSw_TransScen,Additional candidate interfaces for transmission expansion between zones,^(none|NTP_(MT|P2P))$,none,
GSw_TransSquigglinessMin,Minimum squiggliness (straight-line length multiplier) to apply for interzonal transmission; the default value is from the MISO Transmission Cost Estimation Guide (https://www.misoenergy.org/planning/transmission-planning/mtep),float,1.3,
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The new routes are even straighter than the old, so I think now is a good time for this change (and it keeps us more aligned with MISO assumptions)

scalars = reeds.io.get_scalars(case)

interface_params = reeds.inputs.get_distances(case)
## Apply the minimum-squiggliness factor
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This part is new; most of the rest is just reorganized

Comment thread reeds/io.py
# Mixed resolution procedure
if agglevel_variables['lvl'] == 'mult':
### Model zones
dfba = gpd.read_file(os.path.join(reeds_path, 'inputs', 'shapefiles', 'US_PCA'))
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

With this change, we're no longer using this 134-zone shapefile directly. We can wait and remove it in a future PR though, since there might be upstream or downstream processes relying on it.

[['r', 'rr', 'MW_forward', 'MW_reverse']]
.assign(trtype='AC')
)
### DEPRECATED: p19 is islanded with NARIS transmission data, so connect it manually
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Dropped because we only calculate AC distances/costs for interfaces crossed by existing AC lines and this one is not crossed by any lines. So I think p19 is now islanded, but I didn't have any issues with extra ReEDS/PRAS iterations there (and we're now only ~2 PRs away from switching to 90 zones).

Comment thread reeds/io.py
Comment on lines +218 to +219
dfcounty['r'] = county2zone
dfzones = dfcounty.dissolve('r')
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Because this zone map is built from the county map, which is more detailed than the old US_PCA map, all the downstream calculations and plots that use the maps (accessed via reeds.io.get_dfmap() or maps.gpkg) are slowed down. There is a .simplify_coverage() function in geopandas 1.1 that we can use to simplify the geometry and speed them all up again, but it requires an update to the conda environment.

Elaine is also working on a gdxpds update; my plan is to go ahead with the current implementation for now, and then once the new gdxpds version is ready, do a holistic environment update to geopandas 1.1 / pandas 3 / python 3.14 / etc., so everyone only has to update their environment once.

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.

One other package that might be worth adding in an update is the highs package to give access to the HiGHS solver for things like representative period selection.

@patrickbrown4 patrickbrown4 mentioned this pull request May 20, 2026
12 tasks
Network reinforcement costs are approximated by tracing a path along existing transmission lines from each wind/solar POI to each zone "center" within the same state;
the zone center is usually taken as the largest population center in the model zone but is sometimes (for zones without large urban centers) assigned to a high-voltage substation within the zone.[^ref35]
A cost for each reinforcement route is calculated using the cost surface described above, with capital expenditure (CAPEX) costs multiplied by 50% to approximate the lower cost for reconductoring compared to greenfield transmission construction.
Network reinforcement costs are approximated by tracing a path along existing transmission lines from each wind/solar POI to a nearby urban center.
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.

Can you quantify "nearby" and "urban center"? I can't remember the specifics, but this seems like a good place to document those.

@@ -0,0 +1,9 @@
voltage_kv,kcmil,conductor_type,conductor_quantity,amp
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.

What is kcmil?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It's a silly U.S. unit but it's the standard measurement of wire gauge: https://en.wikipedia.org/wiki/Circular_mil

Comment thread inputs/transmission/hvdc_lines.csv Outdated
Cross Sound Cable,330,-72.90222222,41.28666667,-72.8675,40.95916667
Neptune Cable,660,-73.55111111,40.76055556,-74.35308333,40.47371667
Trans Bay Cable,400,-121.89666667,38.03083333,-122.38583333,37.75472222
name,MW,year_online,trtype,certain,from_lon,from_lat,to_lon,to_lat
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.

Is year_online the number of years until it is online? If so, it might be worth updating the name, as I was expecting something like "2028" for the value rather than "0".

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added a description to inputs/transmission/README.md and changed these to their actual historical online year

@@ -0,0 +1,3 @@
name,MW,year_online,trtype,certain,from_lon,from_lat,to_lon,to_lat
TransWestExpress,3000,2032,VSC,1,-107.176147,41.747242,-112.590781,39.541825
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.

This one has year_online as what I would expect, so I think I'm not understanding this column correcting, or what a zero value for this column means.

Comment thread inputs/transmission/README.md
Comment thread inputs/scalars.csv
Comment thread reeds/input_processing/copy_files.py
Comment thread reeds/input_processing/transmission.py
Comment on lines +339 to +348
### TEMPORARY 20260402: Drop county interfaces with no distance/cost
if (level == 'r') and (sw.GSw_RegionResolution in ['county', 'mixed']):
transmission_line_fom = get_transmission_fom(case, interface_params)
indices = ['r', 'rr', 'trtype']
drop = (
dfout
.merge(transmission_line_fom.reset_index(), on=indices, how='left')
)
drop = list(drop.loc[drop.USDperMWyear.isnull(), indices].itertuples(index=False))
dfout = dfout.set_index(indices).drop(drop).reset_index()
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.

Is this what is making WECC county runs fail (mentioned in your PR text)? Or does this make it not fail?

Yunzhi is nearly done with the WECC county bug fix, so it would be nice to not have it immediately broken again if that can be avoided, even if that meant a temporary patched solution until new reV data is available.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

No, it's a bit different; this is to keep GSw_ZoneSet = z3109 (county-level, which includes counties with no transmission links to neighbors) working until we finish reformatting the spatial pipeline, which will allow use of GSw_ZoneSet = z2972 (which aggregates those counties into their neighbors and avoids making islanded zones).

The missing SunZia line at county resolution is caught in check_inputs.py. But I'll add it before merging.

Comment thread runreeds.py Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants