Update interzonal transmission costs/distances and use directly calculated values#95
Conversation
…; only define co2_routes for r,rr pairs with nonzero distance
…thout an interfaces list
…ap() (no longer need to modify zone map in aggregate_regions.py)
| 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. |
There was a problem hiding this comment.
Can you quantify "nearby" and "urban center"? I can't remember the specifics, but this seems like a good place to document those.
There was a problem hiding this comment.
Added some lines to describe the current approach.
| @@ -0,0 +1,9 @@ | |||
| voltage_kv,kcmil,conductor_type,conductor_quantity,amp | |||
There was a problem hiding this comment.
It's a silly U.S. unit but it's the standard measurement of wire gauge: https://en.wikipedia.org/wiki/Circular_mil
| 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 |
There was a problem hiding this comment.
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".
There was a problem hiding this comment.
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 | |||
There was a problem hiding this comment.
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.
| ### 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() |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
The SunZia cost/distance has been added, so the WECC_county case now runs through 2015, the same as on main.
wesleyjcole
left a comment
There was a problem hiding this comment.
Thanks for the updated explanations and documentation.
kodiobika
left a comment
There was a problem hiding this comment.
Thanks for doing this! LGTM
|
|
||
|
|
||
| def get_hvdc_lines(): | ||
| def get_hvdc_lines(filename='hvdc_existing.csv'): |
There was a problem hiding this comment.
No action required here, but this got me wondering - is there a well-defined difference between reeds/inputs.py and reeds/io.py? My instinct would've been to put something like this in the latter but I'm not actually sure which, if any, makes more sense in this case
There was a problem hiding this comment.
My very rough notion has been to use inputs.py for things that are "further up" in the pipeline (closer to the direct inputs/ data) or that require more processing, and io.py for interacting with intermediate files from the {case}/inputs_case folder. I'm sure I haven't been super consistent though (inputs.py is newer and I didn't port everything to it from io.py that would fit those guidelines.) I was also worried about io.py becoming too huge if we use it as a catch-all for everything, but I'm not sure how important that is.
Maybe we could do a standalone cleanup/organizational PR after we're done with all the spatial stuff? We'll need some more layers of organization anyway as we start moving b_inputs.gms into Python.
|
|
||
|
|
||
| def get_dfmap(case=None, levels=None, exclude_water_areas=False): | ||
| def get_dfmap(case=None, levels=None, exclude_water_areas=True): |
There was a problem hiding this comment.
Just checking - the fact that all the instances throughout the code where get_dfmap is currently being used and exclude_water_areas isn't specified will now exclude water areas (where they were previously including them) is intentional right?
There was a problem hiding this comment.
get_dfmap() until now has used the 134-zone shapefile for everything except county zones, and that shapefile excludes water areas. So I think excluding them by default is most consistent, but I didn't actually go through and check all the areas where water areas might matter.
Do you remember offhand the places where including water areas is intended and important?
There was a problem hiding this comment.
Ah got it, thanks that SGTM. No instances come to mind (and from a cursory scan it looks like there are none) so I think we're good
| @@ -0,0 +1,9313 @@ | |||
| start,end,polarity,voltage,cost_MUSD,length_miles | |||
There was a problem hiding this comment.
nit: It'd be nice for consistency to specify the voltage units too, but no need if it's too much of a hassle at this point
There was a problem hiding this comment.
Yeah fair; I kept these because they're the column names for inputs to and outputs from the reV Routing (reVRt) model. I've added notes on the units to inputs/transmission/README.md; think that's enough for now?
There was a problem hiding this comment.
Makes sense, yup that SGTM
| @@ -0,0 +1,499 @@ | |||
| name,from_lon,from_lat,to_lon,to_lat,polarity,voltage,cost_MUSD,length_miles | |||
There was a problem hiding this comment.
nit: Ditto on voltage units ('voltage' -> 'voltage_kv')
| interface_params['geometry'] = interface_params.apply(_make_line, axis=1) | ||
| interface_params = gpd.GeoDataFrame(interface_params, crs='EPSG:4326') | ||
| interface_params['straight_miles'] = ( | ||
| interface_params.geometry.to_crs('EPSG:5070').length |
There was a problem hiding this comment.
Just curious, why 'EPSG:5070' here instead of the usual 'ESRI:102008'?
There was a problem hiding this comment.
They're both equal-area projections, but EPSG:5070 is zoomed in more on the contiguous US, while ESRI:102008 is for all of North America (there are examples of the two at https://gis.stackexchange.com/a/457667). That means that within the CONUS, there's a bit less distortion in distances and areas using EPSG:5070 than ESRI:102008. (Eventually it'd be nice to change everything to EPSG:5070 and make it controlled by a global switch.)
…arams (avoids NTP_MT/NTP_P2P being used in place of SunZia)
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:
inputs/transmission/conductor_{ac or dc}.csv, provide lookup tables for converting from the assumed kV to MW capacitySpatial flexibility
planned_lines-NTP_{MT or P2P}.csv), but now specify individual from/to endpointsinputs/transmission/README.mdinputs/transmission/newlinks_offshore_backbone.csvinputs/zones/{GSw_ZoneSet}/newlinks_offshore_radial.csvGSw_ZoneSetbecause the user might want to control which zone they make landfall in, particularly at county resolutiontransmission.pyAdditional changes
inputs/shapefiles/transmission_endpoints(now ininputs/zones/{GSw_ZoneSet}/zonehash.csv)copy_files.pyaggregate_regions.py(Disaggregate and re-aggregate inputs with regional scope of legacy (134) zones #102 removed everything except for transmission aggregation fromaggregate_regions.py; given the removal of transmission aggregation here, we no longer need the script)reeds.io.get_zonemap()transmission.pyhas been reorganized to make it importablemin_co2_spurline_miles(= 20): moved from hard-coded intransmission.pytoscalars.csvSwitches added/removed/changed
GSw_TranScen: Allowable options changed tonone(default, meaning only interfaces with existing capacity can be expanded),NTP_MT, orNTP_P2PIssues resolved
Validation, testing, and comparison report(s)
I ran the full
cases_test.csvsuite and everything worked except for WECC_county, which failed in 2015, the same year in which it fails on the main branch.Here's some background info and plots of the new costs: 20260515 - transmission costs distances.pptx
Compare reports are available in the same deck.
Checklist for author
Details to double-check
New large data files handled with .h5 instead of .csv<- No, because we will add to the costs/distances as new zone geometries are addedGeneral information to guide review
Did you use LLM tools (chatbot or copilot) in the preparation of this PR? If so, describe how
No