Skip to content

update capacity input database to AEO-NEMS 2026 - #13

Open
atpham88 wants to merge 44 commits into
mainfrom
ap_update_NEMS
Open

update capacity input database to AEO-NEMS 2026#13
atpham88 wants to merge 44 commits into
mainfrom
ap_update_NEMS

Conversation

@atpham88

@atpham88 atpham88 commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Summary

This PR updates ReEDS capacity input database with AEO-NEMS 2026 and EIA860M version June 2026, and also overhauls the NEMS processing script to make it more clear and easier to use as well as remove redundant steps.

Technical details

Implementation notes

Changes made to to this PR include:

  • Remove a_inheritance.py: This script is no longer necessary. We now directly merge AEO-NEMS to EIA860M using the final output file structure established in Clean up input plant database ReEDS#164.
  • b_aeo_cleaning.py --> a_data_cleaning.py: Improve the process of cleaning AEO and EIA860M data to resolve issues saw in prior version of capacity database used in ReEDS (such as applying repower year as new start year).
    • Units with statuses (OA) Out of service but expected to return to service in next calendar year , (OS) Out of service and NOT expected to return to service in next calendar year and (SB) Standby/Backup: available for service but not normally used are considered operating and available for dispatch.
  • c_geospatial_mapping.py --> b_geospatial_mapping.py.
  • d_hydro_classification.py --> c_hydro_classification.py: Remove any mention of state and resource regions (we only use FIPS from now on to capacity input database).
  • e_additional_inputs.py --> d_additional_inputs.py:
    • Remove several manual retirement data adjustment for nuclear power plants (For example Palisades nuclear restarting in 2026 is now captured in new version of EIA860M and no longer need manual adjustment here).
    • Plant max ages are now read directly from main ReEDS repo from inputs/plant_characteristics/maxage.csv and only include one value for each plant type regardless of its size. lfill-gas, pumped-hydro, csp-ns, pvb_pv and pvb_battery are missing from maxage.csv in the ReEDs repo and are assigned values of 100, 100, 30, 30, 30 years respectively.

Additional changes

  • Rename all subfolders to lower case for consistency.
  • Add intermediate_outputs folder within the outputs folder to store outputs from each step of the process.

Issues resolved

  • Several units in current capacity input database have wrong start years (and thus wrong retirement years) due to not having their start years updated to their repowered years. This issue is fixed in this PR.
  • Units with out of service and standby statuses previously are not considered operating and not available for dispatch but now they are. This results in some extra 16GW of capacity available for dispatch compared to last version.

Relevant sources or documentation

  • Output verification:
  • Plot national- and FIPS-level changes in online and retire capacity by tech for each year between 2010 and 2050.
    • Planned online:
planned_oneline_current_conus planned_oneline_new_conus planned_oneline_diff_conus
  • Planned retire:
planned_retire_current_conus planned_retire_new_conus planned_retire_diff_conus
  • Existing capacity map:
    • Current fleet
existing-capacity_main - Updated fleet existing-capacity
  • Documentation: did you update the README for a given a folder?
    Yes
  • ReEDS Documentation: do any parts of the ReEDS documentation need to be updated because of the changes in this pull request?
    No. Updated README for this input processing only.

Comment thread nems_database_processing/d2_fix_upgrades.py Outdated
Comment thread nems_database_processing/inputs/aeo_nems/AEO2023PF_final.csv Outdated
Comment thread nems_database_processing/inputs/aeo_nems/PLTF860_RDB.xlsx
Comment thread nems_database_processing/inputs/eia860M/october_generator2025.xlsx Outdated
Comment thread nems_database_processing/d1_set_retire_years.py Outdated
Comment thread nems_database_processing/run.sh Outdated
Comment thread nems_database_processing/a_data_cleaning.py Outdated
Comment thread nems_database_processing/d2_fix_upgrades.py Outdated
Comment thread nems_database_processing/d2_fix_upgrades.py Outdated

@bsergi bsergi left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

A few thoughts, mostly on processing structure that aren't necessarily directly related to the update itself.

For the plots, could you structure so that the old and new capacity plots are side-by-side and use the same y-axis? I think that would facilitate comparsion. You already include the different plot which covers similar information here, but just a thought for going forward.

* `d_additional_inputs.py`, which includes `d1_set_retire_years.py`, `d2_fix_upgrades.py`, and `d3_merge_psh_dbs.py`: This script handles updated retirement years that are outdated or missing in AEO-NEMS and EIA860M, fix upgrades, and handles other additional adjustments. Any manually adjusted retirement years should be included in `d1_set_retire_years.py`.
* `e_comparison_plotting.py`: This script generates comparison figures between previous version of NEMS and the newly updated version for validation.

# Input files and params to run run.sh

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Would you add some details to this section on where you get the input files? If we download them from somewhere it would be good to include links.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Would you add a bit more to the overview about the general process? My rough understanding is that we start from AEO file, clean it, and then supplement with the 860M.

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.

I added a bit more details in overview and the steps

Comment thread nems_database_processing/run.sh Outdated
coal_plant_retirement='EIA860_2025ER_CoalRetirements.csv'

# Average lithium battery duration:
# Assumed average duration for planned storage units:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

what is this based on on? would be good to document where this assumption comes from.

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 is now removed and storage_duration is calculated based on battery units that came online within 5 years of current year.

return nems_eia860_final

def addHeatrates(nems_eia860):
# Add in heat rates for planned units (AEO inputs):

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

+1 to this comment. It also might be better to store these as an input file and read them in.

# Main directory
dir = os.getcwd()

# Key parameters:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Right now if the script is launched using run.sh it will ignore the command line arguments and use the testing ones below. Before merging you should uncomment these and recomment the ones below.

Alternatively, you could set up the main function to take arguments and then either call directly using testing argument or pass command-line arguments to it from a if __name__ == '__main__': (this is the approach we've been using for a number of other python modules).

# Specify scrubber or unscrubber coal units
aeo_data.loc[aeo_data['reeds_tech'].notna(), 'tech'] = aeo_data['reeds_tech']
aeo_data.loc[(aeo_data['tech'].str.contains("coal", na=False)) &
(aeo_data['T_SYR']<=1969),'tech'] = 'coalolduns'

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

It might be overkill but could you put this in variable with a descriptive name? I think that's usually more informative than a number with no context.

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.

I added two variables coaluns and coalcrs with more descriptions for both now.

# Then, collapse on T_PID and T_UID
aeo_data_mult_g = aeo_data_mult_g.groupby(['T_PID','T_UID','TVIN'],
as_index=False).agg(
{'ctt':'first','wst':'first','THRATE':'mean','TC_SUM':'sum',

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

might suggest storing this as a global dictionary or input file and then reading in here. if you do keep in the code it might be cleaner to read with one assignment per line even though it's more lines. alternatively, there might be a way to use 'first' as the default and then only specify the ones that are different.

{
'ctt':'first',
'wst':'first',
'THRATE':'mean',
'TC_SUM':'sum',
'TC_NP':'sum',
'TC_WIN':'sum',
'battery_duration':'mean',
'T_SYR':'min',
'T_RYR':'first',
'tech':'first',
'EFDcd':'first',
'ECPcd':'first',
'T_PNM':'first',
'T_PCA':'first',
'TRFURB':'first',
'T_VOM':'mean',
'T_FOM':'mean',
'T_SMO':'first',
'T_RMO':'first',
'T_CCSROV':'first',
'T_CCSF':'first',
'T_CCSV':'first',
'T_CCSHR':'first',
'T_CAPAD':'first',
'T_CCSCAPA':'first',
'T_CCSLOC':'first',
'sector':'first',
'TCOUNT':'sum',
'T_LONG':'first',
'T_LAT':'first',
'status':'first',
'nems':'first'
}

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.

I cleaned this up a little bit. first is now used as default and columns that are 'sum' and 'mean' are specified.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

ReEDS has switched to using EPSG:5070 instead of ESRI:102008 for the crs, so you might consider updating that here. It shouldn't change the actual lat/lon values, but it might affect how things get placed into counties.

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.

Thanks for catching. I updated everything to 5070 now. Previously I had some weird issue where I got point(inf,inf) with 5070 so had to use the ESRI one (but I stilled used the 5070 for nearest distance calculation). But that did not seem to be an issue with projections and can be fixed by preloading projection data by conda install -c conda-forge proj-data, which is a note I added to README.

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.

3 participants