Adding technology classes - #12
Conversation
Co-authored-by: Wesley Cole <49044852+wesleyjcole@users.noreply.github.com>
patrickbrown4
left a comment
There was a problem hiding this comment.
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!
patrickbrown4
left a comment
There was a problem hiding this comment.
Thanks An, lots of work in this PR! I think all of my comments are pretty minor and related to style/readability/error-handling rather than functionality, so it seems really close.
- To Brian's point, can you add a comparison between a USA run starting in 2010 and one starting in 2020? If the differences are bigger than expected, and you'd like to wait for a followup PR to look into it more (since there are already lots of changes here), then you could add a temporary
NotImplementedErrorif startyear != 2010here to give more time to work out issues while moving forward with the rest of the changes implemented here. - @wesleyjcole I'm comfortable with the python changes, but I never really wrapped my head around all the prescriptive/exogenous capacity handling in
b_inputs.gmsandc_model.gms, so I'd prefer to defer to you on those parts.
| prescribed_nonRSC['tech'] = prescribed_nonRSC['tech'].str.lower() | ||
| ### assign vintage based on start year of the unit | ||
| prescribed_nonRSC= pd.merge(prescribed_nonRSC, ivt_df, how='left', left_on='tech', right_on='Unnamed: 0') | ||
| prescribed_nonRSC['vin'] = prescribed_nonRSC.apply(lambda row: f"new{row[str(row['StartYear'])]}", axis=1) |
There was a problem hiding this comment.
I like the idea of including the year in the vintage name but it seems inconsistent with the contents of v (below). So it looks like vin is just temporary - maybe note that in a comment?
ReEDS/reeds/input_processing/WriteHintage.py
Lines 616 to 618 in 5102c8c
| $m_required_prescriptions(i,newv,r,t) | ||
| $sum{tt$[m_required_prescriptions(i,newv,r,tt) | ||
| $(yeart(tt)<=yeart(t))], ivt(i,newv,tt) }$(not ban(i))] = yes ; |
There was a problem hiding this comment.
Could you take a look at the inputs.gdx diff for valcap and add it to the compare report? I expect there will be changes given the coal bugfix but it'd be nice to confirm everything else is as expected.
There was a problem hiding this comment.
An: the new valcap should be tighter because only classes with prescribed builds will be included in early years. Before every class would be included if there was a prescribed build.
| cf_adj_t(i,v,t)$[(rsc_i(i) or hydro(i))$sum{r, valcap(i,v,r,t) }] = 1 ; | ||
|
|
||
| * Existing wind uses 2010 cf adjustment | ||
| * Existing wind uses startyear cf adjustment |
There was a problem hiding this comment.
Interesting - so if we use a later startyear, all the existing wind gets a higher CF?
A fix for that will be a pretty big effort. In the meantime (assuming I'm understanding correctly), could you add a caveat to faq.md, here?
- Wind capacity existing in the first modeled year (the first year specified by the `yearset` switch) is treated as if it were built in the first modeled year, using the ATB capacity factor multiplier for that year.
For example, if 2020 is the first modeled year, all wind capacity existing in 2020 uses the ATB capacity factor multiplier for 2020.
This approach neglects variation in capacity factor across different existing wind vintages.
There was a problem hiding this comment.
That's correct for two reasons: 1) we don't have older vintages with older CF values, and 2) we have not applied degradation for older plants. The first is relatively easy to address, as we can just update our CF adjustment parameters (it's harder to make it dynamic, but we can easily make it "correct" for a different startyear). The second is more effort, but Anna S. was going to look into it to see what we can do there.
There was a problem hiding this comment.
Thanks Wesley. I was mostly concerned about the difference between a startyear of 2010 and, for example, 2025. According to the ATB file, the 2010 CF multiplier is 0.61 and the 2025 CF multiplier is 0.96 (57% higher). Using the 2025 CF for all ≤2025 wind capacity seems like a bigger issue than using the 2010 CF for all ≤2010 capacity, and could lead to bigger differences than we want between start years (if I'm understanding correctly).
It will help to look at the results for a run with a >2010 startyear, but if the differences are too large, it might be worth considering whether to disallow >2010 startyears until we update the handling of post-2010 existing wind.
| *beginning year value of 2010 is zero (i.e., no elasticity) | ||
| cd_beta(cendiv,t)$[t.val>2010] = cd_beta0_allsector(cendiv) ; | ||
|
|
||
| nat_beta(t)$(not tfirst(t)) = nat_beta_energy ; | ||
| nat_beta(t)$[t.val>2010] = nat_beta_energy ; | ||
|
|
||
| $else.gassector | ||
|
|
||
| *beginning year value is zero (i.e., no elasticity) | ||
| cd_beta(cendiv,t)$[not tfirst(t)] = cd_beta0(cendiv) ; | ||
| *beginning year value of 2010 is zero (i.e., no elasticity) | ||
| cd_beta(cendiv,t)$[t.val>2010] = cd_beta0(cendiv) ; | ||
|
|
||
| *see documentation for how value is calculated | ||
| nat_beta(t)$(not tfirst(t)) = nat_beta_nonenergy ; | ||
| nat_beta(t)$[t.val>2010] = nat_beta_nonenergy ; |
There was a problem hiding this comment.
Why does 2010 need to be hardcoded here? If it's a property of the gas elasticity data, could you move it to scalars.csv?
There was a problem hiding this comment.
Due to hold the model used to work, we did not include elasticity information in the supply curves for the 2010 year (i.e., beta = 0 for 2010). That has persisted.
wesleyjcole
left a comment
There was a problem hiding this comment.
@bsergi - reeds_to_rev.py uses "existing_capacity". It's possible that the changes here might break that. Just flagging for awareness.
The changes all look good to me. I'll just want to check out an updated comparison report before approving.
|
|
||
| if min_cap > 0: | ||
| #Remove sites with less than minimum capacity threshold, but keep sites that have existing capacity | ||
| df = df[(df['capacity'] >= min_cap) | (df['existing_capacity'] > 0)].copy() |
There was a problem hiding this comment.
Will this reference to existing_capacity cause an issue now that the other instances have been removed?
| @@ -776,46 +681,6 @@ def save_sc_outputs( | |||
| df_sc.to_csv(os.path.join(outpath, 'results', tech + '_supply_curve_raw.csv'), index=False) | |||
| #Round now to prevent infeasibility in model because existing (pre-2010 + prescribed) capacity is slightly higher than supply curve capacity | |||
| df_sc[['capacity','existing_capacity']] = df_sc[['capacity','existing_capacity']].round(decimals) | |||
There was a problem hiding this comment.
Same question here. Will this reference to existing_capacity cause an issue now that the other instances have been removed?
| $m_required_prescriptions(i,newv,r,t) | ||
| $sum{tt$[m_required_prescriptions(i,newv,r,tt) | ||
| $(yeart(tt)<=yeart(t))], ivt(i,newv,tt) }$(not ban(i))] = yes ; |
There was a problem hiding this comment.
An: the new valcap should be tighter because only classes with prescribed builds will be included in early years. Before every class would be included if there was a prescribed build.
| cf_adj_t(i,v,t)$[(rsc_i(i) or hydro(i))$sum{r, valcap(i,v,r,t) }] = 1 ; | ||
|
|
||
| * Existing wind uses 2010 cf adjustment | ||
| * Existing wind uses startyear cf adjustment |
There was a problem hiding this comment.
That's correct for two reasons: 1) we don't have older vintages with older CF values, and 2) we have not applied degradation for older plants. The first is relatively easy to address, as we can just update our CF adjustment parameters (it's harder to make it dynamic, but we can easily make it "correct" for a different startyear). The second is more effort, but Anna S. was going to look into it to see what we can do there.
| *beginning year value of 2010 is zero (i.e., no elasticity) | ||
| cd_beta(cendiv,t)$[t.val>2010] = cd_beta0_allsector(cendiv) ; | ||
|
|
||
| nat_beta(t)$(not tfirst(t)) = nat_beta_energy ; | ||
| nat_beta(t)$[t.val>2010] = nat_beta_energy ; | ||
|
|
||
| $else.gassector | ||
|
|
||
| *beginning year value is zero (i.e., no elasticity) | ||
| cd_beta(cendiv,t)$[not tfirst(t)] = cd_beta0(cendiv) ; | ||
| *beginning year value of 2010 is zero (i.e., no elasticity) | ||
| cd_beta(cendiv,t)$[t.val>2010] = cd_beta0(cendiv) ; | ||
|
|
||
| *see documentation for how value is calculated | ||
| nat_beta(t)$(not tfirst(t)) = nat_beta_nonenergy ; | ||
| nat_beta(t)$[t.val>2010] = nat_beta_nonenergy ; |
There was a problem hiding this comment.
Due to hold the model used to work, we did not include elasticity information in the supply curves for the 2010 year (i.e., beta = 0 for 2010). That has persisted.
Apply Patrick's suggestions Co-authored-by: Patrick Brown <25125211+patrickbrown4@users.noreply.github.com>
…eEDS into ap_flex-start-year
Apply more suggested changes Co-authored-by: Patrick Brown <25125211+patrickbrown4@users.noreply.github.com> Co-authored-by: Wesley Cole <49044852+wesleyjcole@users.noreply.github.com>
| os.path.join(resultspath, f'{tech}_prescribed_builds.csv'), | ||
| os.path.join( | ||
| inputspath, 'capacity_exogenous', | ||
| f'prescribed_builds_{techlabel}_{access_case}.csv', |
There was a problem hiding this comment.
Sorry, I missed this before, but I'm pretty sure these files and functions can be removed too, since they're no longer used:
-
inputs/capacity_exogenous/prescribed_builds_*.csv- Associated entries in
futurefiles.csvandrunfiles.csv
- Associated entries in
-
reeds.io.assemble_prescribed_builds()
There was a problem hiding this comment.
I think I removed all the prescribed_builds, exog_cap, and their associated files/functions now. If I still miss anything, I can clean up the rest in the follow up PR
…eEDS into ap_flex-start-year
Summary
This PR:
startyearswitch and instead establishesstartyearbased onyearsetTechnical details
These changes are made in this PR:
process_unitdata.py: Readunitdata.csvfrominputs_caseand process it to assign sc_point_pids, capacity factors/mean temps to pv, wind, and geothermal units. This file is run immediately aftercopy_files.py. This allows us to remove all the mapping of NEMS units tosc_point_gidsin 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.gmspcatset is removed from the model: The equationseq_force_prescription_powerandeq_force_prescription_energy, along with the variablesEXTRA_PRESCRIPandEXTRA_PRESCRIP_ENERGY, are currently indexed bypcat. Since the prescribed generator classes and vintages are now defined inwritecap.py, thepcatindex 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.eq_cap_new_noretandeq_cap_energy_new_noret. Although this implementation follows the same logic asm_capacity_exog, it explicitly captures prescribed generator builds in the INV variables in the current version of the model.hourlize:exog_cap_{tech}.csvand those are associated withexisting_sitesfiles as they are no longer needed.geothermal_classification.csvis not generated inhourlizebut taken from https://pages.github.nrel.gov/ReEDS/ReEDS-2.0/model_documentation.html#technical-resource-potential.Issues resolved
prescribed_buildto capture all capacity in unitdata.csv. Currently some prescribed coal capacities are missing due tocoaloldunsis missing fromprescribed_buildcategories https://github.com/ReEDS-Model/ReEDS/blob/main/reeds/input_processing/writecapdat.py#L79capnonrsc. This is due to due to thecomment='#'option when read in unitdata.csv, which ignores all the data after unit ID of the rows where unit ID has # character. So whencapnonrscis calculated by group summing from unitdata.csv, some of the categories to sum over are nan and those rows are omitted from the sum.Validation, testing, and comparison report(s)
Total prescribed builds and exogenous caps are generally the same with main, but spatial distribution is slightly different:

Comparison of prescribed builds (
prescribed_build) vs. main (noncumulative_prescriptions) in 2010 for UPV - Total UPV capacity matches and matches with the ReEDS-NEMS plant database but there's some difference in spatial distribution.Comparison of prescribed builds (

prescribed_build) vs. main (noncumulative_prescriptions) in 2015 for UPV - Total UPV capacity in this branch is slightly lower but it matches exactly with NEMS.Comparison of prescribed builds (

prescribed_build) vs. main in 2010 for wind-ons - Wind-ons capacity matches exactly:Comparison of existing cap (

exog_upv) vs. main for UPV - Existing UPV capacity is slightly higher in this branch but matches exactly with NEMS:Comparison of existing cap (

exog_wind-ons) vs. main for wind-ons - Existing wind-ons capacity matches exactly:Comparison report (full here)
coaloldunsprescribed capacity not captured in mainNew annual capacity

Annual retirement

Checklist for author
Details to double-check
hourlize/resource.pywas rerun to regenerate the existing/prescribed VRE capacity dataGeneral information to guide review
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