FINITO Link - #128
Conversation
…O for resource adequacy calcs
| * remove any FINITO scaling, and then apply ReEDS scaling | ||
| $ifthene.linked_objective Sw_FINITO_Link==1 | ||
| + cost_scale * ( Z_finito.l(t)$t_finito(t) * deflator('2018') / obj_scale ) | ||
| $endif.linked_objective |
There was a problem hiding this comment.
I think it might have to do with your fuel cost reporting. You have $tfuel(t) on biopower and gas price reporting, but not for nuclear and coal.
wesleyjcole
left a comment
There was a problem hiding this comment.
This was interesting to see. Nice work on helping to get this where it is.
You do use "linked model" a lot throughout. It might be nice to be more specific with the language there because we have had other linked models, so those comments can get confusing.
| "h2_usage(r,allh,t)",metric tons/hour,total H2 usage by hour (H2-CT/CC and non-electric H2 consumption),,, | ||
| "load_cat(loadtype,r,t)",MWh,Annual exogenous load by category,,, | ||
| "load_rt(r,t)",MWh,Annual exogenous load,,, | ||
| "loadvar_rt(r,t)",MWh,Annual LOAD during rep periods,,, |
There was a problem hiding this comment.
This says "during rep periods," but you sum over all h, so it includes stress periods as well.
There was a problem hiding this comment.
We restrict h to rep periods at the top of report.gms though:
ReEDS/reeds/core/terminus/report.gms
Lines 121 to 122 in bc80431
But if you want to be extra careful (in case we decide to change that down the road and miss it here), you could add a $h_rep(h) to the sum.
| RE_gen_price_nat(t),2004$/MWh,marginal cost of the national RE generation constraint,,, | ||
| "repbioprice(r,t)",2004$/MMBtu,highest marginal bioprice of utilized bins for each region,,, | ||
| "repgasprice(cendiv,t)",2004$/MMBtu,highest marginal gas price of utilized gas bins for each census division,,, | ||
| "repgasprice_finito(cendiv,allh,t)",2004$/MMBtu,highest marginal gas price of utilized gas bins by hour for each census division when using linked model,,, |
There was a problem hiding this comment.
It's not clear from the comment whether this is the price from ReEDS sent to FINITO or from FINITO sent to ReEDS.
| * TODO: activate after FINITO pricing PR is merged | ||
| * + smax{cfp$[gasp(cfp)$map_cf_fe(cfp,'NG')$valcft(cfp,t)], cf_markup(cfp,'NG','Electric_Power',cendiv,t) } |
There was a problem hiding this comment.
Can this be left out of the code and added as an issue instead?
| * remove any FINITO scaling, and then apply ReEDS scaling | ||
| $ifthene.linked_objective Sw_FINITO_Link==1 | ||
| + cost_scale * ( Z_finito.l(t)$t_finito(t) * deflator('2018') / obj_scale ) | ||
| $endif.linked_objective |
There was a problem hiding this comment.
I think it might have to do with your fuel cost reporting. You have $tfuel(t) on biopower and gas price reporting, but not for nuclear and coal.
| shutil.copy(os.path.join(reeds_path,f), os.path.join(case,f)) | ||
| shutil.copy(f, Path(case, f)) |
There was a problem hiding this comment.
Should this be here twice in a row like this? Is line 107 needed?
| #TODO: why so much shifting? validate the dynamics here with FINITO team | ||
| #TODO: also check against reference quantity |
| demandscen,Demand scenario (see inputs\load),N/A,AEO_2026_baseline, | ||
| dr_shedscen,DR shed scenario,demo_data_January_2025,demo_data_January_2025, | ||
| evmcscen,EVMC scenario for load and supply curve and flexibility,Baseline,Baseline, | ||
| GSw_FINITO_Link,Switch to enable linkage with FINITO,0; 1,0, |
There was a problem hiding this comment.
I think we should stick with convention and keep all the GSw_* switches together, so I think this is worth moving down with the rest of them.
| pras_unitsize_source,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,r2x | ||
| pras_vre_combine,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1 | ||
| pras_samples,,,,,,10,10,10,,,,,,,,,,,,,,,,10,10,,,,,, | ||
| ,Default Value,Pacific,USA_defaults,Mid_Case,USA_decarb,github_Pacific,github_Everything,github_MA_county_CC,Pacific_CC,Pacific_weks,Pacific_full_year,Interday_storage,Pacific_2020,Pacific_rep15,WY_county,WECC_county,PJM_county_CC,NYVT_mixed,OR_water,MonteCarlo_Random,MonteCarlo_LHS,Everything,Simple,USA_fast,USA_faster,MultiMetricRA,Pacific_DR,Pacific_MGA,Pacific_LoadSite95,MARICTNYNJPAOH_Offshore,R2P,FINITO,FINITO_Pacific |
There was a problem hiding this comment.
Why add two FINITO cases here instead of just one?
There was a problem hiding this comment.
Agreed, I would keep it to a single small test case.
| load_finito = load_finito.rename(columns=p2zone).T.groupby(level=0).sum().T | ||
|
|
||
| # allocate annual load to hours, assuming flat demand | ||
| # TODO: should this use h_weight_finito? |
| load_hourly_finito = hourly_load.get_hourly_finito_load(inputs_case) | ||
| load = load + load_hourly_finito | ||
|
|
||
| ### Subset to keep_modelyear if provided | ||
| if keep_modelyear: | ||
| load = load.loc[keep_modelyear].copy() | ||
| ### load.h5 is busbar load, but b_inputs.gms ingests end-use load, so scale down by distloss |
There was a problem hiding this comment.
If I'm reading things right, get_hourly_finito_load returns end-use load, but this comment says load.h5 is bubar load, so I think you are adding end-use load to bus-bar load.
It also would be good to add docstrings to the functions to clarify which this kind of thing.
patrickbrown4
left a comment
There was a problem hiding this comment.
Thanks, it's cool to see this coming together.
I guess I have a set of general concerns about maintainability - questions like:
- What's the proposed approach for keeping the linked models working together? It seems hard when one is not open source.
- How do we know when a change to ReEDS (variable/parameter names, indices, concepts) breaks the FINITO linkage? (We've had such issues with reeds2plexos/r2x; running the r2x test as part of the github runner seems to help a lot.)
- There's a lot of duplication of ReEDS data/code within the FINITO repo; what happens when the ReEDS side changes?
I don't think those concerns should keep us from moving forward, but it might be helpful to discuss as a team what the expectations are. For example, the PR template specifies to run the full cases_test.csv for "changes to model structure, spatial/temporal resolution, or other large changes"; since a FINITO case is now included in cases_test.csv, does that mean everyone who makes big changes to ReEDS should get set up to run FINITO? Or do we leave that to periodic checks by a FINITO developer (in which case maybe we should move it from cases_test.csv into a separate file like cases_examples.csv)?
I do think we should consider moving the new python code to a dedicated reeds.finito module to help with testing/maintenance/organization, though I can't think of a good way to do it for the GAMS code.
| # Collect all arguments for initialize_price_response.py | ||
| initialize_price_response_args = f" -c {casedir} -b {BatchName} -cr {caseSwitches['GSw_Trade_PriceResponse_RefScen']} -l {caseSwitches['GSw_FINITO_Link']}" | ||
| # Call initialize_price_response.py file before starting the runs | ||
| os.system('python ' + initialize_price_response_path + initialize_price_response_args) |
There was a problem hiding this comment.
Can you use subprocess.run() instead? It's recommended over os.system() for new code, and is what we use for running PRAS etc.
|
|
||
| # copy directories | ||
| os.makedirs(casedir_finito, exist_ok=True) | ||
| shutil.copytree(os.path.join(caseSwitches['finito_dir'], 'inputs'),os.path.join(casedir,'finito', 'inputs')) |
There was a problem hiding this comment.
(nitpick) Sorry to quibble but in my opinion, Path makes these a bit more readable since it's shorter
| shutil.copytree(os.path.join(caseSwitches['finito_dir'], 'inputs'),os.path.join(casedir,'finito', 'inputs')) | |
| shutil.copytree(Path(caseSwitches['finito_dir'], 'inputs'), Path(casedir, 'finito', 'inputs')) |
| def get_load(inputs_case, sw, keep_modelyear=None, keep_weatheryears=[2012]): | ||
| """ | ||
| """ | ||
| ### Subset to modeled regions | ||
| load = reeds.io.read_file(os.path.join(inputs_case,'load.h5')) | ||
|
|
||
| ### When running the linked model (GSw_FINITO_Link=1) we add reference load estimates | ||
| ### for FINITO load back in to use when selecting representative periods |
There was a problem hiding this comment.
(nitpick) sw is so fast to load that I leave it out of required arguments if case/inputs_case is already included; that simplifies testing and ensures the inputs are in sync. (Would need to adjust the call in reedsplots.plot_seed_stressperiods() too.)
| def get_load(inputs_case, sw, keep_modelyear=None, keep_weatheryears=[2012]): | |
| """ | |
| """ | |
| ### Subset to modeled regions | |
| load = reeds.io.read_file(os.path.join(inputs_case,'load.h5')) | |
| ### When running the linked model (GSw_FINITO_Link=1) we add reference load estimates | |
| ### for FINITO load back in to use when selecting representative periods | |
| def get_load(inputs_case, keep_modelyear=None, keep_weatheryears=[2012]): | |
| """ | |
| """ | |
| ### Subset to modeled regions | |
| load = reeds.io.read_file(os.path.join(inputs_case,'load.h5')) | |
| ### When running the linked model (GSw_FINITO_Link=1) we add reference load estimates | |
| ### for FINITO load back in to use when selecting representative periods | |
| sw = reeds.io.get_switches(inputs_case) |
|
|
||
| ### Rewrite the finito scalar as GAMS-readable definitions | ||
| if int(sw['GSw_FINITO_Link']) == 1: | ||
| pd.read_csv(os.path.join(os.path.dirname(inputs_case),'finito','inputs','scalars.csv')).iloc[:, :-1].to_csv(os.path.join(os.path.dirname(inputs_case),'finito','inputs','scalars.csv'),index=False) |
There was a problem hiding this comment.
Agreed, it seems risky to me to rely on position. I would explicitly specify the entries to be dropped (ignoring errors if they aren't there).
| "h2_usage(r,allh,t)",metric tons/hour,total H2 usage by hour (H2-CT/CC and non-electric H2 consumption),,, | ||
| "load_cat(loadtype,r,t)",MWh,Annual exogenous load by category,,, | ||
| "load_rt(r,t)",MWh,Annual exogenous load,,, | ||
| "loadvar_rt(r,t)",MWh,Annual LOAD during rep periods,,, |
There was a problem hiding this comment.
We restrict h to rep periods at the top of report.gms though:
ReEDS/reeds/core/terminus/report.gms
Lines 121 to 122 in bc80431
But if you want to be extra careful (in case we decide to change that down the road and miss it here), you could add a $h_rep(h) to the sum.
| * ----- ReEDS-FINITO temporal parameters ----- | ||
| *============================================= | ||
| $ifthene.linked_finito_temporal_params %GSw_FINITO_Link% ==1 | ||
| $include finito/model/linked_finito_temporal_params.gms |
There was a problem hiding this comment.
At least I assume this change is required for Windows, but if not we should get rid of %ds% everywhere (in a separate PR)
| $include finito/model/linked_finito_temporal_params.gms | |
| $include finito%ds%model%ds%linked_finito_temporal_params.gms |
| *** Fix FINITO decision variables to their optimized levels for this solve year | ||
| $ifthene.linked_finito_varfix %GSw_FINITO_Link% == 1 | ||
| * Industrial variable fix | ||
| $include finito/model/finito_varfix.gms |
There was a problem hiding this comment.
| $include finito/model/finito_varfix.gms | |
| $include finito%ds%model%ds%finito_varfix.gms |
|
|
||
| *gas used from each bin is the sum of all gas used | ||
| eq_gasused(cendiv,h,t)$[tmodel(t)$((Sw_GasCurve=0) or (Sw_GasCurve=3))].. | ||
| eq_gasused(cendiv,h,t)$[tmodel(t)$tfuel(t)$((Sw_GasCurve=0) or (Sw_GasCurve=3))].. |
There was a problem hiding this comment.
Note that I didn't try to check for cases where tfuel(t) should be applied but is not
| # Validation check: FINITO reference load should not exceed baseline load. | ||
| # If it does, clip to zero and report |
There was a problem hiding this comment.
Should this raise an exception instead?
| with open(cases_linked_path, 'r', newline='') as csvfile: | ||
| reader = csv.reader(csvfile) | ||
| header = next(reader) |
There was a problem hiding this comment.
Both are fine but a one-line would be pd.read_csv(cases_linked_path, header=None).iloc[0].values

Summary
This PR provides a linkage to the FINITO model, which is currently a private repository at https://github.com/NatLabRockies/FINITO). The linkage allows the full electricity sector detail of the ReEDS model to be used in combination with the industrial (and other) sector representation of the FINITO model.
The linkage is turned off by default, so this PR itself should not have impact on ReEDS results (although very small differences were observed in out years, presumably because the ReEDS model itself was touched, even if the matrix is identical).
For a high-level overview of FINITO and the linkage points see this slide deck.
See companion FINITO PR: https://github.com/NatLabRockies/FINITO/pull/438
Technical details
Turning on the linkage adds the constraints of the FINITO model to those of the ReEDS model, and adds the two objective functions together.
To turn on the linkage, clone the FINITO model and checkout the
bs/reeds_link2branch, unless this branch has already been merged intomain, at which pointmain.Configure these switches:
GSw_FINITO_Link: This switch turns on the linkagefinito_dir: This switch needs to point to the FINITO directory as a complete, absolute pathfinito_cases_file: This switch should be the suffix of the cases file in the FINITO directory, which contains the corresponding FINITO scenario configurationfinito_case: This switch is the specific case infinito_cases_fileto be runfirst_year_finito: This switch is the first year that the linkage will be implementedThe
reedsconda environment should be used for the linked model.Linked runs result in the the same set of ReEDS outputs, but new outputs are also added from FINITO in the
finitosubdirectory of the run directory.Relevant sources or documentation
FINITO documentation is at https://github.com/NatLabRockies/FINITO
Validation, testing, and comparison report(s)
A comparison of default (Ref) was made berfore and after this change, showing no changes, as expected:
reeds_report_ref_2026-08-05.zip
Running the USA_fast case with the linkage turned on results in moderate generation differences to the ReEDS standalone "USA_fast", but similar amonts of fossil and renewables by 2050:

Electricity load differences show that FINITO endogenous industrial electricity demand is lower than the reference demand that was removed from the EER load profiles, even in the near term:

Gas price differences align with gas generation differences by 2050, although alignment isn't perfect for the years before:

See more results of the comparison here:
reeds_report_finito_vs_usa-fast_plus4_2026-08-08.zip
Also, a more thorough comparison of the reference industrial electricity load that was removed from ReEDS load profiles, as compared to the endogenous finito load that was added back in, is here:
finito_load_check_2026-08-06.zip
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
Tag points of contact here if you would like additional review of the relevant parts of the model