Skip to content

Flexible start year option & tech classes addition#12

Open
atpham88 wants to merge 105 commits into
mainfrom
ap_flex-start-year
Open

Flexible start year option & tech classes addition#12
atpham88 wants to merge 105 commits into
mainfrom
ap_flex-start-year

Conversation

@atpham88
Copy link
Copy Markdown

@atpham88 atpham88 commented Apr 15, 2026

Summary

This PR:

  • Adds an option to specify a flexible start year, instead of 2010
  • Removes startyear switch and instead establishes startyear based on yearset
  • Adds algorithm to assign pv, wind, and geothermal tech classes to NEMS units

Technical details

These changes are made in this PR:

  • process_unitdata.py: Read unitdata.csv from inputs_case and process it to assign sc_point_pids, capacity factors/mean temps to pv, wind, and geothermal units. This file is run immediately after copy_files.py. This allows us to remove all the mapping of NEMS units to sc_point_pids in the ReEDS_input_processing repo.
  • WriteHintage.py: Allow start year to be flexible instead of 2010.
  • writecapdat.py: Add mappings to solar, wind, geothermal resource class are based on the resource quality (capacity factors for wind and solar and temperature for geothermal) of the technology as it comes from reV.
  • c_supplymodel.gms
    • pcat set is removed from the model: The equations eq_force_prescription_power and eq_force_prescription_energy, along with the variables EXTRA_PRESCRIP and EXTRA_PRESCRIP_ENERGY, are currently indexed by pcat. Since the prescribed generator classes and vintages are now defined in writecap.py, the pcat index is no longer needed in these equations or variables. Accordingly, the equations are revised so that the investment (INV) of prescribed generators—indexed by (i, newv, r, t)—is equal to the sum of the prescribed build values and the additional prescribed investments (EXTRA_PRESCRIP) for those same (i, newv, r, t) combinations.
    • Integrate prescribed retirements when the prescribed facilities retires before their life time: In the NEMS database, the retirement year of some facilities occurs before the end of their expected lifetimes. To accurately capture these early retirements for prescribed facilities, the prescribed retirement values are subtracted from total investments when calculating facility capacities in eq_cap_new_noret and eq_cap_energy_new_noret. Although this implementation follows the same logic as m_capacity_exog, it explicitly captures prescribed generator builds in the INV variables in the current version of the model.
  • hourlize:
    • Remove scripts that generate exog_cap_{tech}.csv files as they are no longer needed.
  • geothermal_classification.csv is not generated in hourlize but taken from https://pages.github.nrel.gov/ReEDS/ReEDS-2.0/model_documentation.html#technical-resource-potential.
  • @wesleyjcole Do you want to add some description of what you did to resolve the geothermal prescribed build issue?

Issues resolved

Known incompatibilities

Relevant sources or documentation

Validation, testing, and comparison report(s)

Pending

Checklist for author

Details to double-check

  • Charge code provided for review
  • 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
    • Timeseries are in Central Time
    • Units are specified
    • Preprocessing steps have been documented and committed to ReEDS_Input_Processing
    • New large data files handled with .h5 instead of .csv
    • If spatially resolved inputs are modified, the following visualizations for each file are included in the PR description (time-averaged if the inputs are time-resolved):
      • Map of absolute values before
      • Map of absolute values after
      • Map of differences: (after - before) or (after / before)
    • If entries are added/removed/changed in the EIA-NEMS unit database:
      • Changes have been committed to ReEDS_Input_Processing
      • hourlize/resource.py was rerun to regenerate the existing/prescribed VRE capacity data
  • 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 (runbatch.py, d_solve_iterate.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

Tag points of contact here if you would like additional review of the relevant parts of the model

@atpham88 atpham88 requested a review from wesleyjcole April 16, 2026 22:32
Comment thread input_processing/copy_files.py Outdated
Comment thread reeds/input_processing/copy_files.py
Comment thread input_processing/writecapdat.py Outdated
Comment thread e_report.gms Outdated
Comment thread sources.csv Outdated
Comment thread sources.csv Outdated
Co-authored-by: Wesley Cole <49044852+wesleyjcole@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

@patrickbrown4 patrickbrown4 left a comment

Choose a reason for hiding this comment

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

I haven't gone through the whole thing yet and will take a closer look next week, but some initial thoughts in the meantime. Happy to discuss if helpful (including whether to try some of the suggested changes here or in a followup PR). Thanks!

Comment thread input_processing/recf.py
Comment thread inputs/capacity_exogenous/classification_upv.csv Outdated
Comment thread inputs/capacity_exogenous/upv_classification.csv Outdated
Comment thread inputs/capacity_exogenous/classification_upv.csv Outdated
Copy link
Copy Markdown
Contributor

@bsergi bsergi left a comment

Choose a reason for hiding this comment

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

Happy to chat through any of the comments on this one.

Comment thread hourlize/define_tech_classes.py Outdated

# Only used for one-off modification of geothermal supply curves
# Remove after hourlize is rerun and resource temp is added to geothermal supply curve
if sys.platform == 'win32':
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.

Does this work for all windows systems?

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 remember it worked for windows for a a stdscens processing script, which Pieter already tested. But we can get someone else with a windows laptop to test again here to be sure.

Comment thread hourlize/README.md Outdated
```

### define_tech_classes.py
Run this scrip after `run_hourlize.py` to generate classification of UPV and wind technologies (`classification_upv.csv`, `classification_wind-ofs.csv`, and `classification_wind-ons.csv`)
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.

It would be nice to not have to add another step that users have to remember. Can we add a if __name__== '__main__': block to tech_classes.py to make it importable and then to call directly from resource.py?

Alternatively, you could also just call this function from the create_exog_rsc from within writecapdat.py and create these on the fly for the relevant access case. That would avoid having to store the extra files in the repo; if they are only used for the assigning the exogenous capacity that might be the better route.

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.

Yes, either way sounds good to me. Let's talk about which one we want to implement today.

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.

define tech class in create_exog_rsc function now.

Comment thread hourlize/define_tech_classes.py Outdated
Comment thread hourlize/define_tech_classes.py Outdated
# Aggregate min/max by class and attach access_case
if tech == 'wind-ofs':
df['subtech'] = 'fixed'
df.loc[df['class']>=6,'subtech'] = 'floating'
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 a little worried about hardcoding this value in here. If this depends on the class_bin_num specification in the config file maybe we can read from that?

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 got this from the raw supply curves. It's always that class 1-5 = fixed and above 5 = floating. Originally I processed the classification from the raw windows supply curve which already has fixed and floating categories so I didn't have to do this. But the supply curve in ReEDS repo does not have these categories, so this is more of a one-off modification to add these categories there. I can remove it the next time hourlize is run. Also, it doesn't impact runs since only the class column is read.

Comment thread hourlize/define_tech_classes.py Outdated
Comment thread b_inputs.gms
capacity_exog(i,"init-1",r,t)${[yeart(t)-sum{tt$tfirst(tt),yeart(tt) }<maxage(i)]} =
max(0,capnonrsc(i,r,"value")
- sum{allt$[allt.val <= t.val], prescribedretirements(allt,r,i,"value") }
capacity_exog(i,v,r,t)${[yeart(t)-sum{tt$tfirst(tt),yeart(tt) }<maxage(i)]$sameas(v,'init-1')} =
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 comment above notes "existing capacity equals all 2010 capacity less retirements" -- should this be changed to "all pre-start year capacity"? If so I'm guessing there are other 2010 references that might need to be updated.

Comment thread b_inputs.gms

*beginning year value is zero (i.e., no elasticity)
cd_beta(cendiv,t)$[not tfirst(t)] = cd_beta0_allsector(cendiv) ;
*beginning year value of 2010 is zero (i.e., no elasticity)
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.

Why hardcode this 2010?

Comment thread c_supplymodel.gms
$(yeart(tt)<=yeart(t))$(tmodel(tt) or tfix(tt))],
INV(i,newv,r,tt) + INV_REFURB(i,newv,r,tt)$[refurbtech(i)$Sw_Refurb]}

INV(i,newv,r,t) + INV_REFURB(i,newv,r,t)$[refurbtech(i)$Sw_Refurb]
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 approach seems a lot cleaner so I like it. Was the goal to help with numerical instability?

Comment thread hourlize/resource.py Outdated
df_pre['capacity'] = df_pre['capacity'].round(decimals)
df_pre = df_pre.sort_values(['year',profile_id_col])
df_pre.to_csv(os.path.join(outpath, 'results', tech + '_prescribed_builds.csv'), index=False)
#Reduce supply curve based on exogenous (pre-start-year) capacity
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 don't understand why you've left this reduction block in. Doesn't the supply curve equation already (eq_rsc_INVlim) already account for exogenous capacity? Wouldn't subtracting here be double couting that capacity?

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.

Maybe it doesn't matter since you need subtract_exog=True for this to be applied and that isn't the default, but I'm wondering whether we should keep it at all since I can't see a time we'd want to use 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.

I missed this when I deleted the blocks above. Removed this now.

Comment thread hourlize/resource.py
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.

If we're processing the existing capacity downstream now I think it might be good to remove the remaining pieces in here, otherwise some folks might wander in here and think this is still being used.

Comment thread b_inputs.gms
/ (1$[not rsc_capacity_scalar_i(ii)] + rsc_capacity_scalar(ii,r,tt)$rsc_capacity_scalar_i(ii)) } ) / 1000 ;


*** Do we need this part when pcat is removed?
Copy link
Copy Markdown
Contributor

@patrickbrown4 patrickbrown4 May 11, 2026

Choose a reason for hiding this comment

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

pcat is still used above; is the intent to remove it completely? (Don't go out of your way to remove it if it's not necessary for the PR; I was just curious since it's also mentioned in the PR text.)

ReEDS/b_inputs.gms

Lines 1179 to 1208 in 097ea5f

set prescriptivelink0(pcat,ii) "initial set of prescribed categories and their technologies - used in assigning prescribed builds"
/
$offlisting
$ondelim
$include inputs_case%ds%prescriptivelink0.csv
$offdelim
$onlisting
/ ;
*include non-numeraire CSPs and then exclude numeraire CSPs in ii dimension of
*prescriptivelink0(pcat,ii) set when Sw_WaterMain is ON
prescriptivelink0("csp-ws",ii)$[(csp1(ii) or csp2(ii) or csp3(ii) or csp4(ii))$Sw_WaterMain] = yes ;
prescriptivelink0("csp-ws",ii)$[csp(ii)$i_numeraire(ii)$Sw_WaterMain] = no ;
set prescriptivelink(pcat,i) "final set of prescribed categories and their technologies - used in the model" ;
prescriptivelink(pcat,i)$prescriptivelink0(pcat,i) = yes ;
alias(pcat,ppcat) ;
* active prescriptivelink for all techs not included in the table above
* but restrict out csp techs in this calculation - since they
* are indexed by a separate pcat (csp-ws) and have special considerations
prescriptivelink(pcat,i)$[sameas(pcat,i)$(not sum{ppcat, prescriptivelink(ppcat,i) })$(not csp1(i))] = yes ;
*only geo_hydro techs are considered to meet geothermal prescriptions
prescriptivelink(pcat,i)$[geo_extra(i)] = no ;
*upgrades have no prescriptions
prescriptivelink(pcat,i)$[upgrade(i)] = no ;

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.

Thank you for the catch. I removed the remaining mentions of pcat now.

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.

4 participants