Add class set to model outputs - #217
Conversation
cavraam
left a comment
There was a problem hiding this comment.
No major concerns in this one, especially given results. A few suggestions to either collapse the 'c' index in reporting or potentially rename some of the output parameters to reflect the specific outputs. No strong feelings either way!
A couple of sanity checks included, prompts to double-check a couple of seemingly trickier code, but the respective parts should be fine.
PR looks good to go!
| # Concatenate existing and inv_rsc | ||
| df_inv = pd.concat([df_bin_exist, df_inv_rsc], sort=False, ignore_index=True) | ||
| # Split tech from class | ||
| df_inv[["tech_cat", "class"]] = df_inv["tech"].str.rsplit("_", n=1, expand=True) |
There was a problem hiding this comment.
got it, because 'class' is already part of the data upstream, right?
There was a problem hiding this comment.
(Not related to this PR) I am also getting a suggested edit on line 1610: 'rcby' seems to be defined in line 1647... I wonder if it needs to be renamed to 'rcy' defined earlier, or move some code around?
There was a problem hiding this comment.
reeds_to_rev.py isn't working right now, but this is a quick fix, so I can add it in here.
There was a problem hiding this comment.
Looks great programmatically. Conceptually, it might make sense to include the class in cap_energy_ivrt and/or cap_firm and cap_firm_iter?
There was a problem hiding this comment.
I don't think it's necessary in cap_energy_ivrt because there are no class-relevant technologies for energy in the initial scope. It would make sense if PSH gets a class at some point.
cap_firm, though, should get a class designation. We'll want to know exactly the capacity contribution of a technology. That will require making m_cc_mar adopt a class, which also needs to happen at some point. I've been focusing on stress periods, but I'll note this for follow-on work once I have a working stress-period formulation with collapsed classes. The output rename would also go along with that.
| 'RPS': {'title': 'Non-RecMap\nRECS\n[GWh]', 'scale':1e-3}, | ||
| 'OpRes': {'title': 'Non-valgen\nopres\n[MWh]', 'scale':1}, | ||
| 'm_rsc_dat': {'title': 'Supply curve\ntweaks [GW]', 'scale':1e-3}, | ||
| 'cspns': {'title': 'Unreassigned\ncsp-ns [MW]', 'scale':1}, |
There was a problem hiding this comment.
could this tag be 'unassigned'?
There was a problem hiding this comment.
It is capacity that was not re-assigned to csp-ns, but that's likely not to be intuitive. I'll update to unassigned.
| if items_column == 'tech': | ||
| df['tech'] = df['tech'].str.lower().map(lambda x: items_map.get(x, x)) | ||
| df = df.groupby(['tech', 'r', 'year']).sum().reset_index() | ||
| valcols = df.select_dtypes('number').columns.difference(['year']) |
There was a problem hiding this comment.
a little less familiar with this part of ReEDS, so commenting just as a point to double-check... post-processing passed the checks, so we should be good
There was a problem hiding this comment.
I took another look and decided that summing over the resource would also help here. Reviewing these and reproducing outputs for ancillary scripts like this will be a key part of testing after the i set is collapsed.
| cap_exist_i(i)$valcap_i_filt(i) = sum{(r,v), cap_exist(i,v,r) } ; | ||
|
|
||
| cap_ivrt(i,v,r,t)$([not (upv(i) or wind(i))]$valcap(i,v,r,t)$trange(t)) = CAP.l(i,v,r,t) ; | ||
| cap_ivrt(i,c,v,r,t)$[i_c(i,c)$(not (upv(i) or wind(i)))$valcap(i,v,r,t)$trange(t)] = CAP.l(i,v,r,t) ; |
There was a problem hiding this comment.
nitpick: do we actually need to track the class? I wonder if it would make sense to just sum over 'c'... otherwise, do we want to potentially rename this to cap_icvrt? I understand that it might be a little bit of a pain to update downstream in reeds.py etc...
There was a problem hiding this comment.
I think we want to track the class. That way this parameter holds the equivalent information as it does now. Otherwise you wouldn't have the information about which wind/pv/etc. class is getting built.
And I agree with the rename and that it will be a bit of a pain. I'm noting that for a follow-up change as well.
| cap_init(i,v,r)$(distpv(i)$valcap_ivr(i,v,r)) = sum{(c,t)$[i_c(i,c)$tfirst(t)], cap_ivrt(i,c,v,r,t) } ; | ||
| inv_ivrt(i,c,v,r,t)$[i_c(i,c)$valcap(i,v,r,t)$trange(t)] = | ||
| [INV.l(i,v,r,t)$(not rsc_i(i)) | ||
| + sum{rscbin$m_rscfeas(r,i,c,rscbin), INV_RSC.l(i,c,v,r,rscbin,t) }$rsc_i(i) |
There was a problem hiding this comment.
this line prompted me to double-check if $i_c(i,c) is accounted for in m_rscfeas in reeds/core/setup/b_inputs.gms. I think the edits in b_inputs.gms below are optional, given that rscfeas is based on rsc_dat (which only has the relevant i-c pairs), but flagging just in case:
- Lines 1443 -- 1453 (related to 'rscfeas'): Do we need sum{ii$i_c(ii,c)$...], ...}?
- Line 1751, 1753: Do we need sum{ii$[i_c(ii,c)$...], ...}?
- Lines 5258 --5260: I see a few sum{ii, ...}, would we need sum{ii**$i_c(ii,c)**, ...}?
There was a problem hiding this comment.
I checked these, and I think they are ok. There may be some redundancy (e.g., some $ conditionals can be removed without impacting the calculation), but I'd rather not clean those up until the classes are collapsed becaused then it will be easier to see if removing a conditional actually does change things.
|
|
||
| ret_ivrt(i,v,r,t)$([trange(t)$(not tfirst(t))$newv(v)]$valcap(i,v,r,t)) = sum{tt$tprev(t,tt), cap_ivrt(i,v,r,tt)} - cap_ivrt(i,v,r,t) + inv_ivrt(i,v,r,t) ; | ||
| ret_ivrt(i,v,r,t)$([abs(ret_ivrt(i,v,r,t) < 1e-6)]$valcap(i,v,r,t)) = 0 ; | ||
| ret_ivrt(i,c,v,r,t)$[i_c(i,c)$trange(t)$(not tfirst(t))$newv(v)$valcap(i,v,r,t)] = sum{tt$tprev(t,tt), cap_ivrt(i,c,v,r,tt)} - cap_ivrt(i,c,v,r,t) + inv_ivrt(i,c,v,r,t) ; |
There was a problem hiding this comment.
similar nitpick to 'cap_ivrt' here: Do we need to explicitly track 'c' in the outputs? If so, would it make sense to rename into ret_icvrt here or later on?
There was a problem hiding this comment.
Agreed - and saving for a follow-up PR.
|
|
||
| gen_new_uncurt(i,r,h,t)$[(vre(i) or storage_hybrid(i)$(not csp(i)))$valcap_irt(i,r,t)] = | ||
| sum{(v,c)$[valinv(i,v,r,t)$i_c(i,c)], (INV.l(i,v,r,t) + INV_REFURB.l(i,v,r,t)) * m_cf(i,c,v,r,h,t) * hours(h) } | ||
| sum{(v,c)$[i_c(i,c)$valinv(i,v,r,t)], (INV.l(i,v,r,t) + INV_REFURB.l(i,c,v,r,t)) * m_cf(i,c,v,r,h,t) * hours(h) } |
There was a problem hiding this comment.
are we potentially double-counting the INV values by having the sum applied to both INV and INV_REFURB instead of the latter only (like above)?
There was a problem hiding this comment.
No - these are unique, so we will need both.
| INV.l(i,v,r,tt) + INV_REFURB.l(i,v,r,tt)$[refurbtech(i)$Sw_Refurb]}) / ilr(i) ; | ||
|
|
||
| cap_out(i,r,t)$[valcap_irt(i,r,t)$tmodel_new(t)] = sum{v$valcap(i,v,r,t), cap_ivrt(i,v,r,t) } ; | ||
| cap_out(i,c,r,t)$[i_c(i,c)$valcap_irt(i,r,t)$tmodel_new(t)] = sum{v$valcap(i,v,r,t), cap_ivrt(i,c,v,r,t) } ; |
There was a problem hiding this comment.
could sum over 'c' as well and not worry about it in reeds2.py?
There was a problem hiding this comment.
I tried to keep the same information as was in the original data where feasible. Since class information was already available and can be calculation directly, I maintained class in the output.
Summary
(Note: this is getting merged into a dev branch and not into main)
This is the 5th PR toward #185, and adds the resource class index
cto the model outputs and downstream consumers. The class index is still redundant, so each technology maps to only one class.Two other meaningful changes:
INV_REFURBgained a class index because it was necessary for several output parameters.csp-nsis no longer hard-coded toupv_5, but is converted dynamically. This revealed a bug where some of the thecsp-nswas getting dropped in the input processing (this PR fixes that bug).Technical details
Implementation notes
INV_REFURB.lwas added to theSw_RemoveSmallNumbersrounding block in5_varfix.gms. It was previously the only investment variable absent from that block.The previous approach for dealing with csp-ns was both fragile and inaccurate: it subtracted all csp-ns from
upv_5, so it would silently do nothing ifupv_5did not exist in a run.writecapdat.pynow assigns each csp-ns unit to a upv resource class from its capacity factor, using the sameget_class_cf_bounds/assign_classhelpers used for upv itself, and writescap_cspnsby(r,c,t). Because the csp-ns resource is in the best-class areas, the reassignment draws from the best class first and moves into the remaining classes only as needed.report.gmsthen takes csp-ns capacity and generation back out of those same classes,in proportion to the upv capacity present in each. The
csp-nsassignment is also added to theerror_checkparameter.Additional changes
The
excesserror check calculation is added tocompare_cases.pyso that it will display properly if it has a non-zero value.Issues resolved
Partially addresses #185.
Known incompatibilities
The PR breaks R2X, so fixes for that will be needed before this dev branch is merged into main. I've added that information to #185 to track it.
Validation, testing, and comparison report(s)
I verified that before making the
csp-nsfix, the outputs were all the same to within rounding noise. A national USA_defaults case returned bascially identical outputs:The
csp-nschanges model outputs because some of thecsp-nsgeneration was missing from the model. The change is pretty small because the missingcsp-nsgeneration was small:Compare report after
csp-nsfix: results-Main,CSP-nsFix.pptx.Checklist for author
Details to double-check
[ ] Documentation updated if necessaryGeneral information to guide review
Did you use LLM tools (chatbot or copilot) in the preparation of this PR? If so, describe how
Yes. I used Claude Opus 5 to implement the changes in this PR and to do many of the output comparisons. I also used it to diagnose and fix the
csp-nsbug found during this PR.Tag points of contact here if you would like additional review of the relevant parts of the model