From 436ade06d57c566aa1e90419b33b22492b804b36 Mon Sep 17 00:00:00 2001 From: bsergi Date: Wed, 15 Apr 2026 14:35:39 -0400 Subject: [PATCH 01/78] branch transfer finito linkage --- ReEDS_Augur/prep_data.py | 32 +++- b_inputs.gms | 50 ++++-- c_supplymodel.gms | 88 ++++++++-- c_supplyobjective.gms | 49 +++--- cases.csv | 6 + cases_finito.csv | 46 +++++ createmodel.gms | 14 ++ d1_temporal_params.gms | 13 +- d2_varfix.gms | 6 + d3_data_dump.gms | 18 +- d_solveoneyear.gms | 19 +++ e_report.gms | 141 ++++++++++----- e_report_dump.py | 27 ++- e_report_params.csv | 1 + input_processing/copy_files.py | 5 + input_processing/hourly_load.py | 47 +++++ input_processing/hourly_repperiods.py | 27 ++- input_processing/hourly_writetimeseries.py | 3 + inputs/emission_constraints/co2_tax.csv | 84 ++++----- reeds/reedsplots.py | 1 + restart_runs.py | 14 +- runbatch.py | 189 ++++++++++++++++++++- 22 files changed, 716 insertions(+), 164 deletions(-) create mode 100644 cases_finito.csv diff --git a/ReEDS_Augur/prep_data.py b/ReEDS_Augur/prep_data.py index 8f4febfcc..9805767d1 100644 --- a/ReEDS_Augur/prep_data.py +++ b/ReEDS_Augur/prep_data.py @@ -147,8 +147,38 @@ def main(t, casedir, iteration=0): h_dt_szn.index.map(hmap_allyrs.set_index(['year', 'hour'])['*timestamp'])) h_dt_szn = h_dt_szn.reset_index().set_index('timestamp') + # load exogenous demand seen by ReEDS load = reeds.io.read_file(os.path.join(inputs_case, 'load.h5'), parse_timestamps=True) + load_year = load.loc[t] + + # when running linked model with FINITO (GSw_FINITO_Link=1), + # we also need to add in the load from FINITO + if int(sw.GSw_FINITO_Link): + load_finito_rt = gdxreeds['load_finito_rt'].rename(columns={'allh':'h', 'Value':'load_MW'}) + + # down select to relevant model year + load_finito = load_finito_rt.loc[load_finito_rt.t==t].drop('t', axis=1).copy() + #TODO: why so much shifting? validate the dynamics here with FINITO team + #TODO: also check against reference quantity + + # map from rep day to actual hour + # since we don't have multi-year profiles for FINITO load + # we assume they repeat across all weather years + # TODO: test that this works with multiple regions + load_finito = pd.merge(load_finito, h_dt_szn.reset_index(), on='h', how='outer')[['timestamp', 'r', 'load_MW']] + load_finito = load_finito.rename(columns={'timestamp':'datetime'}) + # add model year back in + #load_finito = load_finito.assign(year=t) + load_finito = load_finito.pivot(index=['datetime'], columns='r', values='load_MW') + + # convert timezone and fill any missing columns + load_finito.index = load_finito.index.tz_convert(load_year.index.tz) + load_finito = load_finito.reindex(columns=load_year.columns, fill_value=0) + + # add to load + load_new = load_year + load_finito + resources = pd.read_csv(os.path.join(inputs_case, 'resources.csv')) recf = reeds.io.read_file(os.path.join(inputs_case, 'recf.h5'), parse_timestamps=True) recf.columns = pd.MultiIndex.from_tuples([tuple(x.split('|')) for x in recf.columns], @@ -367,7 +397,7 @@ def intify(v): .merge(h_dt_szn_load_years[['h']].reset_index(), left_on='allh', right_on='h') .pivot(index='timestamp', columns='r', values='Value') ) - load_year = load.loc[t].add(can_exports, fill_value=0) + load_year = load_year.add(can_exports, fill_value=0) ### PRAS doesn't yet handle flexible load, so include all H2/DAC load in the ### version we write for PRAS diff --git a/b_inputs.gms b/b_inputs.gms index 0d59e5626..3c135f247 100644 --- a/b_inputs.gms +++ b/b_inputs.gms @@ -1341,7 +1341,8 @@ set tmodel(t) "years to include in the model", tfix(t) "years to fix variables over when summing over previous years", tprev(t,tt) "previous modeled tt from year t", stfeas(st) "states to include in the model", - tsolved(t) "years that have solved" ; + tsolved(t) "years that have solved", + tfuel(t) "years that use ReEDS fuel supply curve module (otherwise uses supply curves in FINITO)" ; *following parameters get re-defined when the solve years have been declared parameter mindiff(t) "minimum difference between t and all other tt that are in tmodel(t)" ; @@ -1354,7 +1355,7 @@ tfix(t) = no ; stfeas(st) = no ; tprev(t,tt) = no ; tsolved(t) = no ; - +tfuel(t)=no; *============================== * Year specification @@ -1379,6 +1380,12 @@ tprev(t,tt)$[tmodel_new(t)$tmodel_new(tt)$(tt.valmindiff(t))] = no ; +* If FINITO linkage is on, remove all modeled years from tfuel after first_year_finito +* as the FINITO supply curves will be used instead of those in ReEDS; +* otherwise, all modeled years use ReEDS supply curves and are eligible for tfuel +tfuel(t)$[tmodel_new(t)]=yes; +tfuel(t)$[tmodel_new(t)$Sw_FINITO_Link$(t.val>=%first_year_finito%)] = no ; + * In order to fill all necessary dimensions of upgrade techs parameters, we require * Sw_UpgradeYear in ban(i) to be a modeled year and thus we compute as either * the GSw_UpgradeYear option or the next modeled years after GSw_UpgradeYear @@ -2331,6 +2338,7 @@ noncumulative_prescriptions(pcat,r,t)$tmodel_new(t) ], prescribednonrsc(tt,pcat,r,"value") + prescribedrsc(tt,pcat,r,"value") } ; +noncumulative_prescriptions(pcat,r,t)$noncumulative_prescriptions(pcat,r,t) = round(noncumulative_prescriptions(pcat,r,t),6); parameter noncumulative_prescriptions_energy(pcat,r,t) "--MWh-- prescribed energy capacity that comes online in a given year" ; noncumulative_prescriptions_energy(pcat,r,t)$tmodel_new(t) @@ -2777,6 +2785,7 @@ h2_ptc(i,v,r,t)$valcap(i,v,r,t) = h2_ptc_in(i,v,t) ; * Otherwise, we assume it receives $0/kg because the cleanliness of its carbon cannot be proven h2_ptc("electrolyzer",v,r,t)$[(not Sw_H2_PTC)] = 0; + set h2_ptc_years(t) "years in which the hydrogen production incentive is active"; h2_ptc_years(t) = tmodel_new(t)$[sum{(i,v,r),h2_ptc(i,v,r,t)}]; @@ -3571,6 +3580,7 @@ $include inputs_case%ds%tsc_binwidth.csv $offdelim $onlisting / ; +tsc_binwidth(r,rr,tscbin) = 1e9; parameter tsc_forward(r,rr,tscbin) "--$/MW-- transmission upgrade cost for forward direction" / @@ -6543,22 +6553,25 @@ $offdelim $ondigit $onlisting / , - min_co2_spurline_distance "--mi-- minimum distance for a spur line (used to provide a floor for pipeline distances in r_cs_distance)" + min_co2_spurline_distance "--mi-- minimum distance for a spur line (used to provide a floor for pipeline distances in r_cs_distance)", + min_r_cs_distance(r) "--mi-- mininum euclidean distance between BA transmission endpoints and storage formations" ; $offempty -* Wherever BA centroids fall within formation boundaries, assume some average spur line distance to connect a CCS or DAC plant with an injection site -min_co2_spurline_distance = 20 ; -r_cs_distance(r,cs)$[r_cs_distance(r,cs) < min_co2_spurline_distance] = min_co2_spurline_distance ; - -* Assign spurline costs -cost_co2_spurline_cap(r,cs,t)$[r_cs(r,cs)$tmodel_new(t)] = Sw_CO2_spurline_cost * r_cs_distance(r,cs) ; +* Wherever BA centroids fall within formation boundaries, r_cs_distance will be equal to 0, to ensure these are not dropped from the set, set these elements to a small number +r_cs_distance(r,cs)$[r_cs(r,cs)$(r_cs_distance(r,cs) = 0)] = 1e-3 ; +* find the closest site to each region +min_r_cs_distance(r) = smin(cs$[r_cs(r,cs)], r_cs_distance(r,cs)); -* CO2 pipelines can be build between any two adjacent BAs -cost_co2_pipeline_cap(r,rr,t)$[routes_adjacent(r,rr)$tmodel_new(t)] = Sw_CO2_pipeline_cost * pipeline_distance(r,rr) ; -cost_co2_pipeline_fom(r,rr,t)$[routes_adjacent(r,rr)$tmodel_new(t)] = Sw_CO2_pipeline_fom * pipeline_distance(r,rr) ; +$ifthene.rcslimit %GSw_CO2_LimitStorageSites% == 1 +* remove the region site combinations that are not the closest +r_cs_distance(r,cs)$[r_cs(r,cs)$(r_cs_distance(r,cs) <= min_r_cs_distance(r))] = min_r_cs_distance(r) ; +r_cs_distance(r,cs)$[r_cs(r,cs)$(r_cs_distance(r,cs) > min_r_cs_distance(r))] = 0 ; +$endif.rcslimit -co2_routes(r,rr)$routes_adjacent(r,rr) = yes ; +* Wherever BA centroids fall within formation boundaries, assume some average spur line distance to connect a CCS or DAC plant with an injection site +min_co2_spurline_distance = 20 ; +r_cs_distance(r,cs)$[r_cs_distance(r,cs)$(r_cs_distance(r,cs) <= min_co2_spurline_distance )] = min_co2_spurline_distance ; $onempty table co2_char(cs,*) "co2 site characteristics including injection rate limit, total storage limit, and break even cost" @@ -6577,9 +6590,18 @@ cost_co2_stor_bec(cs,t) = co2_char(cs,"bec_%GSw_CO2_BEC%"); csfeas(cs)$[co2_storage_limit(cs)$co2_injection_limit(cs)] = yes ; * only want to consider r_cs pairs which have available capacity r_cs(r,cs)$[not csfeas(cs)] = no ; +r_cs(r,cs)$[not r_cs_distance(r,cs)] = no ; +* Assign spurline costs +cost_co2_spurline_cap(r,cs,t)$[r_cs(r,cs)$tmodel_new(t)] = Sw_CO2_spurline_cost * r_cs_distance(r,cs) ; cost_co2_spurline_fom(r,cs,t)$[r_cs(r,cs)$tmodel_new(t)] = Sw_CO2_spurline_fom * r_cs_distance(r,cs) ; +* CO2 pipelines can be build between any two adjacent BAs +cost_co2_pipeline_cap(r,rr,t)$[routes_adjacent(r,rr)$tmodel_new(t)] = Sw_CO2_pipeline_cost * pipeline_distance(r,rr) ; +cost_co2_pipeline_fom(r,rr,t)$[routes_adjacent(r,rr)$tmodel_new(t)] = Sw_CO2_pipeline_fom * pipeline_distance(r,rr) ; + +co2_routes(r,rr)$routes_adjacent(r,rr) = yes ; + cost_co2_pipeline_cap(r,rr,t) = %GSw_CO2_CostAdj% * cost_co2_pipeline_cap(r,rr,t); cost_co2_pipeline_fom(r,rr,t) = %GSw_CO2_CostAdj% * cost_co2_pipeline_fom(r,rr,t); cost_co2_stor_bec(cs,t) = %GSw_CO2_CostAdj% * cost_co2_stor_bec(cs,t) ; @@ -6771,4 +6793,4 @@ emit_rate(etype,e,i,v,r,t)$[not valgen(i,v,r,t)] = 0 ; *============================================================ valinv_init(i,v,r,t) = valinv(i,v,r,t) ; -valcap_init(i,v,r,t) = valcap(i,v,r,t) ; +valcap_init(i,v,r,t) = valcap(i,v,r,t) ; \ No newline at end of file diff --git a/c_supplymodel.gms b/c_supplymodel.gms index 029d53f10..71389843d 100644 --- a/c_supplymodel.gms +++ b/c_supplymodel.gms @@ -396,6 +396,14 @@ eq_loadcon(r,h,t)$tmodel(t).. * (the effect is the same but avoiding the h-indexed OP_LOADSITE reduces solve time) + OP_LOADSITE(r,h,t)$[Sw_LoadSiteCF$(Sw_LoadSiteCF<1)$val_loadsite(r)] + CAP_LOADSITE(r,t)$[(Sw_LoadSiteCF=1)$val_loadsite(r)] + +* [plus] load for industrial and converted fuel facilities (FINITO), +* including the PRM for stress periods +* TODO: should this include distloss? +* [MWh/hr = MW] +$ifthene.linked_load Sw_FINITO_Link==1 + + USE_ELE_FINITO(r,h,t)$[t_finito(t)] * (1 + prm(r,t)$h_stress(h)) +$endif.linked_load ; * --------------------------------------------------------------------------- @@ -1040,7 +1048,6 @@ eq_rsc_INVlim(r,i,rscbin,t)$[tmodel(t) *plus exogenous (pre-start-year) capacity, using its level in the first year (tfirst) + sum{(ii,v,tt)$[tfirst(tt)$rsc_agg(i,ii)$exog_rsc(i)], capacity_exog_rsc(ii,v,r,rscbin,tt) } - ; * --------------------------------------------------------------------------- @@ -2881,7 +2888,7 @@ eq_national_gen(t)$[tmodel(t)$national_gen_frac(t)$Sw_GenMandate].. * --------------------------------------------------------------------------- *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))].. sum{gb,GASUSED(cendiv,gb,h,t) } @@ -2898,7 +2905,7 @@ eq_gasused(cendiv,h,t)$[tmodel(t)$((Sw_GasCurve=0) or (Sw_GasCurve=3))].. * --------------------------------------------------------------------------- * gas from each bin needs to less than its capacity -eq_gasbinlimit(cendiv,gb,t)$[tmodel(t)$(Sw_GasCurve=0)].. +eq_gasbinlimit(cendiv,gb,t)$[tmodel(t)$tfuel(t)$(Sw_GasCurve=0)].. gaslimit(cendiv,gb,t) @@ -2909,7 +2916,7 @@ eq_gasbinlimit(cendiv,gb,t)$[tmodel(t)$(Sw_GasCurve=0)].. * --------------------------------------------------------------------------- -eq_gasbinlimit_nat(gb,t)$[tmodel(t)$(Sw_GasCurve=3)].. +eq_gasbinlimit_nat(gb,t)$[tmodel(t)$tfuel(t)$(Sw_GasCurve=3)].. gaslimit_nat(gb,t) @@ -2922,7 +2929,7 @@ eq_gasbinlimit_nat(gb,t)$[tmodel(t)$(Sw_GasCurve=3)].. * --------------------------------------------------------------------------- -eq_gasaccounting_regional(cendiv,t)$[tmodel(t)$(Sw_GasCurve=1)].. +eq_gasaccounting_regional(cendiv,t)$[tmodel(t)$tfuel(t)$(Sw_GasCurve=1)].. sum{fuelbin, VGASBINQ_REGIONAL(fuelbin,cendiv,t) } @@ -2935,7 +2942,7 @@ eq_gasaccounting_regional(cendiv,t)$[tmodel(t)$(Sw_GasCurve=1)].. * --------------------------------------------------------------------------- -eq_gasaccounting_national(t)$[tmodel(t)$(Sw_GasCurve=1)].. +eq_gasaccounting_national(t)$[tmodel(t)$tfuel(t)$(Sw_GasCurve=1)].. sum{fuelbin,VGASBINQ_NATIONAL(fuelbin,t) } @@ -2948,7 +2955,7 @@ eq_gasaccounting_national(t)$[tmodel(t)$(Sw_GasCurve=1)].. * --------------------------------------------------------------------------- -eq_gasbinlimit_regional(fuelbin,cendiv,t)$[tmodel(t)$(Sw_GasCurve=1)].. +eq_gasbinlimit_regional(fuelbin,cendiv,t)$[tmodel(t)$tfuel(t)$(Sw_GasCurve=1)].. Gasbinwidth_regional(fuelbin,cendiv,t) @@ -2959,7 +2966,7 @@ eq_gasbinlimit_regional(fuelbin,cendiv,t)$[tmodel(t)$(Sw_GasCurve=1)].. * --------------------------------------------------------------------------- -eq_gasbinlimit_national(fuelbin,t)$[tmodel(t)$(Sw_GasCurve=1)].. +eq_gasbinlimit_national(fuelbin,t)$[tmodel(t)$tfuel(t)$(Sw_GasCurve=1)].. Gasbinwidth_national(fuelbin,t) @@ -2973,10 +2980,10 @@ eq_gasbinlimit_national(fuelbin,t)$[tmodel(t)$(Sw_GasCurve=1)].. *============================== * -- Bioenergy Supply Curve -- *============================== +* defer to FINITO representation when models are linked * --------------------------------------------------------------------------- - -eq_bioused(r,t)$[sum{(i,v)$(bio(i) or cofire(i)), valgen(i,v,r,t) }$tmodel(t)].. +eq_bioused(r,t)$[sum{(i,v)$(bio(i) or cofire(i)), valgen(i,v,r,t) }$tmodel(t)$tfuel(t)].. sum{bioclass, BIOUSED(bioclass,r,t) } @@ -2995,7 +3002,7 @@ eq_bioused(r,t)$[sum{(i,v)$(bio(i) or cofire(i)), valgen(i,v,r,t) }$tmodel(t)].. * --------------------------------------------------------------------------- * biomass consumption limit is annual -eq_biousedlimit(bioclass,usda_region,t)$tmodel(t).. +eq_biousedlimit(bioclass,usda_region,t)$[tmodel(t)$tfuel(t)].. biosupply(usda_region,bioclass,"cap") @@ -3565,6 +3572,11 @@ eq_h2_demand(p,t)$[(sameas(p,"H2"))$tmodel(t)$(yeart(t)>=h2_demand_start)$(Sw_H2 + sum{(i,v,r,h)$[valgen(i,v,r,t)$h2_combustion(i)$h_rep(h)], GEN(i,v,r,h,t) * hours(h) * h2_combustion_intensity * heat_rate(i,v,r,t) } + +* hydrogen demand from indusrty (FINITO): FINITO demand [MMBtu/yr] * conversion [metric tons-H2/MMBtu-H2] +$ifthene.linked_h2_nat Sw_FINITO_Link==1 + + [sum{(r,h)$h_rep(h), hours(h) * USE_H2_FINITO(r,h,t) * h2_metric_tons_per_mmbtu }]$t_finito(t) +$endif.linked_h2_nat ; * --------------------------------------------------------------------------- @@ -3596,6 +3608,11 @@ eq_h2_demand_regional(r,h,t) + sum{(i,v)$[valgen(i,v,r,t)$h2_combustion(i)], GEN(i,v,r,h,t) * h2_combustion_intensity * heat_rate(i,v,r,t) } + +* regional hydrogen demand for industry (FINITO) +$ifthene.linked_h2_reg Sw_FINITO_Link==1 + + [ USE_H2_FINITO(r,h,t) * h2_metric_tons_per_mmbtu ]$t_finito(t) +$endif.linked_h2_reg ; * --------------------------------------------------------------------------- @@ -3838,11 +3855,16 @@ eq_co2_capture(r,h,t) * capture from DAC + sum{(i,v)$[dac(i)$valcap(i,v,r,t)$i_p(i,"DAC")], PRODUCE("DAC",i,v,r,h,t) }$Sw_DAC +* (linked) capture from industry [metric_tons-CO2/hr]: +* calculation: hours_per_year [yrs/hr] * capture [scaled_metric_tons-CO2/yr] / co2_scale [scaled_metric_tons-CO2/metric_tons-CO2] +$ifthene.linked_co2_capture Sw_FINITO_Link==1 + + [CAPTURE_CO2EM(r,h,t) / co2_scale]$[t_finito(t)$h_rep(h)] +$endif.linked_co2_capture ; * --------------------------------------------------------------------------- -eq_co2_transport_caplimit(r,rr,h,t)$[co2_routes(r,rr)$Sw_CO2_Detail +eq_co2_transport_caplimit(r,rr,h,t)$[co2_routes(r,rr)$Sw_CO2_Detail$h_rep(h) $tmodel(t)$(yeart(t)>=co2_detail_startyr)].. *capacity computed as cumulative investments of co2 pipelines up to the current year @@ -3858,7 +3880,8 @@ eq_co2_transport_caplimit(r,rr,h,t)$[co2_routes(r,rr)$Sw_CO2_Detail * --------------------------------------------------------------------------- -eq_co2_spurline_caplimit(r,cs,h,t)$[Sw_CO2_Detail$r_cs(r,cs)$tmodel(t)$(yeart(t)>=co2_detail_startyr)].. +eq_co2_spurline_caplimit(r,cs,h,t)$[Sw_CO2_Detail$r_cs(r,cs)$h_rep(h)$tmodel(t) + $(yeart(t)>=co2_detail_startyr)].. *capacity computed as cumulative investments of co2 spurlines up to the current year sum{tt$[(yeart(tt)<=yeart(t))$(tmodel(tt) or tfix(tt))$(yeart(tt)>=co2_detail_startyr)], @@ -3867,11 +3890,16 @@ eq_co2_spurline_caplimit(r,cs,h,t)$[Sw_CO2_Detail$r_cs(r,cs)$tmodel(t)$(yeart(t) =g= CO2_STORED(r,cs,h,t) +* (linked) extraction of CO2 [metric_tons-CO2/hr] +* calculation: hours_per_year [yrs/hr] * (1 / co2_scale [scaled_metric_tons-CO2/metric_tons-CO2]) * use [scaled_metric_tons-CO2/yr] +$ifthene.linked_co2_spurline_caplimit Sw_FINITO_Link==1 + + [ (1 / co2_scale) * EXTRACT_CO2_CS(cs,r,h,t) ]$[h_rep(h)$t_finito(t)] +$endif.linked_co2_spurline_caplimit ; * --------------------------------------------------------------------------- -eq_co2_sink(r,h,t)$[tmodel(t)$Sw_CO2_Detail$(yeart(t)>=co2_detail_startyr)].. +eq_co2_sink(r,h,t)$[tmodel(t)$Sw_CO2_Detail$h_rep(h)$(yeart(t)>=co2_detail_startyr)].. *the amount of co2 stored from r in all of its cs sites sum{cs$r_cs(r,cs), CO2_STORED(r,cs,h,t) } @@ -3886,11 +3914,24 @@ eq_co2_sink(r,h,t)$[tmodel(t)$Sw_CO2_Detail$(yeart(t)>=co2_detail_startyr)].. * net trade + sum{rr$co2_routes(r,rr), CO2_FLOW(rr,r,h,t) - CO2_FLOW(r,rr,h,t) } + +* (linked) extraction - use of CO2 [metric_tons-CO2/hr] +*calculation: hours_per_year [yrs/hr] * (1 / co2_scale [scaled_metric_tons-CO2/metric_tons-CO2]) * extraction/use [scaled_metric_tons-CO2/yr] +$ifthene.linked_co2_sink Sw_FINITO_Link==1 + + (1 / co2_scale) * [ +* extraction from all cs sites in r + + sum{cs$[csfeas(cs)$r_cs(r,cs)], EXTRACT_CO2_CS(cs,r,h,t)} +* total use of CO2, equivalent to supply + - USE_CO2(r,h,t) + ]$[h_rep(h)$t_finito(t)] +$endif.linked_co2_sink + ; * --------------------------------------------------------------------------- -eq_co2_injection_limit(cs,h,t)$[Sw_CO2_Detail$tmodel(t)$(yeart(t)>=co2_detail_startyr)$csfeas(cs)].. +eq_co2_injection_limit(cs,h,t)$[tmodel(t)$Sw_CO2_Detail$h_rep(h) + $(yeart(t)>=co2_detail_startyr)$csfeas(cs)].. * exogenously defined injection limit co2_injection_limit(cs) @@ -3899,6 +3940,14 @@ eq_co2_injection_limit(cs,h,t)$[Sw_CO2_Detail$tmodel(t)$(yeart(t)>=co2_detail_st * must exceed metric tons per hour entering storage sum{r$r_cs(r,cs), CO2_STORED(r,cs,h,t) } + + + +* (linked) extraction of CO2 for use [metric_tons-CO2/hr] +* calculation: hours_per_year [yrs/hr] * (1 / co2_scale [scaled_metric_tons-CO2/metric_tons-CO2]) * use [scaled_metric_tons-CO2/yr] +$ifthene.linked_co2_injection_limit Sw_FINITO_Link==1 + + (1 / co2_scale) * sum{r$[r_cs(r,cs)], EXTRACT_CO2_CS(cs,r,h,t) }$[h_rep(h)$t_finito(t)] +$endif.linked_co2_injection_limit ; * --------------------------------------------------------------------------- @@ -3915,7 +3964,16 @@ eq_co2_cumul_limit(cs,t)$[tmodel(t)$Sw_CO2_Detail$(yeart(t)>=co2_detail_startyr) $[(yeart(tt)<=yeart(t))$(tmodel(tt) or tfix(tt))$(yeart(tt)>=co2_detail_startyr) $r_cs(r,cs)$h_rep(h)], yearweight(tt) * hours(h) * CO2_STORED(r,cs,h,tt) } + +* (linked) cumulative amount extracted over time +$ifthene.linked_co2_storage_cumul_limit Sw_FINITO_Link==1 + - sum{(r,h,tt) + $[(yeart(tt)<=yeart(t))$(tmodel(tt) or tfix(tt))$(yeart(tt)>=co2_detail_startyr) + $r_cs(r,cs)$(tfuel(tt))], + yearweight(tt) * hours(h) * EXTRACT_CO2_CS(cs,r,h,tt) }$[t_finito(t)] +$endif.linked_co2_storage_cumul_limit ; + * --------------------------------------------------------------------------- *=================== diff --git a/c_supplyobjective.gms b/c_supplyobjective.gms index 38a5b0b36..a59553990 100644 --- a/c_supplyobjective.gms +++ b/c_supplyobjective.gms @@ -19,7 +19,17 @@ Variable Z "--$-- total cost of operations and investment, scale varie * objective function is the sum over modeled years of the investment * and operations components -eq_ObjFn.. Z =e= cost_scale * sum{t$tmodel(t), Z_inv(t) + Z_op(t) } ; +eq_ObjFn.. Z =e= cost_scale * ( +* electricity and H2 costs + sum{t$tmodel(t), Z_inv(t) + Z_op(t) } +* economy-wide costs from FINITO: deflate from $2018 to $2004 +* and remove any FINITO scaling +$ifthene.linked_objective Sw_FINITO_Link==1 + + deflator('2018') / obj_scale + * sum{t$[t_finito(t)], Z_finito(t) } +$endif.linked_objective + ) +; *======================================================= * -- Investment component of the objective function -- @@ -221,7 +231,7 @@ eq_Objfn_op(t)$tmodel(t).. * plus cost of H2 fuel when using fixed price (Sw_H2=0) or during stress periods. * When using endogenous H2 price (Sw_H2=1 or Sw_H2=2), H2 fuel cost is captured elsewhere * via the capex + opex costs of H2 production and its associated electricity demand. - + sum{(i,v,r,h)$[valgen(i,v,r,t)$heat_rate(i,v,r,t) + + sum{(i,v,r,h)$[tfuel(t)$valgen(i,v,r,t)$heat_rate(i,v,r,t) $(not gas(i))$(not bio(i))$(not cofire(i)) $((not h2_combustion(i)) or h2_combustion(i)$[(Sw_H2=0) or h_stress(h)])], hours(h) * heat_rate(i,v,r,t) * fuel_price(i,r,t) * GEN(i,v,r,h,t) } @@ -232,50 +242,49 @@ eq_Objfn_op(t)$tmodel(t).. * --cofire coal consumption--- * cofire bio consumption already accounted for in accounting of BIOUSED - + sum{(i,v,r,h)$[valgen(i,v,r,t)$cofire(i)$heat_rate(i,v,r,t)], + + sum{(i,v,r,h)$[tfuel(t)$valgen(i,v,r,t)$cofire(i)$heat_rate(i,v,r,t)], (1-bio_cofire_perc) * hours(h) * heat_rate(i,v,r,t) * fuel_price("coal-new",r,t) * GEN(i,v,r,h,t) } * --- cost of natural gas--- *Sw_GasCurve = 2 (static natural gas prices) *first - gas consumed for electricity generation - + sum{(i,v,r,h)$[valgen(i,v,r,t)$gas(i)$heat_rate(i,v,r,t)$(Sw_GasCurve = 2)], + + sum{(i,v,r,h)$[tfuel(t)$valgen(i,v,r,t)$gas(i)$heat_rate(i,v,r,t)$(Sw_GasCurve = 2)], hours(h) * heat_rate(i,v,r,t) * fuel_price(i,r,t) * GEN(i,v,r,h,t) } *second - gas consumed by gas-powered DAC - + sum{(v,r,h)$[valcap("dac_gas",v,r,t)$(Sw_GasCurve = 2)], - hours(h) * dac_gas_cons_rate("dac_gas",v,t) * PRODUCE("DAC","dac_gas",v,r,h,t) }$Sw_DAC_Gas + + sum{(v,r,h)$[tfuel(t)$valcap("dac_gas",v,r,t)$(Sw_GasCurve = 2)], + hours(h) * dac_gas_cons_rate("dac_gas",v,t) * PRODUCE("DAC","dac_gas",v,r,h,t) }$[Sw_DAC_Gas] *Sw_GasCurve = 0 (census division supply curves natural gas prices) - + sum{(cendiv,gb), sum{h, hours(h) * GASUSED(cendiv,gb,h,t) } + + sum{(cendiv,gb)$[tfuel(t)], sum{h, hours(h) * GASUSED(cendiv,gb,h,t) } * gasprice(cendiv,gb,t) - }$(Sw_GasCurve = 0) + }$[(Sw_GasCurve = 0)] *Sw_GasCurve = 3 (national supply curve for natural gas prices with census division multipliers) - + sum{(h,cendiv,gb), hours(h) * GASUSED(cendiv,gb,h,t) + + sum{(h,cendiv,gb)$[tfuel(t)], hours(h) * GASUSED(cendiv,gb,h,t) * gasadder_cd(cendiv,t,h) + gasprice_nat_bin(gb,t) - }$(Sw_GasCurve = 3) + }$[(Sw_GasCurve = 3)] *Sw_GasCurve = 1 (national and census division supply curves for natural gas prices) *first - anticipated costs of gas consumption given last year's amount - + (sum{(i,r,v,cendiv,h)$[valgen(i,v,r,t)$gas(i)], + + (sum{(i,r,v,cendiv,h)$[valgen(i,v,r,t)$gas(i)$tfuel(t)], gasmultterm(cendiv,t) * szn_adj_gas(h) * cendiv_weights(r,cendiv) * hours(h) * heat_rate(i,v,r,t) * GEN(i,v,r,h,t) } *second - adjustments based on changes from last year's consumption at the regional and national level - + sum{(fuelbin,cendiv), + + sum{(fuelbin,cendiv)$tfuel(t), gasbinp_regional(fuelbin,cendiv,t) * VGASBINQ_REGIONAL(fuelbin,cendiv,t) } - + sum{(fuelbin), + + sum{(fuelbin)$tfuel(t), gasbinp_national(fuelbin,t) * VGASBINQ_NATIONAL(fuelbin,t) } - )$[Sw_GasCurve = 1] + )$[(Sw_GasCurve = 1)] * ---cost of biofuel consumption and biomass transport--- - + sum{(r,bioclass)$[sum{(i,v)$(bio(i) or cofire(i)), valgen(i,v,r,t) }], + + sum{(r,bioclass)$[tfuel(t)$sum{(i,v)$(bio(i) or cofire(i)), valgen(i,v,r,t) }], BIOUSED(bioclass,r,t) * (sum{usda_region$r_usda(r,usda_region), biosupply(usda_region, bioclass, "price") } + bio_transport_cost) } - * --- hurdle costs for transmission flow --- + sum{(r,rr,h,trtype)$[routes(r,rr,trtype,t)$cost_hurdle(r,rr,t)], cost_hurdle(r,rr,t) * FLOW(r,rr,h,t,trtype) * hours(h) } @@ -340,15 +349,15 @@ eq_Objfn_op(t)$tmodel(t).. CO2_SPURLINE_INV(r,cs,tt) } }$[Sw_CO2_Detail$(yeart(t)>=co2_detail_startyr)] * --- CO2 injection break even costs - + sum{(r,cs,h)$r_cs(r,cs), hours(h) * CO2_STORED(r,cs,h,t) * cost_co2_stor_bec(cs,t) }$[Sw_CO2_Detail$(yeart(t)>=co2_detail_startyr)] + + sum{(r,cs,h)$[r_cs(r,cs)$h_rep(h)], hours(h) * CO2_STORED(r,cs,h,t) * cost_co2_stor_bec(cs,t) }$[Sw_CO2_Detail$(yeart(t)>=co2_detail_startyr)] * --- Tax credit for CO2 stored --- * note conversion to 12-year CRF given length of CO2 captured incentive payments - - sum{(i,v,r,h)$[valgen(i,v,r,t)$co2_captured_incentive(i,v,r,t)], + - sum{(i,v,r,h)$[valgen(i,v,r,t)$h_rep(h)$co2_captured_incentive(i,v,r,t)], (crf(t) / crf_co2_incentive(t)) * co2_captured_incentive(i,v,r,t) * hours(h) * capture_rate("CO2",i,v,r,t) * GEN(i,v,r,h,t)} * --- Tax credit for CO2 stored for DAC --- - - sum{(p,i,v,r,h)$[dac(i)$valcap(i,v,r,t)$i_p(i,p)$h_rep(h)], + - sum{(p,i,v,r,h)$[dac(i)$valcap(i,v,r,t)$i_p(i,p)$h_rep(h)$co2_captured_incentive(i,v,r,t)], (crf(t) / crf_co2_incentive(t)) * co2_captured_incentive(i,v,r,t) * hours(h) * PRODUCE(p,i,v,r,h,t)} * --- PTC value for electric power generation --- @@ -362,7 +371,7 @@ eq_Objfn_op(t)$tmodel(t).. - sum{(p,v,r,h)$[valcap("electrolyzer",v,r,t)$(sameas(p,"H2"))$h2_ptc("electrolyzer",v,r,t)$h_rep(h)], hours(h) * PRODUCE(p,"electrolyzer",v,r,h,t) * (crf(t) / crf_h2_incentive(t)) * h2_ptc("electrolyzer",v,r,t) * 1e3} - $[Sw_H2_PTC$Sw_H2$h2_ptc_years(t)$(yeart(t) >= h2_demand_start)] + $[(Sw_H2_PTC)$Sw_H2$h2_ptc_years(t)$(yeart(t) >= h2_demand_start)] *end multiplier for pvf_onm ) diff --git a/cases.csv b/cases.csv index 9d0752e77..59f86c653 100644 --- a/cases.csv +++ b/cases.csv @@ -33,6 +33,11 @@ coalscen,"Coal price scenario (inputs\fuelprices\{coalscen}.csv). The options fo demandscen,Demand scenario (see inputs\load),N/A,AEO_2025_reference, 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, +finito_dir,Directory with cloned FINITO repository,N/A,/projects/finito/molmezt/FINITO, +finito_cases_file,"extension of the FINITO cases file including all scenarios to be run, e.g., 'linked' for cases_linked.csv",N/A,linked, +finito_case,Case name for FINITO in {finito_dir}/cases_linked.csv; use 'same' if the scenario name in cases_linked.csv matches the scenario name in the cases file,N/A,BAU, +first_year_finito,First year when the FINITO module is enabled,N/A,2018, geodiscov,Annual discover rates for new geothermal sites,BAU; TI,BAU, geohydrosupplycurve,Geohydro Supply Curve,ATB_2023,ATB_2023, egssupplycurve,EGS Supply Curve,ATB_2023; reV,reV, @@ -116,6 +121,7 @@ GSw_CO2_pipeline_fom,Set FOM cost for CO2 Pipeline. 2004$/(tonne-mi)/hr-yr. Sugg GSw_CO2_spurline_cost,Set capex cost for CO2 Spurline. 2004$/(tonne-mi)/hr. Suggested value: 2257,N/A,2257, GSw_CO2_spurline_fom,Set FOM cost for CO2 Spurline. 2004$/(tonne-mi)/hr-yr. Suggested value: 60,N/A,60, GSw_CO2_Detail,Switch to enable detailed representation CO2 transportation and storage,0; 1,0, +GSw_CO2_LimitStorageSites,Switch to limit storage sites to the closest site for each region,0; 1,0, GSw_CO2_CostAdj,multiplier for co2 infrastructure when Sw_CO2_Detail = 1,float,1, GSw_CO2_BEC,"Break even cost capacity factor assignment. Must be a suffix to ""bec_"" in the columns of co2_site_char.csv",N/A,70, GSw_CoalIGCC,Turn on/off COAL IGCC,0; 1,1, diff --git a/cases_finito.csv b/cases_finito.csv new file mode 100644 index 000000000..25fb39db5 --- /dev/null +++ b/cases_finito.csv @@ -0,0 +1,46 @@ +,Default Value,ReEDSonly,BAUreg,Reference,Reference-AEO,Reference-HOG,TradeReference,TradeReference-AEO,Trade-HOG,Reference-LowBio,Reference-HighBio,Tax200,Tax200-AEO,NetZero,NetZero-LowBio,NetZero-HighBio,NetZero-SlackLim1500,NetZero-SlackLim1500-CO2Stor-Mid,NetZero-SlackLim1500-CO2Stor-Low,NetZero-SlackLim500-CO2Stor-Low,NetZero-SlackCost2500-CO2Stor-Low,aggreg_54_level +ignore,1,1,0,1,1,1,1,1,1,,,,,,,,,,,,, +GSw_FINITO_Link,1,0,,,,,,,,,,,,,,,,,,,, +finito_dir,/path/to/FINITO/directory,0,,,,,,,,,,,,,,,,,,,, +finito_cases_file,linked,,,,,,,,,,,,,,,,,,,,, +finito_case,BAU,,BAUreg,,BAU-AEO,AEO-HOG,TradeReEDS,TradeReEDS-AEO,TradeReEDS-HOG,BAU-LowBio,BAU-HighBio,,BAU-AEO,NetZero,NetZero-LowBio,NetZero-HighBio,NetZero-SlackLim1500,NetZero-SlackLim1500-CO2Stor-Mid,NetZero-SlackLim1500-CO2Stor-Low,NetZero-SlackLim500-CO2Stor-Low,NetZero-SlackCost2500-CO2Stor-Low,aggreg_54_level +coalscen,AEO_2025_reference,,,,,,,,,,,,,,,,,,,,, +ngscen,AEO_2025_reference,,,,,AEO_2025_HOG,,,AEO_2025_HOG,,,,,,,,,,,,, +uraniumscen,AEO_2025_reference,,,,,,,,,,,,,,,,,,,,, +GSw_Region,country/USA,,st/CO,,,,,,,,,,,,,,,,,,, +GSw_GasCurve,1,,2,,,,,,,,,,,,,,,,,,, +GSw_gopt,2,,,,,,,,,,,,,,,,,,,,, +GSw_NewValCapShrink,1,,,,,,,,,,,,,,,,,,,,, +endyear,2050,,2035,,,,,,,,,,,,,,,,,,, +yearset,2010_2015_2020..2050..5,,,,,,,,,,,,,,,,,,,,, +first_year_finito,2018,,,,,,,,,,,,,,,,,,,,, +incentives_suffix,obbba,,,,,,,,,,,,,,,,,,,,, +GSw_Upgrades,0,,,,,,,,,,,,,,,,,,,,, +GSw_AnnualCap,0,,,,,,,,,,,,,,,,,,,,, +GSw_AnnualCapScen,start2023_100pct2035,,,,,,,,,,,,,,,,,,,,, +GSw_Clean_Air_Act,0,,,,,,,,,,,,,,,,,,,,, +GSw_CSP,0,,,,,,,,,,,,,,,,,,,,, +GSw_Geothermal,0,,,,,,,,,,,,,,,,,,,,, +GSw_H2,2,,,,,,,,,,,,,,,,,,,,, +GSw_H2_PTC,0,,,,,,,,,,,,,,,,,,,,, +GSw_H2_Demand_Case,BAU,,,,,,,,,,,,,,,,,,,,, +GSw_H2_SMR,1,,,,,,,,,,,,,,,,,,,,, +GSw_H2_Transport,0,,,,,,,,,,,,,,,,,,,,, +GSw_H2Combustion,1,,,,,,,,,,,,,,,,,,,,, +GSw_H2Combustionupgrade,1,,,,,,,,,,,,,,,,,,,,, +GSw_H2CombinedCycle,1,,,,,,,,,,,,,,,,,,,,, +GSw_DAC,1,,,,,,,,,,,,,,,,,,,,, +GSw_BECCS,1,,,,,,,,,,,,,,,,,,,,, +GSw_RegionResolution,ba,,,,,,,,,,,,,,,,,,,,,aggreg +GSw_TCPhaseout,1,,,,,,,,,,,,,,,,,,,,, +GSw_CO2_Detail,1,,,,,,,,,,,,,,,,,,,,, +GSw_CO2_LimitStorageSites,1,,,,,,,,,,,,,,,,,,,,, +numhintage,4,,,,,,,,,,,,,,,,,,,,, +GSw_CarbTax,0,,,,,,,,,,,1,1,,,,,,,,, +GSw_CarbTaxOption,default,,,,,,,,,,,t200_2050,t200_2050,,,,,,,,, +GSw_PRM_CapCredit,0,,,,,,,,,,,,,,,,,,,,, +GSw_PRM_StressIterateMax,0,,,,,,,,,,,,,,,,,,,,, +GSw_HourlyNumClusters,9,,,,,,,,,,,,,,,,,,,,, +GSw_HourlyChunkLengthRep,4,,,,,,,,,,,,,,,,,,,,, +GSw_HourlyChunkLengthStress,4,,,,,,,,,,,,,,,,,,,,, +GSw_HierarchyFile,default,,,,,,,,,,,,,,,,,,,,,agg54 diff --git a/createmodel.gms b/createmodel.gms index 36ab4603e..d8ba1dcaf 100644 --- a/createmodel.gms +++ b/createmodel.gms @@ -1,5 +1,19 @@ $include b_inputs.gms + +$ifthene.finito_link %GSw_FINITO_Link% == 1 +$include finito/model/finito_input.gms +$endif.finito_link + +$ifthene.finito_link %GSw_FINITO_Link% == 1 +$include finito/model/finito_vars_eqs.gms +$endif.finito_link + $include c_supplymodel.gms + +$ifthene.finito_link %GSw_FINITO_Link% == 1 +$include finito/model/finito_model.gms +$endif.finito_link + $include c_supplyobjective.gms $include c_mga.gms $include d_solveprep.gms diff --git a/d1_temporal_params.gms b/d1_temporal_params.gms index 444c7fc5e..fcbf8578f 100644 --- a/d1_temporal_params.gms +++ b/d1_temporal_params.gms @@ -299,9 +299,6 @@ can_exports_h(r,h,t)$[hours(h)] = can_exports(r,t) * can_exports_h_frac(h) / hou $endif.Canada -* zero Canada exports when Canada is not modeled -can_imports_szn(r,szn,t)$[Sw_Canada=0] = 0 ; -can_exports_h(r,h,t)$[Sw_Canada=0] = 0 ; $onempty parameter canmexload(r,allh) "load for canadian and mexican regions" @@ -887,12 +884,20 @@ szn_adj_gas(h)$frac_h_quarter_weights(h,"wint") = szn_adj_gas(h) + frac_h_quarter_weights(h,"wint") * szn_adj_gas_winter ; +*============================================= +* ----- ReEDS-FINITO temporal parameters ----- +*============================================= +* remove focused sectors load from load_exog_static +$ifthene.linked_finito_temporal_params %GSw_FINITO_Link% ==1 +$include finito/model/linked_finito_temporal_params.gms +$endif.linked_finito_temporal_params + + *============================================= * -- Round parameters for GAMS -- *============================================= avail(i,r,h)$avail(i,r,h) = round(avail(i,r,h),3) ; -can_imports_szn(r,szn,t)$can_imports_szn(r,szn,t) = round(can_imports_szn(r,szn,t),3) ; can_exports_h(r,h,t)$can_exports_h(r,h,t) = round(can_exports_h(r,h,t),3) ; h_weight_csapr(h)$h_weight_csapr(h) = round(h_weight_csapr(h),3) ; load_exog(r,h,t)$load_exog(r,h,t) = round(load_exog(r,h,t),3) ; diff --git a/d2_varfix.gms b/d2_varfix.gms index 85db24429..3a5237328 100644 --- a/d2_varfix.gms +++ b/d2_varfix.gms @@ -9,6 +9,10 @@ if(Sw_RemoveSmallNumbers = 1, INV_ENERGY.l(i,v,r,tfix)$[abs(INV_ENERGY.l(i,v,r,tfix)) < rhs_tolerance] = 0 ; INV_RSC.l(i,v,r,rscbin,tfix)$[abs(INV_RSC.l(i,v,r,rscbin,tfix)) < rhs_tolerance] = 0 ; INV_POI.l(r,tfix)$[abs(INV_POI.l(r,tfix)) < rhs_tolerance] = 0 ; + INV_REFURB.l(i,v,r,tfix)$[abs(INV_REFURB.l(i,v,r,tfix)) < rhs_tolerance] = 0; + CO2_SPURLINE_INV.l(r,cs,tfix)$[abs(CO2_SPURLINE_INV.l(r,cs,tfix)) < rhs_tolerance] = 0; + CO2_TRANSPORT_INV.l(r,rr,tfix)$[abs(CO2_TRANSPORT_INV.l(r,rr,tfix)) < rhs_tolerance] = 0; + H2_TRANSPORT_INV.l(r,rr,tfix)$[abs(H2_TRANSPORT_INV.l(r,rr,tfix)) < rhs_tolerance] = 0 ; H2_STOR_INV.l(h2_stor,r,tfix)$[abs(H2_STOR_INV.l(h2_stor,r,tfix)) < rhs_tolerance] = 0 ; H2_TRANSPORT_INV.l(r,rr,tfix) $[abs(H2_TRANSPORT_INV.l(r,rr,tfix) ) < rhs_tolerance] = 0 ; ); @@ -119,7 +123,9 @@ H2_STOR_LEVEL_SZN.fx(h2_stor,r,actualszn,tfix)$[(h2_stor_r(h2_stor,r))$(Sw_H2=2) *CO2-related variables CO2_CAPTURED.fx(r,h,tfix)$Sw_CO2_Detail = CO2_CAPTURED.l(r,h,tfix) ; +$ifthene.linked_varfix Sw_FINITO_Link==0 CO2_STORED.fx(r,cs,h,tfix)$[Sw_CO2_Detail$r_cs(r,cs)] = CO2_STORED.l(r,cs,h,tfix) ; +$endif.linked_varfix CO2_FLOW.fx(r,rr,h,tfix)$[Sw_CO2_Detail$co2_routes(r,rr)] = CO2_FLOW.l(r,rr,h,tfix) ; CO2_TRANSPORT_INV.fx(r,rr,tfix)$[Sw_CO2_Detail$co2_routes(r,rr)] = CO2_TRANSPORT_INV.l(r,rr,tfix) ; CO2_SPURLINE_INV.fx(r,cs,tfix)$[Sw_CO2_Detail$r_cs(r,cs)] = CO2_SPURLINE_INV.l(r,cs,tfix) ; diff --git a/d3_data_dump.gms b/d3_data_dump.gms index 29c97036e..30a8ff0b9 100644 --- a/d3_data_dump.gms +++ b/d3_data_dump.gms @@ -8,6 +8,7 @@ This file creates a gdx file with all of the data necessary for the Augur module - availability rates (1 - outage rates) - transmission capacities and loss rates - technology sets + - industrial electricity demand from FINITO when running linked model $offtext $if not set start_year $setglobal start_year %startyear% @@ -62,6 +63,7 @@ h2_usage_regional(r,allh,t) "--metric tons-- H2 usage by region" inv_cond_filt(i,v,t) "--set-- vintage-year mapping for investments by technology" inv_ivrt(i,v,r,t) "--MW-- investments in power generation capacity" inv_energy_ivrt(i,v,r,t) "--MWh-- investments in energy generation capacity" +load_finito_rt(r,allh,t) "--MWh-- total load from industrial sectors and converted fuels modeled by FINITO" m_cf_filt(i,v,r,allh) "--fraction-- capacity factor used in the model" m_cf_szn_filt(i,v,r,allszn) "--fraction-- modelled capacity factors filtered for hydro resources to set seasonal energy constraints" minloadfrac_filt(r,i,allszn) "--fraction-- modelled mingen fraction filtered for hydro resources to set mingen constraints" @@ -161,6 +163,10 @@ h2_usage_regional(r,h,t)$tcur(t) = h2_exogenous_demand_regional(r,'h2',h,t) + sum{(i,v)$[valgen(i,v,r,t)$h2_combustion(i)], GEN.l(i,v,r,h,t) * h2_combustion_intensity * heat_rate(i,v,r,t)} +* regional hydrogen demand for industry (FINITO) +$ifthene.linked_h2_reg_report Sw_FINITO_Link==1 + + [USE_H2_FINITO.l(r,h,t) * h2_metric_tons_per_mmbtu ]$t_finito(t) +$endif.linked_h2_reg_report ) ; @@ -284,15 +290,24 @@ cap_converter_filt(r) = sum{t$tcur(t), CAP_CONVERTER.l(r,t) } ; routes_filt(r,rr,trtype) = sum{t$tcur(t), routes(r,rr,trtype,t) } ; *============================ -* Flexible load data +* Load data *============================ +* flexible load flex_load(r,h) = sum{(flex_type,t)$tcur(t), load_exog_flex(flex_type,r,h,t) } ; flex_load_opt(r,h) = sum{(flex_type,t)$tcur(t), FLEX.l(flex_type,r,h,t) } ; ra_cap_loadsite(r,t)$[Sw_LoadSiteCF$val_loadsite(r)] = CAP_LOADSITE.l(r,t) ; +* FINITO load +$ifthene.linked_load Sw_FINITO_Link==1 + load_finito_rt(r,h,t) = USE_ELE_FINITO.l(r,h,t) ; +$else.linked_load + load_finito_rt(r,h,t) = 0 ; +$endif.linked_load + + *============================ * Extra consumption data *============================ @@ -382,6 +397,7 @@ execute_unload 'ReEDS_Augur%ds%augur_data%ds%reeds_data_%cur_year%.gdx' inv_ivrt inv_energy_ivrt ivt_num + load_finito_rt m_cf_filt m_cf_szn_filt maxage diff --git a/d_solveoneyear.gms b/d_solveoneyear.gms index b3484a805..a62eaaee1 100644 --- a/d_solveoneyear.gms +++ b/d_solveoneyear.gms @@ -23,6 +23,13 @@ tload(t) = no ; tmodel(t) = no ; tmodel("%cur_year%") = yes ; +* (linked) also reset t_finito +$ifthene.linked_finito_time %GSw_FINITO_Link% == 1 +t_finito(t) = no ; +t_finito(t)$[tmodel(t)$(t.val>=first_year_finito)$(t.val<=endyear)] = yes ; +$endif.linked_finito_time + + $log 'Solving sequential case for...' $log ' Case: %case%' $log ' Year: %cur_year%' @@ -241,6 +248,7 @@ $endif.diagnose * ------------------------------ * Solve the Model * ------------------------------ +option limrow = 1e6; $ifthen.valstr %GSw_ValStr% == 1 OPTION lp = convert ; ReEDSmodel.optfile = 1 ; @@ -276,11 +284,22 @@ $endif.mga *** Adjust some parameters based on the solution for this solve year $include d2_post_solve_adjustments.gms +*** Do the same for finito if necessary +$ifthene.linked_finito_shrink %GSw_FINITO_Link% == 1 +* Industrial variable fix +$include finito/model/finito_post_solve_adjustments.gms +$endif.linked_finito_shrink *** Fix decision variables to their optimized levels for this solve year tfix("%cur_year%") = yes ; $include d2_varfix.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 +$endif.linked_finito_varfix + *** Dump data used in calculations between solve years $include d3_data_dump.gms diff --git a/e_report.gms b/e_report.gms index ebd14d10e..f251e657a 100644 --- a/e_report.gms +++ b/e_report.gms @@ -107,7 +107,7 @@ rev_cat "categories for renvenue streams" /load, res_marg, oper_res, rps, charge lcoe_cat "categories for LCOE calculation" /capcost, upgradecost, rsccost, fomcost, vomcost, gen / -loadtype "categories for types of load" / end_use, dist_loss, trans_loss, stor_charge, h2_prod, h2_network, dac / +loadtype "categories for types of load" / end_use, dist_loss, trans_loss, stor_charge, h2_prod, h2_network, dac, finito / h2_demand_type / "electricity", "cross-sector"/ @@ -495,92 +495,121 @@ ptc_out(i,v,t)$[tmodel_new(t)$ptc_value_scaled(i,v,t)] = ptc_value_scaled(i,v,t) * Case 2: the resource of one or more biomass classes ARE exhausted, i.e., BIOUSED.l(bioclass) = biosupply(bioclass) * Marginal Biomass Price = maximum difference between eq_bioused.m and eq_biousedlimit.m(bioclass) across all biomass classes in a region -repbioprice(r,t)$tmodel_new(t) = max{0, smax{bioclass$BIOUSED.l(bioclass,r,t), eq_bioused.m(r,t) - - sum{usda_region$r_usda(r,usda_region), eq_biousedlimit.m(bioclass,usda_region,t) } } } / pvf_onm(t) ; +repbioprice(r,t)$[tmodel_new(t)$tfuel(t)] = + [max{0, smax{bioclass$BIOUSED.l(bioclass,r,t), eq_bioused.m(r,t) - + sum{usda_region$r_usda(r,usda_region), eq_biousedlimit.m(bioclass,usda_region,t) } } } / pvf_onm(t) ] ; -* quantity of biomass used (convert from mmBTU to dry tons using biomass energy content) -bioused_out(bioclass,r,t)$tmodel_new(t) = BIOUSED.l(bioclass,r,t) / bio_energy_content ; -bioused_usda(bioclass,usda_region,t)$tmodel_new(t) = sum{r$r_usda(r,usda_region), bioused_out(bioclass,r,t) } ; +* quantity of biomass used in the power sector (convert from mmBTU to dry tons using biomass energy content) +bioused_out(bioclass,r,t)$[tmodel_new(t)$tfuel(t)] = + [BIOUSED.l(bioclass,r,t) / bio_energy_content ]; + +bioused_usda(bioclass,usda_region,t)$[tmodel_new(t)$tfuel(t)] = + [sum{r$r_usda(r,usda_region), bioused_out(bioclass,r,t) } ]; * 1e9 converts from MMBtu to Quads -repgasquant(cendiv,t)$[(Sw_GasCurve = 0 or Sw_GasCurve = 3)$tmodel_new(t)] = - sum{(gb,h), GASUSED.l(cendiv,gb,h,t) * hours(h) } * gas_scale/ 1e9 ; +repgasquant(cendiv,t)$[(Sw_GasCurve = 0 or Sw_GasCurve = 3)$tmodel_new(t)$tfuel(t)] = + [sum{(gb,h), GASUSED.l(cendiv,gb,h,t) * hours(h) } * gas_scale/ 1e9 ]; -repgasquant(cendiv,t)$[(Sw_GasCurve = 1 or Sw_GasCurve = 2)$tmodel_new(t)] = - ( sum{(i,v,r,h)$[r_cendiv(r,cendiv)$valgen(i,v,r,t)$gas(i)$heat_rate(i,v,r,t)], +repgasquant(cendiv,t)$[(Sw_GasCurve = 1 or Sw_GasCurve = 2 or Sw_FINITO_Link = 1)$tmodel_new(t)] = + [( sum{(i,v,r,h)$[r_cendiv(r,cendiv)$valgen(i,v,r,t)$gas(i)$heat_rate(i,v,r,t)], hours(h) * heat_rate(i,v,r,t) * GEN.l(i,v,r,h,t)} + sum{(v,r,h)$[valcap("dac_gas",v,r,t)$r_cendiv(r,cendiv)], hours(h) * dac_gas_cons_rate("dac_gas",v,t) * PRODUCE.l("DAC","dac_gas",v,r,h,t) }$Sw_DAC_Gas + sum{(p,i,v,r,h)$[r_cendiv(r,cendiv)$valcap(i,v,r,t)$smr(i)], hours(h) * smr_methane_rate * PRODUCE.l(p,i,v,r,h,t) }$Sw_H2 - ) / 1e9 ; + ) / 1e9 ]; -repgasquant_irt(i,r,t)$tmodel_new(t) = - ( sum{(v,h)$[valgen(i,v,r,t)$gas(i)$heat_rate(i,v,r,t)], +repgasquant_irt(i,r,t)$[tmodel_new(t)] = + [( sum{(v,h)$[valgen(i,v,r,t)$gas(i)$heat_rate(i,v,r,t)], hours(h) * heat_rate(i,v,r,t) * GEN.l(i,v,r,h,t) } + sum{(v,h)$[valcap("dac_gas",v,r,t)], hours(h) * dac_gas_cons_rate("dac_gas",v,t) * PRODUCE.l("DAC","dac_gas",v,r,h,t) }$Sw_DAC_Gas + sum{(p,v,h)$[valcap(i,v,r,t)$smr(i)], hours(h) * smr_methane_rate * PRODUCE.l(p,i,v,r,h,t) }$Sw_H2 - ) / 1e9 ; + ) / 1e9 ]; -repgasquant_nat(t)$tmodel_new(t) = sum{cendiv, repgasquant(cendiv,t) } ; +repgasquant_nat(t)$[tmodel_new(t)] = + [sum{cendiv, repgasquant(cendiv,t) } ]; *for reported gasprice (not that used to compute system costs) *scale back to $ / mmbtu -repgasprice(cendiv,t)$[(Sw_GasCurve = 0)$tmodel_new(t)$repgasquant(cendiv,t)] = - smax{gb$[sum{h, GASUSED.l(cendiv,gb,h,t) }], gasprice(cendiv,gb,t) } / gas_scale ; +repgasprice(cendiv,t)$[(Sw_GasCurve = 0)$tmodel_new(t)$repgasquant(cendiv,t)$tfuel(t)]= + [smax{gb$[sum{h, GASUSED.l(cendiv,gb,h,t) }], gasprice(cendiv,gb,t) } / gas_scale ]; -repgasprice(cendiv,t)$[(Sw_GasCurve = 2)$tmodel_new(t)$repgasquant(cendiv,t)] = - sum{(i,v,r,h)$[r_cendiv(r,cendiv)$valgen(i,v,r,t)$gas(i)$heat_rate(i,v,r,t)], +repgasprice(cendiv,t)$[(Sw_GasCurve = 2)$tmodel_new(t)$repgasquant(cendiv,t)$tfuel(t)] = + [sum{(i,v,r,h)$[r_cendiv(r,cendiv)$valgen(i,v,r,t)$gas(i)$heat_rate(i,v,r,t)], hours(h)*heat_rate(i,v,r,t)*fuel_price(i,r,t)*GEN.l(i,v,r,h,t) - } / (repgasquant(cendiv,t) * 1e9) ; + } / (repgasquant(cendiv,t) * 1e9) ]; -repgasprice_r(r,t)$[(Sw_GasCurve = 0 or Sw_GasCurve = 2)$tmodel_new(t)] = sum{cendiv$r_cendiv(r,cendiv), repgasprice(cendiv,t) } ; -repgasprice_r(r,t)$[(Sw_GasCurve = 1)$tmodel_new(t)] = - ( sum{(h,cendiv), - gasmultterm(cendiv,t) * szn_adj_gas(h) * cendiv_weights(r,cendiv) * - hours(h) } / sum{h, hours(h) } +* gas price by timeslice when linked with FINITO (see similar calculation in finito_report.gms) [$2004/MMBtu] +$ifthene.finitogasprice Sw_FINITO_Link == 1 +repgasprice_finito(cendiv,h,t)$[tmodel_new(t)$(not tfuel(t))] = + ( 1/obj_scale * 1/pvf_onm(t) * deflator('2018') * + [ max{ + smax{(eus,usep)$[map_roe_ec_usep("NG",eus,usep)], eq_supplydemand_fe_pool.m("NG",eus,usep,cendiv,h,t)}, + smax{(usep)$[map_roi_ec_usep("NG",usep)],eq_supplydemand_fe_roi.m("NG",usep,cendiv,h,t)}, + smax{(cfp2,cfvin2,fi,r)$[map_ec_ei("NG",fi)$valei_cf(cfp2,cfvin2,fi,r,t)$r_cendiv(r,cendiv)],eq_use_fi_cf.m(cfp2,cfvin2,fi,r,h,t)}, + smax{(tech,vin,fi,r)$[map_ec_ei("NG",fi)$valei_ind(tech,vin,fi,r,t)$r_cendiv(r,cendiv)],eq_use_fi_ind.m(tech,vin,fi,r,h,t)} + } / hours(h) + ] + ) +; +$else.finitogasprice + repgasprice_finito(cendiv,h,t)$[tmodel_new(t)$(not tfuel(t))] = 0 ; +$endif.finitogasprice + +repgasprice(cendiv,t)$[tmodel_new(t)$(not tfuel(t))] = +* average price over hours when linked with FINITO + sum{h, hours(h) * repgasprice_finito(cendiv,h,t) } +; + +repgasprice_r(r,t)$[(Sw_GasCurve = 0 or Sw_GasCurve = 2)$tmodel_new(t)$tfuel(t)] = + [sum{cendiv$r_cendiv(r,cendiv), repgasprice(cendiv,t) } ]; + +repgasprice_r(r,t)$[(Sw_GasCurve = 1)$tmodel_new(t)$tfuel(t)] = + sum{(h,cendiv), + gasmultterm(cendiv,t) * szn_adj_gas(h) * cendiv_weights(r,cendiv) * + hours(h) } / sum{h, hours(h) } - + smax((fuelbin,cendiv)$[VGASBINQ_REGIONAL.l(fuelbin,cendiv,t)$r_cendiv(r,cendiv)], gasbinp_regional(fuelbin,cendiv,t) ) + + smax((fuelbin,cendiv)$[VGASBINQ_REGIONAL.l(fuelbin,cendiv,t)$r_cendiv(r,cendiv)], gasbinp_regional(fuelbin,cendiv,t) ) - + smax(fuelbin$VGASBINQ_NATIONAL.l(fuelbin,t), gasbinp_national(fuelbin,t) ) - ) ; + + smax(fuelbin$VGASBINQ_NATIONAL.l(fuelbin,t), gasbinp_national(fuelbin,t) ) + ; -repgasprice(cendiv,t)$[(Sw_GasCurve = 1)$tmodel_new(t)$repgasquant(cendiv,t)] = - sum{(i,r)$r_cendiv(r,cendiv), repgasprice_r(r,t) * repgasquant_irt(i,r,t) } / repgasquant(cendiv,t) ; +repgasprice(cendiv,t)$[((Sw_GasCurve = 1) or (Sw_FINITO_Link = 1))$tmodel_new(t)$repgasquant(cendiv,t)] = + [sum{(i,r)$r_cendiv(r,cendiv), repgasprice_r(r,t) * repgasquant_irt(i,r,t) } / repgasquant(cendiv,t) ] ; repgasprice_nat(t)$[tmodel_new(t)$sum{cendiv, repgasquant(cendiv,t) }] = - sum{cendiv, repgasprice(cendiv,t) * repgasquant(cendiv,t) } - / sum{cendiv, repgasquant(cendiv,t) } ; + [sum{cendiv, repgasprice(cendiv,t) * repgasquant(cendiv,t) } + / sum{cendiv, repgasquant(cendiv,t) } ]; *======================================== * NATURAL GAS FUEL COSTS *======================================== gasshare_ba(r,cendiv,t)$[r_cendiv(r,cendiv)$tmodel_new(t)$repgasquant(cendiv,t)] = - sum{i$[valgen_irt(i,r,t)$gas(i)],repgasquant_irt(i,r,t) / repgasquant(cendiv,t) } ; + [sum{i$[valgen_irt(i,r,t)$gas(i)],repgasquant_irt(i,r,t) / repgasquant(cendiv,t) } ]; gasshare_techba(i,r,cendiv,t)$[r_cendiv(r,cendiv)$tmodel_new(t)$repgasquant(cendiv,t)$gas(i)] = - repgasquant_irt(i,r,t) / repgasquant(cendiv,t) ; + [repgasquant_irt(i,r,t) / repgasquant(cendiv,t) ]; -gasshare_cendiv(cendiv,t)$[sum{cendiv2,repgasquant(cendiv2,t)}] = repgasquant(cendiv,t) / sum{cendiv2,repgasquant(cendiv2,t)} ; +gasshare_cendiv(cendiv,t)$[sum{cendiv2,repgasquant(cendiv2,t)}] = + [repgasquant(cendiv,t) / sum{cendiv2,repgasquant(cendiv2,t)} ]; -gascost_cendiv(cendiv,t)$tmodel_new(t) = +* cost of natural gas - standalone ReEDS +gascost_cendiv(cendiv,t)$[tmodel_new(t)$tfuel(t)] = *cost of natural gas for Sw_GasCurve = 2 (static natural gas prices) - + sum{(i,v,r,h)$[r_cendiv(r,cendiv)$valgen(i,v,r,t)$gas(i)$heat_rate(i,v,r,t) - $[not bio(i)]$[not cofire(i)]$[Sw_GasCurve = 2]], - hours(h) * heat_rate(i,v,r,t) * fuel_price(i,r,t) * GEN.l(i,v,r,h,t) } + + [ sum{(i,v,r,h)$[r_cendiv(r,cendiv)$valgen(i,v,r,t)$gas(i)$heat_rate(i,v,r,t) + $[not bio(i)]$[not cofire(i)]$[Sw_GasCurve = 2]], + hours(h) * heat_rate(i,v,r,t) * fuel_price(i,r,t) * GEN.l(i,v,r,h,t) } *cost of natural gas for Sw_GasCurve = 0 (census division supply curves natural gas prices) - + sum{gb, sum{h,hours(h) * GASUSED.l(cendiv,gb,h,t) } * gasprice(cendiv,gb,t) - }$[Sw_GasCurve = 0] + + sum{gb, sum{h,hours(h) * GASUSED.l(cendiv,gb,h,t) } * gasprice(cendiv,gb,t) }$[Sw_GasCurve = 0] *cost of natural gas for Sw_GasCurve = 3 (national supply curve for natural gas prices with census division multipliers) - + sum{(h,gb), hours(h) * GASUSED.l(cendiv,gb,h,t) - * gasadder_cd(cendiv,t,h) + gasprice_nat_bin(gb,t) - }$[Sw_GasCurve = 3] + + sum{(h,gb), hours(h) * GASUSED.l(cendiv,gb,h,t) + * gasadder_cd(cendiv,t,h) + gasprice_nat_bin(gb,t) }$[Sw_GasCurve = 3] *cost of natural gas for Sw_GasCurve = 1 (national and census division supply curves for natural gas prices) *first - anticipated costs of gas consumption given last year's amount + (sum{(i,v,r,h)$[valgen(i,v,r,t)$gas(i)], @@ -593,15 +622,25 @@ gascost_cendiv(cendiv,t)$tmodel_new(t) = + sum{(fuelbin), gasbinp_national(fuelbin,t) * VGASBINQ_NATIONAL.l(fuelbin,t) } * gasshare_cendiv(cendiv,t) - )$[Sw_GasCurve = 1]; + )$[Sw_GasCurve = 1] + ]; + +* cost of natural gas - linked with FINITO ('not tfuel' indicates years using FINITO supply curves) +gascost_cendiv(cendiv,t)$[tmodel_new(t)$(not tfuel(t))] = +* cost = gas price multiplied by gas usage [$ = $/MMBtu * MMBtu/MWh * MW * h] + sum{(i,v,r,h)$[valgen(i,v,r,t)$gas(i)$r_cendiv(r, cendiv)], + repgasprice_finito(cendiv,h,t) * heat_rate(i,v,r,t) * GEN.l(i,v,r,h,t) * hours(h) + } +; + *======================================== * BIOFUEL COSTS *======================================== -bioshare_techba(i,r,t)$[(cofire(i) or bio(i))$tmodel_new(t)] = +bioshare_techba(i,r,t)$[(cofire(i) or bio(i))$tmodel_new(t)$tfuel(t)] = * biofuel-based generation of tech i in the BA (biopower + cofire) - (( sum{(v,h)$[valgen(i,v,r,t)$bio(i)], hours(h) * heat_rate(i,v,r,t) * GEN.l(i,v,r,h,t) } + [ (( sum{(v,h)$[valgen(i,v,r,t)$bio(i)], hours(h) * heat_rate(i,v,r,t) * GEN.l(i,v,r,h,t) } + sum{(v,h)$[cofire(i)$valgen(i,v,r,t)], bio_cofire_perc * hours(h) * heat_rate(i,v,r,t) * GEN.l(i,v,r,h,t) } ) / * biofuel-based generation of all techs in the BA (biopower + cofire) @@ -611,6 +650,7 @@ bioshare_techba(i,r,t)$[(cofire(i) or bio(i))$tmodel_new(t)] = )$[ sum{(ii,v,h)$[valgen(ii,v,r,t)$bio(ii)], hours(h) * heat_rate(ii,v,r,t) * GEN.l(ii,v,r,h,t) } + sum{(ii,v,h)$[cofire(ii)$valgen(ii,v,r,t)], bio_cofire_perc * hours(h) * heat_rate(ii,v,r,t) * GEN.l(ii,v,r,h,t) } ] + ] ; *========================= @@ -1740,6 +1780,11 @@ error_check('z') = ( ) * account for penalty paid to deploy capacity beyond interconnection queue limits + sum{(tg,r), cap_penalty(tg) * CAP_ABOVE_LIM.l(tg,r,t) } +* account for costs from FINITO: deflate from $2018 to $2004, +* 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 } ) / z.l ; @@ -2033,6 +2078,8 @@ load_cat("h2_network",r,t)$tmodel_new(t) = ; load_cat("dac",r,t)$tmodel_new(t) = sum{i$dac(i), prod_load_ann(i,r,t) } ; +load_cat("finito",r,t)$tmodel_new(t) = sum{h, hours(h) * USE_ELE_FINITO.l(r,h,t) } ; + *======================================== * H2 NETWORK *======================================== diff --git a/e_report_dump.py b/e_report_dump.py index faa3a20ab..aa3aa78a6 100644 --- a/e_report_dump.py +++ b/e_report_dump.py @@ -67,6 +67,8 @@ def dfdict_to_h5( """ Write dictionary of dataframes to one .h5 file """ + print(f"Saving results to {filepath}") + ### unless a subset is specified, iterate over all keys in dict _symbol_list = dfdict.keys() if symbol_list is None else symbol_list @@ -108,6 +110,8 @@ def dfdict_to_excel( """ Write dictionary of dataframes to one .xlsx file """ + print(f"Saving results to {filepath}") + ### unless a subset is specified, iterate over all keys in dict _symbol_list = dfdict.keys() if symbol_list is None else symbol_list @@ -201,10 +205,13 @@ def postprocess_outputs(case, outputs_path=None, verbose=0): _outputs_path = os.path.join(case, 'outputs') if outputs_path is None else outputs_path ## System cost - reeds.output_calc.calc_systemcost(case).to_csv( - os.path.join(_outputs_path, 'post_systemcost_annualized.csv'), - index=False, - ) + try: + reeds.output_calc.calc_systemcost(case).to_csv( + os.path.join(_outputs_path, 'post_systemcost_annualized.csv'), + index=False, + ) + except FileNotFoundError: + print("Skipping system cost calculation, missing files") ## Reinforcement and spur-line reeds.output_calc.calc_reinforcement_spur_capacity_miles(case).to_csv( @@ -276,6 +283,18 @@ def postprocess_outputs(case, outputs_path=None, verbose=0): ) print("Finished loading outputs gdx") + ### FINITO outputs when running linked model + if int(sw.GSw_FINITO_Link): + print("Loading FINITO outputs gdx") + # TODO: this is much slower than loading the ReEDS outputs, + # need to figure out why and address + finito_outputs = gdxpds.to_dataframes( + os.path.join(outputs_path, f"finito_reeds_outputs_{os.path.basename(case)}.gdx") + ) + print("Finished loading FINITO outputs gdx") + dict_out.update(finito_outputs) + + write_dfdict( dfdict=dict_out, outputs_path=outputs_path, diff --git a/e_report_params.csv b/e_report_params.csv index 70b370208..f7792cb3a 100644 --- a/e_report_params.csv +++ b/e_report_params.csv @@ -142,6 +142,7 @@ raw_op_cost(t),2004$,sum of operational costs from systemcost,,, 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,,, "repgasprice_r(r,t)",2004$/MMBtu,highest marginal gas price of utilized gas bins for each region,1,, repgasprice_nat(t),2004$/MMBtu,weighted-average national natural gas price assuming that plants pay the marginal price,,, "repgasquant(cendiv,t)",Quads,quantity of gas consumed in each census division,,, diff --git a/input_processing/copy_files.py b/input_processing/copy_files.py index 90e22a098..8c53b1643 100644 --- a/input_processing/copy_files.py +++ b/input_processing/copy_files.py @@ -1614,6 +1614,11 @@ def main(reeds_path, inputs_case): source_deflator_map = get_source_deflator_map(reeds_path) + ### 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) + scalar_csv_to_txt(os.path.join(os.path.dirname(inputs_case),'finito','inputs','scalars.csv')) + # Copy non-region files write_non_region_files(non_region_files, sw, inputs_case, regions_and_agglevel, source_deflator_map) diff --git a/input_processing/hourly_load.py b/input_processing/hourly_load.py index 8b2114fb9..a29ee8f56 100644 --- a/input_processing/hourly_load.py +++ b/input_processing/hourly_load.py @@ -596,6 +596,44 @@ def reaggregate_to_model_regions( return regional_load_hourly +#TODO: add docstrings +def get_hourly_finito_load( + inputs_case: str, +) -> pd.DataFrame: + # load reference FINITO load + inputs_case_finito = Path(inputs_case).parent / 'finito' / Path(inputs_case).name + load_finito = pd.read_csv(inputs_case_finito / "load_finito.csv") + + # reshape to match load data. with year in index and region in columns + load_finito = load_finito.melt(id_vars='r', var_name='year', value_name='load_MWh') + load_finito.year = load_finito.year.astype(int) + load_finito = load_finito.pivot(index='year', columns='r', values='load_MWh') + load_finito.columns.name = None + + # allocate annual load to hours, assuming flat demand + # TODO: should this use h_weight_finito? + hours_per_year = 8760 + load_hourly_finito = load_finito / hours_per_year + load_hourly_finito = load_hourly_finito.astype(np.float32) + + return load_hourly_finito + +def remove_finito_load( + load_hourly: pd.DataFrame, + inputs_case: str, +) -> pd.DataFrame: + + # get FINITO reference load + load_hourly_finito = get_hourly_finito_load(inputs_case) + + # subtract FINITO reference load from ReEDS load data, + # aligning by model year (index) and region (columns) + result = load_hourly - load_hourly_finito + + #TODO: add this + # Validation check: confirm that total load - FINITO load > 0 + + return load_hourly #%% =========================================================================== ### --- MAIN FUNCTION --- @@ -697,6 +735,15 @@ def main(reeds_path, inputs_case): peakload = calculate_peak_load(regional_load_hourly, hierarchy) #%%%######################################### + # -- FINITO Load Adjustment -- # + ############################################# + + # note that this step occurs after peakload calculation so that the latter + # includes a baseline estimate of industrial load captured by FINITO + if int(sw.GSw_FINITO_Link): + regional_load_hourly = remove_finito_load(regional_load_hourly, inputs_case) + + ############################################# # -- DR Shed Load Modifications -- # ############################################# diff --git a/input_processing/hourly_repperiods.py b/input_processing/hourly_repperiods.py index 2c57bcc8d..7d13b20e4 100644 --- a/input_processing/hourly_repperiods.py +++ b/input_processing/hourly_repperiods.py @@ -36,6 +36,7 @@ import hourly_plots sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) import reeds +import hourly_load ## Time the operation of this script tic = datetime.datetime.now() @@ -75,11 +76,18 @@ def szn2period(szn): # -- Load Processing -- # ############################### -def get_load(inputs_case, keep_modelyear=None, keep_weatheryears=[2012]): +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'), parse_timestamps=True) + + ### 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 + if int(sw.GSw_FINITO_Link): + 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() @@ -578,6 +586,7 @@ def main( print("Collecting 8760 load data") load = get_load( inputs_case=inputs_case, + sw=sw, keep_modelyear=(int(sw['GSw_HourlyClusterYear']) if int(sw['GSw_HourlyClusterYear']) in modelyears else max(modelyears)), @@ -736,7 +745,7 @@ def main( and (sw['GSw_PRM_StressSeedLoadLevel'].lower() not in ['false','none']) ): ## Get load for all model and weather years - load_allyears = get_load(inputs_case, keep_weatheryears='all').loc[modelyears] + load_allyears = get_load(inputs_case, sw, keep_weatheryears='all').loc[modelyears] ## Add descriptive index load_allyears = load_allyears.merge( timestamps[['year', 'yperiod', 'h_of_period']], left_on='datetime', right_index=True) @@ -925,10 +934,14 @@ def main( description='Create the necessary 8760 and capacity factor data for hourly resolution') parser.add_argument('reeds_path', help='ReEDS-2.0 directory') parser.add_argument('inputs_case', help='ReEDS-2.0/runs/{case}/inputs_case directory') + parser.add_argument('--nolog', '-n', default=False, action='store_true', help='turn off logging for debugging') + args = parser.parse_args() reeds_path = args.reeds_path inputs_case = args.inputs_case + logging = not args.nolog + # #%% Settings for testing # reeds_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) @@ -938,10 +951,11 @@ def main( # interactive = True #%% Set up logger - log = reeds.log.makelog( - scriptname=__file__, - logpath=os.path.join(inputs_case,'..','gamslog.txt'), - ) + if logging: + log = reeds.log.makelog( + scriptname=__file__, + logpath=os.path.join(inputs_case,'..','gamslog.txt'), + ) print('Starting hourly_repperiods.py') #%% Inputs from switches sw = reeds.io.get_switches(inputs_case) @@ -956,6 +970,7 @@ def main( sw=sw, reeds_path=reeds_path, inputs_case=inputs_case, periodtype='rep', make_plots=1, + logging=logging ) ############################################ diff --git a/input_processing/hourly_writetimeseries.py b/input_processing/hourly_writetimeseries.py index 5fa326089..57c5f4363 100644 --- a/input_processing/hourly_writetimeseries.py +++ b/input_processing/hourly_writetimeseries.py @@ -199,6 +199,9 @@ def get_yearly_demand(sw, hmap_myr, hmap_allyrs, inputs_case, periodtype='rep'): reload the raw demand and extract the demand on the modeled days for each year. """ ### Get original demand data, subset to cluster year + ### Note that this does not include FINITO demand when running with the linked model + ### (GSw_FINITO_Link=1) even though the reference estimates for that load is used when + ### identifying the representative days load_in = reeds.io.read_file( os.path.join(inputs_case,'load.h5'), parse_timestamps=True).unstack(level=0) load_in.columns = load_in.columns.rename(['r','t']) diff --git a/inputs/emission_constraints/co2_tax.csv b/inputs/emission_constraints/co2_tax.csv index d9e795517..1e08f6246 100644 --- a/inputs/emission_constraints/co2_tax.csv +++ b/inputs/emission_constraints/co2_tax.csv @@ -1,42 +1,42 @@ -t,default,t050_2035,t050_2050,t100_2035,t100_2050,t150_2035,t150_2050,t200_2035,t200_2050 -2010,0,0,0,0,0,0,0,0,0 -2011,0,0,0,0,0,0,0,0,0 -2012,0,0,0,0,0,0,0,0,0 -2013,0,0,0,0,0,0,0,0,0 -2014,0,0,0,0,0,0,0,0,0 -2015,0,0,0,0,0,0,0,0,0 -2016,0,0,0,0,0,0,0,0,0 -2017,0,0,0,0,0,0,0,0,0 -2018,0,0,0,0,0,0,0,0,0 -2019,0,0,0,0,0,0,0,0,0 -2020,0,0,0,0,0,0,0,0,0 -2021,0,0,0,0,0,0,0,0,0 -2022,30,1,1,1,1,1,1,1,1 -2023,31.5,4.77,2.75,8.62,4.54,12.46,6.32,16.31,8.11 -2024,33.075,8.54,4.5,16.23,8.07,23.92,11.64,31.62,15.21 -2025,34.72875,12.31,6.25,23.85,11.61,35.38,16.96,46.92,22.32 -2026,36.4651875,16.08,8,31.46,15.14,46.85,22.29,62.23,29.43 -2027,38.28844688,19.85,9.75,39.08,18.68,58.31,27.61,77.54,36.54 -2028,40.20286922,23.62,11.5,46.69,22.21,69.77,32.93,92.85,43.64 -2029,42.21301268,27.38,13.25,54.31,25.75,81.23,38.25,108.15,50.75 -2030,44.32366331,31.15,15,61.92,29.29,92.69,43.57,123.46,57.86 -2031,46.53984648,34.92,16.75,69.54,32.82,104.15,48.89,138.77,64.96 -2032,48.8668388,38.69,18.5,77.15,36.36,115.62,54.21,154.08,72.07 -2033,51.31018074,42.46,20.25,84.77,39.89,127.08,59.54,169.38,79.18 -2034,53.87568978,46.23,22,92.38,43.43,138.54,64.86,184.69,86.29 -2035,56.56947427,50,23.75,100,46.96,150,70.18,200,93.39 -2036,59.39794798,50,25.5,100,50.5,150,75.5,200,100.5 -2037,62.36784538,50,27.25,100,54.04,150,80.82,200,107.61 -2038,65.48623765,50,29,100,57.57,150,86.14,200,114.71 -2039,68.76054953,50,30.75,100,61.11,150,91.46,200,121.82 -2040,72.19857701,50,32.5,100,64.64,150,96.79,200,128.93 -2041,75.80850586,50,34.25,100,68.18,150,102.11,200,136.04 -2042,79.59893115,50,36,100,71.71,150,107.43,200,143.14 -2043,83.57887771,50,37.75,100,75.25,150,112.75,200,150.25 -2044,87.7578216,50,39.5,100,78.79,150,118.07,200,157.36 -2045,92.14571268,50,41.25,100,82.32,150,123.39,200,164.46 -2046,96.75299831,50,43,100,85.86,150,128.71,200,171.57 -2047,101.5906482,50,44.75,100,89.39,150,134.04,200,178.68 -2048,106.6701806,50,46.5,100,92.93,150,139.36,200,185.79 -2049,112.0036897,50,48.25,100,96.46,150,144.68,200,192.89 -2050,117.6038742,50,50,100,100,150,150,200,200 \ No newline at end of file +t,default,t050_2035,t050_2050,t100_2035,t100_2050,t150_2035,t150_2050,t200_2035,t200_2050,t200alt_2050 +2010,0,0,0,0,0,0,0,0,0,0 +2011,0,0,0,0,0,0,0,0,0,0 +2012,0,0,0,0,0,0,0,0,0,0 +2013,0,0,0,0,0,0,0,0,0,0 +2014,0,0,0,0,0,0,0,0,0,0 +2015,0,0,0,0,0,0,0,0,0,0 +2016,0,0,0,0,0,0,0,0,0,0 +2017,0,0,0,0,0,0,0,0,0,0 +2018,0,0,0,0,0,0,0,0,0,0 +2019,0,0,0,0,0,0,0,0,0,0 +2020,0,0,0,0,0,0,0,0,0,0 +2021,0,0,0,0,0,0,0,0,0,0 +2022,30,1,1,1,1,1,1,1,1,0 +2023,31.5,4.77,2.75,8.62,4.54,12.46,6.32,16.31,8.11,0 +2024,33.075,8.54,4.5,16.23,8.07,23.92,11.64,31.62,15.21,0 +2025,34.72875,12.31,6.25,23.85,11.61,35.38,16.96,46.92,22.32,0 +2026,36.4651875,16.08,8,31.46,15.14,46.85,22.29,62.23,29.43,0 +2027,38.28844688,19.85,9.75,39.08,18.68,58.31,27.61,77.54,36.54,0 +2028,40.20286922,23.62,11.5,46.69,22.21,69.77,32.93,92.85,43.64,0 +2029,42.21301268,27.38,13.25,54.31,25.75,81.23,38.25,108.15,50.75,0 +2030,44.32366331,31.15,15,61.92,29.29,92.69,43.57,123.46,57.86,0 +2031,46.53984648,34.92,16.75,69.54,32.82,104.15,48.89,138.77,64.96,10 +2032,48.8668388,38.69,18.5,77.15,36.36,115.62,54.21,154.08,72.07,20 +2033,51.31018074,42.46,20.25,84.77,39.89,127.08,59.54,169.38,79.18,30 +2034,53.87568978,46.23,22,92.38,43.43,138.54,64.86,184.69,86.29,40 +2035,56.56947427,50,23.75,100,46.96,150,70.18,200,93.39,50 +2036,59.39794798,50,25.5,100,50.5,150,75.5,200,100.5,60 +2037,62.36784538,50,27.25,100,54.04,150,80.82,200,107.61,70 +2038,65.48623765,50,29,100,57.57,150,86.14,200,114.71,80 +2039,68.76054953,50,30.75,100,61.11,150,91.46,200,121.82,90 +2040,72.19857701,50,32.5,100,64.64,150,96.79,200,128.93,100 +2041,75.80850586,50,34.25,100,68.18,150,102.11,200,136.04,110 +2042,79.59893115,50,36,100,71.71,150,107.43,200,143.14,120 +2043,83.57887771,50,37.75,100,75.25,150,112.75,200,150.25,130 +2044,87.7578216,50,39.5,100,78.79,150,118.07,200,157.36,140 +2045,92.14571268,50,41.25,100,82.32,150,123.39,200,164.46,150 +2046,96.75299831,50,43,100,85.86,150,128.71,200,171.57,160 +2047,101.5906482,50,44.75,100,89.39,150,134.04,200,178.68,170 +2048,106.6701806,50,46.5,100,92.93,150,139.36,200,185.79,180 +2049,112.0036897,50,48.25,100,96.46,150,144.68,200,192.89,190 +2050,117.6038742,50,50,100,100,150,150,200,200,200 diff --git a/reeds/reedsplots.py b/reeds/reedsplots.py index da1013b1f..1630ca17c 100644 --- a/reeds/reedsplots.py +++ b/reeds/reedsplots.py @@ -5020,6 +5020,7 @@ def plot_seed_stressperiods( ### Recalculate peak load days since we dropped duplicates above load_allyears = hourly_repperiods.get_load( os.path.join(case, 'inputs_case'), + sw, keep_weatheryears='all').loc[years] timestamps = pd.read_csv(os.path.join(case, 'inputs_case', 'rep', 'timestamps.csv')) resource_adequacy_years = [int(y) for y in sw.resource_adequacy_years.split('_')] diff --git a/restart_runs.py b/restart_runs.py index dee9a85c6..71eb72e73 100644 --- a/restart_runs.py +++ b/restart_runs.py @@ -4,6 +4,8 @@ import subprocess import argparse import pandas as pd +import re +import reeds from glob import glob from runbatch import submit_slurm_parallel_jobs from runstatus import get_run_status @@ -92,7 +94,14 @@ #%% Copy additional files if desired for f in more_copyfiles: - shutil.copy(os.path.join(reeds_path,f), os.path.join(case,f)) + if f.lower().startswith('finito'): + # if file starts with 'finito' append the finito directory + sw = reeds.io.get_switches(case) + if int(sw.GSw_FINITO_Link): + f_copy = re.sub("^finito/", "" , f, flags=re.IGNORECASE) + shutil.copy(os.path.join(sw.finito_dir,f_copy), os.path.join(case,f)) + else: + shutil.copy(os.path.join(reeds_path,f), os.path.join(case,f)) #%% Make a backup copy of the original bash and sbatch scripts callfile = os.path.join(case,f'call_{casename}.sh') @@ -165,7 +174,8 @@ # It is a single case or we are not on HPC if copy_srun_template: writelines_srun_case = writelines_srun.copy() - writelines_srun_case.append(f"\n#SBATCH --job-name={casename}\n") + writelines_srun_case.append(f"#SBATCH --job-name={casename}") + writelines_srun_case.append(f"#SBATCH --output={os.path.join(case, 'slurm-%j.out')}\n") writelines_srun_case.append(f"sh {callfile}") with open(sbatchfile, 'w') as f: for line in writelines_srun_case: diff --git a/runbatch.py b/runbatch.py index 33b329796..106d27355 100644 --- a/runbatch.py +++ b/runbatch.py @@ -2,6 +2,10 @@ ### --- IMPORTS --- ### =========================================================================== +#%% =========================================================================== +### --- IMPORTS --- +### =========================================================================== + import os import git import queue @@ -11,6 +15,8 @@ import csv import importlib import numpy as np +import gdxpds +from gdxpds.gdx import GdxFile,GdxSymbol,GamsDataType import pandas as pd import subprocess import re @@ -18,6 +24,7 @@ import argparse from pathlib import Path import reeds +import sys # Assert core programs are accessible CORE_PROGRAMS = ["gams"] @@ -99,12 +106,23 @@ def create_case_lists(df_cases:pd.DataFrame, BatchName:str, single:str=''): continue # Add switch settings to list of options passed to GAMS shcom = f' --case={BatchName}_{case}' - for i,v in df_cases[case].items(): + # (linked) Combine the cases files for the linked model + if int(df_cases[case].loc['GSw_FINITO_Link']) == 1 : + # add the FINITO switches to the caseSwitches + df_cases_combine=linked_cases(df_cases,case) + for i,v in df_cases_combine.items(): + #exclude certain switches that don't need to be passed to GAMS + if i not in ['file_replacements','keep_run_terminal']: + shcom = shcom + ' --' + i + '=' + v + caseList.append(shcom) + caseSwitches.append(df_cases_combine.to_dict()) + else: + for i,v in df_cases[case].items(): #exclude certain switches that don't need to be passed to GAMS - if i not in ['file_replacements','keep_run_terminal']: - shcom += f' --{i}={v}' - caseList.append(shcom) - caseSwitches.append(df_cases[case].to_dict()) + if i not in ['file_replacements','keep_run_terminal']: + shcom += f' --{i}={v}' + caseList.append(shcom) + caseSwitches.append(df_cases[case].to_dict()) return caseSwitches, casenames, caseList @@ -554,6 +572,7 @@ def solvestring_sequential( 'GSw_StateCO2ImportLevel', 'GSw_StartMarkets', 'GSw_ValStr', + 'GSw_FINITO_Link', 'solver', 'debug', 'startyear', @@ -807,6 +826,71 @@ def setup_window( OPATH.writelines( "python valuestreams.py" + '\n') +# (linked) update 'df_cases' to include FINITO switches +def linked_cases(df_cases,case): + """ + Updates the cases dataframe to include FINITO-specific switches. + When a switch is duplicated in FINITO and ReEDS, then we default to + the ReEDS value. + + For the FINITO switches, the combined cases file defaults to the case-specific + 'Default Value' in cases_linked.csv first, before using the universal FINITO + 'Default Value' in cases.csv for any un-assigned switches. + """ + # check for valid finito_dir + if not os.path.isdir(df_cases[case]['finito_dir']): + raise ValueError(f"finito_dir = {df_cases[case]['finito_dir']} is not a valid path.") + + # define path to and read the FINITO check_inputs function + finito_check_inputs_path = os.path.join(df_cases[case]['finito_dir'], 'input_processing', 'processing') + sys.path.append(finito_check_inputs_path) + from check_inputs import check_inputs + + ### load the default values for all FINITO switches from ~\FINITO\cases.csv + df_cases_finito = pd.read_csv(os.path.join(df_cases[case]['finito_dir'],'cases.csv'), dtype=object, index_col=0) + df_cases_finito = df_cases_finito[['Choices', 'Default Value']] + + ### load the scenario-specific switches from ~\FINITO\cases_linked.csv + cases_linked_path = os.path.join(df_cases[case]['finito_dir'],f"cases_{df_cases[case]['finito_cases_file']}.csv") + df_cases_suf_finito = pd.read_csv(cases_linked_path, dtype=object, index_col=0) + ## check that case names are unique in cases_linked.csv + # grab the scenario names **exactly** as they appear in the csv file + with open(cases_linked_path, 'r', newline='') as csvfile: + reader = csv.reader(csvfile) + header = next(reader) + # find the duplicate column names and raise an error if any are found + duplicate_columns = set([x for x in header if header.count(x) > 1]) + if duplicate_columns: + raise ValueError(f"The FINITO cases_{df_cases[case]['finito_cases_file']}.csv has the following duplicate column names: {duplicate_columns}") + ### identify the FINITO case + if df_cases[case]['finito_case'] == 'same': + finito_case=case + else: + finito_case=df_cases[case]['finito_case'] + # ensures **exact** match of names between the ReEDS cases_{}.csv and the FINITO cases_linked.csv + if finito_case not in (df_cases_suf_finito.columns): + raise ValueError(f"The 'finito_case' input '{finito_case}' in the ReEDS cases file does not exist in FINITO's cases_{df_cases[case]['finito_cases_file']}.csv.") + + ### first use 'Default Value' from the FINITO cases_linked.csv to fill missing switches + if 'Default Value' in df_cases_suf_finito.columns: + df_cases_suf_finito[finito_case] = df_cases_suf_finito[finito_case].fillna(df_cases_suf_finito['Default Value']) + ### then, use 'Default Value' from the FINITO cases.csv to fill un-assigned switches + df_cases_suf_finito.drop(['Choices','Default Value'], axis='columns',inplace=True, errors='ignore') + df_cases_finito = df_cases_finito.join(df_cases_suf_finito, how='outer') + df_cases_finito[finito_case] = df_cases_finito[finito_case].fillna(df_cases_finito['Default Value']) + + #### create new dataframe for the combined ReEDS and FINITO switches + df_cases_combine = pd.concat([df_cases[case],df_cases_finito[finito_case]]) + ### drop duplicated switches, defaulting to reeds + df_cases_combine = df_cases_combine[~df_cases_combine.index.duplicated(keep='first')] + + #%% Check for incompatibility of FINITO switches + model_sectors = df_cases_finito['Default Value']['focus_sectors'].split('.') + check_inputs(case = case, df_case = df_cases_combine, model_sectors=model_sectors) + + return df_cases_combine + + #%% =========================================================================== ### --- PROCEDURE --- ### =========================================================================== @@ -1260,6 +1344,7 @@ def write_batch_script( yearset_augur = os.path.join('inputs_case','modeledyears.csv') toLogGamsString = ' logOption=4 logFile=gamslog.txt appendLog=1 ' + ## Copy code folders for dirname in ['reeds', 'ReEDS_Augur', 'input_processing', 'reeds2pras']: shutil.copytree( @@ -1271,6 +1356,66 @@ def write_batch_script( #make the augur_data folder os.makedirs(os.path.join(casedir,'ReEDS_Augur','augur_data'), exist_ok=True) os.makedirs(os.path.join(casedir,'ReEDS_Augur','PRAS'), exist_ok=True) + + ## (linked) Copy FINITO code folders and inputs into [casedir]/finito + if int(caseSwitches['GSw_FINITO_Link'])==1: + ## Redirecting the FINITO case-specific directories + # ... finito directory within the case directory + casedir_finito = os.path.join(casedir,'finito') + # ... define the inputs case directory for FINITO + inputs_case_finito = os.path.join(casedir,'finito','inputs_case') + + ## copy directories + os.makedirs(casedir_finito, exist_ok=True) + shutil.copytree(os.path.join(caseSwitches['finito_dir'], 'inputs'),os.path.join(casedir,'finito', 'inputs')) + shutil.copytree(os.path.join(caseSwitches['finito_dir'], 'input_processing'),os.path.join(casedir,'finito', 'input_processing')) + shutil.copytree(os.path.join(caseSwitches['finito_dir'], 'model'),os.path.join(casedir,'finito', 'model')) + shutil.copytree(os.path.join(caseSwitches['finito_dir'], 'visualization'),os.path.join(casedir, 'finito', 'visualization')) + shutil.copytree(os.path.join(caseSwitches['finito_dir'],'inputs'), os.path.join(casedir,'inputs'), dirs_exist_ok=True) + ## copy over the FINITO cases files + shutil.copy2(os.path.join(caseSwitches['finito_dir'], 'cases.csv'), os.path.join(casedir, 'finito')) + shutil.copy2(os.path.join(caseSwitches['finito_dir'], f"cases_{caseSwitches['finito_cases_file']}.csv"), os.path.join(casedir, 'finito')) + + + #%% (GSw_Trade_PriceResponse > 0) If doing a price-responsive trade run, retrieve the reference exports/imports prices + if int(caseSwitches['GSw_Trade_PriceResponse']) > 0: + initialize_price_response_path = os.path.join(casedir_finito, 'input_processing', 'processing', 'initialize_price_response.py') + # 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) + + #%% Filter and copy all input files for each scenario + # Call FINITO copy_files.py file before starting the runs + copy_files_run = os.system( + 'python ' + os.path.join(caseSwitches['finito_dir'], 'input_processing', 'processing', 'copy_files.py') + + f" -c {casedir_finito} -d {inputs_case_finito} --link" + ) + + # Print an error message if copy_files.py encounters any issue + if copy_files_run != 0: + print("\n❌ ERROR: copy_files.py encountered an issue and did not complete successfully.") + print("Please check the console output above for details.") + print("The issue could be due to regionality, focus sector filtering, or file reading errors.") + os._exit(1) + + ## Populate sets for each linked run + + # Pass AEO base year as an argument + # NOTE: make this dynamic before merging in? + aeo_year = caseSwitches['aeo_year'] + # Collect all arguments for autopop_set.py + focus_sectors = caseSwitches['focus_sectors'].replace('.', ' ') + autopop_args = f" -c {casedir_finito} -d {inputs_case_finito} -a {aeo_year} -s {focus_sectors} -f {caseSwitches['GSw_FixedCostSupply']} -rwf {caseSwitches['GSw_Trade_Partners']} -e {caseSwitches['GSw_ROE_EndUses']}" + # Call autopop_set.py file before starting the runs + autopop_path = os.path.join(caseSwitches['finito_dir'], 'input_processing','processing','autopop_set.py') + os.system('python ' + autopop_path + autopop_args) + ## Call read_mecs_heat.py to generate heat/nonheat/feedstock ratios for FINITO Rest of Industry (ROI) + mecs_sectors = focus_sectors + read_mecs_path = os.path.join(caseSwitches['finito_dir'], 'input_processing', 'processing', 'mecs', 'read_mecs_heat.py') + # Collect all arguments for read_mecs_heat.py + read_mecs_args = f' -s {mecs_sectors} -d {inputs_case_finito}' + os.system('python ' + read_mecs_path + read_mecs_args) ###### Replace files according to 'file_replacements' in cases. Ignore quotes in input text. # << is used to separate the file that is to be replaced from the file that is used @@ -1309,6 +1454,7 @@ def write_batch_script( OPATH.writelines("module load conda \n") OPATH.writelines("module load gams \n") + OPATH.writelines("conda deactivate \n") OPATH.writelines("conda activate reeds2 \n") OPATH.writelines('export R_LIBS_USER="$HOME/rlib" \n\n\n') @@ -1412,7 +1558,8 @@ def write_batch_script( + ' gdxcompress=1' + toLogGamsString + f"--fname={batch_case}" - + f" --GSw_calc_powfrac={caseSwitches['GSw_calc_powfrac']} \n" + + f" --GSw_calc_powfrac={caseSwitches['GSw_calc_powfrac']}" + + f" --first_year_finito={caseSwitches['first_year_finito']} \n" ) OPATH.writelines(writescripterrorcheck("e_report.gms")) if not LINUXORMAC: @@ -1425,6 +1572,32 @@ def write_batch_script( + f" {os.path.join(reeds_path,'postprocessing','diagnose','diagnose_process.py')}" + f" --casepath {casedir} \n\n" ) + OPATH.writelines(f'python e_report_dump.py {casedir} -c\n\n') + # (linked) calls finito reporting (from [casedir]/finito/visualization) + if int(caseSwitches['GSw_FINITO_Link'])==1: + OPATH.writelines( + 'gams ' + + f"{os.path.join(casedir,'finito', 'model', 'finito_report.gms')}" + + f" o={os.path.join('lstfiles',f'finito_report_{batch_case}.lst')}" + + (' license=gamslice.txt' if hpc else '') + + (' r=$r' if LINUXORMAC else ' r=!r!') + + ' gdxcompress=1' + + toLogGamsString + + f"--case={batch_case}" + + f" --casedir={casedir}" + + f" --GSw_FINITO_Link={caseSwitches['GSw_FINITO_Link']}" + + f" --GSw_RetailAdder={caseSwitches['GSw_RetailAdder']}" + + f" --finito_inputs_dir={os.path.join('finito','inputs')}" + + f" --inputs_case_finito_dir={os.path.join('finito','inputs_case')}" + + f" --linked_report_dir={caseSwitches['linked_report_dir']} \n" + ) + # (linked) calls finito postprocessing (from [casedir]/finito/visualization) + OPATH.writelines( + f"python {os.path.join(casedir, 'finito', 'visualization', 'postprocessing.py')}" + + " -b 0" + + f" -l {caseSwitches['GSw_FINITO_Link']}" + + f' -c {batch_case} \n\n' + ) ### Run the retail rate module OPATH.writelines( @@ -1727,7 +1900,7 @@ def launch_single_case_run( shellscript = subprocess.Popen( [os.path.join(casedir, 'call_' + batch_case + ext)], shell=True) # Wait for it to finish before killing the thread - shellscript.wait() + #shellscript.wait() else: if int(caseSwitches['keep_run_terminal']) == 1: terminal_keep_flag = ' /k ' @@ -1855,7 +2028,7 @@ def main( help="Check inputs but don't start runs") args = parser.parse_args() - + main( BatchName=args.BatchName, cases_suffix=args.cases_suffix, single=args.single, simult_runs=args.simult_runs, forcelocal=args.forcelocal, skip_checks=args.skip_checks, From 0424508d93b090aad91d14afe9e1e064b15c36ec Mon Sep 17 00:00:00 2001 From: bsergi Date: Thu, 16 Apr 2026 12:42:43 -0400 Subject: [PATCH 02/78] branch transfer finito linkage --- ReEDS_Augur/prep_data.py | 32 +++- b_inputs.gms | 50 ++++-- c_supplymodel.gms | 88 ++++++++-- c_supplyobjective.gms | 49 +++--- cases.csv | 6 + cases_finito.csv | 46 +++++ createmodel.gms | 14 ++ d1_temporal_params.gms | 13 +- d2_varfix.gms | 6 + d3_data_dump.gms | 18 +- d_solveoneyear.gms | 19 +++ e_report.gms | 141 ++++++++++----- e_report_dump.py | 27 ++- e_report_params.csv | 1 + input_processing/copy_files.py | 5 + input_processing/hourly_load.py | 47 +++++ input_processing/hourly_repperiods.py | 27 ++- input_processing/hourly_writetimeseries.py | 3 + inputs/emission_constraints/co2_tax.csv | 84 ++++----- reeds/reedsplots.py | 1 + restart_runs.py | 14 +- runbatch.py | 189 ++++++++++++++++++++- 22 files changed, 716 insertions(+), 164 deletions(-) create mode 100644 cases_finito.csv diff --git a/ReEDS_Augur/prep_data.py b/ReEDS_Augur/prep_data.py index 8f4febfcc..9805767d1 100644 --- a/ReEDS_Augur/prep_data.py +++ b/ReEDS_Augur/prep_data.py @@ -147,8 +147,38 @@ def main(t, casedir, iteration=0): h_dt_szn.index.map(hmap_allyrs.set_index(['year', 'hour'])['*timestamp'])) h_dt_szn = h_dt_szn.reset_index().set_index('timestamp') + # load exogenous demand seen by ReEDS load = reeds.io.read_file(os.path.join(inputs_case, 'load.h5'), parse_timestamps=True) + load_year = load.loc[t] + + # when running linked model with FINITO (GSw_FINITO_Link=1), + # we also need to add in the load from FINITO + if int(sw.GSw_FINITO_Link): + load_finito_rt = gdxreeds['load_finito_rt'].rename(columns={'allh':'h', 'Value':'load_MW'}) + + # down select to relevant model year + load_finito = load_finito_rt.loc[load_finito_rt.t==t].drop('t', axis=1).copy() + #TODO: why so much shifting? validate the dynamics here with FINITO team + #TODO: also check against reference quantity + + # map from rep day to actual hour + # since we don't have multi-year profiles for FINITO load + # we assume they repeat across all weather years + # TODO: test that this works with multiple regions + load_finito = pd.merge(load_finito, h_dt_szn.reset_index(), on='h', how='outer')[['timestamp', 'r', 'load_MW']] + load_finito = load_finito.rename(columns={'timestamp':'datetime'}) + # add model year back in + #load_finito = load_finito.assign(year=t) + load_finito = load_finito.pivot(index=['datetime'], columns='r', values='load_MW') + + # convert timezone and fill any missing columns + load_finito.index = load_finito.index.tz_convert(load_year.index.tz) + load_finito = load_finito.reindex(columns=load_year.columns, fill_value=0) + + # add to load + load_new = load_year + load_finito + resources = pd.read_csv(os.path.join(inputs_case, 'resources.csv')) recf = reeds.io.read_file(os.path.join(inputs_case, 'recf.h5'), parse_timestamps=True) recf.columns = pd.MultiIndex.from_tuples([tuple(x.split('|')) for x in recf.columns], @@ -367,7 +397,7 @@ def intify(v): .merge(h_dt_szn_load_years[['h']].reset_index(), left_on='allh', right_on='h') .pivot(index='timestamp', columns='r', values='Value') ) - load_year = load.loc[t].add(can_exports, fill_value=0) + load_year = load_year.add(can_exports, fill_value=0) ### PRAS doesn't yet handle flexible load, so include all H2/DAC load in the ### version we write for PRAS diff --git a/b_inputs.gms b/b_inputs.gms index 0d59e5626..3c135f247 100644 --- a/b_inputs.gms +++ b/b_inputs.gms @@ -1341,7 +1341,8 @@ set tmodel(t) "years to include in the model", tfix(t) "years to fix variables over when summing over previous years", tprev(t,tt) "previous modeled tt from year t", stfeas(st) "states to include in the model", - tsolved(t) "years that have solved" ; + tsolved(t) "years that have solved", + tfuel(t) "years that use ReEDS fuel supply curve module (otherwise uses supply curves in FINITO)" ; *following parameters get re-defined when the solve years have been declared parameter mindiff(t) "minimum difference between t and all other tt that are in tmodel(t)" ; @@ -1354,7 +1355,7 @@ tfix(t) = no ; stfeas(st) = no ; tprev(t,tt) = no ; tsolved(t) = no ; - +tfuel(t)=no; *============================== * Year specification @@ -1379,6 +1380,12 @@ tprev(t,tt)$[tmodel_new(t)$tmodel_new(tt)$(tt.valmindiff(t))] = no ; +* If FINITO linkage is on, remove all modeled years from tfuel after first_year_finito +* as the FINITO supply curves will be used instead of those in ReEDS; +* otherwise, all modeled years use ReEDS supply curves and are eligible for tfuel +tfuel(t)$[tmodel_new(t)]=yes; +tfuel(t)$[tmodel_new(t)$Sw_FINITO_Link$(t.val>=%first_year_finito%)] = no ; + * In order to fill all necessary dimensions of upgrade techs parameters, we require * Sw_UpgradeYear in ban(i) to be a modeled year and thus we compute as either * the GSw_UpgradeYear option or the next modeled years after GSw_UpgradeYear @@ -2331,6 +2338,7 @@ noncumulative_prescriptions(pcat,r,t)$tmodel_new(t) ], prescribednonrsc(tt,pcat,r,"value") + prescribedrsc(tt,pcat,r,"value") } ; +noncumulative_prescriptions(pcat,r,t)$noncumulative_prescriptions(pcat,r,t) = round(noncumulative_prescriptions(pcat,r,t),6); parameter noncumulative_prescriptions_energy(pcat,r,t) "--MWh-- prescribed energy capacity that comes online in a given year" ; noncumulative_prescriptions_energy(pcat,r,t)$tmodel_new(t) @@ -2777,6 +2785,7 @@ h2_ptc(i,v,r,t)$valcap(i,v,r,t) = h2_ptc_in(i,v,t) ; * Otherwise, we assume it receives $0/kg because the cleanliness of its carbon cannot be proven h2_ptc("electrolyzer",v,r,t)$[(not Sw_H2_PTC)] = 0; + set h2_ptc_years(t) "years in which the hydrogen production incentive is active"; h2_ptc_years(t) = tmodel_new(t)$[sum{(i,v,r),h2_ptc(i,v,r,t)}]; @@ -3571,6 +3580,7 @@ $include inputs_case%ds%tsc_binwidth.csv $offdelim $onlisting / ; +tsc_binwidth(r,rr,tscbin) = 1e9; parameter tsc_forward(r,rr,tscbin) "--$/MW-- transmission upgrade cost for forward direction" / @@ -6543,22 +6553,25 @@ $offdelim $ondigit $onlisting / , - min_co2_spurline_distance "--mi-- minimum distance for a spur line (used to provide a floor for pipeline distances in r_cs_distance)" + min_co2_spurline_distance "--mi-- minimum distance for a spur line (used to provide a floor for pipeline distances in r_cs_distance)", + min_r_cs_distance(r) "--mi-- mininum euclidean distance between BA transmission endpoints and storage formations" ; $offempty -* Wherever BA centroids fall within formation boundaries, assume some average spur line distance to connect a CCS or DAC plant with an injection site -min_co2_spurline_distance = 20 ; -r_cs_distance(r,cs)$[r_cs_distance(r,cs) < min_co2_spurline_distance] = min_co2_spurline_distance ; - -* Assign spurline costs -cost_co2_spurline_cap(r,cs,t)$[r_cs(r,cs)$tmodel_new(t)] = Sw_CO2_spurline_cost * r_cs_distance(r,cs) ; +* Wherever BA centroids fall within formation boundaries, r_cs_distance will be equal to 0, to ensure these are not dropped from the set, set these elements to a small number +r_cs_distance(r,cs)$[r_cs(r,cs)$(r_cs_distance(r,cs) = 0)] = 1e-3 ; +* find the closest site to each region +min_r_cs_distance(r) = smin(cs$[r_cs(r,cs)], r_cs_distance(r,cs)); -* CO2 pipelines can be build between any two adjacent BAs -cost_co2_pipeline_cap(r,rr,t)$[routes_adjacent(r,rr)$tmodel_new(t)] = Sw_CO2_pipeline_cost * pipeline_distance(r,rr) ; -cost_co2_pipeline_fom(r,rr,t)$[routes_adjacent(r,rr)$tmodel_new(t)] = Sw_CO2_pipeline_fom * pipeline_distance(r,rr) ; +$ifthene.rcslimit %GSw_CO2_LimitStorageSites% == 1 +* remove the region site combinations that are not the closest +r_cs_distance(r,cs)$[r_cs(r,cs)$(r_cs_distance(r,cs) <= min_r_cs_distance(r))] = min_r_cs_distance(r) ; +r_cs_distance(r,cs)$[r_cs(r,cs)$(r_cs_distance(r,cs) > min_r_cs_distance(r))] = 0 ; +$endif.rcslimit -co2_routes(r,rr)$routes_adjacent(r,rr) = yes ; +* Wherever BA centroids fall within formation boundaries, assume some average spur line distance to connect a CCS or DAC plant with an injection site +min_co2_spurline_distance = 20 ; +r_cs_distance(r,cs)$[r_cs_distance(r,cs)$(r_cs_distance(r,cs) <= min_co2_spurline_distance )] = min_co2_spurline_distance ; $onempty table co2_char(cs,*) "co2 site characteristics including injection rate limit, total storage limit, and break even cost" @@ -6577,9 +6590,18 @@ cost_co2_stor_bec(cs,t) = co2_char(cs,"bec_%GSw_CO2_BEC%"); csfeas(cs)$[co2_storage_limit(cs)$co2_injection_limit(cs)] = yes ; * only want to consider r_cs pairs which have available capacity r_cs(r,cs)$[not csfeas(cs)] = no ; +r_cs(r,cs)$[not r_cs_distance(r,cs)] = no ; +* Assign spurline costs +cost_co2_spurline_cap(r,cs,t)$[r_cs(r,cs)$tmodel_new(t)] = Sw_CO2_spurline_cost * r_cs_distance(r,cs) ; cost_co2_spurline_fom(r,cs,t)$[r_cs(r,cs)$tmodel_new(t)] = Sw_CO2_spurline_fom * r_cs_distance(r,cs) ; +* CO2 pipelines can be build between any two adjacent BAs +cost_co2_pipeline_cap(r,rr,t)$[routes_adjacent(r,rr)$tmodel_new(t)] = Sw_CO2_pipeline_cost * pipeline_distance(r,rr) ; +cost_co2_pipeline_fom(r,rr,t)$[routes_adjacent(r,rr)$tmodel_new(t)] = Sw_CO2_pipeline_fom * pipeline_distance(r,rr) ; + +co2_routes(r,rr)$routes_adjacent(r,rr) = yes ; + cost_co2_pipeline_cap(r,rr,t) = %GSw_CO2_CostAdj% * cost_co2_pipeline_cap(r,rr,t); cost_co2_pipeline_fom(r,rr,t) = %GSw_CO2_CostAdj% * cost_co2_pipeline_fom(r,rr,t); cost_co2_stor_bec(cs,t) = %GSw_CO2_CostAdj% * cost_co2_stor_bec(cs,t) ; @@ -6771,4 +6793,4 @@ emit_rate(etype,e,i,v,r,t)$[not valgen(i,v,r,t)] = 0 ; *============================================================ valinv_init(i,v,r,t) = valinv(i,v,r,t) ; -valcap_init(i,v,r,t) = valcap(i,v,r,t) ; +valcap_init(i,v,r,t) = valcap(i,v,r,t) ; \ No newline at end of file diff --git a/c_supplymodel.gms b/c_supplymodel.gms index 029d53f10..71389843d 100644 --- a/c_supplymodel.gms +++ b/c_supplymodel.gms @@ -396,6 +396,14 @@ eq_loadcon(r,h,t)$tmodel(t).. * (the effect is the same but avoiding the h-indexed OP_LOADSITE reduces solve time) + OP_LOADSITE(r,h,t)$[Sw_LoadSiteCF$(Sw_LoadSiteCF<1)$val_loadsite(r)] + CAP_LOADSITE(r,t)$[(Sw_LoadSiteCF=1)$val_loadsite(r)] + +* [plus] load for industrial and converted fuel facilities (FINITO), +* including the PRM for stress periods +* TODO: should this include distloss? +* [MWh/hr = MW] +$ifthene.linked_load Sw_FINITO_Link==1 + + USE_ELE_FINITO(r,h,t)$[t_finito(t)] * (1 + prm(r,t)$h_stress(h)) +$endif.linked_load ; * --------------------------------------------------------------------------- @@ -1040,7 +1048,6 @@ eq_rsc_INVlim(r,i,rscbin,t)$[tmodel(t) *plus exogenous (pre-start-year) capacity, using its level in the first year (tfirst) + sum{(ii,v,tt)$[tfirst(tt)$rsc_agg(i,ii)$exog_rsc(i)], capacity_exog_rsc(ii,v,r,rscbin,tt) } - ; * --------------------------------------------------------------------------- @@ -2881,7 +2888,7 @@ eq_national_gen(t)$[tmodel(t)$national_gen_frac(t)$Sw_GenMandate].. * --------------------------------------------------------------------------- *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))].. sum{gb,GASUSED(cendiv,gb,h,t) } @@ -2898,7 +2905,7 @@ eq_gasused(cendiv,h,t)$[tmodel(t)$((Sw_GasCurve=0) or (Sw_GasCurve=3))].. * --------------------------------------------------------------------------- * gas from each bin needs to less than its capacity -eq_gasbinlimit(cendiv,gb,t)$[tmodel(t)$(Sw_GasCurve=0)].. +eq_gasbinlimit(cendiv,gb,t)$[tmodel(t)$tfuel(t)$(Sw_GasCurve=0)].. gaslimit(cendiv,gb,t) @@ -2909,7 +2916,7 @@ eq_gasbinlimit(cendiv,gb,t)$[tmodel(t)$(Sw_GasCurve=0)].. * --------------------------------------------------------------------------- -eq_gasbinlimit_nat(gb,t)$[tmodel(t)$(Sw_GasCurve=3)].. +eq_gasbinlimit_nat(gb,t)$[tmodel(t)$tfuel(t)$(Sw_GasCurve=3)].. gaslimit_nat(gb,t) @@ -2922,7 +2929,7 @@ eq_gasbinlimit_nat(gb,t)$[tmodel(t)$(Sw_GasCurve=3)].. * --------------------------------------------------------------------------- -eq_gasaccounting_regional(cendiv,t)$[tmodel(t)$(Sw_GasCurve=1)].. +eq_gasaccounting_regional(cendiv,t)$[tmodel(t)$tfuel(t)$(Sw_GasCurve=1)].. sum{fuelbin, VGASBINQ_REGIONAL(fuelbin,cendiv,t) } @@ -2935,7 +2942,7 @@ eq_gasaccounting_regional(cendiv,t)$[tmodel(t)$(Sw_GasCurve=1)].. * --------------------------------------------------------------------------- -eq_gasaccounting_national(t)$[tmodel(t)$(Sw_GasCurve=1)].. +eq_gasaccounting_national(t)$[tmodel(t)$tfuel(t)$(Sw_GasCurve=1)].. sum{fuelbin,VGASBINQ_NATIONAL(fuelbin,t) } @@ -2948,7 +2955,7 @@ eq_gasaccounting_national(t)$[tmodel(t)$(Sw_GasCurve=1)].. * --------------------------------------------------------------------------- -eq_gasbinlimit_regional(fuelbin,cendiv,t)$[tmodel(t)$(Sw_GasCurve=1)].. +eq_gasbinlimit_regional(fuelbin,cendiv,t)$[tmodel(t)$tfuel(t)$(Sw_GasCurve=1)].. Gasbinwidth_regional(fuelbin,cendiv,t) @@ -2959,7 +2966,7 @@ eq_gasbinlimit_regional(fuelbin,cendiv,t)$[tmodel(t)$(Sw_GasCurve=1)].. * --------------------------------------------------------------------------- -eq_gasbinlimit_national(fuelbin,t)$[tmodel(t)$(Sw_GasCurve=1)].. +eq_gasbinlimit_national(fuelbin,t)$[tmodel(t)$tfuel(t)$(Sw_GasCurve=1)].. Gasbinwidth_national(fuelbin,t) @@ -2973,10 +2980,10 @@ eq_gasbinlimit_national(fuelbin,t)$[tmodel(t)$(Sw_GasCurve=1)].. *============================== * -- Bioenergy Supply Curve -- *============================== +* defer to FINITO representation when models are linked * --------------------------------------------------------------------------- - -eq_bioused(r,t)$[sum{(i,v)$(bio(i) or cofire(i)), valgen(i,v,r,t) }$tmodel(t)].. +eq_bioused(r,t)$[sum{(i,v)$(bio(i) or cofire(i)), valgen(i,v,r,t) }$tmodel(t)$tfuel(t)].. sum{bioclass, BIOUSED(bioclass,r,t) } @@ -2995,7 +3002,7 @@ eq_bioused(r,t)$[sum{(i,v)$(bio(i) or cofire(i)), valgen(i,v,r,t) }$tmodel(t)].. * --------------------------------------------------------------------------- * biomass consumption limit is annual -eq_biousedlimit(bioclass,usda_region,t)$tmodel(t).. +eq_biousedlimit(bioclass,usda_region,t)$[tmodel(t)$tfuel(t)].. biosupply(usda_region,bioclass,"cap") @@ -3565,6 +3572,11 @@ eq_h2_demand(p,t)$[(sameas(p,"H2"))$tmodel(t)$(yeart(t)>=h2_demand_start)$(Sw_H2 + sum{(i,v,r,h)$[valgen(i,v,r,t)$h2_combustion(i)$h_rep(h)], GEN(i,v,r,h,t) * hours(h) * h2_combustion_intensity * heat_rate(i,v,r,t) } + +* hydrogen demand from indusrty (FINITO): FINITO demand [MMBtu/yr] * conversion [metric tons-H2/MMBtu-H2] +$ifthene.linked_h2_nat Sw_FINITO_Link==1 + + [sum{(r,h)$h_rep(h), hours(h) * USE_H2_FINITO(r,h,t) * h2_metric_tons_per_mmbtu }]$t_finito(t) +$endif.linked_h2_nat ; * --------------------------------------------------------------------------- @@ -3596,6 +3608,11 @@ eq_h2_demand_regional(r,h,t) + sum{(i,v)$[valgen(i,v,r,t)$h2_combustion(i)], GEN(i,v,r,h,t) * h2_combustion_intensity * heat_rate(i,v,r,t) } + +* regional hydrogen demand for industry (FINITO) +$ifthene.linked_h2_reg Sw_FINITO_Link==1 + + [ USE_H2_FINITO(r,h,t) * h2_metric_tons_per_mmbtu ]$t_finito(t) +$endif.linked_h2_reg ; * --------------------------------------------------------------------------- @@ -3838,11 +3855,16 @@ eq_co2_capture(r,h,t) * capture from DAC + sum{(i,v)$[dac(i)$valcap(i,v,r,t)$i_p(i,"DAC")], PRODUCE("DAC",i,v,r,h,t) }$Sw_DAC +* (linked) capture from industry [metric_tons-CO2/hr]: +* calculation: hours_per_year [yrs/hr] * capture [scaled_metric_tons-CO2/yr] / co2_scale [scaled_metric_tons-CO2/metric_tons-CO2] +$ifthene.linked_co2_capture Sw_FINITO_Link==1 + + [CAPTURE_CO2EM(r,h,t) / co2_scale]$[t_finito(t)$h_rep(h)] +$endif.linked_co2_capture ; * --------------------------------------------------------------------------- -eq_co2_transport_caplimit(r,rr,h,t)$[co2_routes(r,rr)$Sw_CO2_Detail +eq_co2_transport_caplimit(r,rr,h,t)$[co2_routes(r,rr)$Sw_CO2_Detail$h_rep(h) $tmodel(t)$(yeart(t)>=co2_detail_startyr)].. *capacity computed as cumulative investments of co2 pipelines up to the current year @@ -3858,7 +3880,8 @@ eq_co2_transport_caplimit(r,rr,h,t)$[co2_routes(r,rr)$Sw_CO2_Detail * --------------------------------------------------------------------------- -eq_co2_spurline_caplimit(r,cs,h,t)$[Sw_CO2_Detail$r_cs(r,cs)$tmodel(t)$(yeart(t)>=co2_detail_startyr)].. +eq_co2_spurline_caplimit(r,cs,h,t)$[Sw_CO2_Detail$r_cs(r,cs)$h_rep(h)$tmodel(t) + $(yeart(t)>=co2_detail_startyr)].. *capacity computed as cumulative investments of co2 spurlines up to the current year sum{tt$[(yeart(tt)<=yeart(t))$(tmodel(tt) or tfix(tt))$(yeart(tt)>=co2_detail_startyr)], @@ -3867,11 +3890,16 @@ eq_co2_spurline_caplimit(r,cs,h,t)$[Sw_CO2_Detail$r_cs(r,cs)$tmodel(t)$(yeart(t) =g= CO2_STORED(r,cs,h,t) +* (linked) extraction of CO2 [metric_tons-CO2/hr] +* calculation: hours_per_year [yrs/hr] * (1 / co2_scale [scaled_metric_tons-CO2/metric_tons-CO2]) * use [scaled_metric_tons-CO2/yr] +$ifthene.linked_co2_spurline_caplimit Sw_FINITO_Link==1 + + [ (1 / co2_scale) * EXTRACT_CO2_CS(cs,r,h,t) ]$[h_rep(h)$t_finito(t)] +$endif.linked_co2_spurline_caplimit ; * --------------------------------------------------------------------------- -eq_co2_sink(r,h,t)$[tmodel(t)$Sw_CO2_Detail$(yeart(t)>=co2_detail_startyr)].. +eq_co2_sink(r,h,t)$[tmodel(t)$Sw_CO2_Detail$h_rep(h)$(yeart(t)>=co2_detail_startyr)].. *the amount of co2 stored from r in all of its cs sites sum{cs$r_cs(r,cs), CO2_STORED(r,cs,h,t) } @@ -3886,11 +3914,24 @@ eq_co2_sink(r,h,t)$[tmodel(t)$Sw_CO2_Detail$(yeart(t)>=co2_detail_startyr)].. * net trade + sum{rr$co2_routes(r,rr), CO2_FLOW(rr,r,h,t) - CO2_FLOW(r,rr,h,t) } + +* (linked) extraction - use of CO2 [metric_tons-CO2/hr] +*calculation: hours_per_year [yrs/hr] * (1 / co2_scale [scaled_metric_tons-CO2/metric_tons-CO2]) * extraction/use [scaled_metric_tons-CO2/yr] +$ifthene.linked_co2_sink Sw_FINITO_Link==1 + + (1 / co2_scale) * [ +* extraction from all cs sites in r + + sum{cs$[csfeas(cs)$r_cs(r,cs)], EXTRACT_CO2_CS(cs,r,h,t)} +* total use of CO2, equivalent to supply + - USE_CO2(r,h,t) + ]$[h_rep(h)$t_finito(t)] +$endif.linked_co2_sink + ; * --------------------------------------------------------------------------- -eq_co2_injection_limit(cs,h,t)$[Sw_CO2_Detail$tmodel(t)$(yeart(t)>=co2_detail_startyr)$csfeas(cs)].. +eq_co2_injection_limit(cs,h,t)$[tmodel(t)$Sw_CO2_Detail$h_rep(h) + $(yeart(t)>=co2_detail_startyr)$csfeas(cs)].. * exogenously defined injection limit co2_injection_limit(cs) @@ -3899,6 +3940,14 @@ eq_co2_injection_limit(cs,h,t)$[Sw_CO2_Detail$tmodel(t)$(yeart(t)>=co2_detail_st * must exceed metric tons per hour entering storage sum{r$r_cs(r,cs), CO2_STORED(r,cs,h,t) } + + + +* (linked) extraction of CO2 for use [metric_tons-CO2/hr] +* calculation: hours_per_year [yrs/hr] * (1 / co2_scale [scaled_metric_tons-CO2/metric_tons-CO2]) * use [scaled_metric_tons-CO2/yr] +$ifthene.linked_co2_injection_limit Sw_FINITO_Link==1 + + (1 / co2_scale) * sum{r$[r_cs(r,cs)], EXTRACT_CO2_CS(cs,r,h,t) }$[h_rep(h)$t_finito(t)] +$endif.linked_co2_injection_limit ; * --------------------------------------------------------------------------- @@ -3915,7 +3964,16 @@ eq_co2_cumul_limit(cs,t)$[tmodel(t)$Sw_CO2_Detail$(yeart(t)>=co2_detail_startyr) $[(yeart(tt)<=yeart(t))$(tmodel(tt) or tfix(tt))$(yeart(tt)>=co2_detail_startyr) $r_cs(r,cs)$h_rep(h)], yearweight(tt) * hours(h) * CO2_STORED(r,cs,h,tt) } + +* (linked) cumulative amount extracted over time +$ifthene.linked_co2_storage_cumul_limit Sw_FINITO_Link==1 + - sum{(r,h,tt) + $[(yeart(tt)<=yeart(t))$(tmodel(tt) or tfix(tt))$(yeart(tt)>=co2_detail_startyr) + $r_cs(r,cs)$(tfuel(tt))], + yearweight(tt) * hours(h) * EXTRACT_CO2_CS(cs,r,h,tt) }$[t_finito(t)] +$endif.linked_co2_storage_cumul_limit ; + * --------------------------------------------------------------------------- *=================== diff --git a/c_supplyobjective.gms b/c_supplyobjective.gms index 38a5b0b36..a59553990 100644 --- a/c_supplyobjective.gms +++ b/c_supplyobjective.gms @@ -19,7 +19,17 @@ Variable Z "--$-- total cost of operations and investment, scale varie * objective function is the sum over modeled years of the investment * and operations components -eq_ObjFn.. Z =e= cost_scale * sum{t$tmodel(t), Z_inv(t) + Z_op(t) } ; +eq_ObjFn.. Z =e= cost_scale * ( +* electricity and H2 costs + sum{t$tmodel(t), Z_inv(t) + Z_op(t) } +* economy-wide costs from FINITO: deflate from $2018 to $2004 +* and remove any FINITO scaling +$ifthene.linked_objective Sw_FINITO_Link==1 + + deflator('2018') / obj_scale + * sum{t$[t_finito(t)], Z_finito(t) } +$endif.linked_objective + ) +; *======================================================= * -- Investment component of the objective function -- @@ -221,7 +231,7 @@ eq_Objfn_op(t)$tmodel(t).. * plus cost of H2 fuel when using fixed price (Sw_H2=0) or during stress periods. * When using endogenous H2 price (Sw_H2=1 or Sw_H2=2), H2 fuel cost is captured elsewhere * via the capex + opex costs of H2 production and its associated electricity demand. - + sum{(i,v,r,h)$[valgen(i,v,r,t)$heat_rate(i,v,r,t) + + sum{(i,v,r,h)$[tfuel(t)$valgen(i,v,r,t)$heat_rate(i,v,r,t) $(not gas(i))$(not bio(i))$(not cofire(i)) $((not h2_combustion(i)) or h2_combustion(i)$[(Sw_H2=0) or h_stress(h)])], hours(h) * heat_rate(i,v,r,t) * fuel_price(i,r,t) * GEN(i,v,r,h,t) } @@ -232,50 +242,49 @@ eq_Objfn_op(t)$tmodel(t).. * --cofire coal consumption--- * cofire bio consumption already accounted for in accounting of BIOUSED - + sum{(i,v,r,h)$[valgen(i,v,r,t)$cofire(i)$heat_rate(i,v,r,t)], + + sum{(i,v,r,h)$[tfuel(t)$valgen(i,v,r,t)$cofire(i)$heat_rate(i,v,r,t)], (1-bio_cofire_perc) * hours(h) * heat_rate(i,v,r,t) * fuel_price("coal-new",r,t) * GEN(i,v,r,h,t) } * --- cost of natural gas--- *Sw_GasCurve = 2 (static natural gas prices) *first - gas consumed for electricity generation - + sum{(i,v,r,h)$[valgen(i,v,r,t)$gas(i)$heat_rate(i,v,r,t)$(Sw_GasCurve = 2)], + + sum{(i,v,r,h)$[tfuel(t)$valgen(i,v,r,t)$gas(i)$heat_rate(i,v,r,t)$(Sw_GasCurve = 2)], hours(h) * heat_rate(i,v,r,t) * fuel_price(i,r,t) * GEN(i,v,r,h,t) } *second - gas consumed by gas-powered DAC - + sum{(v,r,h)$[valcap("dac_gas",v,r,t)$(Sw_GasCurve = 2)], - hours(h) * dac_gas_cons_rate("dac_gas",v,t) * PRODUCE("DAC","dac_gas",v,r,h,t) }$Sw_DAC_Gas + + sum{(v,r,h)$[tfuel(t)$valcap("dac_gas",v,r,t)$(Sw_GasCurve = 2)], + hours(h) * dac_gas_cons_rate("dac_gas",v,t) * PRODUCE("DAC","dac_gas",v,r,h,t) }$[Sw_DAC_Gas] *Sw_GasCurve = 0 (census division supply curves natural gas prices) - + sum{(cendiv,gb), sum{h, hours(h) * GASUSED(cendiv,gb,h,t) } + + sum{(cendiv,gb)$[tfuel(t)], sum{h, hours(h) * GASUSED(cendiv,gb,h,t) } * gasprice(cendiv,gb,t) - }$(Sw_GasCurve = 0) + }$[(Sw_GasCurve = 0)] *Sw_GasCurve = 3 (national supply curve for natural gas prices with census division multipliers) - + sum{(h,cendiv,gb), hours(h) * GASUSED(cendiv,gb,h,t) + + sum{(h,cendiv,gb)$[tfuel(t)], hours(h) * GASUSED(cendiv,gb,h,t) * gasadder_cd(cendiv,t,h) + gasprice_nat_bin(gb,t) - }$(Sw_GasCurve = 3) + }$[(Sw_GasCurve = 3)] *Sw_GasCurve = 1 (national and census division supply curves for natural gas prices) *first - anticipated costs of gas consumption given last year's amount - + (sum{(i,r,v,cendiv,h)$[valgen(i,v,r,t)$gas(i)], + + (sum{(i,r,v,cendiv,h)$[valgen(i,v,r,t)$gas(i)$tfuel(t)], gasmultterm(cendiv,t) * szn_adj_gas(h) * cendiv_weights(r,cendiv) * hours(h) * heat_rate(i,v,r,t) * GEN(i,v,r,h,t) } *second - adjustments based on changes from last year's consumption at the regional and national level - + sum{(fuelbin,cendiv), + + sum{(fuelbin,cendiv)$tfuel(t), gasbinp_regional(fuelbin,cendiv,t) * VGASBINQ_REGIONAL(fuelbin,cendiv,t) } - + sum{(fuelbin), + + sum{(fuelbin)$tfuel(t), gasbinp_national(fuelbin,t) * VGASBINQ_NATIONAL(fuelbin,t) } - )$[Sw_GasCurve = 1] + )$[(Sw_GasCurve = 1)] * ---cost of biofuel consumption and biomass transport--- - + sum{(r,bioclass)$[sum{(i,v)$(bio(i) or cofire(i)), valgen(i,v,r,t) }], + + sum{(r,bioclass)$[tfuel(t)$sum{(i,v)$(bio(i) or cofire(i)), valgen(i,v,r,t) }], BIOUSED(bioclass,r,t) * (sum{usda_region$r_usda(r,usda_region), biosupply(usda_region, bioclass, "price") } + bio_transport_cost) } - * --- hurdle costs for transmission flow --- + sum{(r,rr,h,trtype)$[routes(r,rr,trtype,t)$cost_hurdle(r,rr,t)], cost_hurdle(r,rr,t) * FLOW(r,rr,h,t,trtype) * hours(h) } @@ -340,15 +349,15 @@ eq_Objfn_op(t)$tmodel(t).. CO2_SPURLINE_INV(r,cs,tt) } }$[Sw_CO2_Detail$(yeart(t)>=co2_detail_startyr)] * --- CO2 injection break even costs - + sum{(r,cs,h)$r_cs(r,cs), hours(h) * CO2_STORED(r,cs,h,t) * cost_co2_stor_bec(cs,t) }$[Sw_CO2_Detail$(yeart(t)>=co2_detail_startyr)] + + sum{(r,cs,h)$[r_cs(r,cs)$h_rep(h)], hours(h) * CO2_STORED(r,cs,h,t) * cost_co2_stor_bec(cs,t) }$[Sw_CO2_Detail$(yeart(t)>=co2_detail_startyr)] * --- Tax credit for CO2 stored --- * note conversion to 12-year CRF given length of CO2 captured incentive payments - - sum{(i,v,r,h)$[valgen(i,v,r,t)$co2_captured_incentive(i,v,r,t)], + - sum{(i,v,r,h)$[valgen(i,v,r,t)$h_rep(h)$co2_captured_incentive(i,v,r,t)], (crf(t) / crf_co2_incentive(t)) * co2_captured_incentive(i,v,r,t) * hours(h) * capture_rate("CO2",i,v,r,t) * GEN(i,v,r,h,t)} * --- Tax credit for CO2 stored for DAC --- - - sum{(p,i,v,r,h)$[dac(i)$valcap(i,v,r,t)$i_p(i,p)$h_rep(h)], + - sum{(p,i,v,r,h)$[dac(i)$valcap(i,v,r,t)$i_p(i,p)$h_rep(h)$co2_captured_incentive(i,v,r,t)], (crf(t) / crf_co2_incentive(t)) * co2_captured_incentive(i,v,r,t) * hours(h) * PRODUCE(p,i,v,r,h,t)} * --- PTC value for electric power generation --- @@ -362,7 +371,7 @@ eq_Objfn_op(t)$tmodel(t).. - sum{(p,v,r,h)$[valcap("electrolyzer",v,r,t)$(sameas(p,"H2"))$h2_ptc("electrolyzer",v,r,t)$h_rep(h)], hours(h) * PRODUCE(p,"electrolyzer",v,r,h,t) * (crf(t) / crf_h2_incentive(t)) * h2_ptc("electrolyzer",v,r,t) * 1e3} - $[Sw_H2_PTC$Sw_H2$h2_ptc_years(t)$(yeart(t) >= h2_demand_start)] + $[(Sw_H2_PTC)$Sw_H2$h2_ptc_years(t)$(yeart(t) >= h2_demand_start)] *end multiplier for pvf_onm ) diff --git a/cases.csv b/cases.csv index 9d0752e77..59f86c653 100644 --- a/cases.csv +++ b/cases.csv @@ -33,6 +33,11 @@ coalscen,"Coal price scenario (inputs\fuelprices\{coalscen}.csv). The options fo demandscen,Demand scenario (see inputs\load),N/A,AEO_2025_reference, 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, +finito_dir,Directory with cloned FINITO repository,N/A,/projects/finito/molmezt/FINITO, +finito_cases_file,"extension of the FINITO cases file including all scenarios to be run, e.g., 'linked' for cases_linked.csv",N/A,linked, +finito_case,Case name for FINITO in {finito_dir}/cases_linked.csv; use 'same' if the scenario name in cases_linked.csv matches the scenario name in the cases file,N/A,BAU, +first_year_finito,First year when the FINITO module is enabled,N/A,2018, geodiscov,Annual discover rates for new geothermal sites,BAU; TI,BAU, geohydrosupplycurve,Geohydro Supply Curve,ATB_2023,ATB_2023, egssupplycurve,EGS Supply Curve,ATB_2023; reV,reV, @@ -116,6 +121,7 @@ GSw_CO2_pipeline_fom,Set FOM cost for CO2 Pipeline. 2004$/(tonne-mi)/hr-yr. Sugg GSw_CO2_spurline_cost,Set capex cost for CO2 Spurline. 2004$/(tonne-mi)/hr. Suggested value: 2257,N/A,2257, GSw_CO2_spurline_fom,Set FOM cost for CO2 Spurline. 2004$/(tonne-mi)/hr-yr. Suggested value: 60,N/A,60, GSw_CO2_Detail,Switch to enable detailed representation CO2 transportation and storage,0; 1,0, +GSw_CO2_LimitStorageSites,Switch to limit storage sites to the closest site for each region,0; 1,0, GSw_CO2_CostAdj,multiplier for co2 infrastructure when Sw_CO2_Detail = 1,float,1, GSw_CO2_BEC,"Break even cost capacity factor assignment. Must be a suffix to ""bec_"" in the columns of co2_site_char.csv",N/A,70, GSw_CoalIGCC,Turn on/off COAL IGCC,0; 1,1, diff --git a/cases_finito.csv b/cases_finito.csv new file mode 100644 index 000000000..25fb39db5 --- /dev/null +++ b/cases_finito.csv @@ -0,0 +1,46 @@ +,Default Value,ReEDSonly,BAUreg,Reference,Reference-AEO,Reference-HOG,TradeReference,TradeReference-AEO,Trade-HOG,Reference-LowBio,Reference-HighBio,Tax200,Tax200-AEO,NetZero,NetZero-LowBio,NetZero-HighBio,NetZero-SlackLim1500,NetZero-SlackLim1500-CO2Stor-Mid,NetZero-SlackLim1500-CO2Stor-Low,NetZero-SlackLim500-CO2Stor-Low,NetZero-SlackCost2500-CO2Stor-Low,aggreg_54_level +ignore,1,1,0,1,1,1,1,1,1,,,,,,,,,,,,, +GSw_FINITO_Link,1,0,,,,,,,,,,,,,,,,,,,, +finito_dir,/path/to/FINITO/directory,0,,,,,,,,,,,,,,,,,,,, +finito_cases_file,linked,,,,,,,,,,,,,,,,,,,,, +finito_case,BAU,,BAUreg,,BAU-AEO,AEO-HOG,TradeReEDS,TradeReEDS-AEO,TradeReEDS-HOG,BAU-LowBio,BAU-HighBio,,BAU-AEO,NetZero,NetZero-LowBio,NetZero-HighBio,NetZero-SlackLim1500,NetZero-SlackLim1500-CO2Stor-Mid,NetZero-SlackLim1500-CO2Stor-Low,NetZero-SlackLim500-CO2Stor-Low,NetZero-SlackCost2500-CO2Stor-Low,aggreg_54_level +coalscen,AEO_2025_reference,,,,,,,,,,,,,,,,,,,,, +ngscen,AEO_2025_reference,,,,,AEO_2025_HOG,,,AEO_2025_HOG,,,,,,,,,,,,, +uraniumscen,AEO_2025_reference,,,,,,,,,,,,,,,,,,,,, +GSw_Region,country/USA,,st/CO,,,,,,,,,,,,,,,,,,, +GSw_GasCurve,1,,2,,,,,,,,,,,,,,,,,,, +GSw_gopt,2,,,,,,,,,,,,,,,,,,,,, +GSw_NewValCapShrink,1,,,,,,,,,,,,,,,,,,,,, +endyear,2050,,2035,,,,,,,,,,,,,,,,,,, +yearset,2010_2015_2020..2050..5,,,,,,,,,,,,,,,,,,,,, +first_year_finito,2018,,,,,,,,,,,,,,,,,,,,, +incentives_suffix,obbba,,,,,,,,,,,,,,,,,,,,, +GSw_Upgrades,0,,,,,,,,,,,,,,,,,,,,, +GSw_AnnualCap,0,,,,,,,,,,,,,,,,,,,,, +GSw_AnnualCapScen,start2023_100pct2035,,,,,,,,,,,,,,,,,,,,, +GSw_Clean_Air_Act,0,,,,,,,,,,,,,,,,,,,,, +GSw_CSP,0,,,,,,,,,,,,,,,,,,,,, +GSw_Geothermal,0,,,,,,,,,,,,,,,,,,,,, +GSw_H2,2,,,,,,,,,,,,,,,,,,,,, +GSw_H2_PTC,0,,,,,,,,,,,,,,,,,,,,, +GSw_H2_Demand_Case,BAU,,,,,,,,,,,,,,,,,,,,, +GSw_H2_SMR,1,,,,,,,,,,,,,,,,,,,,, +GSw_H2_Transport,0,,,,,,,,,,,,,,,,,,,,, +GSw_H2Combustion,1,,,,,,,,,,,,,,,,,,,,, +GSw_H2Combustionupgrade,1,,,,,,,,,,,,,,,,,,,,, +GSw_H2CombinedCycle,1,,,,,,,,,,,,,,,,,,,,, +GSw_DAC,1,,,,,,,,,,,,,,,,,,,,, +GSw_BECCS,1,,,,,,,,,,,,,,,,,,,,, +GSw_RegionResolution,ba,,,,,,,,,,,,,,,,,,,,,aggreg +GSw_TCPhaseout,1,,,,,,,,,,,,,,,,,,,,, +GSw_CO2_Detail,1,,,,,,,,,,,,,,,,,,,,, +GSw_CO2_LimitStorageSites,1,,,,,,,,,,,,,,,,,,,,, +numhintage,4,,,,,,,,,,,,,,,,,,,,, +GSw_CarbTax,0,,,,,,,,,,,1,1,,,,,,,,, +GSw_CarbTaxOption,default,,,,,,,,,,,t200_2050,t200_2050,,,,,,,,, +GSw_PRM_CapCredit,0,,,,,,,,,,,,,,,,,,,,, +GSw_PRM_StressIterateMax,0,,,,,,,,,,,,,,,,,,,,, +GSw_HourlyNumClusters,9,,,,,,,,,,,,,,,,,,,,, +GSw_HourlyChunkLengthRep,4,,,,,,,,,,,,,,,,,,,,, +GSw_HourlyChunkLengthStress,4,,,,,,,,,,,,,,,,,,,,, +GSw_HierarchyFile,default,,,,,,,,,,,,,,,,,,,,,agg54 diff --git a/createmodel.gms b/createmodel.gms index 36ab4603e..d8ba1dcaf 100644 --- a/createmodel.gms +++ b/createmodel.gms @@ -1,5 +1,19 @@ $include b_inputs.gms + +$ifthene.finito_link %GSw_FINITO_Link% == 1 +$include finito/model/finito_input.gms +$endif.finito_link + +$ifthene.finito_link %GSw_FINITO_Link% == 1 +$include finito/model/finito_vars_eqs.gms +$endif.finito_link + $include c_supplymodel.gms + +$ifthene.finito_link %GSw_FINITO_Link% == 1 +$include finito/model/finito_model.gms +$endif.finito_link + $include c_supplyobjective.gms $include c_mga.gms $include d_solveprep.gms diff --git a/d1_temporal_params.gms b/d1_temporal_params.gms index 444c7fc5e..fcbf8578f 100644 --- a/d1_temporal_params.gms +++ b/d1_temporal_params.gms @@ -299,9 +299,6 @@ can_exports_h(r,h,t)$[hours(h)] = can_exports(r,t) * can_exports_h_frac(h) / hou $endif.Canada -* zero Canada exports when Canada is not modeled -can_imports_szn(r,szn,t)$[Sw_Canada=0] = 0 ; -can_exports_h(r,h,t)$[Sw_Canada=0] = 0 ; $onempty parameter canmexload(r,allh) "load for canadian and mexican regions" @@ -887,12 +884,20 @@ szn_adj_gas(h)$frac_h_quarter_weights(h,"wint") = szn_adj_gas(h) + frac_h_quarter_weights(h,"wint") * szn_adj_gas_winter ; +*============================================= +* ----- ReEDS-FINITO temporal parameters ----- +*============================================= +* remove focused sectors load from load_exog_static +$ifthene.linked_finito_temporal_params %GSw_FINITO_Link% ==1 +$include finito/model/linked_finito_temporal_params.gms +$endif.linked_finito_temporal_params + + *============================================= * -- Round parameters for GAMS -- *============================================= avail(i,r,h)$avail(i,r,h) = round(avail(i,r,h),3) ; -can_imports_szn(r,szn,t)$can_imports_szn(r,szn,t) = round(can_imports_szn(r,szn,t),3) ; can_exports_h(r,h,t)$can_exports_h(r,h,t) = round(can_exports_h(r,h,t),3) ; h_weight_csapr(h)$h_weight_csapr(h) = round(h_weight_csapr(h),3) ; load_exog(r,h,t)$load_exog(r,h,t) = round(load_exog(r,h,t),3) ; diff --git a/d2_varfix.gms b/d2_varfix.gms index 85db24429..3a5237328 100644 --- a/d2_varfix.gms +++ b/d2_varfix.gms @@ -9,6 +9,10 @@ if(Sw_RemoveSmallNumbers = 1, INV_ENERGY.l(i,v,r,tfix)$[abs(INV_ENERGY.l(i,v,r,tfix)) < rhs_tolerance] = 0 ; INV_RSC.l(i,v,r,rscbin,tfix)$[abs(INV_RSC.l(i,v,r,rscbin,tfix)) < rhs_tolerance] = 0 ; INV_POI.l(r,tfix)$[abs(INV_POI.l(r,tfix)) < rhs_tolerance] = 0 ; + INV_REFURB.l(i,v,r,tfix)$[abs(INV_REFURB.l(i,v,r,tfix)) < rhs_tolerance] = 0; + CO2_SPURLINE_INV.l(r,cs,tfix)$[abs(CO2_SPURLINE_INV.l(r,cs,tfix)) < rhs_tolerance] = 0; + CO2_TRANSPORT_INV.l(r,rr,tfix)$[abs(CO2_TRANSPORT_INV.l(r,rr,tfix)) < rhs_tolerance] = 0; + H2_TRANSPORT_INV.l(r,rr,tfix)$[abs(H2_TRANSPORT_INV.l(r,rr,tfix)) < rhs_tolerance] = 0 ; H2_STOR_INV.l(h2_stor,r,tfix)$[abs(H2_STOR_INV.l(h2_stor,r,tfix)) < rhs_tolerance] = 0 ; H2_TRANSPORT_INV.l(r,rr,tfix) $[abs(H2_TRANSPORT_INV.l(r,rr,tfix) ) < rhs_tolerance] = 0 ; ); @@ -119,7 +123,9 @@ H2_STOR_LEVEL_SZN.fx(h2_stor,r,actualszn,tfix)$[(h2_stor_r(h2_stor,r))$(Sw_H2=2) *CO2-related variables CO2_CAPTURED.fx(r,h,tfix)$Sw_CO2_Detail = CO2_CAPTURED.l(r,h,tfix) ; +$ifthene.linked_varfix Sw_FINITO_Link==0 CO2_STORED.fx(r,cs,h,tfix)$[Sw_CO2_Detail$r_cs(r,cs)] = CO2_STORED.l(r,cs,h,tfix) ; +$endif.linked_varfix CO2_FLOW.fx(r,rr,h,tfix)$[Sw_CO2_Detail$co2_routes(r,rr)] = CO2_FLOW.l(r,rr,h,tfix) ; CO2_TRANSPORT_INV.fx(r,rr,tfix)$[Sw_CO2_Detail$co2_routes(r,rr)] = CO2_TRANSPORT_INV.l(r,rr,tfix) ; CO2_SPURLINE_INV.fx(r,cs,tfix)$[Sw_CO2_Detail$r_cs(r,cs)] = CO2_SPURLINE_INV.l(r,cs,tfix) ; diff --git a/d3_data_dump.gms b/d3_data_dump.gms index 29c97036e..30a8ff0b9 100644 --- a/d3_data_dump.gms +++ b/d3_data_dump.gms @@ -8,6 +8,7 @@ This file creates a gdx file with all of the data necessary for the Augur module - availability rates (1 - outage rates) - transmission capacities and loss rates - technology sets + - industrial electricity demand from FINITO when running linked model $offtext $if not set start_year $setglobal start_year %startyear% @@ -62,6 +63,7 @@ h2_usage_regional(r,allh,t) "--metric tons-- H2 usage by region" inv_cond_filt(i,v,t) "--set-- vintage-year mapping for investments by technology" inv_ivrt(i,v,r,t) "--MW-- investments in power generation capacity" inv_energy_ivrt(i,v,r,t) "--MWh-- investments in energy generation capacity" +load_finito_rt(r,allh,t) "--MWh-- total load from industrial sectors and converted fuels modeled by FINITO" m_cf_filt(i,v,r,allh) "--fraction-- capacity factor used in the model" m_cf_szn_filt(i,v,r,allszn) "--fraction-- modelled capacity factors filtered for hydro resources to set seasonal energy constraints" minloadfrac_filt(r,i,allszn) "--fraction-- modelled mingen fraction filtered for hydro resources to set mingen constraints" @@ -161,6 +163,10 @@ h2_usage_regional(r,h,t)$tcur(t) = h2_exogenous_demand_regional(r,'h2',h,t) + sum{(i,v)$[valgen(i,v,r,t)$h2_combustion(i)], GEN.l(i,v,r,h,t) * h2_combustion_intensity * heat_rate(i,v,r,t)} +* regional hydrogen demand for industry (FINITO) +$ifthene.linked_h2_reg_report Sw_FINITO_Link==1 + + [USE_H2_FINITO.l(r,h,t) * h2_metric_tons_per_mmbtu ]$t_finito(t) +$endif.linked_h2_reg_report ) ; @@ -284,15 +290,24 @@ cap_converter_filt(r) = sum{t$tcur(t), CAP_CONVERTER.l(r,t) } ; routes_filt(r,rr,trtype) = sum{t$tcur(t), routes(r,rr,trtype,t) } ; *============================ -* Flexible load data +* Load data *============================ +* flexible load flex_load(r,h) = sum{(flex_type,t)$tcur(t), load_exog_flex(flex_type,r,h,t) } ; flex_load_opt(r,h) = sum{(flex_type,t)$tcur(t), FLEX.l(flex_type,r,h,t) } ; ra_cap_loadsite(r,t)$[Sw_LoadSiteCF$val_loadsite(r)] = CAP_LOADSITE.l(r,t) ; +* FINITO load +$ifthene.linked_load Sw_FINITO_Link==1 + load_finito_rt(r,h,t) = USE_ELE_FINITO.l(r,h,t) ; +$else.linked_load + load_finito_rt(r,h,t) = 0 ; +$endif.linked_load + + *============================ * Extra consumption data *============================ @@ -382,6 +397,7 @@ execute_unload 'ReEDS_Augur%ds%augur_data%ds%reeds_data_%cur_year%.gdx' inv_ivrt inv_energy_ivrt ivt_num + load_finito_rt m_cf_filt m_cf_szn_filt maxage diff --git a/d_solveoneyear.gms b/d_solveoneyear.gms index b3484a805..a62eaaee1 100644 --- a/d_solveoneyear.gms +++ b/d_solveoneyear.gms @@ -23,6 +23,13 @@ tload(t) = no ; tmodel(t) = no ; tmodel("%cur_year%") = yes ; +* (linked) also reset t_finito +$ifthene.linked_finito_time %GSw_FINITO_Link% == 1 +t_finito(t) = no ; +t_finito(t)$[tmodel(t)$(t.val>=first_year_finito)$(t.val<=endyear)] = yes ; +$endif.linked_finito_time + + $log 'Solving sequential case for...' $log ' Case: %case%' $log ' Year: %cur_year%' @@ -241,6 +248,7 @@ $endif.diagnose * ------------------------------ * Solve the Model * ------------------------------ +option limrow = 1e6; $ifthen.valstr %GSw_ValStr% == 1 OPTION lp = convert ; ReEDSmodel.optfile = 1 ; @@ -276,11 +284,22 @@ $endif.mga *** Adjust some parameters based on the solution for this solve year $include d2_post_solve_adjustments.gms +*** Do the same for finito if necessary +$ifthene.linked_finito_shrink %GSw_FINITO_Link% == 1 +* Industrial variable fix +$include finito/model/finito_post_solve_adjustments.gms +$endif.linked_finito_shrink *** Fix decision variables to their optimized levels for this solve year tfix("%cur_year%") = yes ; $include d2_varfix.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 +$endif.linked_finito_varfix + *** Dump data used in calculations between solve years $include d3_data_dump.gms diff --git a/e_report.gms b/e_report.gms index ebd14d10e..f251e657a 100644 --- a/e_report.gms +++ b/e_report.gms @@ -107,7 +107,7 @@ rev_cat "categories for renvenue streams" /load, res_marg, oper_res, rps, charge lcoe_cat "categories for LCOE calculation" /capcost, upgradecost, rsccost, fomcost, vomcost, gen / -loadtype "categories for types of load" / end_use, dist_loss, trans_loss, stor_charge, h2_prod, h2_network, dac / +loadtype "categories for types of load" / end_use, dist_loss, trans_loss, stor_charge, h2_prod, h2_network, dac, finito / h2_demand_type / "electricity", "cross-sector"/ @@ -495,92 +495,121 @@ ptc_out(i,v,t)$[tmodel_new(t)$ptc_value_scaled(i,v,t)] = ptc_value_scaled(i,v,t) * Case 2: the resource of one or more biomass classes ARE exhausted, i.e., BIOUSED.l(bioclass) = biosupply(bioclass) * Marginal Biomass Price = maximum difference between eq_bioused.m and eq_biousedlimit.m(bioclass) across all biomass classes in a region -repbioprice(r,t)$tmodel_new(t) = max{0, smax{bioclass$BIOUSED.l(bioclass,r,t), eq_bioused.m(r,t) - - sum{usda_region$r_usda(r,usda_region), eq_biousedlimit.m(bioclass,usda_region,t) } } } / pvf_onm(t) ; +repbioprice(r,t)$[tmodel_new(t)$tfuel(t)] = + [max{0, smax{bioclass$BIOUSED.l(bioclass,r,t), eq_bioused.m(r,t) - + sum{usda_region$r_usda(r,usda_region), eq_biousedlimit.m(bioclass,usda_region,t) } } } / pvf_onm(t) ] ; -* quantity of biomass used (convert from mmBTU to dry tons using biomass energy content) -bioused_out(bioclass,r,t)$tmodel_new(t) = BIOUSED.l(bioclass,r,t) / bio_energy_content ; -bioused_usda(bioclass,usda_region,t)$tmodel_new(t) = sum{r$r_usda(r,usda_region), bioused_out(bioclass,r,t) } ; +* quantity of biomass used in the power sector (convert from mmBTU to dry tons using biomass energy content) +bioused_out(bioclass,r,t)$[tmodel_new(t)$tfuel(t)] = + [BIOUSED.l(bioclass,r,t) / bio_energy_content ]; + +bioused_usda(bioclass,usda_region,t)$[tmodel_new(t)$tfuel(t)] = + [sum{r$r_usda(r,usda_region), bioused_out(bioclass,r,t) } ]; * 1e9 converts from MMBtu to Quads -repgasquant(cendiv,t)$[(Sw_GasCurve = 0 or Sw_GasCurve = 3)$tmodel_new(t)] = - sum{(gb,h), GASUSED.l(cendiv,gb,h,t) * hours(h) } * gas_scale/ 1e9 ; +repgasquant(cendiv,t)$[(Sw_GasCurve = 0 or Sw_GasCurve = 3)$tmodel_new(t)$tfuel(t)] = + [sum{(gb,h), GASUSED.l(cendiv,gb,h,t) * hours(h) } * gas_scale/ 1e9 ]; -repgasquant(cendiv,t)$[(Sw_GasCurve = 1 or Sw_GasCurve = 2)$tmodel_new(t)] = - ( sum{(i,v,r,h)$[r_cendiv(r,cendiv)$valgen(i,v,r,t)$gas(i)$heat_rate(i,v,r,t)], +repgasquant(cendiv,t)$[(Sw_GasCurve = 1 or Sw_GasCurve = 2 or Sw_FINITO_Link = 1)$tmodel_new(t)] = + [( sum{(i,v,r,h)$[r_cendiv(r,cendiv)$valgen(i,v,r,t)$gas(i)$heat_rate(i,v,r,t)], hours(h) * heat_rate(i,v,r,t) * GEN.l(i,v,r,h,t)} + sum{(v,r,h)$[valcap("dac_gas",v,r,t)$r_cendiv(r,cendiv)], hours(h) * dac_gas_cons_rate("dac_gas",v,t) * PRODUCE.l("DAC","dac_gas",v,r,h,t) }$Sw_DAC_Gas + sum{(p,i,v,r,h)$[r_cendiv(r,cendiv)$valcap(i,v,r,t)$smr(i)], hours(h) * smr_methane_rate * PRODUCE.l(p,i,v,r,h,t) }$Sw_H2 - ) / 1e9 ; + ) / 1e9 ]; -repgasquant_irt(i,r,t)$tmodel_new(t) = - ( sum{(v,h)$[valgen(i,v,r,t)$gas(i)$heat_rate(i,v,r,t)], +repgasquant_irt(i,r,t)$[tmodel_new(t)] = + [( sum{(v,h)$[valgen(i,v,r,t)$gas(i)$heat_rate(i,v,r,t)], hours(h) * heat_rate(i,v,r,t) * GEN.l(i,v,r,h,t) } + sum{(v,h)$[valcap("dac_gas",v,r,t)], hours(h) * dac_gas_cons_rate("dac_gas",v,t) * PRODUCE.l("DAC","dac_gas",v,r,h,t) }$Sw_DAC_Gas + sum{(p,v,h)$[valcap(i,v,r,t)$smr(i)], hours(h) * smr_methane_rate * PRODUCE.l(p,i,v,r,h,t) }$Sw_H2 - ) / 1e9 ; + ) / 1e9 ]; -repgasquant_nat(t)$tmodel_new(t) = sum{cendiv, repgasquant(cendiv,t) } ; +repgasquant_nat(t)$[tmodel_new(t)] = + [sum{cendiv, repgasquant(cendiv,t) } ]; *for reported gasprice (not that used to compute system costs) *scale back to $ / mmbtu -repgasprice(cendiv,t)$[(Sw_GasCurve = 0)$tmodel_new(t)$repgasquant(cendiv,t)] = - smax{gb$[sum{h, GASUSED.l(cendiv,gb,h,t) }], gasprice(cendiv,gb,t) } / gas_scale ; +repgasprice(cendiv,t)$[(Sw_GasCurve = 0)$tmodel_new(t)$repgasquant(cendiv,t)$tfuel(t)]= + [smax{gb$[sum{h, GASUSED.l(cendiv,gb,h,t) }], gasprice(cendiv,gb,t) } / gas_scale ]; -repgasprice(cendiv,t)$[(Sw_GasCurve = 2)$tmodel_new(t)$repgasquant(cendiv,t)] = - sum{(i,v,r,h)$[r_cendiv(r,cendiv)$valgen(i,v,r,t)$gas(i)$heat_rate(i,v,r,t)], +repgasprice(cendiv,t)$[(Sw_GasCurve = 2)$tmodel_new(t)$repgasquant(cendiv,t)$tfuel(t)] = + [sum{(i,v,r,h)$[r_cendiv(r,cendiv)$valgen(i,v,r,t)$gas(i)$heat_rate(i,v,r,t)], hours(h)*heat_rate(i,v,r,t)*fuel_price(i,r,t)*GEN.l(i,v,r,h,t) - } / (repgasquant(cendiv,t) * 1e9) ; + } / (repgasquant(cendiv,t) * 1e9) ]; -repgasprice_r(r,t)$[(Sw_GasCurve = 0 or Sw_GasCurve = 2)$tmodel_new(t)] = sum{cendiv$r_cendiv(r,cendiv), repgasprice(cendiv,t) } ; -repgasprice_r(r,t)$[(Sw_GasCurve = 1)$tmodel_new(t)] = - ( sum{(h,cendiv), - gasmultterm(cendiv,t) * szn_adj_gas(h) * cendiv_weights(r,cendiv) * - hours(h) } / sum{h, hours(h) } +* gas price by timeslice when linked with FINITO (see similar calculation in finito_report.gms) [$2004/MMBtu] +$ifthene.finitogasprice Sw_FINITO_Link == 1 +repgasprice_finito(cendiv,h,t)$[tmodel_new(t)$(not tfuel(t))] = + ( 1/obj_scale * 1/pvf_onm(t) * deflator('2018') * + [ max{ + smax{(eus,usep)$[map_roe_ec_usep("NG",eus,usep)], eq_supplydemand_fe_pool.m("NG",eus,usep,cendiv,h,t)}, + smax{(usep)$[map_roi_ec_usep("NG",usep)],eq_supplydemand_fe_roi.m("NG",usep,cendiv,h,t)}, + smax{(cfp2,cfvin2,fi,r)$[map_ec_ei("NG",fi)$valei_cf(cfp2,cfvin2,fi,r,t)$r_cendiv(r,cendiv)],eq_use_fi_cf.m(cfp2,cfvin2,fi,r,h,t)}, + smax{(tech,vin,fi,r)$[map_ec_ei("NG",fi)$valei_ind(tech,vin,fi,r,t)$r_cendiv(r,cendiv)],eq_use_fi_ind.m(tech,vin,fi,r,h,t)} + } / hours(h) + ] + ) +; +$else.finitogasprice + repgasprice_finito(cendiv,h,t)$[tmodel_new(t)$(not tfuel(t))] = 0 ; +$endif.finitogasprice + +repgasprice(cendiv,t)$[tmodel_new(t)$(not tfuel(t))] = +* average price over hours when linked with FINITO + sum{h, hours(h) * repgasprice_finito(cendiv,h,t) } +; + +repgasprice_r(r,t)$[(Sw_GasCurve = 0 or Sw_GasCurve = 2)$tmodel_new(t)$tfuel(t)] = + [sum{cendiv$r_cendiv(r,cendiv), repgasprice(cendiv,t) } ]; + +repgasprice_r(r,t)$[(Sw_GasCurve = 1)$tmodel_new(t)$tfuel(t)] = + sum{(h,cendiv), + gasmultterm(cendiv,t) * szn_adj_gas(h) * cendiv_weights(r,cendiv) * + hours(h) } / sum{h, hours(h) } - + smax((fuelbin,cendiv)$[VGASBINQ_REGIONAL.l(fuelbin,cendiv,t)$r_cendiv(r,cendiv)], gasbinp_regional(fuelbin,cendiv,t) ) + + smax((fuelbin,cendiv)$[VGASBINQ_REGIONAL.l(fuelbin,cendiv,t)$r_cendiv(r,cendiv)], gasbinp_regional(fuelbin,cendiv,t) ) - + smax(fuelbin$VGASBINQ_NATIONAL.l(fuelbin,t), gasbinp_national(fuelbin,t) ) - ) ; + + smax(fuelbin$VGASBINQ_NATIONAL.l(fuelbin,t), gasbinp_national(fuelbin,t) ) + ; -repgasprice(cendiv,t)$[(Sw_GasCurve = 1)$tmodel_new(t)$repgasquant(cendiv,t)] = - sum{(i,r)$r_cendiv(r,cendiv), repgasprice_r(r,t) * repgasquant_irt(i,r,t) } / repgasquant(cendiv,t) ; +repgasprice(cendiv,t)$[((Sw_GasCurve = 1) or (Sw_FINITO_Link = 1))$tmodel_new(t)$repgasquant(cendiv,t)] = + [sum{(i,r)$r_cendiv(r,cendiv), repgasprice_r(r,t) * repgasquant_irt(i,r,t) } / repgasquant(cendiv,t) ] ; repgasprice_nat(t)$[tmodel_new(t)$sum{cendiv, repgasquant(cendiv,t) }] = - sum{cendiv, repgasprice(cendiv,t) * repgasquant(cendiv,t) } - / sum{cendiv, repgasquant(cendiv,t) } ; + [sum{cendiv, repgasprice(cendiv,t) * repgasquant(cendiv,t) } + / sum{cendiv, repgasquant(cendiv,t) } ]; *======================================== * NATURAL GAS FUEL COSTS *======================================== gasshare_ba(r,cendiv,t)$[r_cendiv(r,cendiv)$tmodel_new(t)$repgasquant(cendiv,t)] = - sum{i$[valgen_irt(i,r,t)$gas(i)],repgasquant_irt(i,r,t) / repgasquant(cendiv,t) } ; + [sum{i$[valgen_irt(i,r,t)$gas(i)],repgasquant_irt(i,r,t) / repgasquant(cendiv,t) } ]; gasshare_techba(i,r,cendiv,t)$[r_cendiv(r,cendiv)$tmodel_new(t)$repgasquant(cendiv,t)$gas(i)] = - repgasquant_irt(i,r,t) / repgasquant(cendiv,t) ; + [repgasquant_irt(i,r,t) / repgasquant(cendiv,t) ]; -gasshare_cendiv(cendiv,t)$[sum{cendiv2,repgasquant(cendiv2,t)}] = repgasquant(cendiv,t) / sum{cendiv2,repgasquant(cendiv2,t)} ; +gasshare_cendiv(cendiv,t)$[sum{cendiv2,repgasquant(cendiv2,t)}] = + [repgasquant(cendiv,t) / sum{cendiv2,repgasquant(cendiv2,t)} ]; -gascost_cendiv(cendiv,t)$tmodel_new(t) = +* cost of natural gas - standalone ReEDS +gascost_cendiv(cendiv,t)$[tmodel_new(t)$tfuel(t)] = *cost of natural gas for Sw_GasCurve = 2 (static natural gas prices) - + sum{(i,v,r,h)$[r_cendiv(r,cendiv)$valgen(i,v,r,t)$gas(i)$heat_rate(i,v,r,t) - $[not bio(i)]$[not cofire(i)]$[Sw_GasCurve = 2]], - hours(h) * heat_rate(i,v,r,t) * fuel_price(i,r,t) * GEN.l(i,v,r,h,t) } + + [ sum{(i,v,r,h)$[r_cendiv(r,cendiv)$valgen(i,v,r,t)$gas(i)$heat_rate(i,v,r,t) + $[not bio(i)]$[not cofire(i)]$[Sw_GasCurve = 2]], + hours(h) * heat_rate(i,v,r,t) * fuel_price(i,r,t) * GEN.l(i,v,r,h,t) } *cost of natural gas for Sw_GasCurve = 0 (census division supply curves natural gas prices) - + sum{gb, sum{h,hours(h) * GASUSED.l(cendiv,gb,h,t) } * gasprice(cendiv,gb,t) - }$[Sw_GasCurve = 0] + + sum{gb, sum{h,hours(h) * GASUSED.l(cendiv,gb,h,t) } * gasprice(cendiv,gb,t) }$[Sw_GasCurve = 0] *cost of natural gas for Sw_GasCurve = 3 (national supply curve for natural gas prices with census division multipliers) - + sum{(h,gb), hours(h) * GASUSED.l(cendiv,gb,h,t) - * gasadder_cd(cendiv,t,h) + gasprice_nat_bin(gb,t) - }$[Sw_GasCurve = 3] + + sum{(h,gb), hours(h) * GASUSED.l(cendiv,gb,h,t) + * gasadder_cd(cendiv,t,h) + gasprice_nat_bin(gb,t) }$[Sw_GasCurve = 3] *cost of natural gas for Sw_GasCurve = 1 (national and census division supply curves for natural gas prices) *first - anticipated costs of gas consumption given last year's amount + (sum{(i,v,r,h)$[valgen(i,v,r,t)$gas(i)], @@ -593,15 +622,25 @@ gascost_cendiv(cendiv,t)$tmodel_new(t) = + sum{(fuelbin), gasbinp_national(fuelbin,t) * VGASBINQ_NATIONAL.l(fuelbin,t) } * gasshare_cendiv(cendiv,t) - )$[Sw_GasCurve = 1]; + )$[Sw_GasCurve = 1] + ]; + +* cost of natural gas - linked with FINITO ('not tfuel' indicates years using FINITO supply curves) +gascost_cendiv(cendiv,t)$[tmodel_new(t)$(not tfuel(t))] = +* cost = gas price multiplied by gas usage [$ = $/MMBtu * MMBtu/MWh * MW * h] + sum{(i,v,r,h)$[valgen(i,v,r,t)$gas(i)$r_cendiv(r, cendiv)], + repgasprice_finito(cendiv,h,t) * heat_rate(i,v,r,t) * GEN.l(i,v,r,h,t) * hours(h) + } +; + *======================================== * BIOFUEL COSTS *======================================== -bioshare_techba(i,r,t)$[(cofire(i) or bio(i))$tmodel_new(t)] = +bioshare_techba(i,r,t)$[(cofire(i) or bio(i))$tmodel_new(t)$tfuel(t)] = * biofuel-based generation of tech i in the BA (biopower + cofire) - (( sum{(v,h)$[valgen(i,v,r,t)$bio(i)], hours(h) * heat_rate(i,v,r,t) * GEN.l(i,v,r,h,t) } + [ (( sum{(v,h)$[valgen(i,v,r,t)$bio(i)], hours(h) * heat_rate(i,v,r,t) * GEN.l(i,v,r,h,t) } + sum{(v,h)$[cofire(i)$valgen(i,v,r,t)], bio_cofire_perc * hours(h) * heat_rate(i,v,r,t) * GEN.l(i,v,r,h,t) } ) / * biofuel-based generation of all techs in the BA (biopower + cofire) @@ -611,6 +650,7 @@ bioshare_techba(i,r,t)$[(cofire(i) or bio(i))$tmodel_new(t)] = )$[ sum{(ii,v,h)$[valgen(ii,v,r,t)$bio(ii)], hours(h) * heat_rate(ii,v,r,t) * GEN.l(ii,v,r,h,t) } + sum{(ii,v,h)$[cofire(ii)$valgen(ii,v,r,t)], bio_cofire_perc * hours(h) * heat_rate(ii,v,r,t) * GEN.l(ii,v,r,h,t) } ] + ] ; *========================= @@ -1740,6 +1780,11 @@ error_check('z') = ( ) * account for penalty paid to deploy capacity beyond interconnection queue limits + sum{(tg,r), cap_penalty(tg) * CAP_ABOVE_LIM.l(tg,r,t) } +* account for costs from FINITO: deflate from $2018 to $2004, +* 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 } ) / z.l ; @@ -2033,6 +2078,8 @@ load_cat("h2_network",r,t)$tmodel_new(t) = ; load_cat("dac",r,t)$tmodel_new(t) = sum{i$dac(i), prod_load_ann(i,r,t) } ; +load_cat("finito",r,t)$tmodel_new(t) = sum{h, hours(h) * USE_ELE_FINITO.l(r,h,t) } ; + *======================================== * H2 NETWORK *======================================== diff --git a/e_report_dump.py b/e_report_dump.py index faa3a20ab..aa3aa78a6 100644 --- a/e_report_dump.py +++ b/e_report_dump.py @@ -67,6 +67,8 @@ def dfdict_to_h5( """ Write dictionary of dataframes to one .h5 file """ + print(f"Saving results to {filepath}") + ### unless a subset is specified, iterate over all keys in dict _symbol_list = dfdict.keys() if symbol_list is None else symbol_list @@ -108,6 +110,8 @@ def dfdict_to_excel( """ Write dictionary of dataframes to one .xlsx file """ + print(f"Saving results to {filepath}") + ### unless a subset is specified, iterate over all keys in dict _symbol_list = dfdict.keys() if symbol_list is None else symbol_list @@ -201,10 +205,13 @@ def postprocess_outputs(case, outputs_path=None, verbose=0): _outputs_path = os.path.join(case, 'outputs') if outputs_path is None else outputs_path ## System cost - reeds.output_calc.calc_systemcost(case).to_csv( - os.path.join(_outputs_path, 'post_systemcost_annualized.csv'), - index=False, - ) + try: + reeds.output_calc.calc_systemcost(case).to_csv( + os.path.join(_outputs_path, 'post_systemcost_annualized.csv'), + index=False, + ) + except FileNotFoundError: + print("Skipping system cost calculation, missing files") ## Reinforcement and spur-line reeds.output_calc.calc_reinforcement_spur_capacity_miles(case).to_csv( @@ -276,6 +283,18 @@ def postprocess_outputs(case, outputs_path=None, verbose=0): ) print("Finished loading outputs gdx") + ### FINITO outputs when running linked model + if int(sw.GSw_FINITO_Link): + print("Loading FINITO outputs gdx") + # TODO: this is much slower than loading the ReEDS outputs, + # need to figure out why and address + finito_outputs = gdxpds.to_dataframes( + os.path.join(outputs_path, f"finito_reeds_outputs_{os.path.basename(case)}.gdx") + ) + print("Finished loading FINITO outputs gdx") + dict_out.update(finito_outputs) + + write_dfdict( dfdict=dict_out, outputs_path=outputs_path, diff --git a/e_report_params.csv b/e_report_params.csv index 70b370208..f7792cb3a 100644 --- a/e_report_params.csv +++ b/e_report_params.csv @@ -142,6 +142,7 @@ raw_op_cost(t),2004$,sum of operational costs from systemcost,,, 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,,, "repgasprice_r(r,t)",2004$/MMBtu,highest marginal gas price of utilized gas bins for each region,1,, repgasprice_nat(t),2004$/MMBtu,weighted-average national natural gas price assuming that plants pay the marginal price,,, "repgasquant(cendiv,t)",Quads,quantity of gas consumed in each census division,,, diff --git a/input_processing/copy_files.py b/input_processing/copy_files.py index 90e22a098..8c53b1643 100644 --- a/input_processing/copy_files.py +++ b/input_processing/copy_files.py @@ -1614,6 +1614,11 @@ def main(reeds_path, inputs_case): source_deflator_map = get_source_deflator_map(reeds_path) + ### 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) + scalar_csv_to_txt(os.path.join(os.path.dirname(inputs_case),'finito','inputs','scalars.csv')) + # Copy non-region files write_non_region_files(non_region_files, sw, inputs_case, regions_and_agglevel, source_deflator_map) diff --git a/input_processing/hourly_load.py b/input_processing/hourly_load.py index 8b2114fb9..a29ee8f56 100644 --- a/input_processing/hourly_load.py +++ b/input_processing/hourly_load.py @@ -596,6 +596,44 @@ def reaggregate_to_model_regions( return regional_load_hourly +#TODO: add docstrings +def get_hourly_finito_load( + inputs_case: str, +) -> pd.DataFrame: + # load reference FINITO load + inputs_case_finito = Path(inputs_case).parent / 'finito' / Path(inputs_case).name + load_finito = pd.read_csv(inputs_case_finito / "load_finito.csv") + + # reshape to match load data. with year in index and region in columns + load_finito = load_finito.melt(id_vars='r', var_name='year', value_name='load_MWh') + load_finito.year = load_finito.year.astype(int) + load_finito = load_finito.pivot(index='year', columns='r', values='load_MWh') + load_finito.columns.name = None + + # allocate annual load to hours, assuming flat demand + # TODO: should this use h_weight_finito? + hours_per_year = 8760 + load_hourly_finito = load_finito / hours_per_year + load_hourly_finito = load_hourly_finito.astype(np.float32) + + return load_hourly_finito + +def remove_finito_load( + load_hourly: pd.DataFrame, + inputs_case: str, +) -> pd.DataFrame: + + # get FINITO reference load + load_hourly_finito = get_hourly_finito_load(inputs_case) + + # subtract FINITO reference load from ReEDS load data, + # aligning by model year (index) and region (columns) + result = load_hourly - load_hourly_finito + + #TODO: add this + # Validation check: confirm that total load - FINITO load > 0 + + return load_hourly #%% =========================================================================== ### --- MAIN FUNCTION --- @@ -697,6 +735,15 @@ def main(reeds_path, inputs_case): peakload = calculate_peak_load(regional_load_hourly, hierarchy) #%%%######################################### + # -- FINITO Load Adjustment -- # + ############################################# + + # note that this step occurs after peakload calculation so that the latter + # includes a baseline estimate of industrial load captured by FINITO + if int(sw.GSw_FINITO_Link): + regional_load_hourly = remove_finito_load(regional_load_hourly, inputs_case) + + ############################################# # -- DR Shed Load Modifications -- # ############################################# diff --git a/input_processing/hourly_repperiods.py b/input_processing/hourly_repperiods.py index 2c57bcc8d..7d13b20e4 100644 --- a/input_processing/hourly_repperiods.py +++ b/input_processing/hourly_repperiods.py @@ -36,6 +36,7 @@ import hourly_plots sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) import reeds +import hourly_load ## Time the operation of this script tic = datetime.datetime.now() @@ -75,11 +76,18 @@ def szn2period(szn): # -- Load Processing -- # ############################### -def get_load(inputs_case, keep_modelyear=None, keep_weatheryears=[2012]): +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'), parse_timestamps=True) + + ### 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 + if int(sw.GSw_FINITO_Link): + 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() @@ -578,6 +586,7 @@ def main( print("Collecting 8760 load data") load = get_load( inputs_case=inputs_case, + sw=sw, keep_modelyear=(int(sw['GSw_HourlyClusterYear']) if int(sw['GSw_HourlyClusterYear']) in modelyears else max(modelyears)), @@ -736,7 +745,7 @@ def main( and (sw['GSw_PRM_StressSeedLoadLevel'].lower() not in ['false','none']) ): ## Get load for all model and weather years - load_allyears = get_load(inputs_case, keep_weatheryears='all').loc[modelyears] + load_allyears = get_load(inputs_case, sw, keep_weatheryears='all').loc[modelyears] ## Add descriptive index load_allyears = load_allyears.merge( timestamps[['year', 'yperiod', 'h_of_period']], left_on='datetime', right_index=True) @@ -925,10 +934,14 @@ def main( description='Create the necessary 8760 and capacity factor data for hourly resolution') parser.add_argument('reeds_path', help='ReEDS-2.0 directory') parser.add_argument('inputs_case', help='ReEDS-2.0/runs/{case}/inputs_case directory') + parser.add_argument('--nolog', '-n', default=False, action='store_true', help='turn off logging for debugging') + args = parser.parse_args() reeds_path = args.reeds_path inputs_case = args.inputs_case + logging = not args.nolog + # #%% Settings for testing # reeds_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) @@ -938,10 +951,11 @@ def main( # interactive = True #%% Set up logger - log = reeds.log.makelog( - scriptname=__file__, - logpath=os.path.join(inputs_case,'..','gamslog.txt'), - ) + if logging: + log = reeds.log.makelog( + scriptname=__file__, + logpath=os.path.join(inputs_case,'..','gamslog.txt'), + ) print('Starting hourly_repperiods.py') #%% Inputs from switches sw = reeds.io.get_switches(inputs_case) @@ -956,6 +970,7 @@ def main( sw=sw, reeds_path=reeds_path, inputs_case=inputs_case, periodtype='rep', make_plots=1, + logging=logging ) ############################################ diff --git a/input_processing/hourly_writetimeseries.py b/input_processing/hourly_writetimeseries.py index 5fa326089..57c5f4363 100644 --- a/input_processing/hourly_writetimeseries.py +++ b/input_processing/hourly_writetimeseries.py @@ -199,6 +199,9 @@ def get_yearly_demand(sw, hmap_myr, hmap_allyrs, inputs_case, periodtype='rep'): reload the raw demand and extract the demand on the modeled days for each year. """ ### Get original demand data, subset to cluster year + ### Note that this does not include FINITO demand when running with the linked model + ### (GSw_FINITO_Link=1) even though the reference estimates for that load is used when + ### identifying the representative days load_in = reeds.io.read_file( os.path.join(inputs_case,'load.h5'), parse_timestamps=True).unstack(level=0) load_in.columns = load_in.columns.rename(['r','t']) diff --git a/inputs/emission_constraints/co2_tax.csv b/inputs/emission_constraints/co2_tax.csv index d9e795517..1e08f6246 100644 --- a/inputs/emission_constraints/co2_tax.csv +++ b/inputs/emission_constraints/co2_tax.csv @@ -1,42 +1,42 @@ -t,default,t050_2035,t050_2050,t100_2035,t100_2050,t150_2035,t150_2050,t200_2035,t200_2050 -2010,0,0,0,0,0,0,0,0,0 -2011,0,0,0,0,0,0,0,0,0 -2012,0,0,0,0,0,0,0,0,0 -2013,0,0,0,0,0,0,0,0,0 -2014,0,0,0,0,0,0,0,0,0 -2015,0,0,0,0,0,0,0,0,0 -2016,0,0,0,0,0,0,0,0,0 -2017,0,0,0,0,0,0,0,0,0 -2018,0,0,0,0,0,0,0,0,0 -2019,0,0,0,0,0,0,0,0,0 -2020,0,0,0,0,0,0,0,0,0 -2021,0,0,0,0,0,0,0,0,0 -2022,30,1,1,1,1,1,1,1,1 -2023,31.5,4.77,2.75,8.62,4.54,12.46,6.32,16.31,8.11 -2024,33.075,8.54,4.5,16.23,8.07,23.92,11.64,31.62,15.21 -2025,34.72875,12.31,6.25,23.85,11.61,35.38,16.96,46.92,22.32 -2026,36.4651875,16.08,8,31.46,15.14,46.85,22.29,62.23,29.43 -2027,38.28844688,19.85,9.75,39.08,18.68,58.31,27.61,77.54,36.54 -2028,40.20286922,23.62,11.5,46.69,22.21,69.77,32.93,92.85,43.64 -2029,42.21301268,27.38,13.25,54.31,25.75,81.23,38.25,108.15,50.75 -2030,44.32366331,31.15,15,61.92,29.29,92.69,43.57,123.46,57.86 -2031,46.53984648,34.92,16.75,69.54,32.82,104.15,48.89,138.77,64.96 -2032,48.8668388,38.69,18.5,77.15,36.36,115.62,54.21,154.08,72.07 -2033,51.31018074,42.46,20.25,84.77,39.89,127.08,59.54,169.38,79.18 -2034,53.87568978,46.23,22,92.38,43.43,138.54,64.86,184.69,86.29 -2035,56.56947427,50,23.75,100,46.96,150,70.18,200,93.39 -2036,59.39794798,50,25.5,100,50.5,150,75.5,200,100.5 -2037,62.36784538,50,27.25,100,54.04,150,80.82,200,107.61 -2038,65.48623765,50,29,100,57.57,150,86.14,200,114.71 -2039,68.76054953,50,30.75,100,61.11,150,91.46,200,121.82 -2040,72.19857701,50,32.5,100,64.64,150,96.79,200,128.93 -2041,75.80850586,50,34.25,100,68.18,150,102.11,200,136.04 -2042,79.59893115,50,36,100,71.71,150,107.43,200,143.14 -2043,83.57887771,50,37.75,100,75.25,150,112.75,200,150.25 -2044,87.7578216,50,39.5,100,78.79,150,118.07,200,157.36 -2045,92.14571268,50,41.25,100,82.32,150,123.39,200,164.46 -2046,96.75299831,50,43,100,85.86,150,128.71,200,171.57 -2047,101.5906482,50,44.75,100,89.39,150,134.04,200,178.68 -2048,106.6701806,50,46.5,100,92.93,150,139.36,200,185.79 -2049,112.0036897,50,48.25,100,96.46,150,144.68,200,192.89 -2050,117.6038742,50,50,100,100,150,150,200,200 \ No newline at end of file +t,default,t050_2035,t050_2050,t100_2035,t100_2050,t150_2035,t150_2050,t200_2035,t200_2050,t200alt_2050 +2010,0,0,0,0,0,0,0,0,0,0 +2011,0,0,0,0,0,0,0,0,0,0 +2012,0,0,0,0,0,0,0,0,0,0 +2013,0,0,0,0,0,0,0,0,0,0 +2014,0,0,0,0,0,0,0,0,0,0 +2015,0,0,0,0,0,0,0,0,0,0 +2016,0,0,0,0,0,0,0,0,0,0 +2017,0,0,0,0,0,0,0,0,0,0 +2018,0,0,0,0,0,0,0,0,0,0 +2019,0,0,0,0,0,0,0,0,0,0 +2020,0,0,0,0,0,0,0,0,0,0 +2021,0,0,0,0,0,0,0,0,0,0 +2022,30,1,1,1,1,1,1,1,1,0 +2023,31.5,4.77,2.75,8.62,4.54,12.46,6.32,16.31,8.11,0 +2024,33.075,8.54,4.5,16.23,8.07,23.92,11.64,31.62,15.21,0 +2025,34.72875,12.31,6.25,23.85,11.61,35.38,16.96,46.92,22.32,0 +2026,36.4651875,16.08,8,31.46,15.14,46.85,22.29,62.23,29.43,0 +2027,38.28844688,19.85,9.75,39.08,18.68,58.31,27.61,77.54,36.54,0 +2028,40.20286922,23.62,11.5,46.69,22.21,69.77,32.93,92.85,43.64,0 +2029,42.21301268,27.38,13.25,54.31,25.75,81.23,38.25,108.15,50.75,0 +2030,44.32366331,31.15,15,61.92,29.29,92.69,43.57,123.46,57.86,0 +2031,46.53984648,34.92,16.75,69.54,32.82,104.15,48.89,138.77,64.96,10 +2032,48.8668388,38.69,18.5,77.15,36.36,115.62,54.21,154.08,72.07,20 +2033,51.31018074,42.46,20.25,84.77,39.89,127.08,59.54,169.38,79.18,30 +2034,53.87568978,46.23,22,92.38,43.43,138.54,64.86,184.69,86.29,40 +2035,56.56947427,50,23.75,100,46.96,150,70.18,200,93.39,50 +2036,59.39794798,50,25.5,100,50.5,150,75.5,200,100.5,60 +2037,62.36784538,50,27.25,100,54.04,150,80.82,200,107.61,70 +2038,65.48623765,50,29,100,57.57,150,86.14,200,114.71,80 +2039,68.76054953,50,30.75,100,61.11,150,91.46,200,121.82,90 +2040,72.19857701,50,32.5,100,64.64,150,96.79,200,128.93,100 +2041,75.80850586,50,34.25,100,68.18,150,102.11,200,136.04,110 +2042,79.59893115,50,36,100,71.71,150,107.43,200,143.14,120 +2043,83.57887771,50,37.75,100,75.25,150,112.75,200,150.25,130 +2044,87.7578216,50,39.5,100,78.79,150,118.07,200,157.36,140 +2045,92.14571268,50,41.25,100,82.32,150,123.39,200,164.46,150 +2046,96.75299831,50,43,100,85.86,150,128.71,200,171.57,160 +2047,101.5906482,50,44.75,100,89.39,150,134.04,200,178.68,170 +2048,106.6701806,50,46.5,100,92.93,150,139.36,200,185.79,180 +2049,112.0036897,50,48.25,100,96.46,150,144.68,200,192.89,190 +2050,117.6038742,50,50,100,100,150,150,200,200,200 diff --git a/reeds/reedsplots.py b/reeds/reedsplots.py index da1013b1f..1630ca17c 100644 --- a/reeds/reedsplots.py +++ b/reeds/reedsplots.py @@ -5020,6 +5020,7 @@ def plot_seed_stressperiods( ### Recalculate peak load days since we dropped duplicates above load_allyears = hourly_repperiods.get_load( os.path.join(case, 'inputs_case'), + sw, keep_weatheryears='all').loc[years] timestamps = pd.read_csv(os.path.join(case, 'inputs_case', 'rep', 'timestamps.csv')) resource_adequacy_years = [int(y) for y in sw.resource_adequacy_years.split('_')] diff --git a/restart_runs.py b/restart_runs.py index dee9a85c6..71eb72e73 100644 --- a/restart_runs.py +++ b/restart_runs.py @@ -4,6 +4,8 @@ import subprocess import argparse import pandas as pd +import re +import reeds from glob import glob from runbatch import submit_slurm_parallel_jobs from runstatus import get_run_status @@ -92,7 +94,14 @@ #%% Copy additional files if desired for f in more_copyfiles: - shutil.copy(os.path.join(reeds_path,f), os.path.join(case,f)) + if f.lower().startswith('finito'): + # if file starts with 'finito' append the finito directory + sw = reeds.io.get_switches(case) + if int(sw.GSw_FINITO_Link): + f_copy = re.sub("^finito/", "" , f, flags=re.IGNORECASE) + shutil.copy(os.path.join(sw.finito_dir,f_copy), os.path.join(case,f)) + else: + shutil.copy(os.path.join(reeds_path,f), os.path.join(case,f)) #%% Make a backup copy of the original bash and sbatch scripts callfile = os.path.join(case,f'call_{casename}.sh') @@ -165,7 +174,8 @@ # It is a single case or we are not on HPC if copy_srun_template: writelines_srun_case = writelines_srun.copy() - writelines_srun_case.append(f"\n#SBATCH --job-name={casename}\n") + writelines_srun_case.append(f"#SBATCH --job-name={casename}") + writelines_srun_case.append(f"#SBATCH --output={os.path.join(case, 'slurm-%j.out')}\n") writelines_srun_case.append(f"sh {callfile}") with open(sbatchfile, 'w') as f: for line in writelines_srun_case: diff --git a/runbatch.py b/runbatch.py index 33b329796..106d27355 100644 --- a/runbatch.py +++ b/runbatch.py @@ -2,6 +2,10 @@ ### --- IMPORTS --- ### =========================================================================== +#%% =========================================================================== +### --- IMPORTS --- +### =========================================================================== + import os import git import queue @@ -11,6 +15,8 @@ import csv import importlib import numpy as np +import gdxpds +from gdxpds.gdx import GdxFile,GdxSymbol,GamsDataType import pandas as pd import subprocess import re @@ -18,6 +24,7 @@ import argparse from pathlib import Path import reeds +import sys # Assert core programs are accessible CORE_PROGRAMS = ["gams"] @@ -99,12 +106,23 @@ def create_case_lists(df_cases:pd.DataFrame, BatchName:str, single:str=''): continue # Add switch settings to list of options passed to GAMS shcom = f' --case={BatchName}_{case}' - for i,v in df_cases[case].items(): + # (linked) Combine the cases files for the linked model + if int(df_cases[case].loc['GSw_FINITO_Link']) == 1 : + # add the FINITO switches to the caseSwitches + df_cases_combine=linked_cases(df_cases,case) + for i,v in df_cases_combine.items(): + #exclude certain switches that don't need to be passed to GAMS + if i not in ['file_replacements','keep_run_terminal']: + shcom = shcom + ' --' + i + '=' + v + caseList.append(shcom) + caseSwitches.append(df_cases_combine.to_dict()) + else: + for i,v in df_cases[case].items(): #exclude certain switches that don't need to be passed to GAMS - if i not in ['file_replacements','keep_run_terminal']: - shcom += f' --{i}={v}' - caseList.append(shcom) - caseSwitches.append(df_cases[case].to_dict()) + if i not in ['file_replacements','keep_run_terminal']: + shcom += f' --{i}={v}' + caseList.append(shcom) + caseSwitches.append(df_cases[case].to_dict()) return caseSwitches, casenames, caseList @@ -554,6 +572,7 @@ def solvestring_sequential( 'GSw_StateCO2ImportLevel', 'GSw_StartMarkets', 'GSw_ValStr', + 'GSw_FINITO_Link', 'solver', 'debug', 'startyear', @@ -807,6 +826,71 @@ def setup_window( OPATH.writelines( "python valuestreams.py" + '\n') +# (linked) update 'df_cases' to include FINITO switches +def linked_cases(df_cases,case): + """ + Updates the cases dataframe to include FINITO-specific switches. + When a switch is duplicated in FINITO and ReEDS, then we default to + the ReEDS value. + + For the FINITO switches, the combined cases file defaults to the case-specific + 'Default Value' in cases_linked.csv first, before using the universal FINITO + 'Default Value' in cases.csv for any un-assigned switches. + """ + # check for valid finito_dir + if not os.path.isdir(df_cases[case]['finito_dir']): + raise ValueError(f"finito_dir = {df_cases[case]['finito_dir']} is not a valid path.") + + # define path to and read the FINITO check_inputs function + finito_check_inputs_path = os.path.join(df_cases[case]['finito_dir'], 'input_processing', 'processing') + sys.path.append(finito_check_inputs_path) + from check_inputs import check_inputs + + ### load the default values for all FINITO switches from ~\FINITO\cases.csv + df_cases_finito = pd.read_csv(os.path.join(df_cases[case]['finito_dir'],'cases.csv'), dtype=object, index_col=0) + df_cases_finito = df_cases_finito[['Choices', 'Default Value']] + + ### load the scenario-specific switches from ~\FINITO\cases_linked.csv + cases_linked_path = os.path.join(df_cases[case]['finito_dir'],f"cases_{df_cases[case]['finito_cases_file']}.csv") + df_cases_suf_finito = pd.read_csv(cases_linked_path, dtype=object, index_col=0) + ## check that case names are unique in cases_linked.csv + # grab the scenario names **exactly** as they appear in the csv file + with open(cases_linked_path, 'r', newline='') as csvfile: + reader = csv.reader(csvfile) + header = next(reader) + # find the duplicate column names and raise an error if any are found + duplicate_columns = set([x for x in header if header.count(x) > 1]) + if duplicate_columns: + raise ValueError(f"The FINITO cases_{df_cases[case]['finito_cases_file']}.csv has the following duplicate column names: {duplicate_columns}") + ### identify the FINITO case + if df_cases[case]['finito_case'] == 'same': + finito_case=case + else: + finito_case=df_cases[case]['finito_case'] + # ensures **exact** match of names between the ReEDS cases_{}.csv and the FINITO cases_linked.csv + if finito_case not in (df_cases_suf_finito.columns): + raise ValueError(f"The 'finito_case' input '{finito_case}' in the ReEDS cases file does not exist in FINITO's cases_{df_cases[case]['finito_cases_file']}.csv.") + + ### first use 'Default Value' from the FINITO cases_linked.csv to fill missing switches + if 'Default Value' in df_cases_suf_finito.columns: + df_cases_suf_finito[finito_case] = df_cases_suf_finito[finito_case].fillna(df_cases_suf_finito['Default Value']) + ### then, use 'Default Value' from the FINITO cases.csv to fill un-assigned switches + df_cases_suf_finito.drop(['Choices','Default Value'], axis='columns',inplace=True, errors='ignore') + df_cases_finito = df_cases_finito.join(df_cases_suf_finito, how='outer') + df_cases_finito[finito_case] = df_cases_finito[finito_case].fillna(df_cases_finito['Default Value']) + + #### create new dataframe for the combined ReEDS and FINITO switches + df_cases_combine = pd.concat([df_cases[case],df_cases_finito[finito_case]]) + ### drop duplicated switches, defaulting to reeds + df_cases_combine = df_cases_combine[~df_cases_combine.index.duplicated(keep='first')] + + #%% Check for incompatibility of FINITO switches + model_sectors = df_cases_finito['Default Value']['focus_sectors'].split('.') + check_inputs(case = case, df_case = df_cases_combine, model_sectors=model_sectors) + + return df_cases_combine + + #%% =========================================================================== ### --- PROCEDURE --- ### =========================================================================== @@ -1260,6 +1344,7 @@ def write_batch_script( yearset_augur = os.path.join('inputs_case','modeledyears.csv') toLogGamsString = ' logOption=4 logFile=gamslog.txt appendLog=1 ' + ## Copy code folders for dirname in ['reeds', 'ReEDS_Augur', 'input_processing', 'reeds2pras']: shutil.copytree( @@ -1271,6 +1356,66 @@ def write_batch_script( #make the augur_data folder os.makedirs(os.path.join(casedir,'ReEDS_Augur','augur_data'), exist_ok=True) os.makedirs(os.path.join(casedir,'ReEDS_Augur','PRAS'), exist_ok=True) + + ## (linked) Copy FINITO code folders and inputs into [casedir]/finito + if int(caseSwitches['GSw_FINITO_Link'])==1: + ## Redirecting the FINITO case-specific directories + # ... finito directory within the case directory + casedir_finito = os.path.join(casedir,'finito') + # ... define the inputs case directory for FINITO + inputs_case_finito = os.path.join(casedir,'finito','inputs_case') + + ## copy directories + os.makedirs(casedir_finito, exist_ok=True) + shutil.copytree(os.path.join(caseSwitches['finito_dir'], 'inputs'),os.path.join(casedir,'finito', 'inputs')) + shutil.copytree(os.path.join(caseSwitches['finito_dir'], 'input_processing'),os.path.join(casedir,'finito', 'input_processing')) + shutil.copytree(os.path.join(caseSwitches['finito_dir'], 'model'),os.path.join(casedir,'finito', 'model')) + shutil.copytree(os.path.join(caseSwitches['finito_dir'], 'visualization'),os.path.join(casedir, 'finito', 'visualization')) + shutil.copytree(os.path.join(caseSwitches['finito_dir'],'inputs'), os.path.join(casedir,'inputs'), dirs_exist_ok=True) + ## copy over the FINITO cases files + shutil.copy2(os.path.join(caseSwitches['finito_dir'], 'cases.csv'), os.path.join(casedir, 'finito')) + shutil.copy2(os.path.join(caseSwitches['finito_dir'], f"cases_{caseSwitches['finito_cases_file']}.csv"), os.path.join(casedir, 'finito')) + + + #%% (GSw_Trade_PriceResponse > 0) If doing a price-responsive trade run, retrieve the reference exports/imports prices + if int(caseSwitches['GSw_Trade_PriceResponse']) > 0: + initialize_price_response_path = os.path.join(casedir_finito, 'input_processing', 'processing', 'initialize_price_response.py') + # 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) + + #%% Filter and copy all input files for each scenario + # Call FINITO copy_files.py file before starting the runs + copy_files_run = os.system( + 'python ' + os.path.join(caseSwitches['finito_dir'], 'input_processing', 'processing', 'copy_files.py') + + f" -c {casedir_finito} -d {inputs_case_finito} --link" + ) + + # Print an error message if copy_files.py encounters any issue + if copy_files_run != 0: + print("\n❌ ERROR: copy_files.py encountered an issue and did not complete successfully.") + print("Please check the console output above for details.") + print("The issue could be due to regionality, focus sector filtering, or file reading errors.") + os._exit(1) + + ## Populate sets for each linked run + + # Pass AEO base year as an argument + # NOTE: make this dynamic before merging in? + aeo_year = caseSwitches['aeo_year'] + # Collect all arguments for autopop_set.py + focus_sectors = caseSwitches['focus_sectors'].replace('.', ' ') + autopop_args = f" -c {casedir_finito} -d {inputs_case_finito} -a {aeo_year} -s {focus_sectors} -f {caseSwitches['GSw_FixedCostSupply']} -rwf {caseSwitches['GSw_Trade_Partners']} -e {caseSwitches['GSw_ROE_EndUses']}" + # Call autopop_set.py file before starting the runs + autopop_path = os.path.join(caseSwitches['finito_dir'], 'input_processing','processing','autopop_set.py') + os.system('python ' + autopop_path + autopop_args) + ## Call read_mecs_heat.py to generate heat/nonheat/feedstock ratios for FINITO Rest of Industry (ROI) + mecs_sectors = focus_sectors + read_mecs_path = os.path.join(caseSwitches['finito_dir'], 'input_processing', 'processing', 'mecs', 'read_mecs_heat.py') + # Collect all arguments for read_mecs_heat.py + read_mecs_args = f' -s {mecs_sectors} -d {inputs_case_finito}' + os.system('python ' + read_mecs_path + read_mecs_args) ###### Replace files according to 'file_replacements' in cases. Ignore quotes in input text. # << is used to separate the file that is to be replaced from the file that is used @@ -1309,6 +1454,7 @@ def write_batch_script( OPATH.writelines("module load conda \n") OPATH.writelines("module load gams \n") + OPATH.writelines("conda deactivate \n") OPATH.writelines("conda activate reeds2 \n") OPATH.writelines('export R_LIBS_USER="$HOME/rlib" \n\n\n') @@ -1412,7 +1558,8 @@ def write_batch_script( + ' gdxcompress=1' + toLogGamsString + f"--fname={batch_case}" - + f" --GSw_calc_powfrac={caseSwitches['GSw_calc_powfrac']} \n" + + f" --GSw_calc_powfrac={caseSwitches['GSw_calc_powfrac']}" + + f" --first_year_finito={caseSwitches['first_year_finito']} \n" ) OPATH.writelines(writescripterrorcheck("e_report.gms")) if not LINUXORMAC: @@ -1425,6 +1572,32 @@ def write_batch_script( + f" {os.path.join(reeds_path,'postprocessing','diagnose','diagnose_process.py')}" + f" --casepath {casedir} \n\n" ) + OPATH.writelines(f'python e_report_dump.py {casedir} -c\n\n') + # (linked) calls finito reporting (from [casedir]/finito/visualization) + if int(caseSwitches['GSw_FINITO_Link'])==1: + OPATH.writelines( + 'gams ' + + f"{os.path.join(casedir,'finito', 'model', 'finito_report.gms')}" + + f" o={os.path.join('lstfiles',f'finito_report_{batch_case}.lst')}" + + (' license=gamslice.txt' if hpc else '') + + (' r=$r' if LINUXORMAC else ' r=!r!') + + ' gdxcompress=1' + + toLogGamsString + + f"--case={batch_case}" + + f" --casedir={casedir}" + + f" --GSw_FINITO_Link={caseSwitches['GSw_FINITO_Link']}" + + f" --GSw_RetailAdder={caseSwitches['GSw_RetailAdder']}" + + f" --finito_inputs_dir={os.path.join('finito','inputs')}" + + f" --inputs_case_finito_dir={os.path.join('finito','inputs_case')}" + + f" --linked_report_dir={caseSwitches['linked_report_dir']} \n" + ) + # (linked) calls finito postprocessing (from [casedir]/finito/visualization) + OPATH.writelines( + f"python {os.path.join(casedir, 'finito', 'visualization', 'postprocessing.py')}" + + " -b 0" + + f" -l {caseSwitches['GSw_FINITO_Link']}" + + f' -c {batch_case} \n\n' + ) ### Run the retail rate module OPATH.writelines( @@ -1727,7 +1900,7 @@ def launch_single_case_run( shellscript = subprocess.Popen( [os.path.join(casedir, 'call_' + batch_case + ext)], shell=True) # Wait for it to finish before killing the thread - shellscript.wait() + #shellscript.wait() else: if int(caseSwitches['keep_run_terminal']) == 1: terminal_keep_flag = ' /k ' @@ -1855,7 +2028,7 @@ def main( help="Check inputs but don't start runs") args = parser.parse_args() - + main( BatchName=args.BatchName, cases_suffix=args.cases_suffix, single=args.single, simult_runs=args.simult_runs, forcelocal=args.forcelocal, skip_checks=args.skip_checks, From 5fd48485826754d2e972fad27e56b32eb2878849 Mon Sep 17 00:00:00 2001 From: mmowers Date: Thu, 23 Apr 2026 17:38:10 -0400 Subject: [PATCH 03/78] Fix windows bug in outputs for linked ReEDS-FINITO runs --- runbatch.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/runbatch.py b/runbatch.py index 106d27355..104b690e1 100644 --- a/runbatch.py +++ b/runbatch.py @@ -1562,7 +1562,7 @@ def write_batch_script( + f" --first_year_finito={caseSwitches['first_year_finito']} \n" ) OPATH.writelines(writescripterrorcheck("e_report.gms")) - if not LINUXORMAC: + if not LINUXORMAC and int(caseSwitches['GSw_FINITO_Link']) != 1: OPATH.writelines("endlocal\n") OPATH.writelines(f'python {logger}\n') OPATH.writelines(f'python e_report_dump.py {casedir} -c\n\n') @@ -1598,7 +1598,8 @@ def write_batch_script( + f" -l {caseSwitches['GSw_FINITO_Link']}" + f' -c {batch_case} \n\n' ) - + if not LINUXORMAC: + OPATH.writelines("endlocal\n") ### Run the retail rate module OPATH.writelines( "python" From 99147d174e00466f0e0b0ef6925ac2710f6355cf Mon Sep 17 00:00:00 2001 From: bsergi Date: Wed, 13 May 2026 12:46:33 -0400 Subject: [PATCH 04/78] Update to autopop_set.py call --- runbatch.py | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/runbatch.py b/runbatch.py index 067880d79..b0c937b68 100644 --- a/runbatch.py +++ b/runbatch.py @@ -1399,7 +1399,7 @@ def write_batch_script( shutil.copytree(os.path.join(caseSwitches['finito_dir'], 'input_processing'),os.path.join(casedir,'finito', 'input_processing')) shutil.copytree(os.path.join(caseSwitches['finito_dir'], 'model'),os.path.join(casedir,'finito', 'model')) shutil.copytree(os.path.join(caseSwitches['finito_dir'], 'visualization'),os.path.join(casedir, 'finito', 'visualization')) - shutil.copytree(os.path.join(caseSwitches['finito_dir'],'inputs'), os.path.join(casedir,'inputs'), dirs_exist_ok=True) + ## copy over the FINITO cases files shutil.copy2(os.path.join(caseSwitches['finito_dir'], 'cases.csv'), os.path.join(casedir, 'finito')) shutil.copy2(os.path.join(caseSwitches['finito_dir'], f"cases_{caseSwitches['finito_cases_file']}.csv"), os.path.join(casedir, 'finito')) @@ -1427,19 +1427,15 @@ def write_batch_script( print("The issue could be due to regionality, focus sector filtering, or file reading errors.") os._exit(1) - ## Populate sets for each linked run - - # Pass AEO base year as an argument - # NOTE: make this dynamic before merging in? - aeo_year = caseSwitches['aeo_year'] - # Collect all arguments for autopop_set.py - focus_sectors = caseSwitches['focus_sectors'].replace('.', ' ') - autopop_args = f" -c {casedir_finito} -d {inputs_case_finito} -a {aeo_year} -s {focus_sectors} -f {caseSwitches['GSw_FixedCostSupply']} -rwf {caseSwitches['GSw_Trade_Partners']} -e {caseSwitches['GSw_ROE_EndUses']}" - # Call autopop_set.py file before starting the runs - autopop_path = os.path.join(caseSwitches['finito_dir'], 'input_processing','processing','autopop_set.py') - os.system('python ' + autopop_path + autopop_args) + ## Populate sets for each linked run using autopop_set.py + #autopop_args = f" -c {casedir_finito} -d {inputs_case_finito} -a {aeo_year} -s {focus_sectors} -f {caseSwitches['GSw_FixedCostSupply']} -rwf {caseSwitches['GSw_Trade_Partners']} -e {caseSwitches['GSw_ROE_EndUses']}" + os.system( + 'python ' + os.path.join(caseSwitches['finito_dir'], 'input_processing', 'processing', 'autopop_set.py') + + f" -c {casedir_finito} -d {inputs_case_finito} --link" + ) + ## Call read_mecs_heat.py to generate heat/nonheat/feedstock ratios for FINITO Rest of Industry (ROI) - mecs_sectors = focus_sectors + mecs_sectors = caseSwitches['focus_sectors'].replace('.', ' ') read_mecs_path = os.path.join(caseSwitches['finito_dir'], 'input_processing', 'processing', 'mecs', 'read_mecs_heat.py') # Collect all arguments for read_mecs_heat.py read_mecs_args = f' -s {mecs_sectors} -d {inputs_case_finito}' From 6c98dde896f345d659a0528063360b0b1ee02b0d Mon Sep 17 00:00:00 2001 From: mmowers Date: Sat, 30 May 2026 04:43:29 +0000 Subject: [PATCH 05/78] Update region switches in cases_finito.csv --- cases_finito.csv | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cases_finito.csv b/cases_finito.csv index 25fb39db5..086d15d94 100644 --- a/cases_finito.csv +++ b/cases_finito.csv @@ -8,6 +8,7 @@ coalscen,AEO_2025_reference,,,,,,,,,,,,,,,,,,,,, ngscen,AEO_2025_reference,,,,,AEO_2025_HOG,,,AEO_2025_HOG,,,,,,,,,,,,, uraniumscen,AEO_2025_reference,,,,,,,,,,,,,,,,,,,,, GSw_Region,country/USA,,st/CO,,,,,,,,,,,,,,,,,,, +GSw_ZoneSet,z132,,,,,,,,,,,,,,,,,,,,,z54 GSw_GasCurve,1,,2,,,,,,,,,,,,,,,,,,, GSw_gopt,2,,,,,,,,,,,,,,,,,,,,, GSw_NewValCapShrink,1,,,,,,,,,,,,,,,,,,,,, @@ -31,7 +32,6 @@ GSw_H2Combustionupgrade,1,,,,,,,,,,,,,,,,,,,,, GSw_H2CombinedCycle,1,,,,,,,,,,,,,,,,,,,,, GSw_DAC,1,,,,,,,,,,,,,,,,,,,,, GSw_BECCS,1,,,,,,,,,,,,,,,,,,,,, -GSw_RegionResolution,ba,,,,,,,,,,,,,,,,,,,,,aggreg GSw_TCPhaseout,1,,,,,,,,,,,,,,,,,,,,, GSw_CO2_Detail,1,,,,,,,,,,,,,,,,,,,,, GSw_CO2_LimitStorageSites,1,,,,,,,,,,,,,,,,,,,,, @@ -43,4 +43,3 @@ GSw_PRM_StressIterateMax,0,,,,,,,,,,,,,,,,,,,,, GSw_HourlyNumClusters,9,,,,,,,,,,,,,,,,,,,,, GSw_HourlyChunkLengthRep,4,,,,,,,,,,,,,,,,,,,,, GSw_HourlyChunkLengthStress,4,,,,,,,,,,,,,,,,,,,,, -GSw_HierarchyFile,default,,,,,,,,,,,,,,,,,,,,,agg54 From 046b21de2d271495d705f32264b2739ed73d965f Mon Sep 17 00:00:00 2001 From: mmowers Date: Tue, 2 Jun 2026 19:58:47 -0400 Subject: [PATCH 06/78] Fix bug in report.gms for linked FINITO runs --- reeds/core/terminus/report.gms | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/reeds/core/terminus/report.gms b/reeds/core/terminus/report.gms index e0026dd29..882a2621d 100644 --- a/reeds/core/terminus/report.gms +++ b/reeds/core/terminus/report.gms @@ -547,8 +547,8 @@ $ifthene.finitogasprice Sw_FINITO_Link == 1 repgasprice_finito(cendiv,h,t)$[tmodel_new(t)$(not tfuel(t))] = ( 1/obj_scale * 1/pvf_onm(t) * deflator('2018') * [ max{ - smax{(eus,usep)$[map_roe_ec_usep("NG",eus,usep)], eq_supplydemand_fe_pool.m("NG",eus,usep,cendiv,h,t)}, - smax{(usep)$[map_roi_ec_usep("NG",usep)],eq_supplydemand_fe_roi.m("NG",usep,cendiv,h,t)}, + smax{(eus,usep,r)$[map_roe_ec_usep("NG",eus,usep)$r_cendiv(r,cendiv)], eq_supplydemand_fe_pool.m("NG",eus,usep,r,h,t)}, + smax{(usep,r)$[map_roi_ec_usep("NG",usep)$r_cendiv(r,cendiv)],eq_supplydemand_fe_roi.m("NG",usep,r,h,t)}, smax{(cfp2,cfvin2,fi,r)$[map_ec_ei("NG",fi)$valei_cf(cfp2,cfvin2,fi,r,t)$r_cendiv(r,cendiv)],eq_use_fi_cf.m(cfp2,cfvin2,fi,r,h,t)}, smax{(tech,vin,fi,r)$[map_ec_ei("NG",fi)$valei_ind(tech,vin,fi,r,t)$r_cendiv(r,cendiv)],eq_use_fi_ind.m(tech,vin,fi,r,h,t)} } / hours(h) From 8cecf3cdb1064a2eb58745553692ee731ba79640 Mon Sep 17 00:00:00 2001 From: mmowers Date: Tue, 2 Jun 2026 20:05:34 -0400 Subject: [PATCH 07/78] Add comment to report.gms --- reeds/core/terminus/report.gms | 1 + 1 file changed, 1 insertion(+) diff --git a/reeds/core/terminus/report.gms b/reeds/core/terminus/report.gms index 882a2621d..8c0d84419 100644 --- a/reeds/core/terminus/report.gms +++ b/reeds/core/terminus/report.gms @@ -543,6 +543,7 @@ repgasprice(cendiv,t)$[(Sw_GasCurve = 2)$tmodel_new(t)$repgasquant(cendiv,t)$tfu * gas price by timeslice when linked with FINITO (see similar calculation in finito_report.gms) [$2004/MMBtu] +* We maybe should be taking weighted averages instead of max across regions and categories (pool vs roi etc.) $ifthene.finitogasprice Sw_FINITO_Link == 1 repgasprice_finito(cendiv,h,t)$[tmodel_new(t)$(not tfuel(t))] = ( 1/obj_scale * 1/pvf_onm(t) * deflator('2018') * From b0720c9b312090383c38149788db23794a5982ef Mon Sep 17 00:00:00 2001 From: mmowers Date: Wed, 3 Jun 2026 11:21:58 -0400 Subject: [PATCH 08/78] Convert USE_ELE_FINITO to busbar load when using in ReEDS --- reeds/core/setup/c_model.gms | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/reeds/core/setup/c_model.gms b/reeds/core/setup/c_model.gms index 3f95c9083..a81e5ee42 100644 --- a/reeds/core/setup/c_model.gms +++ b/reeds/core/setup/c_model.gms @@ -399,10 +399,10 @@ eq_loadcon(r,h,t)$tmodel(t).. * [plus] load for industrial and converted fuel facilities (FINITO), * including the PRM for stress periods -* TODO: should this include distloss? +* USE_ELE_FINITO is end-use, so divide by (1-distloss) to convert it to busbar * [MWh/hr = MW] $ifthene.linked_load Sw_FINITO_Link==1 - + USE_ELE_FINITO(r,h,t)$[t_finito(t)] * (1 + prm(r,t)$h_stress(h)) + + (USE_ELE_FINITO(r,h,t) / (1.0 - distloss))$[t_finito(t)] * (1 + prm(r,t)$h_stress(h)) $endif.linked_load ; From bf47a9ba02bf72e004ca3c06527f9a486171945d Mon Sep 17 00:00:00 2001 From: bsergi Date: Mon, 8 Jun 2026 11:04:45 -0400 Subject: [PATCH 09/78] error handling for missing finito gdx file --- reeds/core/terminus/report_dump.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/reeds/core/terminus/report_dump.py b/reeds/core/terminus/report_dump.py index 4b3e5c810..2a18ed4b3 100644 --- a/reeds/core/terminus/report_dump.py +++ b/reeds/core/terminus/report_dump.py @@ -282,13 +282,13 @@ def postprocess_outputs(case, outputs_path=None, verbose=0): ### FINITO outputs when running linked model if int(sw.GSw_FINITO_Link): print("Loading FINITO outputs gdx") - # TODO: this is much slower than loading the ReEDS outputs, - # need to figure out why and address - finito_outputs = gdxpds.to_dataframes( - os.path.join(outputs_path, f"finito_reeds_outputs_{os.path.basename(case)}.gdx") - ) - print("Finished loading FINITO outputs gdx") - dict_out.update(finito_outputs) + finito_gdx = os.path.join(outputs_path, f"finito_reeds_outputs_{os.path.basename(case)}.gdx") + if os.path.isfile(finito_gdx): + finito_outputs = gdxpds.to_dataframes(finito_gdx) + dict_out.update(finito_outputs) + print("Finished loading FINITO outputs gdx") + else: + print(f"FINITO outputs in {finito_gdx} could not be found, skipping.") write_dfdict( From eaaaa7e9f5513a499713a008d2862215a1fe9643 Mon Sep 17 00:00:00 2001 From: bsergi Date: Mon, 8 Jun 2026 11:07:51 -0400 Subject: [PATCH 10/78] error handling for finito gdx --- reeds/core/terminus/report_dump.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/reeds/core/terminus/report_dump.py b/reeds/core/terminus/report_dump.py index 2a18ed4b3..617433c95 100644 --- a/reeds/core/terminus/report_dump.py +++ b/reeds/core/terminus/report_dump.py @@ -283,12 +283,13 @@ def postprocess_outputs(case, outputs_path=None, verbose=0): if int(sw.GSw_FINITO_Link): print("Loading FINITO outputs gdx") finito_gdx = os.path.join(outputs_path, f"finito_reeds_outputs_{os.path.basename(case)}.gdx") - if os.path.isfile(finito_gdx): + try: finito_outputs = gdxpds.to_dataframes(finito_gdx) dict_out.update(finito_outputs) print("Finished loading FINITO outputs gdx") - else: - print(f"FINITO outputs in {finito_gdx} could not be found, skipping.") + except Exception as err: + print(err) + print(f"Error loading FINITO outputs in {finito_gdx}, skipping.") write_dfdict( From 877b591623ce72443d45cb2c063cae17c553af02 Mon Sep 17 00:00:00 2001 From: mmowers Date: Tue, 9 Jun 2026 01:19:38 -0400 Subject: [PATCH 11/78] Fix bug in standalone ReEDS outputs by putting if statement around FINITO output --- reeds/core/terminus/report.gms | 2 ++ 1 file changed, 2 insertions(+) diff --git a/reeds/core/terminus/report.gms b/reeds/core/terminus/report.gms index 8c0d84419..cdc0b9433 100644 --- a/reeds/core/terminus/report.gms +++ b/reeds/core/terminus/report.gms @@ -2079,7 +2079,9 @@ load_cat("h2_network",r,t)$tmodel_new(t) = ; load_cat("dac",r,t)$tmodel_new(t) = sum{i$dac(i), prod_load_ann(i,r,t) } ; +$ifthene.finitoloadcat Sw_FINITO_Link == 1 load_cat("finito",r,t)$tmodel_new(t) = sum{h, hours(h) * USE_ELE_FINITO.l(r,h,t) } ; +$endif.finitoloadcat *======================================== * H2 NETWORK From 86c4093e8baebc020c9b493724730ba1702e0285 Mon Sep 17 00:00:00 2001 From: mmowers Date: Tue, 9 Jun 2026 12:09:19 -0400 Subject: [PATCH 12/78] Update to z54 by default in cases_finito.csv --- cases_finito.csv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cases_finito.csv b/cases_finito.csv index 086d15d94..ca5459a34 100644 --- a/cases_finito.csv +++ b/cases_finito.csv @@ -8,7 +8,7 @@ coalscen,AEO_2025_reference,,,,,,,,,,,,,,,,,,,,, ngscen,AEO_2025_reference,,,,,AEO_2025_HOG,,,AEO_2025_HOG,,,,,,,,,,,,, uraniumscen,AEO_2025_reference,,,,,,,,,,,,,,,,,,,,, GSw_Region,country/USA,,st/CO,,,,,,,,,,,,,,,,,,, -GSw_ZoneSet,z132,,,,,,,,,,,,,,,,,,,,,z54 +GSw_ZoneSet,z54,,,,,,,,,,,,,,,,,,,,,z54 GSw_GasCurve,1,,2,,,,,,,,,,,,,,,,,,, GSw_gopt,2,,,,,,,,,,,,,,,,,,,,, GSw_NewValCapShrink,1,,,,,,,,,,,,,,,,,,,,, From 74443a6da0ee4e2bc7dd5be63f8fdf949502fb61 Mon Sep 17 00:00:00 2001 From: mmowers Date: Sat, 13 Jun 2026 00:24:20 -0400 Subject: [PATCH 13/78] Remove round statement to align with main --- reeds/core/setup/b_inputs.gms | 1 - 1 file changed, 1 deletion(-) diff --git a/reeds/core/setup/b_inputs.gms b/reeds/core/setup/b_inputs.gms index c8a410a77..c45bda48d 100644 --- a/reeds/core/setup/b_inputs.gms +++ b/reeds/core/setup/b_inputs.gms @@ -1989,7 +1989,6 @@ noncumulative_prescriptions(pcat,r,t)$tmodel_new(t) ], prescribednonrsc(tt,pcat,r,"value") + prescribedrsc(tt,pcat,r,"value") } ; -noncumulative_prescriptions(pcat,r,t)$noncumulative_prescriptions(pcat,r,t) = round(noncumulative_prescriptions(pcat,r,t),6); parameter noncumulative_prescriptions_energy(pcat,r,t) "--MWh-- prescribed energy capacity that comes online in a given year" ; noncumulative_prescriptions_energy(pcat,r,t)$tmodel_new(t) From 968806411a267b7abfb362b2d8c709410c548e53 Mon Sep 17 00:00:00 2001 From: mmowers Date: Thu, 18 Jun 2026 21:49:35 -0400 Subject: [PATCH 14/78] Cleaning up unnecessary changes for cleaner comparison to main --- cases.csv | 1 - helpers/restart_runs.py | 3 +- inputs/emission_constraints/co2_tax.csv | 84 ++++++++++++------------- reeds/core/setup/b_inputs.gms | 30 +++------ reeds/core/setup/c_model.gms | 12 ++-- reeds/core/setup/d_objective.gms | 15 ++--- reeds/core/solve/3_solve_oneyear.gms | 1 - reeds/core/solve/5_varfix.gms | 6 -- reeds/core/terminus/report.gms | 69 ++++++++++---------- runreeds.py | 14 ++--- 10 files changed, 102 insertions(+), 133 deletions(-) diff --git a/cases.csv b/cases.csv index b7a343d16..ece24ca2d 100644 --- a/cases.csv +++ b/cases.csv @@ -121,7 +121,6 @@ GSw_CO2_pipeline_fom,Set FOM cost for CO2 Pipeline. 2004$/(tonne-mi)/hr-yr. Sugg GSw_CO2_spurline_cost,Set capex cost for CO2 Spurline. 2004$/(tonne-mi)/hr. Suggested value: 2257,N/A,2257, GSw_CO2_spurline_fom,Set FOM cost for CO2 Spurline. 2004$/(tonne-mi)/hr-yr. Suggested value: 60,N/A,60, GSw_CO2_Detail,Switch to enable detailed representation CO2 transportation and storage,0; 1,0, -GSw_CO2_LimitStorageSites,Switch to limit storage sites to the closest site for each region,0; 1,0, GSw_CO2_CostAdj,multiplier for co2 infrastructure when Sw_CO2_Detail = 1,float,1, GSw_CO2_BEC,"Break even cost capacity factor assignment. Must be a suffix to ""bec_"" in the columns of co2_site_char.csv",N/A,70, GSw_CoalIGCC,Turn on/off COAL IGCC,0; 1,1, diff --git a/helpers/restart_runs.py b/helpers/restart_runs.py index 9b02315cb..d353d2aef 100644 --- a/helpers/restart_runs.py +++ b/helpers/restart_runs.py @@ -181,8 +181,7 @@ # It is a single case or we are not on HPC if copy_srun_template: writelines_srun_case = writelines_srun.copy() - writelines_srun_case.append(f"#SBATCH --job-name={casename}") - writelines_srun_case.append(f"#SBATCH --output={os.path.join(case, 'slurm-%j.out')}\n") + writelines_srun_case.append(f"\n#SBATCH --job-name={casename}\n") writelines_srun_case.append(f"sh {callfile}") with open(sbatchfile, 'w') as f: for line in writelines_srun_case: diff --git a/inputs/emission_constraints/co2_tax.csv b/inputs/emission_constraints/co2_tax.csv index 1e08f6246..d9e795517 100644 --- a/inputs/emission_constraints/co2_tax.csv +++ b/inputs/emission_constraints/co2_tax.csv @@ -1,42 +1,42 @@ -t,default,t050_2035,t050_2050,t100_2035,t100_2050,t150_2035,t150_2050,t200_2035,t200_2050,t200alt_2050 -2010,0,0,0,0,0,0,0,0,0,0 -2011,0,0,0,0,0,0,0,0,0,0 -2012,0,0,0,0,0,0,0,0,0,0 -2013,0,0,0,0,0,0,0,0,0,0 -2014,0,0,0,0,0,0,0,0,0,0 -2015,0,0,0,0,0,0,0,0,0,0 -2016,0,0,0,0,0,0,0,0,0,0 -2017,0,0,0,0,0,0,0,0,0,0 -2018,0,0,0,0,0,0,0,0,0,0 -2019,0,0,0,0,0,0,0,0,0,0 -2020,0,0,0,0,0,0,0,0,0,0 -2021,0,0,0,0,0,0,0,0,0,0 -2022,30,1,1,1,1,1,1,1,1,0 -2023,31.5,4.77,2.75,8.62,4.54,12.46,6.32,16.31,8.11,0 -2024,33.075,8.54,4.5,16.23,8.07,23.92,11.64,31.62,15.21,0 -2025,34.72875,12.31,6.25,23.85,11.61,35.38,16.96,46.92,22.32,0 -2026,36.4651875,16.08,8,31.46,15.14,46.85,22.29,62.23,29.43,0 -2027,38.28844688,19.85,9.75,39.08,18.68,58.31,27.61,77.54,36.54,0 -2028,40.20286922,23.62,11.5,46.69,22.21,69.77,32.93,92.85,43.64,0 -2029,42.21301268,27.38,13.25,54.31,25.75,81.23,38.25,108.15,50.75,0 -2030,44.32366331,31.15,15,61.92,29.29,92.69,43.57,123.46,57.86,0 -2031,46.53984648,34.92,16.75,69.54,32.82,104.15,48.89,138.77,64.96,10 -2032,48.8668388,38.69,18.5,77.15,36.36,115.62,54.21,154.08,72.07,20 -2033,51.31018074,42.46,20.25,84.77,39.89,127.08,59.54,169.38,79.18,30 -2034,53.87568978,46.23,22,92.38,43.43,138.54,64.86,184.69,86.29,40 -2035,56.56947427,50,23.75,100,46.96,150,70.18,200,93.39,50 -2036,59.39794798,50,25.5,100,50.5,150,75.5,200,100.5,60 -2037,62.36784538,50,27.25,100,54.04,150,80.82,200,107.61,70 -2038,65.48623765,50,29,100,57.57,150,86.14,200,114.71,80 -2039,68.76054953,50,30.75,100,61.11,150,91.46,200,121.82,90 -2040,72.19857701,50,32.5,100,64.64,150,96.79,200,128.93,100 -2041,75.80850586,50,34.25,100,68.18,150,102.11,200,136.04,110 -2042,79.59893115,50,36,100,71.71,150,107.43,200,143.14,120 -2043,83.57887771,50,37.75,100,75.25,150,112.75,200,150.25,130 -2044,87.7578216,50,39.5,100,78.79,150,118.07,200,157.36,140 -2045,92.14571268,50,41.25,100,82.32,150,123.39,200,164.46,150 -2046,96.75299831,50,43,100,85.86,150,128.71,200,171.57,160 -2047,101.5906482,50,44.75,100,89.39,150,134.04,200,178.68,170 -2048,106.6701806,50,46.5,100,92.93,150,139.36,200,185.79,180 -2049,112.0036897,50,48.25,100,96.46,150,144.68,200,192.89,190 -2050,117.6038742,50,50,100,100,150,150,200,200,200 +t,default,t050_2035,t050_2050,t100_2035,t100_2050,t150_2035,t150_2050,t200_2035,t200_2050 +2010,0,0,0,0,0,0,0,0,0 +2011,0,0,0,0,0,0,0,0,0 +2012,0,0,0,0,0,0,0,0,0 +2013,0,0,0,0,0,0,0,0,0 +2014,0,0,0,0,0,0,0,0,0 +2015,0,0,0,0,0,0,0,0,0 +2016,0,0,0,0,0,0,0,0,0 +2017,0,0,0,0,0,0,0,0,0 +2018,0,0,0,0,0,0,0,0,0 +2019,0,0,0,0,0,0,0,0,0 +2020,0,0,0,0,0,0,0,0,0 +2021,0,0,0,0,0,0,0,0,0 +2022,30,1,1,1,1,1,1,1,1 +2023,31.5,4.77,2.75,8.62,4.54,12.46,6.32,16.31,8.11 +2024,33.075,8.54,4.5,16.23,8.07,23.92,11.64,31.62,15.21 +2025,34.72875,12.31,6.25,23.85,11.61,35.38,16.96,46.92,22.32 +2026,36.4651875,16.08,8,31.46,15.14,46.85,22.29,62.23,29.43 +2027,38.28844688,19.85,9.75,39.08,18.68,58.31,27.61,77.54,36.54 +2028,40.20286922,23.62,11.5,46.69,22.21,69.77,32.93,92.85,43.64 +2029,42.21301268,27.38,13.25,54.31,25.75,81.23,38.25,108.15,50.75 +2030,44.32366331,31.15,15,61.92,29.29,92.69,43.57,123.46,57.86 +2031,46.53984648,34.92,16.75,69.54,32.82,104.15,48.89,138.77,64.96 +2032,48.8668388,38.69,18.5,77.15,36.36,115.62,54.21,154.08,72.07 +2033,51.31018074,42.46,20.25,84.77,39.89,127.08,59.54,169.38,79.18 +2034,53.87568978,46.23,22,92.38,43.43,138.54,64.86,184.69,86.29 +2035,56.56947427,50,23.75,100,46.96,150,70.18,200,93.39 +2036,59.39794798,50,25.5,100,50.5,150,75.5,200,100.5 +2037,62.36784538,50,27.25,100,54.04,150,80.82,200,107.61 +2038,65.48623765,50,29,100,57.57,150,86.14,200,114.71 +2039,68.76054953,50,30.75,100,61.11,150,91.46,200,121.82 +2040,72.19857701,50,32.5,100,64.64,150,96.79,200,128.93 +2041,75.80850586,50,34.25,100,68.18,150,102.11,200,136.04 +2042,79.59893115,50,36,100,71.71,150,107.43,200,143.14 +2043,83.57887771,50,37.75,100,75.25,150,112.75,200,150.25 +2044,87.7578216,50,39.5,100,78.79,150,118.07,200,157.36 +2045,92.14571268,50,41.25,100,82.32,150,123.39,200,164.46 +2046,96.75299831,50,43,100,85.86,150,128.71,200,171.57 +2047,101.5906482,50,44.75,100,89.39,150,134.04,200,178.68 +2048,106.6701806,50,46.5,100,92.93,150,139.36,200,185.79 +2049,112.0036897,50,48.25,100,96.46,150,144.68,200,192.89 +2050,117.6038742,50,50,100,100,150,150,200,200 \ No newline at end of file diff --git a/reeds/core/setup/b_inputs.gms b/reeds/core/setup/b_inputs.gms index c45bda48d..771cc6565 100644 --- a/reeds/core/setup/b_inputs.gms +++ b/reeds/core/setup/b_inputs.gms @@ -2432,7 +2432,6 @@ h2_ptc(i,v,r,t)$valcap(i,v,r,t) = h2_ptc_in(i,v,t) ; * Otherwise, we assume it receives $0/kg because the cleanliness of its carbon cannot be proven h2_ptc("electrolyzer",v,r,t)$[(not Sw_H2_PTC)] = 0; - set h2_ptc_years(t) "years in which the hydrogen production incentive is active"; h2_ptc_years(t) = tmodel_new(t)$[sum{(i,v,r),h2_ptc(i,v,r,t)}]; @@ -3130,7 +3129,6 @@ $include inputs_case%ds%tsc_binwidth.csv $offdelim $onlisting / ; -tsc_binwidth(r,rr,tscbin) = 1e9; parameter tsc_forward(r,rr,tscbin) "--$/MW-- transmission upgrade cost for forward direction" / @@ -5950,19 +5948,18 @@ $include inputs_case%ds%r_cs_distance_mi.csv $offdelim $ondigit $onlisting -/ , - min_r_cs_distance(r) "--mi-- minimum euclidean distance between BA transmission endpoints and storage formations" +/ ; $offempty -* find the closest storage site to each region -min_r_cs_distance(r) = smin(cs$[r_cs(r,cs)], r_cs_distance(r,cs)); +* Assign spurline costs +cost_co2_spurline_cap(r,cs,t)$[r_cs(r,cs)$tmodel_new(t)] = Sw_CO2_spurline_cost * r_cs_distance(r,cs) ; + +* CO2 pipelines can be build between any two adjacent BAs +cost_co2_pipeline_cap(r,rr,t)$[routes_adjacent(r,rr)$tmodel_new(t)] = Sw_CO2_pipeline_cost * pipeline_distance(r,rr) ; +cost_co2_pipeline_fom(r,rr,t)$[routes_adjacent(r,rr)$tmodel_new(t)] = Sw_CO2_pipeline_fom * pipeline_distance(r,rr) ; -$ifthene.rcslimit %GSw_CO2_LimitStorageSites% == 1 -* remove the region-site combinations that are not the closest -r_cs_distance(r,cs)$[r_cs(r,cs)$(r_cs_distance(r,cs) <= min_r_cs_distance(r))] = min_r_cs_distance(r) ; -r_cs_distance(r,cs)$[r_cs(r,cs)$(r_cs_distance(r,cs) > min_r_cs_distance(r))] = 0 ; -$endif.rcslimit +co2_routes(r,rr)$[routes_adjacent(r,rr)$pipeline_distance(r,rr)] = yes ; $onempty table co2_char(cs,*) "co2 site characteristics including injection rate limit, total storage limit, and break even cost" @@ -5981,18 +5978,9 @@ cost_co2_stor_bec(cs,t) = co2_char(cs,"bec_%GSw_CO2_BEC%"); csfeas(cs)$[co2_storage_limit(cs)$co2_injection_limit(cs)] = yes ; * only want to consider r_cs pairs which have available capacity r_cs(r,cs)$[not csfeas(cs)] = no ; -r_cs(r,cs)$[not r_cs_distance(r,cs)] = no ; -* Assign spurline costs -cost_co2_spurline_cap(r,cs,t)$[r_cs(r,cs)$tmodel_new(t)] = Sw_CO2_spurline_cost * r_cs_distance(r,cs) ; cost_co2_spurline_fom(r,cs,t)$[r_cs(r,cs)$tmodel_new(t)] = Sw_CO2_spurline_fom * r_cs_distance(r,cs) ; -* CO2 pipelines can be build between any two adjacent BAs -cost_co2_pipeline_cap(r,rr,t)$[routes_adjacent(r,rr)$tmodel_new(t)] = Sw_CO2_pipeline_cost * pipeline_distance(r,rr) ; -cost_co2_pipeline_fom(r,rr,t)$[routes_adjacent(r,rr)$tmodel_new(t)] = Sw_CO2_pipeline_fom * pipeline_distance(r,rr) ; - -co2_routes(r,rr)$[routes_adjacent(r,rr)$pipeline_distance(r,rr)] = yes ; - cost_co2_pipeline_cap(r,rr,t) = %GSw_CO2_CostAdj% * cost_co2_pipeline_cap(r,rr,t); cost_co2_pipeline_fom(r,rr,t) = %GSw_CO2_CostAdj% * cost_co2_pipeline_fom(r,rr,t); cost_co2_stor_bec(cs,t) = %GSw_CO2_CostAdj% * cost_co2_stor_bec(cs,t) ; @@ -6167,4 +6155,4 @@ emit_rate(etype,e,i,v,r,t)$[not valgen(i,v,r,t)] = 0 ; *============================================================ valinv_init(i,v,r,t) = valinv(i,v,r,t) ; -valcap_init(i,v,r,t) = valcap(i,v,r,t) ; \ No newline at end of file +valcap_init(i,v,r,t) = valcap(i,v,r,t) ; diff --git a/reeds/core/setup/c_model.gms b/reeds/core/setup/c_model.gms index 3fa3758cf..1f9e28a8f 100644 --- a/reeds/core/setup/c_model.gms +++ b/reeds/core/setup/c_model.gms @@ -1048,6 +1048,7 @@ eq_rsc_INVlim(r,i,rscbin,t)$[tmodel(t) *plus exogenous (pre-start-year) capacity, using its level in the first year (tfirst) + sum{(ii,v,tt)$[tfirst(tt)$rsc_agg(i,ii)$exog_rsc(i)], capacity_exog_rsc(ii,v,r,rscbin,tt) } + ; * --------------------------------------------------------------------------- @@ -3864,7 +3865,7 @@ $endif.linked_co2_capture * --------------------------------------------------------------------------- -eq_co2_transport_caplimit(r,rr,h,t)$[co2_routes(r,rr)$Sw_CO2_Detail$h_rep(h) +eq_co2_transport_caplimit(r,rr,h,t)$[co2_routes(r,rr)$Sw_CO2_Detail $tmodel(t)$(yeart(t)>=co2_detail_startyr)].. *capacity computed as cumulative investments of co2 pipelines up to the current year @@ -3880,8 +3881,7 @@ eq_co2_transport_caplimit(r,rr,h,t)$[co2_routes(r,rr)$Sw_CO2_Detail$h_rep(h) * --------------------------------------------------------------------------- -eq_co2_spurline_caplimit(r,cs,h,t)$[Sw_CO2_Detail$r_cs(r,cs)$h_rep(h)$tmodel(t) - $(yeart(t)>=co2_detail_startyr)].. +eq_co2_spurline_caplimit(r,cs,h,t)$[Sw_CO2_Detail$r_cs(r,cs)$tmodel(t)$(yeart(t)>=co2_detail_startyr)].. *capacity computed as cumulative investments of co2 spurlines up to the current year sum{tt$[(yeart(tt)<=yeart(t))$(tmodel(tt) or tfix(tt))$(yeart(tt)>=co2_detail_startyr)], @@ -3899,7 +3899,7 @@ $endif.linked_co2_spurline_caplimit * --------------------------------------------------------------------------- -eq_co2_sink(r,h,t)$[tmodel(t)$Sw_CO2_Detail$h_rep(h)$(yeart(t)>=co2_detail_startyr)].. +eq_co2_sink(r,h,t)$[tmodel(t)$Sw_CO2_Detail$(yeart(t)>=co2_detail_startyr)].. *the amount of co2 stored from r in all of its cs sites sum{cs$r_cs(r,cs), CO2_STORED(r,cs,h,t) } @@ -3930,8 +3930,7 @@ $endif.linked_co2_sink * --------------------------------------------------------------------------- -eq_co2_injection_limit(cs,h,t)$[tmodel(t)$Sw_CO2_Detail$h_rep(h) - $(yeart(t)>=co2_detail_startyr)$csfeas(cs)].. +eq_co2_injection_limit(cs,h,t)$[Sw_CO2_Detail$tmodel(t)$(yeart(t)>=co2_detail_startyr)$csfeas(cs)].. * exogenously defined injection limit co2_injection_limit(cs) @@ -3973,7 +3972,6 @@ $ifthene.linked_co2_storage_cumul_limit Sw_FINITO_Link==1 yearweight(tt) * hours(h) * EXTRACT_CO2_CS(cs,r,h,tt) }$[t_finito(t)] $endif.linked_co2_storage_cumul_limit ; - * --------------------------------------------------------------------------- *=================== diff --git a/reeds/core/setup/d_objective.gms b/reeds/core/setup/d_objective.gms index 2400f9449..af8672bf0 100644 --- a/reeds/core/setup/d_objective.gms +++ b/reeds/core/setup/d_objective.gms @@ -259,12 +259,12 @@ eq_Objfn_op(t)$tmodel(t).. *Sw_GasCurve = 0 (census division supply curves natural gas prices) + sum{(cendiv,gb)$[tfuel(t)], sum{h, hours(h) * GASUSED(cendiv,gb,h,t) } * gasprice(cendiv,gb,t) - }$[(Sw_GasCurve = 0)] + }$(Sw_GasCurve = 0) *Sw_GasCurve = 3 (national supply curve for natural gas prices with census division multipliers) + sum{(h,cendiv,gb)$[tfuel(t)], hours(h) * GASUSED(cendiv,gb,h,t) * gasadder_cd(cendiv,t,h) + gasprice_nat_bin(gb,t) - }$[(Sw_GasCurve = 3)] + }$(Sw_GasCurve = 3) *Sw_GasCurve = 1 (national and census division supply curves for natural gas prices) *first - anticipated costs of gas consumption given last year's amount @@ -279,12 +279,13 @@ eq_Objfn_op(t)$tmodel(t).. + sum{(fuelbin)$tfuel(t), gasbinp_national(fuelbin,t) * VGASBINQ_NATIONAL(fuelbin,t) } - )$[(Sw_GasCurve = 1)] + )$[Sw_GasCurve = 1] * ---cost of biofuel consumption and biomass transport--- + sum{(r,bioclass)$[tfuel(t)$sum{(i,v)$(bio(i) or cofire(i)), valgen(i,v,r,t) }], BIOUSED(bioclass,r,t) * (sum{usda_region$r_usda(r,usda_region), biosupply(usda_region, bioclass, "price") } + bio_transport_cost) } + * --- hurdle costs for transmission flow --- + sum{(r,rr,h,trtype)$[routes(r,rr,trtype,t)$cost_hurdle(r,rr,t)], cost_hurdle(r,rr,t) * FLOW(r,rr,h,t,trtype) * hours(h) } @@ -349,15 +350,15 @@ eq_Objfn_op(t)$tmodel(t).. CO2_SPURLINE_INV(r,cs,tt) } }$[Sw_CO2_Detail$(yeart(t)>=co2_detail_startyr)] * --- CO2 injection break even costs - + sum{(r,cs,h)$[r_cs(r,cs)$h_rep(h)], hours(h) * CO2_STORED(r,cs,h,t) * cost_co2_stor_bec(cs,t) }$[Sw_CO2_Detail$(yeart(t)>=co2_detail_startyr)] + + sum{(r,cs,h)$r_cs(r,cs), hours(h) * CO2_STORED(r,cs,h,t) * cost_co2_stor_bec(cs,t) }$[Sw_CO2_Detail$(yeart(t)>=co2_detail_startyr)] * --- Tax credit for CO2 stored --- * note conversion to 12-year CRF given length of CO2 captured incentive payments - - sum{(i,v,r,h)$[valgen(i,v,r,t)$h_rep(h)$co2_captured_incentive(i,v,r,t)], + - sum{(i,v,r,h)$[valgen(i,v,r,t)$co2_captured_incentive(i,v,r,t)], (crf(t) / crf_co2_incentive(t)) * co2_captured_incentive(i,v,r,t) * hours(h) * capture_rate("CO2",i,v,r,t) * GEN(i,v,r,h,t)} * --- Tax credit for CO2 stored for DAC --- - - sum{(p,i,v,r,h)$[dac(i)$valcap(i,v,r,t)$i_p(i,p)$h_rep(h)$co2_captured_incentive(i,v,r,t)], + - sum{(p,i,v,r,h)$[dac(i)$valcap(i,v,r,t)$i_p(i,p)$h_rep(h)], (crf(t) / crf_co2_incentive(t)) * co2_captured_incentive(i,v,r,t) * hours(h) * PRODUCE(p,i,v,r,h,t)} * --- PTC value for electric power generation --- @@ -371,7 +372,7 @@ eq_Objfn_op(t)$tmodel(t).. - sum{(p,v,r,h)$[valcap("electrolyzer",v,r,t)$(sameas(p,"H2"))$h2_ptc("electrolyzer",v,r,t)$h_rep(h)], hours(h) * PRODUCE(p,"electrolyzer",v,r,h,t) * (crf(t) / crf_h2_incentive(t)) * h2_ptc("electrolyzer",v,r,t) * 1e3} - $[(Sw_H2_PTC)$Sw_H2$h2_ptc_years(t)$(yeart(t) >= h2_demand_start)] + $[Sw_H2_PTC$Sw_H2$h2_ptc_years(t)$(yeart(t) >= h2_demand_start)] *end multiplier for pvf_onm ) diff --git a/reeds/core/solve/3_solve_oneyear.gms b/reeds/core/solve/3_solve_oneyear.gms index 43e6c7476..d059ff3a0 100644 --- a/reeds/core/solve/3_solve_oneyear.gms +++ b/reeds/core/solve/3_solve_oneyear.gms @@ -247,7 +247,6 @@ $endif.diagnose * ------------------------------ * Solve the Model * ------------------------------ -option limrow = 1e6; $ifthen.valstr %GSw_ValStr% == 1 OPTION lp = convert ; ReEDSmodel.optfile = 1 ; diff --git a/reeds/core/solve/5_varfix.gms b/reeds/core/solve/5_varfix.gms index 6230d69a3..9b133772c 100644 --- a/reeds/core/solve/5_varfix.gms +++ b/reeds/core/solve/5_varfix.gms @@ -9,10 +9,6 @@ if(Sw_RemoveSmallNumbers = 1, INV_ENERGY.l(i,v,r,tfix)$[abs(INV_ENERGY.l(i,v,r,tfix)) < rhs_tolerance] = 0 ; INV_RSC.l(i,v,r,rscbin,tfix)$[abs(INV_RSC.l(i,v,r,rscbin,tfix)) < rhs_tolerance] = 0 ; INV_POI.l(r,tfix)$[abs(INV_POI.l(r,tfix)) < rhs_tolerance] = 0 ; - INV_REFURB.l(i,v,r,tfix)$[abs(INV_REFURB.l(i,v,r,tfix)) < rhs_tolerance] = 0; - CO2_SPURLINE_INV.l(r,cs,tfix)$[abs(CO2_SPURLINE_INV.l(r,cs,tfix)) < rhs_tolerance] = 0; - CO2_TRANSPORT_INV.l(r,rr,tfix)$[abs(CO2_TRANSPORT_INV.l(r,rr,tfix)) < rhs_tolerance] = 0; - H2_TRANSPORT_INV.l(r,rr,tfix)$[abs(H2_TRANSPORT_INV.l(r,rr,tfix)) < rhs_tolerance] = 0 ; H2_STOR_INV.l(h2_stor,r,tfix)$[abs(H2_STOR_INV.l(h2_stor,r,tfix)) < rhs_tolerance] = 0 ; H2_TRANSPORT_INV.l(r,rr,tfix) $[abs(H2_TRANSPORT_INV.l(r,rr,tfix) ) < rhs_tolerance] = 0 ; ); @@ -123,9 +119,7 @@ H2_STOR_LEVEL_SZN.fx(h2_stor,r,actualszn,tfix)$[(h2_stor_r(h2_stor,r))$(Sw_H2=2) *CO2-related variables CO2_CAPTURED.fx(r,h,tfix)$Sw_CO2_Detail = CO2_CAPTURED.l(r,h,tfix) ; -$ifthene.linked_varfix Sw_FINITO_Link==0 CO2_STORED.fx(r,cs,h,tfix)$[Sw_CO2_Detail$r_cs(r,cs)] = CO2_STORED.l(r,cs,h,tfix) ; -$endif.linked_varfix CO2_FLOW.fx(r,rr,h,tfix)$[Sw_CO2_Detail$co2_routes(r,rr)] = CO2_FLOW.l(r,rr,h,tfix) ; CO2_TRANSPORT_INV.fx(r,rr,tfix)$[Sw_CO2_Detail$co2_routes(r,rr)] = CO2_TRANSPORT_INV.l(r,rr,tfix) ; CO2_SPURLINE_INV.fx(r,cs,tfix)$[Sw_CO2_Detail$r_cs(r,cs)] = CO2_SPURLINE_INV.l(r,cs,tfix) ; diff --git a/reeds/core/terminus/report.gms b/reeds/core/terminus/report.gms index cdc0b9433..5a0f5e8dc 100644 --- a/reeds/core/terminus/report.gms +++ b/reeds/core/terminus/report.gms @@ -495,51 +495,46 @@ ptc_out(i,v,t)$[tmodel_new(t)$ptc_value_scaled(i,v,t)] = ptc_value_scaled(i,v,t) * Case 2: the resource of one or more biomass classes ARE exhausted, i.e., BIOUSED.l(bioclass) = biosupply(bioclass) * Marginal Biomass Price = maximum difference between eq_bioused.m and eq_biousedlimit.m(bioclass) across all biomass classes in a region -repbioprice(r,t)$[tmodel_new(t)$tfuel(t)] = - [max{0, smax{bioclass$BIOUSED.l(bioclass,r,t), eq_bioused.m(r,t) - - sum{usda_region$r_usda(r,usda_region), eq_biousedlimit.m(bioclass,usda_region,t) } } } / pvf_onm(t) ] ; +repbioprice(r,t)$[tmodel_new(t)$tfuel(t)] = max{0, smax{bioclass$BIOUSED.l(bioclass,r,t), eq_bioused.m(r,t) - + sum{usda_region$r_usda(r,usda_region), eq_biousedlimit.m(bioclass,usda_region,t) } } } / pvf_onm(t) ; -* quantity of biomass used in the power sector (convert from mmBTU to dry tons using biomass energy content) -bioused_out(bioclass,r,t)$[tmodel_new(t)$tfuel(t)] = - [BIOUSED.l(bioclass,r,t) / bio_energy_content ]; - -bioused_usda(bioclass,usda_region,t)$[tmodel_new(t)$tfuel(t)] = - [sum{r$r_usda(r,usda_region), bioused_out(bioclass,r,t) } ]; +* quantity of biomass used (convert from mmBTU to dry tons using biomass energy content) +bioused_out(bioclass,r,t)$[tmodel_new(t)$tfuel(t)] = BIOUSED.l(bioclass,r,t) / bio_energy_content ; +bioused_usda(bioclass,usda_region,t)$[tmodel_new(t)$tfuel(t)] = sum{r$r_usda(r,usda_region), bioused_out(bioclass,r,t) } ; * 1e9 converts from MMBtu to Quads repgasquant(cendiv,t)$[(Sw_GasCurve = 0 or Sw_GasCurve = 3)$tmodel_new(t)$tfuel(t)] = - [sum{(gb,h), GASUSED.l(cendiv,gb,h,t) * hours(h) } * gas_scale/ 1e9 ]; + sum{(gb,h), GASUSED.l(cendiv,gb,h,t) * hours(h) } * gas_scale/ 1e9 ; repgasquant(cendiv,t)$[(Sw_GasCurve = 1 or Sw_GasCurve = 2 or Sw_FINITO_Link = 1)$tmodel_new(t)] = - [( sum{(i,v,r,h)$[r_cendiv(r,cendiv)$valgen(i,v,r,t)$gas(i)$heat_rate(i,v,r,t)], + ( sum{(i,v,r,h)$[r_cendiv(r,cendiv)$valgen(i,v,r,t)$gas(i)$heat_rate(i,v,r,t)], hours(h) * heat_rate(i,v,r,t) * GEN.l(i,v,r,h,t)} + sum{(v,r,h)$[valcap("dac_gas",v,r,t)$r_cendiv(r,cendiv)], hours(h) * dac_gas_cons_rate("dac_gas",v,t) * PRODUCE.l("DAC","dac_gas",v,r,h,t) }$Sw_DAC_Gas + sum{(p,i,v,r,h)$[r_cendiv(r,cendiv)$valcap(i,v,r,t)$smr(i)], hours(h) * smr_methane_rate * PRODUCE.l(p,i,v,r,h,t) }$Sw_H2 - ) / 1e9 ]; + ) / 1e9 ; -repgasquant_irt(i,r,t)$[tmodel_new(t)] = - [( sum{(v,h)$[valgen(i,v,r,t)$gas(i)$heat_rate(i,v,r,t)], +repgasquant_irt(i,r,t)$tmodel_new(t) = + ( sum{(v,h)$[valgen(i,v,r,t)$gas(i)$heat_rate(i,v,r,t)], hours(h) * heat_rate(i,v,r,t) * GEN.l(i,v,r,h,t) } + sum{(v,h)$[valcap("dac_gas",v,r,t)], hours(h) * dac_gas_cons_rate("dac_gas",v,t) * PRODUCE.l("DAC","dac_gas",v,r,h,t) }$Sw_DAC_Gas + sum{(p,v,h)$[valcap(i,v,r,t)$smr(i)], hours(h) * smr_methane_rate * PRODUCE.l(p,i,v,r,h,t) }$Sw_H2 - ) / 1e9 ]; + ) / 1e9 ; -repgasquant_nat(t)$[tmodel_new(t)] = - [sum{cendiv, repgasquant(cendiv,t) } ]; +repgasquant_nat(t)$tmodel_new(t) = sum{cendiv, repgasquant(cendiv,t) } ; *for reported gasprice (not that used to compute system costs) *scale back to $ / mmbtu -repgasprice(cendiv,t)$[(Sw_GasCurve = 0)$tmodel_new(t)$repgasquant(cendiv,t)$tfuel(t)]= - [smax{gb$[sum{h, GASUSED.l(cendiv,gb,h,t) }], gasprice(cendiv,gb,t) } / gas_scale ]; +repgasprice(cendiv,t)$[(Sw_GasCurve = 0)$tmodel_new(t)$repgasquant(cendiv,t)$tfuel(t)] = + smax{gb$[sum{h, GASUSED.l(cendiv,gb,h,t) }], gasprice(cendiv,gb,t) } / gas_scale ; repgasprice(cendiv,t)$[(Sw_GasCurve = 2)$tmodel_new(t)$repgasquant(cendiv,t)$tfuel(t)] = - [sum{(i,v,r,h)$[r_cendiv(r,cendiv)$valgen(i,v,r,t)$gas(i)$heat_rate(i,v,r,t)], + sum{(i,v,r,h)$[r_cendiv(r,cendiv)$valgen(i,v,r,t)$gas(i)$heat_rate(i,v,r,t)], hours(h)*heat_rate(i,v,r,t)*fuel_price(i,r,t)*GEN.l(i,v,r,h,t) - } / (repgasquant(cendiv,t) * 1e9) ]; + } / (repgasquant(cendiv,t) * 1e9) ; * gas price by timeslice when linked with FINITO (see similar calculation in finito_report.gms) [$2004/MMBtu] @@ -579,38 +574,40 @@ repgasprice_r(r,t)$[(Sw_GasCurve = 1)$tmodel_new(t)$tfuel(t)] = ; repgasprice(cendiv,t)$[((Sw_GasCurve = 1) or (Sw_FINITO_Link = 1))$tmodel_new(t)$repgasquant(cendiv,t)] = - [sum{(i,r)$r_cendiv(r,cendiv), repgasprice_r(r,t) * repgasquant_irt(i,r,t) } / repgasquant(cendiv,t) ] ; + sum{(i,r)$r_cendiv(r,cendiv), repgasprice_r(r,t) * repgasquant_irt(i,r,t) } / repgasquant(cendiv,t) ; repgasprice_nat(t)$[tmodel_new(t)$sum{cendiv, repgasquant(cendiv,t) }] = - [sum{cendiv, repgasprice(cendiv,t) * repgasquant(cendiv,t) } - / sum{cendiv, repgasquant(cendiv,t) } ]; + sum{cendiv, repgasprice(cendiv,t) * repgasquant(cendiv,t) } + / sum{cendiv, repgasquant(cendiv,t) } ; *======================================== * NATURAL GAS FUEL COSTS *======================================== gasshare_ba(r,cendiv,t)$[r_cendiv(r,cendiv)$tmodel_new(t)$repgasquant(cendiv,t)] = - [sum{i$[valgen_irt(i,r,t)$gas(i)],repgasquant_irt(i,r,t) / repgasquant(cendiv,t) } ]; + sum{i$[valgen_irt(i,r,t)$gas(i)],repgasquant_irt(i,r,t) / repgasquant(cendiv,t) } ; gasshare_techba(i,r,cendiv,t)$[r_cendiv(r,cendiv)$tmodel_new(t)$repgasquant(cendiv,t)$gas(i)] = - [repgasquant_irt(i,r,t) / repgasquant(cendiv,t) ]; + repgasquant_irt(i,r,t) / repgasquant(cendiv,t) ; gasshare_cendiv(cendiv,t)$[sum{cendiv2,repgasquant(cendiv2,t)}] = - [repgasquant(cendiv,t) / sum{cendiv2,repgasquant(cendiv2,t)} ]; + repgasquant(cendiv,t) / sum{cendiv2,repgasquant(cendiv2,t)} ; * cost of natural gas - standalone ReEDS gascost_cendiv(cendiv,t)$[tmodel_new(t)$tfuel(t)] = *cost of natural gas for Sw_GasCurve = 2 (static natural gas prices) - + [ sum{(i,v,r,h)$[r_cendiv(r,cendiv)$valgen(i,v,r,t)$gas(i)$heat_rate(i,v,r,t) - $[not bio(i)]$[not cofire(i)]$[Sw_GasCurve = 2]], - hours(h) * heat_rate(i,v,r,t) * fuel_price(i,r,t) * GEN.l(i,v,r,h,t) } + + sum{(i,v,r,h)$[r_cendiv(r,cendiv)$valgen(i,v,r,t)$gas(i)$heat_rate(i,v,r,t) + $[not bio(i)]$[not cofire(i)]$[Sw_GasCurve = 2]], + hours(h) * heat_rate(i,v,r,t) * fuel_price(i,r,t) * GEN.l(i,v,r,h,t) } *cost of natural gas for Sw_GasCurve = 0 (census division supply curves natural gas prices) - + sum{gb, sum{h,hours(h) * GASUSED.l(cendiv,gb,h,t) } * gasprice(cendiv,gb,t) }$[Sw_GasCurve = 0] + + sum{gb, sum{h,hours(h) * GASUSED.l(cendiv,gb,h,t) } * gasprice(cendiv,gb,t) + }$[Sw_GasCurve = 0] *cost of natural gas for Sw_GasCurve = 3 (national supply curve for natural gas prices with census division multipliers) - + sum{(h,gb), hours(h) * GASUSED.l(cendiv,gb,h,t) - * gasadder_cd(cendiv,t,h) + gasprice_nat_bin(gb,t) }$[Sw_GasCurve = 3] + + sum{(h,gb), hours(h) * GASUSED.l(cendiv,gb,h,t) + * gasadder_cd(cendiv,t,h) + gasprice_nat_bin(gb,t) + }$[Sw_GasCurve = 3] *cost of natural gas for Sw_GasCurve = 1 (national and census division supply curves for natural gas prices) *first - anticipated costs of gas consumption given last year's amount + (sum{(i,v,r,h)$[valgen(i,v,r,t)$gas(i)], @@ -623,8 +620,7 @@ gascost_cendiv(cendiv,t)$[tmodel_new(t)$tfuel(t)] = + sum{(fuelbin), gasbinp_national(fuelbin,t) * VGASBINQ_NATIONAL.l(fuelbin,t) } * gasshare_cendiv(cendiv,t) - )$[Sw_GasCurve = 1] - ]; + )$[Sw_GasCurve = 1]; * cost of natural gas - linked with FINITO ('not tfuel' indicates years using FINITO supply curves) gascost_cendiv(cendiv,t)$[tmodel_new(t)$(not tfuel(t))] = @@ -641,7 +637,7 @@ gascost_cendiv(cendiv,t)$[tmodel_new(t)$(not tfuel(t))] = bioshare_techba(i,r,t)$[(cofire(i) or bio(i))$tmodel_new(t)$tfuel(t)] = * biofuel-based generation of tech i in the BA (biopower + cofire) - [ (( sum{(v,h)$[valgen(i,v,r,t)$bio(i)], hours(h) * heat_rate(i,v,r,t) * GEN.l(i,v,r,h,t) } + (( sum{(v,h)$[valgen(i,v,r,t)$bio(i)], hours(h) * heat_rate(i,v,r,t) * GEN.l(i,v,r,h,t) } + sum{(v,h)$[cofire(i)$valgen(i,v,r,t)], bio_cofire_perc * hours(h) * heat_rate(i,v,r,t) * GEN.l(i,v,r,h,t) } ) / * biofuel-based generation of all techs in the BA (biopower + cofire) @@ -651,7 +647,6 @@ bioshare_techba(i,r,t)$[(cofire(i) or bio(i))$tmodel_new(t)$tfuel(t)] = )$[ sum{(ii,v,h)$[valgen(ii,v,r,t)$bio(ii)], hours(h) * heat_rate(ii,v,r,t) * GEN.l(ii,v,r,h,t) } + sum{(ii,v,h)$[cofire(ii)$valgen(ii,v,r,t)], bio_cofire_perc * hours(h) * heat_rate(ii,v,r,t) * GEN.l(ii,v,r,h,t) } ] - ] ; *========================= diff --git a/runreeds.py b/runreeds.py index fa7fea750..0b07da7f2 100644 --- a/runreeds.py +++ b/runreeds.py @@ -13,8 +13,6 @@ import csv import importlib import numpy as np -import gdxpds -from gdxpds.gdx import GdxFile,GdxSymbol,GamsDataType import pandas as pd import subprocess import re @@ -1358,7 +1356,6 @@ def write_batch_script( OPATH.writelines("module load conda \n") OPATH.writelines("module load gams \n") - OPATH.writelines("conda deactivate \n") OPATH.writelines("conda activate reeds2 \n") OPATH.writelines('export R_LIBS_USER="$HOME/rlib" \n\n\n') @@ -1438,8 +1435,7 @@ def write_batch_script( ################################# # -- OUTPUT PROCESSING -- # ################################# - - ## create reporting files + #create reporting files big_comment('Output processing', OPATH) if not LINUXORMAC: OPATH.writelines("setlocal enabledelayedexpansion\n") @@ -1513,7 +1509,7 @@ def write_batch_script( + f" --casepath {casedir} \n\n" ) - ## Run the retail rate module + ### Run the retail rate module OPATH.writelines( "python" + f" {os.path.join(reeds_path,'postprocessing','retail_rate_module','retail_rate_calculations.py')}" @@ -1527,7 +1523,7 @@ def write_batch_script( f"{casedir}\n\n" ) - ## Make script to unload all data to .gdx file + ### Make script to unload all data to .gdx file command = ( f"gams {Path('reeds','core','terminus','dump_alldata.gms')}" + ' o='+os.path.join('lstfiles','dump_alldata_{}_{}.lst'.format(BatchName,case)) @@ -1816,7 +1812,7 @@ def launch_single_case_run( shellscript = subprocess.Popen( [os.path.join(casedir, 'call_' + batch_case + ext)], shell=True) # Wait for it to finish before killing the thread - #shellscript.wait() + shellscript.wait() else: if int(caseSwitches['keep_run_terminal']) == 1: terminal_keep_flag = ' /k ' @@ -1944,7 +1940,7 @@ def main( help="Check inputs but don't start runs") args = parser.parse_args() - + main( BatchName=args.BatchName, cases_suffix=args.cases_suffix, single=args.single, simult_runs=args.simult_runs, forcelocal=args.forcelocal, skip_checks=args.skip_checks, From 93bd58bc4f2df90e6b22ce350a4ca29c144f99dc Mon Sep 17 00:00:00 2001 From: mmowers Date: Mon, 29 Jun 2026 15:09:08 -0400 Subject: [PATCH 15/78] Remove old switch from cases_finito.csv --- cases_finito.csv | 1 - 1 file changed, 1 deletion(-) diff --git a/cases_finito.csv b/cases_finito.csv index ca5459a34..6e6c732e7 100644 --- a/cases_finito.csv +++ b/cases_finito.csv @@ -34,7 +34,6 @@ GSw_DAC,1,,,,,,,,,,,,,,,,,,,,, GSw_BECCS,1,,,,,,,,,,,,,,,,,,,,, GSw_TCPhaseout,1,,,,,,,,,,,,,,,,,,,,, GSw_CO2_Detail,1,,,,,,,,,,,,,,,,,,,,, -GSw_CO2_LimitStorageSites,1,,,,,,,,,,,,,,,,,,,,, numhintage,4,,,,,,,,,,,,,,,,,,,,, GSw_CarbTax,0,,,,,,,,,,,1,1,,,,,,,,, GSw_CarbTaxOption,default,,,,,,,,,,,t200_2050,t200_2050,,,,,,,,, From d687d4764bc3d33eef126a165eaf199c31193be4 Mon Sep 17 00:00:00 2001 From: mmowers Date: Tue, 30 Jun 2026 16:50:35 -0400 Subject: [PATCH 16/78] Remove call to removed finito file --- reeds/core/solve/3_solve_oneyear.gms | 4 ---- 1 file changed, 4 deletions(-) diff --git a/reeds/core/solve/3_solve_oneyear.gms b/reeds/core/solve/3_solve_oneyear.gms index d059ff3a0..1ea02eba9 100644 --- a/reeds/core/solve/3_solve_oneyear.gms +++ b/reeds/core/solve/3_solve_oneyear.gms @@ -282,10 +282,6 @@ $endif.mga *** Adjust some parameters based on the solution for this solve year $include reeds%ds%core%ds%solve%ds%4_post_solve_adjustments.gms -$ifthene.linked_finito_shrink %GSw_FINITO_Link% == 1 -* Industrial variable fix -$include finito/model/finito_post_solve_adjustments.gms -$endif.linked_finito_shrink *** Fix decision variables to their optimized levels for this solve year tfix("%cur_year%") = yes ; From 5aa754031ec6eec73c4e92b8c550acf26f749c8d Mon Sep 17 00:00:00 2001 From: mmowers Date: Thu, 2 Jul 2026 12:31:01 -0400 Subject: [PATCH 17/78] Revert to main for Canada switch behavior --- reeds/core/solve/2_temporal_params.gms | 3 +++ 1 file changed, 3 insertions(+) diff --git a/reeds/core/solve/2_temporal_params.gms b/reeds/core/solve/2_temporal_params.gms index 6e0b2a814..0a5c76acf 100644 --- a/reeds/core/solve/2_temporal_params.gms +++ b/reeds/core/solve/2_temporal_params.gms @@ -302,6 +302,9 @@ can_exports_h(r,h,t)$[hours(h)] = can_exports(r,t) * can_exports_h_frac(h) / hou $endif.Canada +* zero Canada exports when Canada is not modeled +can_imports_szn(r,szn,t)$[Sw_Canada=0] = 0 ; +can_exports_h(r,h,t)$[Sw_Canada=0] = 0 ; $onempty parameter canmexload(r,allh) "load for canadian and mexican regions" From ea246a9ceb1b8f666d80e00a8bd66b9367a0804a Mon Sep 17 00:00:00 2001 From: mmowers Date: Thu, 2 Jul 2026 12:33:21 -0400 Subject: [PATCH 18/78] Revert to main for rounding of parameters --- reeds/core/solve/2_temporal_params.gms | 1 + 1 file changed, 1 insertion(+) diff --git a/reeds/core/solve/2_temporal_params.gms b/reeds/core/solve/2_temporal_params.gms index 0a5c76acf..b61565bda 100644 --- a/reeds/core/solve/2_temporal_params.gms +++ b/reeds/core/solve/2_temporal_params.gms @@ -904,6 +904,7 @@ $endif.linked_finito_temporal_params *============================================= avail(i,r,h)$avail(i,r,h) = round(avail(i,r,h),3) ; +can_imports_szn(r,szn,t)$can_imports_szn(r,szn,t) = round(can_imports_szn(r,szn,t),3) ; can_exports_h(r,h,t)$can_exports_h(r,h,t) = round(can_exports_h(r,h,t),3) ; frac_h_ccseason_weights(h,ccseason)$frac_h_ccseason_weights(h,ccseason) = round(frac_h_ccseason_weights(h,ccseason),3) ; gasadder_cd(cendiv,t,h)$gasadder_cd(cendiv,t,h) = round(gasadder_cd(cendiv,t,h),3) ; From 88ff50ddbb7a393ae77cc60137bbb885ce911608 Mon Sep 17 00:00:00 2001 From: mmowers Date: Thu, 2 Jul 2026 17:11:09 -0400 Subject: [PATCH 19/78] Add FINITO case to cases_test.csv --- cases.csv | 2 +- cases_test.csv | 130 +++++++++++++++++++++++++------------------------ 2 files changed, 67 insertions(+), 65 deletions(-) diff --git a/cases.csv b/cases.csv index ece24ca2d..48ead8dfd 100644 --- a/cases.csv +++ b/cases.csv @@ -36,7 +36,7 @@ evmcscen,EVMC scenario for load and supply curve and flexibility,Baseline,Baseli GSw_FINITO_Link,Switch to enable linkage with FINITO,0; 1,0, finito_dir,Directory with cloned FINITO repository,N/A,/projects/finito/molmezt/FINITO, finito_cases_file,"extension of the FINITO cases file including all scenarios to be run, e.g., 'linked' for cases_linked.csv",N/A,linked, -finito_case,Case name for FINITO in {finito_dir}/cases_linked.csv; use 'same' if the scenario name in cases_linked.csv matches the scenario name in the cases file,N/A,BAU, +finito_case,Case name for FINITO in {finito_dir}/cases_linked.csv; use 'same' if the scenario name in cases_linked.csv matches the scenario name in the cases file,N/A,BAUfast, first_year_finito,First year when the FINITO module is enabled,N/A,2018, geodiscov,Annual discover rates for new geothermal sites,BAU; TI,BAU, geohydrosupplycurve,Geohydro Supply Curve,ATB_2023,ATB_2023, diff --git a/cases_test.csv b/cases_test.csv index 9c2be24af..f526f5498 100644 --- a/cases_test.csv +++ b/cases_test.csv @@ -1,64 +1,66 @@ -,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,Pacific_DR,Pacific_MGA,Pacific_LoadSite95,MARICTNYNJPAOH_Offshore,R2P -ignore,1,0,,,,,,,,,,,,,,,,,,,,,,,,,,,, -GSw_Region,cendiv/Pacific,,country/USA,country/USA,country/USA,,st/ID.WY.NE.IA.IL,st/MA,,,,,,,st/WY,interconnect/western,transreg/PJM,st/NY.VT,st/OR,st/NE.NY.PA,st/NE.NY.PA,st/ID.WY.NE.IA.IL,st/KS,country/USA,country/USA,,,,st/MA.RI.CT.NY.NJ.PA.OH, -endyear,2032,,2050,2050,2050,2029,2060,2026,,,,,,,,,,,2035,2030,2030,2060,2035,2050,2050,,,,, -yearset,,,,,,,2010..2060..10,,,,,,,,,,,,,2010..2050..5,2010..2050..5,2010..2060..10,,,2010_2025_2050,,,,, -GSw_ZoneSet,,,,,,,z54,z3109,,,,,,,z3109,z3109,z3109,PJMcounty,,,,z54,,z54,z48,,,,, -GSw_GasCurve,2,,1,1,,,,,,,,,,,,,,,,,,,,1,1,,,,, -GSw_Geothermal,,,,2,,,,,,,,,,,,,,,,,,,0,,0,,,,, -GSw_GrowthPenalties,,,,1,,,,,,,,,,,,,,,,,,,,,,,,,, -GSw_Upstream,,,,1,,,,,,,,,,,,,,,,,,,,,,,,,, -GSw_TransHurdleRate,,,,1,,,,,,,,,,,,,,,,,,,,,,,,,, -distpvscen,,,,,stscen2023_mid_case_95_by_2035,,,,,,,,,,,,,,,,,,,,,,,,, -GSw_AnnualCap,,,,,2,,1,,,,,,,,,,,,,,,1,,,,,,,, -GSw_AnnualCapScen,,,,,start2024_90pct2035_100pct2045,,start2027_95pct2035,,,,,,,,,,,,,,,start2027_95pct2035,,,,,,,, -GSw_LoadProfiles,,,,,EER2025_100by2050,EER2025_IRAlow,EER2025_IRAlow,EER2025_IRAlow,,,,,historic,,,,,,,,,EER2025_100by2050,,,,historic,,,, -GSw_NG_CRF_penalty,,,,,ramp_2045,,ramp_2023_2035,,,,,,,,,,,,,,,ramp_2023_2035,,,,,,,, -GSw_PRM_NetImportLimit,,,,,0,,,,,,,,,,,,,,,,,,,,,,,,, -GSw_RetirePenalty,,,,,0,,,,,,,,,,,,,,,,,,,,,,,,, -GSw_FakeData,,,,,,1,1,1,,,,,,,,,,,,,,,,,,,,,, -GSw_PRM_CapCredit,,,,,,,,1,1,,,,,,,,1,,,,,,,,,,,,, -GSw_PRM_scenario,,,,,,,,,static,,,,,,,,static,,,,,,,,,,,,, -GSw_HourlyType,,,,,,,,,,wek,year,,,,,,,,,,,,,,,,,,, -GSw_InterDayLinkage,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,, -GSw_HourlyWeatherYears,,,,,,,2012_2013,,,,,,2020,2007_2008_2009_2010_2011_2012_2013_2016_2017_2018_2019_2020_2021_2022_2023,,,,,,,,2012_2013,,,,2018,,,, -GSw_HourlyClusterMapMethod,,,,,,,,,,,,,,bestfirst,,,,,,,,,,,,,,,, -GSw_WaterCapacity,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,, -GSw_WaterMain,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,, -GSw_WaterUse,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,, -resource_adequacy_years,,,,,,,2011_2012_2013_2021_2022_2023,,,,,,,,,,,,,,,2011_2012_2013_2021_2022_2023,,,,,,,, -GSw_HourlyClusterAlgorithm,,,,,,,,,,,,,,,,,,,,user,user,,,,,,,,, -MCS_runs,,,,,,,,,,,,,,,,,,,,2,2,,,,,,,,, -MCS_dist_groups,,,,,,,,,,,,,,,,,,,,tech.hydro.nuclear.gas.coal.load_country,upv_tri.nuclear_tri.ng_fuel_price_tri.load_country_unif,,,,,,,,, -MCS_lhs,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,, -GSw_PRM_StressIterateMax,,,,,,,,0,,,,,,,,0,0,,,1,1,,,,,,,,, -GSw_ReducedResource,,,,,,,1,,,,,,,,,,,,,,,1,,,,,,,, -GSw_SitingUPV,,,,,,limited,limited,limited,,,,,,,,,,,,,,limited,,,,,,,, -GSw_SitingWindOfs,,,,,,limited,limited,limited,,,,,,,,,,,,,,open,,,,,,,, -GSw_SitingWindOns,,,,,,limited,limited,limited,,,,,,,,,,,,,,limited,,,,,,,, -GSw_TransScen,,,,,,,NTP_MT,,,,,,,,,,,,,,,NTP_MT,,,,,,,, -GSw_CO2_Detail,,,,,,,1,,,,,,,,,,,,,,,1,,,,,,,, -GSw_DAC,,,,,,,1,,,,,,,,,,,,,,,1,,,,,,,, -GSw_NoFossilOffsetCDR,,,,,,,1,,,,,,,,,,,,,,,1,,,,,,,, -GSw_Biopower,,,,,,,,,,,,,,,,,,,,,,,0,,0,,,,, -GSw_HourlyChunkLengthRep,,,,,,,,,,,,,,,,,,,,,,,6,4,4,,,,, -GSw_HourlyChunkLengthStress,,,,,,,,,,,,,,,,,,,,,,,6,4,4,,,,, -GSw_LfillGas,,,,,,,,,,,,,,,,,,,,,,,0,,0,,,,, -GSw_Nuclear,,,,,,,,,,,,,,,,,,,,,,,0,,0,,,,, -GSw_OpRes,,,,,,,,,,,,,,,,,,,,,,,0,,0,,,,, -GSw_StartCost,,,,,,,,,,,,,,,,,,,,,,,0,0,0,,,,, -GSw_H2,,,,,,,,,,,,,,,,,,,,,,,,0,0,,,,, -GSw_H2_PTC,,,,,,,,,,,,,,,,,,,,,,,,0,0,,,,, -GSw_H2Combustion,,,,,,,,,,,,,,,,,,,,,,,,,0,,,,, -GSw_DRShed,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,, -GSw_MGA_CostDelta,,,,,,,,,,,,,,,,,,,,,,,,,,,0.01,,, -GSw_LoadSiteCF,,,,,,,,,,,,,,,,,,,,,,,,,,,,0.95,, -GSw_OffshoreZones,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1, -GSw_OffshoreBackbone,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1, -GSw_OffshoreBackflow,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1, -pras_agg_ogs_lfillgas,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1 -pras_existing_unit_size,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,0 -pras_scheduled_outage,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,0 -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,Pacific_DR,Pacific_MGA,Pacific_LoadSite95,MARICTNYNJPAOH_Offshore,R2P,FINITO +ignore,1,0,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +GSw_Region,cendiv/Pacific,,country/USA,country/USA,country/USA,,st/ID.WY.NE.IA.IL,st/MA,,,,,,,st/WY,interconnect/western,transreg/PJM,st/NY.VT,st/OR,st/NE.NY.PA,st/NE.NY.PA,st/ID.WY.NE.IA.IL,st/KS,country/USA,country/USA,,,,st/MA.RI.CT.NY.NJ.PA.OH,, +endyear,2032,,2050,2050,2050,2029,2060,2026,,,,,,,,,,,2035,2030,2030,2060,2035,2050,2050,,,,,, +yearset,,,,,,,2010..2060..10,,,,,,,,,,,,,2010..2050..5,2010..2050..5,2010..2060..10,,,2010_2025_2050,,,,,,2010..2050..5 +GSw_ZoneSet,,,,,,,z54,z3109,,,,,,,z3109,z3109,z3109,PJMcounty,,,,z54,,z54,z48,,,,,,z54 +GSw_GasCurve,2,,1,1,,,,,,,,,,,,,,,,,,,,1,1,,,,,, +GSw_Geothermal,,,,2,,,,,,,,,,,,,,,,,,,0,,0,,,,,, +GSw_GrowthPenalties,,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,, +GSw_Upstream,,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,, +GSw_TransHurdleRate,,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,, +distpvscen,,,,,stscen2023_mid_case_95_by_2035,,,,,,,,,,,,,,,,,,,,,,,,,, +GSw_AnnualCap,,,,,2,,1,,,,,,,,,,,,,,,1,,,,,,,,, +GSw_AnnualCapScen,,,,,start2024_90pct2035_100pct2045,,start2027_95pct2035,,,,,,,,,,,,,,,start2027_95pct2035,,,,,,,,, +GSw_LoadProfiles,,,,,EER2025_100by2050,EER2025_IRAlow,EER2025_IRAlow,EER2025_IRAlow,,,,,historic,,,,,,,,,EER2025_100by2050,,,,historic,,,,, +GSw_NG_CRF_penalty,,,,,ramp_2045,,ramp_2023_2035,,,,,,,,,,,,,,,ramp_2023_2035,,,,,,,,, +GSw_PRM_NetImportLimit,,,,,0,,,,,,,,,,,,,,,,,,,,,,,,,, +GSw_RetirePenalty,,,,,0,,,,,,,,,,,,,,,,,,,,,,,,,, +GSw_FakeData,,,,,,1,1,1,,,,,,,,,,,,,,,,,,,,,,, +GSw_PRM_CapCredit,,,,,,,,1,1,,,,,,,,1,,,,,,,,,,,,,, +GSw_PRM_scenario,,,,,,,,,static,,,,,,,,static,,,,,,,,,,,,,, +GSw_HourlyType,,,,,,,,,,wek,year,,,,,,,,,,,,,,,,,,,, +GSw_InterDayLinkage,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,,, +GSw_HourlyWeatherYears,,,,,,,2012_2013,,,,,,2020,2007_2008_2009_2010_2011_2012_2013_2016_2017_2018_2019_2020_2021_2022_2023,,,,,,,,2012_2013,,,,2018,,,,, +GSw_HourlyClusterMapMethod,,,,,,,,,,,,,,bestfirst,,,,,,,,,,,,,,,,, +GSw_WaterCapacity,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,, +GSw_WaterMain,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,, +GSw_WaterUse,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,, +resource_adequacy_years,,,,,,,2011_2012_2013_2021_2022_2023,,,,,,,,,,,,,,,2011_2012_2013_2021_2022_2023,,,,,,,,, +GSw_HourlyClusterAlgorithm,,,,,,,,,,,,,,,,,,,,user,user,,,,,,,,,, +MCS_runs,,,,,,,,,,,,,,,,,,,,2,2,,,,,,,,,, +MCS_dist_groups,,,,,,,,,,,,,,,,,,,,tech.hydro.nuclear.gas.coal.load_country,upv_tri.nuclear_tri.ng_fuel_price_tri.load_country_unif,,,,,,,,,, +MCS_lhs,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,, +GSw_PRM_StressIterateMax,,,,,,,,0,,,,,,,,0,0,,,1,1,,,,,,,,,,2 +GSw_ReducedResource,,,,,,,1,,,,,,,,,,,,,,,1,,,,,,,,, +GSw_SitingUPV,,,,,,limited,limited,limited,,,,,,,,,,,,,,limited,,,,,,,,, +GSw_SitingWindOfs,,,,,,limited,limited,limited,,,,,,,,,,,,,,open,,,,,,,,, +GSw_SitingWindOns,,,,,,limited,limited,limited,,,,,,,,,,,,,,limited,,,,,,,,, +GSw_TransScen,,,,,,,NTP_MT,,,,,,,,,,,,,,,NTP_MT,,,,,,,,, +GSw_CO2_Detail,,,,,,,1,,,,,,,,,,,,,,,1,,,,,,,,, +GSw_DAC,,,,,,,1,,,,,,,,,,,,,,,1,,,,,,,,, +GSw_NoFossilOffsetCDR,,,,,,,1,,,,,,,,,,,,,,,1,,,,,,,,, +GSw_Biopower,,,,,,,,,,,,,,,,,,,,,,,0,,0,,,,,, +GSw_HourlyChunkLengthRep,,,,,,,,,,,,,,,,,,,,,,,6,4,4,,,,,, +GSw_HourlyChunkLengthStress,,,,,,,,,,,,,,,,,,,,,,,6,4,4,,,,,, +GSw_LfillGas,,,,,,,,,,,,,,,,,,,,,,,0,,0,,,,,, +GSw_Nuclear,,,,,,,,,,,,,,,,,,,,,,,0,,0,,,,,, +GSw_OpRes,,,,,,,,,,,,,,,,,,,,,,,0,,0,,,,,, +GSw_StartCost,,,,,,,,,,,,,,,,,,,,,,,0,0,0,,,,,, +GSw_H2,,,,,,,,,,,,,,,,,,,,,,,,0,0,,,,,, +GSw_H2_PTC,,,,,,,,,,,,,,,,,,,,,,,,0,0,,,,,, +GSw_H2Combustion,,,,,,,,,,,,,,,,,,,,,,,,,0,,,,,, +GSw_DRShed,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,, +GSw_MGA_CostDelta,,,,,,,,,,,,,,,,,,,,,,,,,,,0.01,,,, +GSw_LoadSiteCF,,,,,,,,,,,,,,,,,,,,,,,,,,,,0.95,,, +GSw_OffshoreZones,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,, +GSw_OffshoreBackbone,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,, +GSw_OffshoreBackflow,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,, +pras_agg_ogs_lfillgas,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1, +pras_existing_unit_size,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,0, +pras_scheduled_outage,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,0, +pras_unitsize_source,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,r2x, +pras_vre_combine,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1, +pras_samples,,,,,,10,10,10,,,,,,,,,,,,,,,,10,10,,,,,, +GSw_FINITO_Link,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1 +finito_dir,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,/projects/futuregrid/mmowers/FINITO From 6310db765f1ed1c698d03c131e4435081f049b5f Mon Sep 17 00:00:00 2001 From: mmowers Date: Thu, 2 Jul 2026 17:57:31 -0400 Subject: [PATCH 20/78] Update FINITO case --- cases_test.csv | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cases_test.csv b/cases_test.csv index f526f5498..107e3a27b 100644 --- a/cases_test.csv +++ b/cases_test.csv @@ -1,7 +1,7 @@ ,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,Pacific_DR,Pacific_MGA,Pacific_LoadSite95,MARICTNYNJPAOH_Offshore,R2P,FINITO ignore,1,0,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -GSw_Region,cendiv/Pacific,,country/USA,country/USA,country/USA,,st/ID.WY.NE.IA.IL,st/MA,,,,,,,st/WY,interconnect/western,transreg/PJM,st/NY.VT,st/OR,st/NE.NY.PA,st/NE.NY.PA,st/ID.WY.NE.IA.IL,st/KS,country/USA,country/USA,,,,st/MA.RI.CT.NY.NJ.PA.OH,, -endyear,2032,,2050,2050,2050,2029,2060,2026,,,,,,,,,,,2035,2030,2030,2060,2035,2050,2050,,,,,, +GSw_Region,cendiv/Pacific,,country/USA,country/USA,country/USA,,st/ID.WY.NE.IA.IL,st/MA,,,,,,,st/WY,interconnect/western,transreg/PJM,st/NY.VT,st/OR,st/NE.NY.PA,st/NE.NY.PA,st/ID.WY.NE.IA.IL,st/KS,country/USA,country/USA,,,,st/MA.RI.CT.NY.NJ.PA.OH,,country/USA +endyear,2032,,2050,2050,2050,2029,2060,2026,,,,,,,,,,,2035,2030,2030,2060,2035,2050,2050,,,,,,2050 yearset,,,,,,,2010..2060..10,,,,,,,,,,,,,2010..2050..5,2010..2050..5,2010..2060..10,,,2010_2025_2050,,,,,,2010..2050..5 GSw_ZoneSet,,,,,,,z54,z3109,,,,,,,z3109,z3109,z3109,PJMcounty,,,,z54,,z54,z48,,,,,,z54 GSw_GasCurve,2,,1,1,,,,,,,,,,,,,,,,,,,,1,1,,,,,, From 02f14b8ed7869b9a0daf4d0da5c06932fd213af4 Mon Sep 17 00:00:00 2001 From: bsergi Date: Mon, 6 Jul 2026 16:02:37 -0400 Subject: [PATCH 21/78] clean up finito_dir entry --- cases.csv | 2 +- runreeds.py | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/cases.csv b/cases.csv index 48ead8dfd..8bdb5efc6 100644 --- a/cases.csv +++ b/cases.csv @@ -34,7 +34,7 @@ demandscen,Demand scenario (see inputs\load),N/A,AEO_2025_reference, 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, -finito_dir,Directory with cloned FINITO repository,N/A,/projects/finito/molmezt/FINITO, +finito_dir,Directory with cloned FINITO repository,N/A,'/path/to/FINITO/directory', finito_cases_file,"extension of the FINITO cases file including all scenarios to be run, e.g., 'linked' for cases_linked.csv",N/A,linked, finito_case,Case name for FINITO in {finito_dir}/cases_linked.csv; use 'same' if the scenario name in cases_linked.csv matches the scenario name in the cases file,N/A,BAUfast, first_year_finito,First year when the FINITO module is enabled,N/A,2018, diff --git a/runreeds.py b/runreeds.py index 0b07da7f2..640441483 100644 --- a/runreeds.py +++ b/runreeds.py @@ -780,7 +780,10 @@ def linked_cases(df_cases,case): """ # check for valid finito_dir if not os.path.isdir(df_cases[case]['finito_dir']): - raise ValueError(f"finito_dir = {df_cases[case]['finito_dir']} is not a valid path.") + raise ValueError( + f"finito_dir = {df_cases[case]['finito_dir']} is not a valid path. " + "Please ensure this path points to a cloned version of the FINITO repository. " + ) # define path to and read the FINITO check_inputs function finito_check_inputs_path = os.path.join(df_cases[case]['finito_dir'], 'input_processing', 'processing') From 58f122758e02f864b092a593b30d74b2a05dcdd5 Mon Sep 17 00:00:00 2001 From: mmowers Date: Mon, 13 Jul 2026 20:02:37 -0600 Subject: [PATCH 22/78] For now, use only rep period load (not stress period load) from FINITO for resource adequacy calcs --- reeds/core/solve/6_data_dump.gms | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/reeds/core/solve/6_data_dump.gms b/reeds/core/solve/6_data_dump.gms index cf534766c..6a0a58f70 100644 --- a/reeds/core/solve/6_data_dump.gms +++ b/reeds/core/solve/6_data_dump.gms @@ -302,7 +302,8 @@ ra_cap_loadsite(r,t)$[Sw_LoadSiteCF$val_loadsite(r)] = CAP_LOADSITE.l(r,t) ; * FINITO load $ifthene.linked_load Sw_FINITO_Link==1 - load_finito_rt(r,h,t) = USE_ELE_FINITO.l(r,h,t) ; +* limit to representative timeslices since prep_data.py maps these to rep-period timestamps + load_finito_rt(r,h,t)$h_rep(h) = USE_ELE_FINITO.l(r,h,t) ; $else.linked_load load_finito_rt(r,h,t) = 0 ; $endif.linked_load From 259f9f1cd4537ab6dbbb23636aaf82d471eef6f7 Mon Sep 17 00:00:00 2001 From: mmowers Date: Mon, 13 Jul 2026 20:04:40 -0600 Subject: [PATCH 23/78] Fix FINITO load inclusion in handoff to PRAS --- reeds/resource_adequacy/prep_data.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/reeds/resource_adequacy/prep_data.py b/reeds/resource_adequacy/prep_data.py index f351c1177..cbc99174f 100644 --- a/reeds/resource_adequacy/prep_data.py +++ b/reeds/resource_adequacy/prep_data.py @@ -175,9 +175,10 @@ def main(t, casedir, iteration=0): # convert timezone and fill any missing columns load_finito.index = load_finito.index.tz_convert(load_year.index.tz) load_finito = load_finito.reindex(columns=load_year.columns, fill_value=0) + load_finito = load_finito.fillna(0) # add to load - load_new = load_year + load_finito + load_year = load_year + load_finito resources = pd.read_csv(os.path.join(inputs_case, 'resources.csv')) recf = reeds.io.read_file(os.path.join(inputs_case, 'recf.h5'), parse_timestamps=True) From c82fbc341ddf877eeabe6947f8b6f546e2991cf7 Mon Sep 17 00:00:00 2001 From: mmowers Date: Mon, 13 Jul 2026 20:08:27 -0600 Subject: [PATCH 24/78] Remove completed TODO item --- reeds/resource_adequacy/prep_data.py | 1 - 1 file changed, 1 deletion(-) diff --git a/reeds/resource_adequacy/prep_data.py b/reeds/resource_adequacy/prep_data.py index cbc99174f..ac6b30f56 100644 --- a/reeds/resource_adequacy/prep_data.py +++ b/reeds/resource_adequacy/prep_data.py @@ -165,7 +165,6 @@ def main(t, casedir, iteration=0): # map from rep day to actual hour # since we don't have multi-year profiles for FINITO load # we assume they repeat across all weather years - # TODO: test that this works with multiple regions load_finito = pd.merge(load_finito, h_dt_szn.reset_index(), on='h', how='outer')[['timestamp', 'r', 'load_MW']] load_finito = load_finito.rename(columns={'timestamp':'datetime'}) # add model year back in From 27ca6a76ba1b761c2170191a30852f7cfc5bd86f Mon Sep 17 00:00:00 2001 From: mmowers Date: Tue, 14 Jul 2026 18:27:26 -0400 Subject: [PATCH 25/78] Add USA_fast case settings to FINITO case in cases_test.csv --- cases_test.csv | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/cases_test.csv b/cases_test.csv index 107e3a27b..21246cad2 100644 --- a/cases_test.csv +++ b/cases_test.csv @@ -31,7 +31,7 @@ GSw_HourlyClusterAlgorithm,,,,,,,,,,,,,,,,,,,,user,user,,,,,,,,,, MCS_runs,,,,,,,,,,,,,,,,,,,,2,2,,,,,,,,,, MCS_dist_groups,,,,,,,,,,,,,,,,,,,,tech.hydro.nuclear.gas.coal.load_country,upv_tri.nuclear_tri.ng_fuel_price_tri.load_country_unif,,,,,,,,,, MCS_lhs,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,, -GSw_PRM_StressIterateMax,,,,,,,,0,,,,,,,,0,0,,,1,1,,,,,,,,,,2 +GSw_PRM_StressIterateMax,,,,,,,,0,,,,,,,,0,0,,,1,1,,,,,,,,,, GSw_ReducedResource,,,,,,,1,,,,,,,,,,,,,,,1,,,,,,,,, GSw_SitingUPV,,,,,,limited,limited,limited,,,,,,,,,,,,,,limited,,,,,,,,, GSw_SitingWindOfs,,,,,,limited,limited,limited,,,,,,,,,,,,,,open,,,,,,,,, @@ -41,14 +41,14 @@ GSw_CO2_Detail,,,,,,,1,,,,,,,,,,,,,,,1,,,,,,,,, GSw_DAC,,,,,,,1,,,,,,,,,,,,,,,1,,,,,,,,, GSw_NoFossilOffsetCDR,,,,,,,1,,,,,,,,,,,,,,,1,,,,,,,,, GSw_Biopower,,,,,,,,,,,,,,,,,,,,,,,0,,0,,,,,, -GSw_HourlyChunkLengthRep,,,,,,,,,,,,,,,,,,,,,,,6,4,4,,,,,, -GSw_HourlyChunkLengthStress,,,,,,,,,,,,,,,,,,,,,,,6,4,4,,,,,, +GSw_HourlyChunkLengthRep,,,,,,,,,,,,,,,,,,,,,,,6,4,4,,,,,,4 +GSw_HourlyChunkLengthStress,,,,,,,,,,,,,,,,,,,,,,,6,4,4,,,,,,4 GSw_LfillGas,,,,,,,,,,,,,,,,,,,,,,,0,,0,,,,,, GSw_Nuclear,,,,,,,,,,,,,,,,,,,,,,,0,,0,,,,,, GSw_OpRes,,,,,,,,,,,,,,,,,,,,,,,0,,0,,,,,, -GSw_StartCost,,,,,,,,,,,,,,,,,,,,,,,0,0,0,,,,,, -GSw_H2,,,,,,,,,,,,,,,,,,,,,,,,0,0,,,,,, -GSw_H2_PTC,,,,,,,,,,,,,,,,,,,,,,,,0,0,,,,,, +GSw_StartCost,,,,,,,,,,,,,,,,,,,,,,,0,0,0,,,,,,0 +GSw_H2,,,,,,,,,,,,,,,,,,,,,,,,0,0,,,,,,0 +GSw_H2_PTC,,,,,,,,,,,,,,,,,,,,,,,,0,0,,,,,,0 GSw_H2Combustion,,,,,,,,,,,,,,,,,,,,,,,,,0,,,,,, GSw_DRShed,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,, GSw_MGA_CostDelta,,,,,,,,,,,,,,,,,,,,,,,,,,,0.01,,,, @@ -61,6 +61,6 @@ pras_existing_unit_size,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,0, pras_scheduled_outage,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,0, pras_unitsize_source,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,r2x, pras_vre_combine,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1, -pras_samples,,,,,,10,10,10,,,,,,,,,,,,,,,,10,10,,,,,, +pras_samples,,,,,,10,10,10,,,,,,,,,,,,,,,,10,10,,,,,,10 GSw_FINITO_Link,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1 finito_dir,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,/projects/futuregrid/mmowers/FINITO From 8f0f56450c1aa10d481c07d88b3ebc2ef0380a77 Mon Sep 17 00:00:00 2001 From: mmowers Date: Fri, 17 Jul 2026 00:21:29 -0400 Subject: [PATCH 26/78] Fix bug in finito load adjustments --- reeds/input_processing/hourly_load.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/reeds/input_processing/hourly_load.py b/reeds/input_processing/hourly_load.py index 8d44e8ca3..29e1f90df 100644 --- a/reeds/input_processing/hourly_load.py +++ b/reeds/input_processing/hourly_load.py @@ -632,8 +632,8 @@ def remove_finito_load( #TODO: add this # Validation check: confirm that total load - FINITO load > 0 - - return load_hourly + + return result #%% =========================================================================== ### --- MAIN FUNCTION --- From 33717fdd570094cc66d9229eeb44740a3c289f09 Mon Sep 17 00:00:00 2001 From: mmowers Date: Fri, 17 Jul 2026 00:25:38 -0400 Subject: [PATCH 27/78] Convert finito reference load to busbar before subtracting --- reeds/input_processing/hourly_load.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/reeds/input_processing/hourly_load.py b/reeds/input_processing/hourly_load.py index 29e1f90df..cc1e74124 100644 --- a/reeds/input_processing/hourly_load.py +++ b/reeds/input_processing/hourly_load.py @@ -620,13 +620,19 @@ def get_hourly_finito_load( def remove_finito_load( load_hourly: pd.DataFrame, - inputs_case: str, + inputs_case: str, + distloss: float, ) -> pd.DataFrame: # get FINITO reference load load_hourly_finito = get_hourly_finito_load(inputs_case) - # subtract FINITO reference load from ReEDS load data, + # FINITO reference load is end-use (facility consumption), so convert to + # busbar for consistency with the busbar load it is subtracted from + # (and with the same gross-up applied to USE_ELE_FINITO in eq_loadcon) + load_hourly_finito = load_hourly_finito / (1 - distloss) + + # subtract FINITO reference load from ReEDS load data, # aligning by model year (index) and region (columns) result = load_hourly - load_hourly_finito @@ -741,7 +747,9 @@ def main(reeds_path, inputs_case): # note that this step occurs after peakload calculation so that the latter # includes a baseline estimate of industrial load captured by FINITO if int(sw.GSw_FINITO_Link): - regional_load_hourly = remove_finito_load(regional_load_hourly, inputs_case) + regional_load_hourly = remove_finito_load( + regional_load_hourly, inputs_case, scalars['distloss'] + ) ############################################# # -- DR Shed Load Modifications -- # From c04037e04c0bb856f04c3d8a6cd4a516b1bdfcd7 Mon Sep 17 00:00:00 2001 From: mmowers Date: Fri, 17 Jul 2026 00:27:00 -0400 Subject: [PATCH 28/78] Simplify comment --- reeds/input_processing/hourly_load.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/reeds/input_processing/hourly_load.py b/reeds/input_processing/hourly_load.py index cc1e74124..2ef9c5e77 100644 --- a/reeds/input_processing/hourly_load.py +++ b/reeds/input_processing/hourly_load.py @@ -627,9 +627,7 @@ def remove_finito_load( # get FINITO reference load load_hourly_finito = get_hourly_finito_load(inputs_case) - # FINITO reference load is end-use (facility consumption), so convert to - # busbar for consistency with the busbar load it is subtracted from - # (and with the same gross-up applied to USE_ELE_FINITO in eq_loadcon) + # Convert to busbar load_hourly_finito = load_hourly_finito / (1 - distloss) # subtract FINITO reference load from ReEDS load data, From c75cc9b8f75fcfeff19473be7ca6ea6aaff3f3ae Mon Sep 17 00:00:00 2001 From: mmowers Date: Fri, 17 Jul 2026 00:44:10 -0400 Subject: [PATCH 29/78] Clip negative loads after subtracting finito reference load, and add ValueError for NaNs. --- reeds/input_processing/hourly_load.py | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/reeds/input_processing/hourly_load.py b/reeds/input_processing/hourly_load.py index 2ef9c5e77..211893c60 100644 --- a/reeds/input_processing/hourly_load.py +++ b/reeds/input_processing/hourly_load.py @@ -634,8 +634,26 @@ def remove_finito_load( # aligning by model year (index) and region (columns) result = load_hourly - load_hourly_finito - #TODO: add this - # Validation check: confirm that total load - FINITO load > 0 + # any missing region or model year in load_finito.csv shows up as NaN + if result.isnull().any().any(): + raise ValueError( + 'FINITO reference load is missing regions or years present in the ' + 'ReEDS load data; check load_finito.csv' + ) + + # Validation check: FINITO reference load should not exceed baseline load. + # If it does, clip to zero and report + negative = result < 0 + if negative.any().any(): + clipped = (-result[negative]).groupby('year').sum() + clipped = clipped.stack().loc[lambda x: x > 0].rename('clipped_MWh') + print( + 'WARNING: FINITO reference load exceeds baseline load; clipping ' + f'{clipped.sum():.0f} MWh (summed over weather years) to zero.\n' + 'Clipped MWh by (year, region):\n' + + clipped.to_string() + ) + result = result.clip(lower=0) return result From 449b115a151786482633ac723c7410fdf02e2e0e Mon Sep 17 00:00:00 2001 From: mmowers Date: Sat, 18 Jul 2026 18:30:34 -0600 Subject: [PATCH 30/78] Fix region handling in FINITO-linkage --- reeds/input_processing/hourly_load.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/reeds/input_processing/hourly_load.py b/reeds/input_processing/hourly_load.py index 211893c60..e6220f04b 100644 --- a/reeds/input_processing/hourly_load.py +++ b/reeds/input_processing/hourly_load.py @@ -610,6 +610,25 @@ def get_hourly_finito_load( load_finito = load_finito.pivot(index='year', columns='r', values='load_MWh') load_finito.columns.name = None + # load_finito.csv is at z134 (p) resolution, so aggregate to this + # run's model regions + county2p = reeds.io.get_county2zone(GSw_ZoneSet='z134') + county2zone = reeds.io.get_county2zone(case=Path(inputs_case).parent) + p2zone = ( + pd.concat({'p': county2p, 'zone': county2zone}, axis=1) + .dropna() + .drop_duplicates() + ) + zones_per_p = p2zone.groupby('p')['zone'].nunique() + if (zones_per_p > 1).any(): + raise ValueError( + 'Cannot aggregate load_finito.csv from z134 to model regions because ' + 'these p regions span multiple model regions: ' + f'{zones_per_p.loc[zones_per_p > 1].index.tolist()}' + ) + p2zone = p2zone.set_index('p')['zone'] + 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? hours_per_year = 8760 From 75937357d04629c525803b8d43034014b662ed8b Mon Sep 17 00:00:00 2001 From: mmowers Date: Wed, 22 Jul 2026 13:17:49 -0600 Subject: [PATCH 31/78] Fix H2 fixed-price bug in ReEDS-FINITO linkage --- reeds/core/setup/d_objective.gms | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/reeds/core/setup/d_objective.gms b/reeds/core/setup/d_objective.gms index 3c8478659..83f1309fb 100644 --- a/reeds/core/setup/d_objective.gms +++ b/reeds/core/setup/d_objective.gms @@ -231,7 +231,11 @@ eq_Objfn_op(t)$tmodel(t).. * plus cost of H2 fuel when using fixed price (Sw_H2=0) or during stress periods. * When using endogenous H2 price (Sw_H2=1 or Sw_H2=2), H2 fuel cost is captured elsewhere * via the capex + opex costs of H2 production and its associated electricity demand. - + sum{(i,v,r,h)$[tfuel(t)$valgen(i,v,r,t)$heat_rate(i,v,r,t) +* Note on FINITO linkage: tfuel(t)=no for linked years, which hands fuel costs to +* FINITO. Coal/nuclear/gas remain in FINITO's fe_reeds accounting, but H2 combustion is +* excluded there (linked_finito_input.gms), so keep charging it here via the fixed price +* (Sw_H2=0) so power-sector H2 fuel is not free in linked runs. + + sum{(i,v,r,h)$[[tfuel(t) or h2_combustion(i)]$valgen(i,v,r,t)$heat_rate(i,v,r,t) $(not gas(i))$(not bio(i))$(not cofire(i)) $((not h2_combustion(i)) or h2_combustion(i)$[(Sw_H2=0) or h_stress(h)])], hours(h) * heat_rate(i,v,r,t) * fuel_price(i,r,t) * GEN(i,v,r,h,t) } From 27da55fb477faf9862ec300d351e0e61cee0f794 Mon Sep 17 00:00:00 2001 From: bsergi Date: Thu, 30 Jul 2026 12:47:21 -0400 Subject: [PATCH 32/78] reduce some duplicative code --- runreeds.py | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/runreeds.py b/runreeds.py index 19769ffb3..54dd77653 100644 --- a/runreeds.py +++ b/runreeds.py @@ -100,23 +100,18 @@ def create_case_lists(df_cases:pd.DataFrame, BatchName:str, single:str=''): continue # Add switch settings to list of options passed to GAMS shcom = f' --case={BatchName}_{case}' + # (ReEDS-FINITO) Combine the cases files for the linked model if int(df_cases[case].loc['GSw_FINITO_Link']) == 1 : # add the FINITO switches to the caseSwitches - df_cases_combine=linked_cases(df_cases,case) - for i,v in df_cases_combine.items(): - #exclude certain switches that don't need to be passed to GAMS - if i not in ['file_replacements','keep_run_terminal']: - shcom = shcom + ' --' + i + '=' + v - caseList.append(shcom) - caseSwitches.append(df_cases_combine.to_dict()) - else: - for i,v in df_cases[case].items(): - #exclude certain switches that don't need to be passed to GAMS - if i not in ['file_replacements','keep_run_terminal']: - shcom += f' --{i}={v}' - caseList.append(shcom) - caseSwitches.append(df_cases[case].to_dict()) + df_cases=linked_cases(df_cases,case) + + #exclude certain switches that don't need to be passed to GAMS + for i,v in df_cases[case].items(): + if i not in ['file_replacements','keep_run_terminal']: + shcom += f' --{i}={v}' + caseList.append(shcom) + caseSwitches.append(df_cases[case].to_dict()) return caseSwitches, casenames, caseList From 7174aa27771b8afe5a32ff8db585af4620cac6df Mon Sep 17 00:00:00 2001 From: bsergi Date: Fri, 31 Jul 2026 10:59:19 -0400 Subject: [PATCH 33/78] remove cases_finito.csv --- cases_finito.csv | 44 -------------------------------------------- 1 file changed, 44 deletions(-) delete mode 100644 cases_finito.csv diff --git a/cases_finito.csv b/cases_finito.csv deleted file mode 100644 index 6e6c732e7..000000000 --- a/cases_finito.csv +++ /dev/null @@ -1,44 +0,0 @@ -,Default Value,ReEDSonly,BAUreg,Reference,Reference-AEO,Reference-HOG,TradeReference,TradeReference-AEO,Trade-HOG,Reference-LowBio,Reference-HighBio,Tax200,Tax200-AEO,NetZero,NetZero-LowBio,NetZero-HighBio,NetZero-SlackLim1500,NetZero-SlackLim1500-CO2Stor-Mid,NetZero-SlackLim1500-CO2Stor-Low,NetZero-SlackLim500-CO2Stor-Low,NetZero-SlackCost2500-CO2Stor-Low,aggreg_54_level -ignore,1,1,0,1,1,1,1,1,1,,,,,,,,,,,,, -GSw_FINITO_Link,1,0,,,,,,,,,,,,,,,,,,,, -finito_dir,/path/to/FINITO/directory,0,,,,,,,,,,,,,,,,,,,, -finito_cases_file,linked,,,,,,,,,,,,,,,,,,,,, -finito_case,BAU,,BAUreg,,BAU-AEO,AEO-HOG,TradeReEDS,TradeReEDS-AEO,TradeReEDS-HOG,BAU-LowBio,BAU-HighBio,,BAU-AEO,NetZero,NetZero-LowBio,NetZero-HighBio,NetZero-SlackLim1500,NetZero-SlackLim1500-CO2Stor-Mid,NetZero-SlackLim1500-CO2Stor-Low,NetZero-SlackLim500-CO2Stor-Low,NetZero-SlackCost2500-CO2Stor-Low,aggreg_54_level -coalscen,AEO_2025_reference,,,,,,,,,,,,,,,,,,,,, -ngscen,AEO_2025_reference,,,,,AEO_2025_HOG,,,AEO_2025_HOG,,,,,,,,,,,,, -uraniumscen,AEO_2025_reference,,,,,,,,,,,,,,,,,,,,, -GSw_Region,country/USA,,st/CO,,,,,,,,,,,,,,,,,,, -GSw_ZoneSet,z54,,,,,,,,,,,,,,,,,,,,,z54 -GSw_GasCurve,1,,2,,,,,,,,,,,,,,,,,,, -GSw_gopt,2,,,,,,,,,,,,,,,,,,,,, -GSw_NewValCapShrink,1,,,,,,,,,,,,,,,,,,,,, -endyear,2050,,2035,,,,,,,,,,,,,,,,,,, -yearset,2010_2015_2020..2050..5,,,,,,,,,,,,,,,,,,,,, -first_year_finito,2018,,,,,,,,,,,,,,,,,,,,, -incentives_suffix,obbba,,,,,,,,,,,,,,,,,,,,, -GSw_Upgrades,0,,,,,,,,,,,,,,,,,,,,, -GSw_AnnualCap,0,,,,,,,,,,,,,,,,,,,,, -GSw_AnnualCapScen,start2023_100pct2035,,,,,,,,,,,,,,,,,,,,, -GSw_Clean_Air_Act,0,,,,,,,,,,,,,,,,,,,,, -GSw_CSP,0,,,,,,,,,,,,,,,,,,,,, -GSw_Geothermal,0,,,,,,,,,,,,,,,,,,,,, -GSw_H2,2,,,,,,,,,,,,,,,,,,,,, -GSw_H2_PTC,0,,,,,,,,,,,,,,,,,,,,, -GSw_H2_Demand_Case,BAU,,,,,,,,,,,,,,,,,,,,, -GSw_H2_SMR,1,,,,,,,,,,,,,,,,,,,,, -GSw_H2_Transport,0,,,,,,,,,,,,,,,,,,,,, -GSw_H2Combustion,1,,,,,,,,,,,,,,,,,,,,, -GSw_H2Combustionupgrade,1,,,,,,,,,,,,,,,,,,,,, -GSw_H2CombinedCycle,1,,,,,,,,,,,,,,,,,,,,, -GSw_DAC,1,,,,,,,,,,,,,,,,,,,,, -GSw_BECCS,1,,,,,,,,,,,,,,,,,,,,, -GSw_TCPhaseout,1,,,,,,,,,,,,,,,,,,,,, -GSw_CO2_Detail,1,,,,,,,,,,,,,,,,,,,,, -numhintage,4,,,,,,,,,,,,,,,,,,,,, -GSw_CarbTax,0,,,,,,,,,,,1,1,,,,,,,,, -GSw_CarbTaxOption,default,,,,,,,,,,,t200_2050,t200_2050,,,,,,,,, -GSw_PRM_CapCredit,0,,,,,,,,,,,,,,,,,,,,, -GSw_PRM_StressIterateMax,0,,,,,,,,,,,,,,,,,,,,, -GSw_HourlyNumClusters,9,,,,,,,,,,,,,,,,,,,,, -GSw_HourlyChunkLengthRep,4,,,,,,,,,,,,,,,,,,,,, -GSw_HourlyChunkLengthStress,4,,,,,,,,,,,,,,,,,,,,, From 9f94ef42d89fa8dc211970767a4124e5e5e8983d Mon Sep 17 00:00:00 2001 From: bsergi Date: Fri, 31 Jul 2026 11:00:37 -0400 Subject: [PATCH 34/78] some comment and formatting changes --- reeds/core/setup/c_model.gms | 9 +++++---- reeds/core/setup/d_objective.gms | 3 +-- reeds/core/solve/2_temporal_params.gms | 1 - 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/reeds/core/setup/c_model.gms b/reeds/core/setup/c_model.gms index 88ede27b0..bf72459c1 100644 --- a/reeds/core/setup/c_model.gms +++ b/reeds/core/setup/c_model.gms @@ -2982,7 +2982,7 @@ eq_gasbinlimit_national(fuelbin,t)$[tmodel(t)$tfuel(t)$(Sw_GasCurve=1)].. *============================== * -- Bioenergy Supply Curve -- *============================== -* defer to FINITO representation when models are linked +* defer to FINITO representation when models are linked (see eq_use_bs_reeds) * --------------------------------------------------------------------------- eq_bioused(r,t)$[sum{(i,v)$(bio(i) or cofire(i)), valgen(i,v,r,t) }$tmodel(t)$tfuel(t)].. @@ -3575,7 +3575,8 @@ eq_h2_demand(p,t)$[(sameas(p,"H2"))$tmodel(t)$(yeart(t)>=h2_demand_start)$(Sw_H2 GEN(i,v,r,h,t) * hours(h) * h2_combustion_intensity * heat_rate(i,v,r,t) } -* hydrogen demand from indusrty (FINITO): FINITO demand [MMBtu/yr] * conversion [metric tons-H2/MMBtu-H2] +* hydrogen demand from industry when linked with FINITO: demand [MMBtu/yr] * conversion [metric tons-H2/MMBtu-H2] +* TODO: should we disable exogenous H2 demand when linked? $ifthene.linked_h2_nat Sw_FINITO_Link==1 + [sum{(r,h)$h_rep(h), hours(h) * USE_H2_FINITO(r,h,t) * h2_metric_tons_per_mmbtu }]$t_finito(t) $endif.linked_h2_nat @@ -3611,7 +3612,7 @@ eq_h2_demand_regional(r,h,t) GEN(i,v,r,h,t) * h2_combustion_intensity * heat_rate(i,v,r,t) } -* regional hydrogen demand for industry (FINITO) +* when linked includeregional hydrogen demand for industry from FINITO $ifthene.linked_h2_reg Sw_FINITO_Link==1 + [ USE_H2_FINITO(r,h,t) * h2_metric_tons_per_mmbtu ]$t_finito(t) $endif.linked_h2_reg @@ -3857,7 +3858,7 @@ eq_co2_capture(r,h,t) * capture from DAC + sum{(i,v)$[dac(i)$valcap(i,v,r,t)$i_p(i,"DAC")], PRODUCE("DAC",i,v,r,h,t) }$Sw_DAC -* (ReEDS-FINITO) capture from industry [metric_tons-CO2/hr]: +* capture from industry when linked with FINITO [metric_tons-CO2/hr]: * calculation: hours_per_year [yrs/hr] * capture [scaled_metric_tons-CO2/yr] / co2_scale [scaled_metric_tons-CO2/metric_tons-CO2] $ifthene.linked_co2_capture Sw_FINITO_Link==1 + [CAPTURE_CO2EM(r,h,t) / co2_scale]$[t_finito(t)$h_rep(h)] diff --git a/reeds/core/setup/d_objective.gms b/reeds/core/setup/d_objective.gms index 9c5e98e1a..90b8e3ecb 100644 --- a/reeds/core/setup/d_objective.gms +++ b/reeds/core/setup/d_objective.gms @@ -25,8 +25,7 @@ eq_ObjFn.. Z =e= cost_scale * ( * economy-wide costs from FINITO: deflate from $2018 to $2004 * and remove any FINITO scaling $ifthene.linked_objective Sw_FINITO_Link==1 - + deflator('2018') / obj_scale - * sum{t$[t_finito(t)], Z_finito(t) } + + deflator('2018') / obj_scale * sum{t$[t_finito(t)], Z_finito(t) } $endif.linked_objective ) ; diff --git a/reeds/core/solve/2_temporal_params.gms b/reeds/core/solve/2_temporal_params.gms index ff3244a28..6b9c83753 100644 --- a/reeds/core/solve/2_temporal_params.gms +++ b/reeds/core/solve/2_temporal_params.gms @@ -902,7 +902,6 @@ gasprice_adj_cendiv(cendiv,h)$(Sw_GasPriceAdjMethod = 0) = 1 ; *============================================= * ----- ReEDS-FINITO temporal parameters ----- *============================================= -* remove focused sectors load from load_exog_static $ifthene.linked_finito_temporal_params %GSw_FINITO_Link% ==1 $include finito/model/linked_finito_temporal_params.gms $endif.linked_finito_temporal_params From 2e604e7b8b437230a0ed5bc859c0536227ddad48 Mon Sep 17 00:00:00 2001 From: bsergi Date: Fri, 31 Jul 2026 11:04:53 -0400 Subject: [PATCH 35/78] add FINITO column to cases_test.csv --- cases_test.csv | 110 ++++++++++++++++++++++++------------------------- 1 file changed, 55 insertions(+), 55 deletions(-) diff --git a/cases_test.csv b/cases_test.csv index 0f5846562..441b0617b 100644 --- a/cases_test.csv +++ b/cases_test.csv @@ -1,68 +1,68 @@ ,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 -ignore,1,0,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +ignore,1,0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, GSw_Region,cendiv/Pacific,,country/USA,country/USA,country/USA,,st/ID.WY.NE.IA.IL,st/MA,,,,,,,st/WY,interconnect/western,transreg/PJM,st/NY.VT,st/OR,st/NE.NY.PA,st/NE.NY.PA,st/ID.WY.NE.IA.IL,st/KS,country/USA,country/USA,st/NY.NJ,,,,st/MA.RI.CT.NY.NJ.PA.OH,,country/USA endyear,2032,,2050,2050,2050,2029,2060,2026,,,,,,,,,,,2035,2030,2030,2060,2035,2050,2050,2050,,,,,,2050 yearset,,,,,,,2010..2060..10,,,,,,,,,,,,,2010..2050..5,2010..2050..5,2010..2060..10,,2010..2050..5,2010_2025_2050,2010..2050..5,,,,,,2010..2050..5 GSw_ZoneSet,,,,,,,z54,z3109,,,,,,,z3109,z3109,z3109,PJMcounty,,,,z54,z134,z54,z48,,,,,,,z54 -GSw_GasCurve,2,,1,1,,,,,,,,,,,,,,,,,,,,1,1,,,,,, -GSw_Geothermal,,,,2,,,,,,,,,,,,,,,,,,,0,,0,,,,,, -GSw_GrowthPenalties,,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,, -GSw_Upstream,,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,, -GSw_TransHurdleRate,,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,, -distpvscen,,,,,stscen2023_mid_case_95_by_2035,,,,,,,,,,,,,,,,,,,,,,,,,, -GSw_AnnualCap,,,,,2,,1,,,,,,,,,,,,,,,1,,,,,,,,, -GSw_AnnualCapScen,,,,,start2024_90pct2035_100pct2045,,start2027_95pct2035,,,,,,,,,,,,,,,start2027_95pct2035,,,,,,,,, -GSw_LoadProfiles,,,,,EER2025_100by2050,EER2025_IRAlow,EER2025_IRAlow,EER2025_IRAlow,,,,,historic,,,,,,,,,EER2025_100by2050,,,,,historic,,,, -GSw_NG_CRF_penalty,,,,,ramp_2045,,ramp_2023_2035,,,,,,,,,,,,,,,ramp_2023_2035,,,,,,,,, -GSw_PRM_NetImportLimit,,,,,0,,,,,,,,,,,,,,,,,,,,,,,,,, -GSw_RetirePenalty,,,,,0,,,,,,,,,,,,,,,,,,,,,,,,,, -GSw_FakeData,,,,,,1,1,1,,,,,,,,,,,,,,,,,,,,,,, -GSw_PRM_CapCredit,,,,,,,,1,1,,,,,,,,1,,,,,,,,,,,,,, -GSw_PRM_scenario,,,,,,,,,static,,,,,,,,static,,,,,,,,,,,,,, -GSw_PRM_UpdateMethod,,,,,,,,,1,,,,,,,,,,,,,,,,,,,,,, -GSw_HourlyType,,,,,,,,,,wek,year,,,,,,,,,,,,,,,,,,,, -GSw_InterDayLinkage,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,,, -GSw_HourlyWeatherYears,,,,,,,2012_2013,,,,,,2020,2007_2008_2009_2010_2011_2012_2013_2016_2017_2018_2019_2020_2021_2022_2023,,,,,,,,2012_2013,,,,,2018,,,, -GSw_HourlyClusterMapMethod,,,,,,,,,,,,,,bestfirst,,,,,,,,,,,,,,,,, -GSw_WaterCapacity,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,, -GSw_WaterMain,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,, -GSw_WaterUse,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,, -resource_adequacy_years,,,,,,,2011_2012_2013_2021_2022_2023,,,,,,,,,,,,,,,2011_2012_2013_2021_2022_2023,,,,,,,,, -GSw_HourlyClusterAlgorithm,,,,,,,,,,,,,,,,,,,,user,user,,,,,,,,,, -MCS_runs,,,,,,,,,,,,,,,,,,,,2,2,,,,,,,,,, -MCS_dist_groups,,,,,,,,,,,,,,,,,,,,tech.hydro.nuclear.gas.coal.load_country,upv_tri.nuclear_tri.ng_fuel_price_tri.load_country_unif,,,,,,,,,, -MCS_lhs,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,, -GSw_PRM_StressIterateMax,,,,,,,,0,,,,,,,,0,0,,,1,1,,,,,,,,,, -GSw_ReducedResource,,,,,,,1,,,,,,,,,,,,,,,1,,,,,,,,, -GSw_SitingUPV,,,,,,limited,limited,limited,,,,,,,,,,,,,,limited,,,,,,,,, -GSw_SitingWindOfs,,,,,,limited,limited,limited,,,,,,,,,,,,,,open,,,,,,,,, -GSw_SitingWindOns,,,,,,limited,limited,limited,,,,,,,,,,,,,,limited,,,,,,,,, -GSw_TransScen,,,,,,,NTP_MT,,,,,,,,,,,,,,,NTP_MT,,,,,,,,, -GSw_CO2_Detail,,,,,,,1,,,,,,,,,,,,,,,1,,,,,,,,, -GSw_DAC,,,,,,,1,,,,,,,,,,,,,,,1,,,,,,,,, -GSw_NoFossilOffsetCDR,,,,,,,1,,,,,,,,,,,,,,,1,,,,,,,,, -GSw_Biopower,,,,,,,,,,,,,,,,,,,,,,,0,,0,,,,,, +GSw_GasCurve,2,,1,1,,,,,,,,,,,,,,,,,,,,1,1,,,,,,, +GSw_Geothermal,,,,2,,,,,,,,,,,,,,,,,,,0,,0,,,,,,, +GSw_GrowthPenalties,,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,, +GSw_Upstream,,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,, +GSw_TransHurdleRate,,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,, +distpvscen,,,,,stscen2023_mid_case_95_by_2035,,,,,,,,,,,,,,,,,,,,,,,,,,, +GSw_AnnualCap,,,,,2,,1,,,,,,,,,,,,,,,1,,,,,,,,,, +GSw_AnnualCapScen,,,,,start2024_90pct2035_100pct2045,,start2027_95pct2035,,,,,,,,,,,,,,,start2027_95pct2035,,,,,,,,,, +GSw_LoadProfiles,,,,,EER2025_100by2050,EER2025_IRAlow,EER2025_IRAlow,EER2025_IRAlow,,,,,historic,,,,,,,,,EER2025_100by2050,,,,,historic,,,,, +GSw_NG_CRF_penalty,,,,,ramp_2045,,ramp_2023_2035,,,,,,,,,,,,,,,ramp_2023_2035,,,,,,,,,, +GSw_PRM_NetImportLimit,,,,,0,,,,,,,,,,,,,,,,,,,,,,,,,,, +GSw_RetirePenalty,,,,,0,,,,,,,,,,,,,,,,,,,,,,,,,,, +GSw_FakeData,,,,,,1,1,1,,,,,,,,,,,,,,,,,,,,,,,, +GSw_PRM_CapCredit,,,,,,,,1,1,,,,,,,,1,,,,,,,,,,,,,,, +GSw_PRM_scenario,,,,,,,,,static,,,,,,,,static,,,,,,,,,,,,,,, +GSw_PRM_UpdateMethod,,,,,,,,,1,,,,,,,,,,,,,,,,,,,,,,, +GSw_HourlyType,,,,,,,,,,wek,year,,,,,,,,,,,,,,,,,,,,, +GSw_InterDayLinkage,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,,,, +GSw_HourlyWeatherYears,,,,,,,2012_2013,,,,,,2020,2007_2008_2009_2010_2011_2012_2013_2016_2017_2018_2019_2020_2021_2022_2023,,,,,,,,2012_2013,,,,,2018,,,,, +GSw_HourlyClusterMapMethod,,,,,,,,,,,,,,bestfirst,,,,,,,,,,,,,,,,,, +GSw_WaterCapacity,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,, +GSw_WaterMain,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,, +GSw_WaterUse,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,, +resource_adequacy_years,,,,,,,2011_2012_2013_2021_2022_2023,,,,,,,,,,,,,,,2011_2012_2013_2021_2022_2023,,,,,,,,,, +GSw_HourlyClusterAlgorithm,,,,,,,,,,,,,,,,,,,,user,user,,,,,,,,,,, +MCS_runs,,,,,,,,,,,,,,,,,,,,2,2,,,,,,,,,,, +MCS_dist_groups,,,,,,,,,,,,,,,,,,,,tech.hydro.nuclear.gas.coal.load_country,upv_tri.nuclear_tri.ng_fuel_price_tri.load_country_unif,,,,,,,,,,, +MCS_lhs,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,, +GSw_PRM_StressIterateMax,,,,,,,,0,,,,,,,,0,0,,,1,1,,,,,,,,,,, +GSw_ReducedResource,,,,,,,1,,,,,,,,,,,,,,,1,,,,,,,,,, +GSw_SitingUPV,,,,,,limited,limited,limited,,,,,,,,,,,,,,limited,,,,,,,,,, +GSw_SitingWindOfs,,,,,,limited,limited,limited,,,,,,,,,,,,,,open,,,,,,,,,, +GSw_SitingWindOns,,,,,,limited,limited,limited,,,,,,,,,,,,,,limited,,,,,,,,,, +GSw_TransScen,,,,,,,NTP_MT,,,,,,,,,,,,,,,NTP_MT,,,,,,,,,, +GSw_CO2_Detail,,,,,,,1,,,,,,,,,,,,,,,1,,,,,,,,,, +GSw_DAC,,,,,,,1,,,,,,,,,,,,,,,1,,,,,,,,,, +GSw_NoFossilOffsetCDR,,,,,,,1,,,,,,,,,,,,,,,1,,,,,,,,,, +GSw_Biopower,,,,,,,,,,,,,,,,,,,,,,,0,,0,,,,,,, GSw_HourlyChunkLengthRep,,,,,,,,,,,,,,,,,,,,,,,6,4,4,,,,,,,4 GSw_HourlyChunkLengthStress,,,,,,,,,,,,,,,,,,,,,,,6,4,4,,,,,,,4 -GSw_LfillGas,,,,,,,,,,,,,,,,,,,,,,,0,,0,,,,,, -GSw_Nuclear,,,,,,,,,,,,,,,,,,,,,,,0,,0,,,,,, -GSw_OpRes,,,,,,,,,,,,,,,,,,,,,,,0,,0,,,,,, +GSw_LfillGas,,,,,,,,,,,,,,,,,,,,,,,0,,0,,,,,,, +GSw_Nuclear,,,,,,,,,,,,,,,,,,,,,,,0,,0,,,,,,, +GSw_OpRes,,,,,,,,,,,,,,,,,,,,,,,0,,0,,,,,,, GSw_StartCost,,,,,,,,,,,,,,,,,,,,,,,0,0,0,,,,,,,0 GSw_H2,,,,,,,,,,,,,,,,,,,,,,,,0,0,,,,,,,0 GSw_H2_PTC,,,,,,,,,,,,,,,,,,,,,,,,0,0,,,,,,,0 -GSw_H2Combustion,,,,,,,,,,,,,,,,,,,,,,,,,0,,,,,, -GSw_PRM_StressThresholdMetrics,,,,,,,,,,,,,,,,,,,,,,,,,,NEUE/LOLH/LOLE/LOLD/duration/depth,,,,, -GSw_DRShed,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,, -GSw_MGA_CostDelta,,,,,,,,,,,,,,,,,,,,,,,,,,,,0.01,,, -GSw_LoadSiteCF,,,,,,,,,,,,,,,,,,,,,,,,,,,,,0.95,, -GSw_OffshoreZones,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1, -GSw_OffshoreBackbone,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1, -GSw_OffshoreBackflow,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1, -pras_agg_ogs_lfillgas,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1 -pras_existing_unit_size,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,0 -pras_scheduled_outage,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,0 -pras_unitsize_source,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,r2x -pras_vre_combine,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1 +GSw_H2Combustion,,,,,,,,,,,,,,,,,,,,,,,,,0,,,,,,, +GSw_PRM_StressThresholdMetrics,,,,,,,,,,,,,,,,,,,,,,,,,,NEUE/LOLH/LOLE/LOLD/duration/depth,,,,,, +GSw_DRShed,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,, +GSw_MGA_CostDelta,,,,,,,,,,,,,,,,,,,,,,,,,,,,0.01,,,, +GSw_LoadSiteCF,,,,,,,,,,,,,,,,,,,,,,,,,,,,,0.95,,, +GSw_OffshoreZones,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,, +GSw_OffshoreBackbone,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,, +GSw_OffshoreBackflow,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,, +pras_agg_ogs_lfillgas,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1, +pras_existing_unit_size,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,0, +pras_scheduled_outage,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,0, +pras_unitsize_source,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,r2x, +pras_vre_combine,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1, pras_samples,,,,,,10,10,10,,,,,,,,,,,,,,,,10,10,,,,,,,10 GSw_FINITO_Link,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1 finito_dir,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,/projects/futuregrid/mmowers/FINITO From df894f18004ead3342cf6de49083259ce0591f97 Mon Sep 17 00:00:00 2001 From: bsergi Date: Fri, 31 Jul 2026 11:23:46 -0400 Subject: [PATCH 36/78] fix bug in FINITO cases file setup --- runreeds.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/runreeds.py b/runreeds.py index a7cc35c0c..4962ebe2b 100644 --- a/runreeds.py +++ b/runreeds.py @@ -100,18 +100,18 @@ def create_case_lists(df_cases:pd.DataFrame, BatchName:str, single:str=''): continue # Add switch settings to list of options passed to GAMS shcom = f' --case={BatchName}_{case}' - + case_out = df_cases[case].copy() # (ReEDS-FINITO) Combine the cases files for the linked model - if int(df_cases[case].loc['GSw_FINITO_Link']) == 1 : + if int(case_out.loc['GSw_FINITO_Link']) == 1 : # add the FINITO switches to the caseSwitches - df_cases=linked_cases(df_cases,case) - + case_out=linked_cases(df_cases,case) + #exclude certain switches that don't need to be passed to GAMS - for i,v in df_cases[case].items(): + for i,v in case_out.items(): if i not in ['file_replacements','keep_run_terminal']: shcom += f' --{i}={v}' caseList.append(shcom) - caseSwitches.append(df_cases[case].to_dict()) + caseSwitches.append(case_out.to_dict()) return caseSwitches, casenames, caseList From 5f3bf1b3fa9333b4ea00c3ec251d1dfcd095fa0d Mon Sep 17 00:00:00 2001 From: bsergi Date: Fri, 31 Jul 2026 11:24:24 -0400 Subject: [PATCH 37/78] updates to gas and biomass pricing (need to finalize) --- reeds/core/terminus/report.gms | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/reeds/core/terminus/report.gms b/reeds/core/terminus/report.gms index a45a671ef..a14975557 100644 --- a/reeds/core/terminus/report.gms +++ b/reeds/core/terminus/report.gms @@ -498,6 +498,17 @@ ptc_out(i,v,t)$[tmodel_new(t)$ptc_value_scaled(i,v,t)] = ptc_value_scaled(i,v,t) repbioprice(r,t)$[tmodel_new(t)$tfuel(t)] = max{0, smax{bioclass$BIOUSED.l(bioclass,r,t), eq_bioused.m(r,t) - sum{usda_region$r_usda(r,usda_region), eq_biousedlimit.m(bioclass,usda_region,t) } } } / pvf_onm(t) ; +* when running linked model use FINITO biomass clearing prices +$ifthene.finitobioprice Sw_FINITO_Link == 1 +* here we take the weighted average of prices across biomass products used for power +repbioprice(r,t)$[tmodel_new(t)$(not tfuel(t))] = + 1/(obj_scale) * 1/(pvf_onm(t)) * deflator('2018') * + sum{(i,v,bs), USE_BS_REEDS(i,v,bs,r,t) * eq_supplydemand_bs.m(bs,r,t) } + / sum{(i,v,bs), USE_BS_REEDS(i,v,bs,r,t) } +; +$endif.finitobioprice + + * quantity of biomass used (convert from mmBTU to dry tons using biomass energy content) bioused_out(bioclass,r,t)$[tmodel_new(t)$tfuel(t)] = BIOUSED.l(bioclass,r,t) / bio_energy_content ; bioused_usda(bioclass,usda_region,t)$[tmodel_new(t)$tfuel(t)] = sum{r$r_usda(r,usda_region), bioused_out(bioclass,r,t) } ; @@ -547,22 +558,21 @@ repgasprice(cendiv,t)$[(Sw_GasCurve = 2)$tmodel_new(t)$repgasquant(cendiv,t)$tfu * We maybe should be taking weighted averages instead of max across regions and categories (pool vs roi etc.) $ifthene.finitogasprice Sw_FINITO_Link == 1 repgasprice_finito(cendiv,h,t)$[tmodel_new(t)$(not tfuel(t))] = - ( 1/obj_scale * 1/pvf_onm(t) * deflator('2018') * - [ max{ - smax{(eus,usep,r)$[map_roe_ec_usep("NG",eus,usep)$r_cendiv(r,cendiv)], eq_supplydemand_fe_pool.m("NG",eus,usep,r,h,t)}, - smax{(usep,r)$[map_roi_ec_usep("NG",usep)$r_cendiv(r,cendiv)],eq_supplydemand_fe_roi.m("NG",usep,r,h,t)}, - smax{(cfp2,cfvin2,fi,r)$[map_ec_ei("NG",fi)$valei_cf(cfp2,cfvin2,fi,r,t)$r_cendiv(r,cendiv)],eq_use_fi_cf.m(cfp2,cfvin2,fi,r,h,t)}, - smax{(tech,vin,fi,r)$[map_ec_ei("NG",fi)$valei_ind(tech,vin,fi,r,t)$r_cendiv(r,cendiv)],eq_use_fi_ind.m(tech,vin,fi,r,h,t)} - } / hours(h) - ] - ) +* citygate price of natural gas + deflator('2018') * [ + ( 1/(obj_scale) * 1/(pvf_onm(t)) * smax{(cfp,st)$r_st(r,st), eq_supplydemand_cf.M(cfp,'NG',st,h,t) } / hours(h) ) +* electric-sector markup for natural gas +* TODO: activate after FINITO pricing PR is merged +* + smax{(cfp,cendiv)$[r_cendiv(r,cendiv)$gasp(cfp)$map_cf_fe(cfp,'NG')$valcft(cfp,t)],cf_markup(cfp,'NG','Electric_Power',cendiv,t)} + ] ; $else.finitogasprice repgasprice_finito(cendiv,h,t)$[tmodel_new(t)$(not tfuel(t))] = 0 ; $endif.finitogasprice +* when linked, overwrite ReEDS values with gas prices from FINITO +* for any years that aren't using the ReEDS supply curves repgasprice(cendiv,t)$[tmodel_new(t)$(not tfuel(t))] = -* average price over hours when linked with FINITO sum{h, hours(h) * repgasprice_finito(cendiv,h,t) } ; From d0738d6261e10ae831fa701b9d79794bd82a1c33 Mon Sep 17 00:00:00 2001 From: mmowers Date: Fri, 31 Jul 2026 17:17:52 -0400 Subject: [PATCH 38/78] Add FINITO_Pacific case to cases_test.csv --- cases_test.csv | 137 +++++++++++++++++++++++++------------------------ 1 file changed, 69 insertions(+), 68 deletions(-) diff --git a/cases_test.csv b/cases_test.csv index 441b0617b..1dd0d5e5e 100644 --- a/cases_test.csv +++ b/cases_test.csv @@ -1,68 +1,69 @@ -,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 -ignore,1,0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -GSw_Region,cendiv/Pacific,,country/USA,country/USA,country/USA,,st/ID.WY.NE.IA.IL,st/MA,,,,,,,st/WY,interconnect/western,transreg/PJM,st/NY.VT,st/OR,st/NE.NY.PA,st/NE.NY.PA,st/ID.WY.NE.IA.IL,st/KS,country/USA,country/USA,st/NY.NJ,,,,st/MA.RI.CT.NY.NJ.PA.OH,,country/USA -endyear,2032,,2050,2050,2050,2029,2060,2026,,,,,,,,,,,2035,2030,2030,2060,2035,2050,2050,2050,,,,,,2050 -yearset,,,,,,,2010..2060..10,,,,,,,,,,,,,2010..2050..5,2010..2050..5,2010..2060..10,,2010..2050..5,2010_2025_2050,2010..2050..5,,,,,,2010..2050..5 -GSw_ZoneSet,,,,,,,z54,z3109,,,,,,,z3109,z3109,z3109,PJMcounty,,,,z54,z134,z54,z48,,,,,,,z54 -GSw_GasCurve,2,,1,1,,,,,,,,,,,,,,,,,,,,1,1,,,,,,, -GSw_Geothermal,,,,2,,,,,,,,,,,,,,,,,,,0,,0,,,,,,, -GSw_GrowthPenalties,,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,, -GSw_Upstream,,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,, -GSw_TransHurdleRate,,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,, -distpvscen,,,,,stscen2023_mid_case_95_by_2035,,,,,,,,,,,,,,,,,,,,,,,,,,, -GSw_AnnualCap,,,,,2,,1,,,,,,,,,,,,,,,1,,,,,,,,,, -GSw_AnnualCapScen,,,,,start2024_90pct2035_100pct2045,,start2027_95pct2035,,,,,,,,,,,,,,,start2027_95pct2035,,,,,,,,,, -GSw_LoadProfiles,,,,,EER2025_100by2050,EER2025_IRAlow,EER2025_IRAlow,EER2025_IRAlow,,,,,historic,,,,,,,,,EER2025_100by2050,,,,,historic,,,,, -GSw_NG_CRF_penalty,,,,,ramp_2045,,ramp_2023_2035,,,,,,,,,,,,,,,ramp_2023_2035,,,,,,,,,, -GSw_PRM_NetImportLimit,,,,,0,,,,,,,,,,,,,,,,,,,,,,,,,,, -GSw_RetirePenalty,,,,,0,,,,,,,,,,,,,,,,,,,,,,,,,,, -GSw_FakeData,,,,,,1,1,1,,,,,,,,,,,,,,,,,,,,,,,, -GSw_PRM_CapCredit,,,,,,,,1,1,,,,,,,,1,,,,,,,,,,,,,,, -GSw_PRM_scenario,,,,,,,,,static,,,,,,,,static,,,,,,,,,,,,,,, -GSw_PRM_UpdateMethod,,,,,,,,,1,,,,,,,,,,,,,,,,,,,,,,, -GSw_HourlyType,,,,,,,,,,wek,year,,,,,,,,,,,,,,,,,,,,, -GSw_InterDayLinkage,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,,,, -GSw_HourlyWeatherYears,,,,,,,2012_2013,,,,,,2020,2007_2008_2009_2010_2011_2012_2013_2016_2017_2018_2019_2020_2021_2022_2023,,,,,,,,2012_2013,,,,,2018,,,,, -GSw_HourlyClusterMapMethod,,,,,,,,,,,,,,bestfirst,,,,,,,,,,,,,,,,,, -GSw_WaterCapacity,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,, -GSw_WaterMain,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,, -GSw_WaterUse,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,, -resource_adequacy_years,,,,,,,2011_2012_2013_2021_2022_2023,,,,,,,,,,,,,,,2011_2012_2013_2021_2022_2023,,,,,,,,,, -GSw_HourlyClusterAlgorithm,,,,,,,,,,,,,,,,,,,,user,user,,,,,,,,,,, -MCS_runs,,,,,,,,,,,,,,,,,,,,2,2,,,,,,,,,,, -MCS_dist_groups,,,,,,,,,,,,,,,,,,,,tech.hydro.nuclear.gas.coal.load_country,upv_tri.nuclear_tri.ng_fuel_price_tri.load_country_unif,,,,,,,,,,, -MCS_lhs,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,, -GSw_PRM_StressIterateMax,,,,,,,,0,,,,,,,,0,0,,,1,1,,,,,,,,,,, -GSw_ReducedResource,,,,,,,1,,,,,,,,,,,,,,,1,,,,,,,,,, -GSw_SitingUPV,,,,,,limited,limited,limited,,,,,,,,,,,,,,limited,,,,,,,,,, -GSw_SitingWindOfs,,,,,,limited,limited,limited,,,,,,,,,,,,,,open,,,,,,,,,, -GSw_SitingWindOns,,,,,,limited,limited,limited,,,,,,,,,,,,,,limited,,,,,,,,,, -GSw_TransScen,,,,,,,NTP_MT,,,,,,,,,,,,,,,NTP_MT,,,,,,,,,, -GSw_CO2_Detail,,,,,,,1,,,,,,,,,,,,,,,1,,,,,,,,,, -GSw_DAC,,,,,,,1,,,,,,,,,,,,,,,1,,,,,,,,,, -GSw_NoFossilOffsetCDR,,,,,,,1,,,,,,,,,,,,,,,1,,,,,,,,,, -GSw_Biopower,,,,,,,,,,,,,,,,,,,,,,,0,,0,,,,,,, -GSw_HourlyChunkLengthRep,,,,,,,,,,,,,,,,,,,,,,,6,4,4,,,,,,,4 -GSw_HourlyChunkLengthStress,,,,,,,,,,,,,,,,,,,,,,,6,4,4,,,,,,,4 -GSw_LfillGas,,,,,,,,,,,,,,,,,,,,,,,0,,0,,,,,,, -GSw_Nuclear,,,,,,,,,,,,,,,,,,,,,,,0,,0,,,,,,, -GSw_OpRes,,,,,,,,,,,,,,,,,,,,,,,0,,0,,,,,,, -GSw_StartCost,,,,,,,,,,,,,,,,,,,,,,,0,0,0,,,,,,,0 -GSw_H2,,,,,,,,,,,,,,,,,,,,,,,,0,0,,,,,,,0 -GSw_H2_PTC,,,,,,,,,,,,,,,,,,,,,,,,0,0,,,,,,,0 -GSw_H2Combustion,,,,,,,,,,,,,,,,,,,,,,,,,0,,,,,,, -GSw_PRM_StressThresholdMetrics,,,,,,,,,,,,,,,,,,,,,,,,,,NEUE/LOLH/LOLE/LOLD/duration/depth,,,,,, -GSw_DRShed,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,, -GSw_MGA_CostDelta,,,,,,,,,,,,,,,,,,,,,,,,,,,,0.01,,,, -GSw_LoadSiteCF,,,,,,,,,,,,,,,,,,,,,,,,,,,,,0.95,,, -GSw_OffshoreZones,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,, -GSw_OffshoreBackbone,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,, -GSw_OffshoreBackflow,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,, -pras_agg_ogs_lfillgas,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1, -pras_existing_unit_size,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,0, -pras_scheduled_outage,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,0, -pras_unitsize_source,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,r2x, -pras_vre_combine,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1, -pras_samples,,,,,,10,10,10,,,,,,,,,,,,,,,,10,10,,,,,,,10 -GSw_FINITO_Link,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1 -finito_dir,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,/projects/futuregrid/mmowers/FINITO +,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 +ignore,1,0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +GSw_Region,cendiv/Pacific,,country/USA,country/USA,country/USA,,st/ID.WY.NE.IA.IL,st/MA,,,,,,,st/WY,interconnect/western,transreg/PJM,st/NY.VT,st/OR,st/NE.NY.PA,st/NE.NY.PA,st/ID.WY.NE.IA.IL,st/KS,country/USA,country/USA,st/NY.NJ,,,,st/MA.RI.CT.NY.NJ.PA.OH,,country/USA, +endyear,2032,,2050,2050,2050,2029,2060,2026,,,,,,,,,,,2035,2030,2030,2060,2035,2050,2050,2050,,,,,,2050, +yearset,,,,,,,2010..2060..10,,,,,,,,,,,,,2010..2050..5,2010..2050..5,2010..2060..10,,2010..2050..5,2010_2025_2050,2010..2050..5,,,,,,2010..2050..5, +GSw_ZoneSet,,,,,,,z54,z3109,,,,,,,z3109,z3109,z3109,PJMcounty,,,,z54,z134,z54,z48,,,,,,,z54,z54 +GSw_GasCurve,2,,1,1,,,,,,,,,,,,,,,,,,,,1,1,,,,,,,, +GSw_Geothermal,,,,2,,,,,,,,,,,,,,,,,,,0,,0,,,,,,,, +GSw_GrowthPenalties,,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +GSw_Upstream,,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +GSw_TransHurdleRate,,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +distpvscen,,,,,stscen2023_mid_case_95_by_2035,,,,,,,,,,,,,,,,,,,,,,,,,,,, +GSw_AnnualCap,,,,,2,,1,,,,,,,,,,,,,,,1,,,,,,,,,,, +GSw_AnnualCapScen,,,,,start2024_90pct2035_100pct2045,,start2027_95pct2035,,,,,,,,,,,,,,,start2027_95pct2035,,,,,,,,,,, +GSw_LoadProfiles,,,,,EER2025_100by2050,EER2025_IRAlow,EER2025_IRAlow,EER2025_IRAlow,,,,,historic,,,,,,,,,EER2025_100by2050,,,,,historic,,,,,, +GSw_NG_CRF_penalty,,,,,ramp_2045,,ramp_2023_2035,,,,,,,,,,,,,,,ramp_2023_2035,,,,,,,,,,, +GSw_PRM_NetImportLimit,,,,,0,,,,,,,,,,,,,,,,,,,,,,,,,,,, +GSw_RetirePenalty,,,,,0,,,,,,,,,,,,,,,,,,,,,,,,,,,, +GSw_FakeData,,,,,,1,1,1,,,,,,,,,,,,,,,,,,,,,,,,, +GSw_PRM_CapCredit,,,,,,,,1,1,,,,,,,,1,,,,,,,,,,,,,,,, +GSw_PRM_scenario,,,,,,,,,static,,,,,,,,static,,,,,,,,,,,,,,,, +GSw_PRM_UpdateMethod,,,,,,,,,1,,,,,,,,,,,,,,,,,,,,,,,, +GSw_HourlyType,,,,,,,,,,wek,year,,,,,,,,,,,,,,,,,,,,,, +GSw_InterDayLinkage,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,,,,, +GSw_HourlyWeatherYears,,,,,,,2012_2013,,,,,,2020,2007_2008_2009_2010_2011_2012_2013_2016_2017_2018_2019_2020_2021_2022_2023,,,,,,,,2012_2013,,,,,2018,,,,,, +GSw_HourlyClusterMapMethod,,,,,,,,,,,,,,bestfirst,,,,,,,,,,,,,,,,,,, +GSw_WaterCapacity,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,, +GSw_WaterMain,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,, +GSw_WaterUse,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,, +resource_adequacy_years,,,,,,,2011_2012_2013_2021_2022_2023,,,,,,,,,,,,,,,2011_2012_2013_2021_2022_2023,,,,,,,,,,, +GSw_HourlyClusterAlgorithm,,,,,,,,,,,,,,,,,,,,user,user,,,,,,,,,,,, +MCS_runs,,,,,,,,,,,,,,,,,,,,2,2,,,,,,,,,,,, +MCS_dist_groups,,,,,,,,,,,,,,,,,,,,tech.hydro.nuclear.gas.coal.load_country,upv_tri.nuclear_tri.ng_fuel_price_tri.load_country_unif,,,,,,,,,,,, +MCS_lhs,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,, +GSw_PRM_StressIterateMax,,,,,,,,0,,,,,,,,0,0,,,1,1,,,,,,,,,,,, +GSw_ReducedResource,,,,,,,1,,,,,,,,,,,,,,,1,,,,,,,,,,, +GSw_SitingUPV,,,,,,limited,limited,limited,,,,,,,,,,,,,,limited,,,,,,,,,,, +GSw_SitingWindOfs,,,,,,limited,limited,limited,,,,,,,,,,,,,,open,,,,,,,,,,, +GSw_SitingWindOns,,,,,,limited,limited,limited,,,,,,,,,,,,,,limited,,,,,,,,,,, +GSw_TransScen,,,,,,,NTP_MT,,,,,,,,,,,,,,,NTP_MT,,,,,,,,,,, +GSw_CO2_Detail,,,,,,,1,,,,,,,,,,,,,,,1,,,,,,,,,,, +GSw_DAC,,,,,,,1,,,,,,,,,,,,,,,1,,,,,,,,,,, +GSw_NoFossilOffsetCDR,,,,,,,1,,,,,,,,,,,,,,,1,,,,,,,,,,, +GSw_Biopower,,,,,,,,,,,,,,,,,,,,,,,0,,0,,,,,,,, +GSw_HourlyChunkLengthRep,,,,,,,,,,,,,,,,,,,,,,,6,4,4,,,,,,,4,4 +GSw_HourlyChunkLengthStress,,,,,,,,,,,,,,,,,,,,,,,6,4,4,,,,,,,4,4 +GSw_LfillGas,,,,,,,,,,,,,,,,,,,,,,,0,,0,,,,,,,, +GSw_Nuclear,,,,,,,,,,,,,,,,,,,,,,,0,,0,,,,,,,, +GSw_OpRes,,,,,,,,,,,,,,,,,,,,,,,0,,0,,,,,,,, +GSw_StartCost,,,,,,,,,,,,,,,,,,,,,,,0,0,0,,,,,,,0,0 +GSw_H2,,,,,,,,,,,,,,,,,,,,,,,,0,0,,,,,,,0,0 +GSw_H2_PTC,,,,,,,,,,,,,,,,,,,,,,,,0,0,,,,,,,0,0 +GSw_H2Combustion,,,,,,,,,,,,,,,,,,,,,,,,,0,,,,,,,, +GSw_PRM_StressThresholdMetrics,,,,,,,,,,,,,,,,,,,,,,,,,,NEUE/LOLH/LOLE/LOLD/duration/depth,,,,,,, +GSw_DRShed,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,, +GSw_MGA_CostDelta,,,,,,,,,,,,,,,,,,,,,,,,,,,,0.01,,,,, +GSw_LoadSiteCF,,,,,,,,,,,,,,,,,,,,,,,,,,,,,0.95,,,, +GSw_OffshoreZones,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,, +GSw_OffshoreBackbone,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,, +GSw_OffshoreBackflow,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,, +pras_agg_ogs_lfillgas,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,, +pras_existing_unit_size,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,0,, +pras_scheduled_outage,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,0,, +pras_unitsize_source,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,r2x,, +pras_vre_combine,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,, +pras_samples,,,,,,10,10,10,,,,,,,,,,,,,,,,10,10,,,,,,,10,10 +GSw_FINITO_Link,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,1 +finito_dir,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,/projects/futuregrid/mmowers/FINITO,/projects/futuregrid/mmowers/FINITO +finito_case,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,BAUfast_reg From 48b4a061920b5ed714139b546d3606debd52bece Mon Sep 17 00:00:00 2001 From: mmowers Date: Wed, 5 Aug 2026 09:48:38 -0600 Subject: [PATCH 39/78] Fix bug: Add missing .l suffix --- reeds/core/terminus/report.gms | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/reeds/core/terminus/report.gms b/reeds/core/terminus/report.gms index a14975557..1e1478f4e 100644 --- a/reeds/core/terminus/report.gms +++ b/reeds/core/terminus/report.gms @@ -503,8 +503,8 @@ $ifthene.finitobioprice Sw_FINITO_Link == 1 * here we take the weighted average of prices across biomass products used for power repbioprice(r,t)$[tmodel_new(t)$(not tfuel(t))] = 1/(obj_scale) * 1/(pvf_onm(t)) * deflator('2018') * - sum{(i,v,bs), USE_BS_REEDS(i,v,bs,r,t) * eq_supplydemand_bs.m(bs,r,t) } - / sum{(i,v,bs), USE_BS_REEDS(i,v,bs,r,t) } + sum{(i,v,bs), USE_BS_REEDS.l(i,v,bs,r,t) * eq_supplydemand_bs.m(bs,r,t) } + / sum{(i,v,bs), USE_BS_REEDS.l(i,v,bs,r,t) } ; $endif.finitobioprice From 3f6e960ebdd844de67b0c8ec3db571eb7ab912bd Mon Sep 17 00:00:00 2001 From: mmowers Date: Wed, 5 Aug 2026 10:17:32 -0600 Subject: [PATCH 40/78] Fix FINITO gas price aggregation bug in report.gms --- reeds/core/terminus/report.gms | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/reeds/core/terminus/report.gms b/reeds/core/terminus/report.gms index 1e1478f4e..45255d0a1 100644 --- a/reeds/core/terminus/report.gms +++ b/reeds/core/terminus/report.gms @@ -560,7 +560,8 @@ $ifthene.finitogasprice Sw_FINITO_Link == 1 repgasprice_finito(cendiv,h,t)$[tmodel_new(t)$(not tfuel(t))] = * citygate price of natural gas deflator('2018') * [ - ( 1/(obj_scale) * 1/(pvf_onm(t)) * smax{(cfp,st)$r_st(r,st), eq_supplydemand_cf.M(cfp,'NG',st,h,t) } / hours(h) ) +* (st_cendiv is not carried into report.gms's restart, so derive st-in-cendiv from r_st + r_cendiv) + ( 1/(obj_scale) * 1/(pvf_onm(t)) * smax{(cfp,st)$sum{r$[r_st(r,st)$r_cendiv(r,cendiv)],1}, eq_supplydemand_cf.M(cfp,'NG',st,h,t) } / hours(h) ) * electric-sector markup for natural gas * TODO: activate after FINITO pricing PR is merged * + smax{(cfp,cendiv)$[r_cendiv(r,cendiv)$gasp(cfp)$map_cf_fe(cfp,'NG')$valcft(cfp,t)],cf_markup(cfp,'NG','Electric_Power',cendiv,t)} From 7ee81669b3c7eb176c541ba6faf88564c06abf5f Mon Sep 17 00:00:00 2001 From: mmowers Date: Wed, 5 Aug 2026 17:32:56 -0600 Subject: [PATCH 41/78] Fix divide by zero in ReEDS-FINITO repbioprice output --- reeds/core/terminus/report.gms | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/reeds/core/terminus/report.gms b/reeds/core/terminus/report.gms index 45255d0a1..aabbee7fb 100644 --- a/reeds/core/terminus/report.gms +++ b/reeds/core/terminus/report.gms @@ -501,8 +501,8 @@ repbioprice(r,t)$[tmodel_new(t)$tfuel(t)] = max{0, smax{bioclass$BIOUSED.l(biocl * when running linked model use FINITO biomass clearing prices $ifthene.finitobioprice Sw_FINITO_Link == 1 * here we take the weighted average of prices across biomass products used for power -repbioprice(r,t)$[tmodel_new(t)$(not tfuel(t))] = - 1/(obj_scale) * 1/(pvf_onm(t)) * deflator('2018') * +repbioprice(r,t)$[tmodel_new(t)$(not tfuel(t))$sum{(i,v,bs), USE_BS_REEDS.l(i,v,bs,r,t) }] = + 1/(obj_scale) * 1/(pvf_onm(t)) * deflator('2018') * sum{(i,v,bs), USE_BS_REEDS.l(i,v,bs,r,t) * eq_supplydemand_bs.m(bs,r,t) } / sum{(i,v,bs), USE_BS_REEDS.l(i,v,bs,r,t) } ; From ac23719e1251022b933c2f412fba6dffa75a9a97 Mon Sep 17 00:00:00 2001 From: mmowers Date: Thu, 6 Aug 2026 17:05:04 -0600 Subject: [PATCH 42/78] Fix repgasprice_finito weighting --- reeds/core/terminus/report.gms | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reeds/core/terminus/report.gms b/reeds/core/terminus/report.gms index aabbee7fb..2de7c94d2 100644 --- a/reeds/core/terminus/report.gms +++ b/reeds/core/terminus/report.gms @@ -574,7 +574,7 @@ $endif.finitogasprice * when linked, overwrite ReEDS values with gas prices from FINITO * for any years that aren't using the ReEDS supply curves repgasprice(cendiv,t)$[tmodel_new(t)$(not tfuel(t))] = - sum{h, hours(h) * repgasprice_finito(cendiv,h,t) } + sum{h, hours(h) * repgasprice_finito(cendiv,h,t) } / sum{h, hours(h) } ; repgasprice_r(r,t)$[(Sw_GasCurve = 0 or Sw_GasCurve = 2)$tmodel_new(t)$tfuel(t)] = From d5c230ea27111583a7bed27d4a81b6447caae2b4 Mon Sep 17 00:00:00 2001 From: mmowers Date: Thu, 6 Aug 2026 17:07:46 -0600 Subject: [PATCH 43/78] Fix repgasprice overwrite for finito-linked years --- reeds/core/terminus/report.gms | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reeds/core/terminus/report.gms b/reeds/core/terminus/report.gms index 2de7c94d2..28839219a 100644 --- a/reeds/core/terminus/report.gms +++ b/reeds/core/terminus/report.gms @@ -590,7 +590,7 @@ repgasprice_r(r,t)$[(Sw_GasCurve = 1)$tmodel_new(t)$tfuel(t)] = + smax(fuelbin$VGASBINQ_NATIONAL.l(fuelbin,t), gasbinp_national(fuelbin,t) ) ) ; -repgasprice(cendiv,t)$[((Sw_GasCurve = 1) or (Sw_FINITO_Link = 1))$tmodel_new(t)$repgasquant(cendiv,t)] = +repgasprice(cendiv,t)$[((Sw_GasCurve = 1) or (Sw_FINITO_Link = 1))$tmodel_new(t)$repgasquant(cendiv,t)$tfuel(t)] = sum{(i,r)$r_cendiv(r,cendiv), repgasprice_r(r,t) * repgasquant_irt(i,r,t) } / repgasquant(cendiv,t) ; repgasprice_nat(t)$[tmodel_new(t)$sum{cendiv, repgasquant(cendiv,t) }] = From 49b2f31c95efd5f0a9a2877a735124c5bb22dc98 Mon Sep 17 00:00:00 2001 From: mmowers Date: Thu, 6 Aug 2026 21:31:44 -0600 Subject: [PATCH 44/78] Fix ng price in finito-linked runs by using the correct finito constraint marginal --- reeds/core/terminus/report.gms | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/reeds/core/terminus/report.gms b/reeds/core/terminus/report.gms index 28839219a..9df52cf95 100644 --- a/reeds/core/terminus/report.gms +++ b/reeds/core/terminus/report.gms @@ -554,16 +554,14 @@ repgasprice(cendiv,t)$[(Sw_GasCurve = 2)$tmodel_new(t)$repgasquant(cendiv,t)$tfu } / (repgasquant(cendiv,t) * 1e9) ; -* gas price by timeslice when linked with FINITO (see similar calculation in finito_report.gms) [$2004/MMBtu] -* We maybe should be taking weighted averages instead of max across regions and categories (pool vs roi etc.) +* gas price when linked with FINITO [$2004/MMBtu] $ifthene.finitogasprice Sw_FINITO_Link == 1 repgasprice_finito(cendiv,h,t)$[tmodel_new(t)$(not tfuel(t))] = * citygate price of natural gas deflator('2018') * [ -* (st_cendiv is not carried into report.gms's restart, so derive st-in-cendiv from r_st + r_cendiv) - ( 1/(obj_scale) * 1/(pvf_onm(t)) * smax{(cfp,st)$sum{r$[r_st(r,st)$r_cendiv(r,cendiv)],1}, eq_supplydemand_cf.M(cfp,'NG',st,h,t) } / hours(h) ) + ( 1/(obj_scale) * 1/(pvf_onm(t)) * eq_supplydemand_fsc.m('NG',cendiv,t) ) * electric-sector markup for natural gas -* TODO: activate after FINITO pricing PR is merged +* TODO: activate after FINITO pricing PR is merged (note: the markup below is cf-based and needs rework since NG is fe_fsc, not fe_cf) * + smax{(cfp,cendiv)$[r_cendiv(r,cendiv)$gasp(cfp)$map_cf_fe(cfp,'NG')$valcft(cfp,t)],cf_markup(cfp,'NG','Electric_Power',cendiv,t)} ] ; From d899f5fbe45dc1eb7c858ab47266c516854dbb1b Mon Sep 17 00:00:00 2001 From: mmowers Date: Thu, 6 Aug 2026 22:07:40 -0600 Subject: [PATCH 45/78] Add finito_load_check.py --- reeds/resource_adequacy/finito_load_check.py | 384 +++++++++++++++++++ 1 file changed, 384 insertions(+) create mode 100644 reeds/resource_adequacy/finito_load_check.py diff --git a/reeds/resource_adequacy/finito_load_check.py b/reeds/resource_adequacy/finito_load_check.py new file mode 100644 index 000000000..ba52c0293 --- /dev/null +++ b/reeds/resource_adequacy/finito_load_check.py @@ -0,0 +1,384 @@ +#%% Notes +""" +Diagnostic for the FINITO load coupling in a linked ReEDS-FINITO run. + +For a given run it reconstructs and compares the four load quantities that flow +through the coupling, at hourly/regional resolution, for every FINITO model year: + + 1. original - exogenous ReEDS load BEFORE FINITO reference removal + (reconstructed as load.h5 + reference; see note below) + 2. reference - flat 2018 baseline industrial load that hourly_load.py removes + (from finito/inputs_case/load_finito.csv, busbar) + 3. endogenous - FINITO's optimized industrial load added back in the model + (load_finito_rt / USE_ELE_FINITO from the handoff GDX), mapped + to hours exactly as reeds/resource_adequacy/prep_data.py does + 4. final - load PRAS/RA sees = load.h5 + endogenous + +It then (a) runs correctness checks (NaNs, region coverage, the accounting +identity, clipping evidence, base-year calibration, magnitude sanity), (b) writes +hourly regional CSVs for the representative weather year for items 1-3, and (c) +makes charts (annual system-wide for all four, net adjustment, duration curve, +diurnal shape, and auto-ranked regional spotlights). + +IMPORTANT - these are NOT a subtract-then-add-back identity: the reference removed +is a flat 2018 baseline, the endogenous load added is FINITO's shaped, optimized +demand. "original" is therefore RECONSTRUCTED from load.h5 + reference, so the +identity load.h5 == original - reference is definitional; the load-bearing checks +are on endogenous (independent, from the GDX), on `final`, and on the magnitude +relationship between reference and endogenous. + +This diagnostic REUSES the production code paths (hourly_load.get_hourly_finito_load +for the reference, and prep_data's exact endogenous mapping) so it validates the +real functions rather than a parallel reimplementation. + +Run: python reeds/resource_adequacy/finito_load_check.py +""" + +#%%### USER SWITCHES ########################################################### +# Path to the linked run to check +RUN_PATH = '/kfs2/projects/futuregrid/mmowers/ReEDS/runs/v20260722_133722_FINITO' + +# Weather year the representative periods are drawn from (GSw_HourlyWeatherYears); +# used for the hourly CSV exports and the single-year charts. +REP_WEATHER_YEAR = 2012 + +# FINITO model years to process. None = auto-detect (solved years with a handoff +# GDX that are >= first_year_finito). Or provide a list, e.g. [2030, 2050]. +MODEL_YEARS = None + +# Model year to spotlight in the single-year charts (duration curve, diurnal, +# regional bars). None = latest processed FINITO year. +SPOTLIGHT_YEAR = None + +# Model years to write hourly regional CSVs for. None = all processed years. +CSV_MODEL_YEARS = None + +# Number of regions to spotlight in the regional net-adjustment chart. +N_SPOTLIGHT_REGIONS = 12 + +# Output directory. None = /outputs/finito_load_check +OUTPUT_DIR = None + +# Whether to render charts (set False for a checks-only, CSV-only run). +MAKE_PLOTS = True +############################################################################### + +#%% Imports +import os +import sys +from pathlib import Path + +# make `reeds` importable when run as a standalone script from the repo +_REPO_ROOT = str(Path(__file__).resolve().parents[2]) +if _REPO_ROOT not in sys.path: + sys.path.insert(0, _REPO_ROOT) + +import gdxpds # noqa: E402 (import before pandas on Linux to avoid a lib conflict) +import numpy as np # noqa: E402 +import pandas as pd # noqa: E402 +import reeds # noqa: E402 +from reeds.input_processing.hourly_load import get_hourly_finito_load # noqa: E402 + +if MAKE_PLOTS: + import matplotlib as mpl + mpl.use('Agg') + import matplotlib.pyplot as plt + +# ---- Okabe-Ito, a pre-validated colorblind-safe categorical palette ---------- +# Assigned in fixed order by the role each series plays (never cycled). +COLORS = { + 'original': '#0072B2', # blue - total, pre-adjustment + 'reference': '#D55E00', # vermillion- removed + 'endogenous': '#009E73', # green - added + 'final': '#000000', # black - total, post-adjustment +} +DIVERGE_POS = '#009E73' # green - net addition +DIVERGE_NEG = '#D55E00' # vermillion - net removal +GRID = '#D9D9D9' +INK = '#222222' +MUTED = '#888888' + + +#%% Helpers +def _style_axes(ax): + ax.grid(True, color=GRID, linewidth=0.6, zorder=0) + ax.set_axisbelow(True) + for s in ('top', 'right'): + ax.spines[s].set_visible(False) + for s in ('left', 'bottom'): + ax.spines[s].set_color(MUTED) + ax.tick_params(colors=INK, labelsize=9) + return ax + + +def build_h_dt_szn(inputs_case): + """Rep-timeslice -> full multi-weather-year timestamp map (mirrors prep_data).""" + h_dt_szn = pd.read_csv(os.path.join(inputs_case, 'rep', 'h_dt_szn.csv')) + hmap_allyrs = pd.read_csv( + os.path.join(inputs_case, 'rep', 'hmap_allyrs.csv'), low_memory=False) + h_dt_szn = h_dt_szn.set_index(['year', 'hour']) + h_dt_szn['timestamp'] = pd.to_datetime( + h_dt_szn.index.map(hmap_allyrs.set_index(['year', 'hour'])['*timestamp'])) + return h_dt_szn.reset_index().set_index('timestamp') + + +def map_endogenous(gdx_file, t, h_dt_szn, ref_columns, tz): + """FINITO endogenous load (load_finito_rt) mapped to hours. + + Mirrors reeds/resource_adequacy/prep_data.py lines ~158-177 exactly. + """ + gdxreeds = gdxpds.to_dataframes(gdx_file) + lf = gdxreeds['load_finito_rt'].rename(columns={'allh': 'h', 'Value': 'load_MW'}) + lf['t'] = lf['t'].astype(int) + load_finito = lf.loc[lf.t == t].drop('t', axis=1).copy() + load_finito = pd.merge( + load_finito, h_dt_szn.reset_index(), on='h', how='outer' + )[['timestamp', 'r', 'load_MW']].rename(columns={'timestamp': 'datetime'}) + load_finito = load_finito.pivot(index=['datetime'], columns='r', values='load_MW') + load_finito.index = load_finito.index.tz_convert(tz) + load_finito = load_finito.reindex(columns=ref_columns, fill_value=0).fillna(0) + return load_finito + + +#%% Main +def main(run_path=RUN_PATH): + inputs_case = os.path.join(run_path, 'inputs_case') + outdir = OUTPUT_DIR or os.path.join(run_path, 'outputs', 'finito_load_check') + os.makedirs(outdir, exist_ok=True) + + sw = reeds.io.get_switches(run_path) + if not int(sw.GSw_FINITO_Link): + raise SystemExit('This run is not a linked FINITO run (GSw_FINITO_Link=0).') + scalars = reeds.io.get_scalars(inputs_case) + distloss = float(scalars['distloss']) + first_year_finito = int(sw.first_year_finito) + + # --- exogenous load seen by ReEDS (post-removal); this IS load.h5 ----------- + load = reeds.io.read_file(os.path.join(inputs_case, 'load.h5'), parse_timestamps=True) + tz = load.index.get_level_values('datetime').tz + regions = list(load.columns) + + # --- reference (flat 2018 baseline), busbar; mirrors remove_finito_load ----- + reference = (get_hourly_finito_load(inputs_case) / (1 - distloss)) + reference = reference.reindex(columns=regions, fill_value=0) + + h_dt_szn = build_h_dt_szn(inputs_case) + + # --- which model years to process ------------------------------------------ + handoff = os.path.join(run_path, 'handoff', 'reeds_data') + avail = sorted(int(f.split('_')[-1].split('.')[0]) + for f in os.listdir(handoff) if f.startswith('reeds_data_')) + years = MODEL_YEARS or [y for y in avail if y >= first_year_finito] + csv_years = CSV_MODEL_YEARS or years + spotlight = SPOTLIGHT_YEAR or years[-1] + + print(f'FINITO load check | run: {os.path.basename(run_path)}') + print(f' regions: {len(regions)} | distloss: {distloss} | ' + f'first_year_finito: {first_year_finito}') + print(f' model years: {years}\n') + + # accumulate lightweight aggregates for charts; keep only rep-WY slices hourly + annual = {k: {} for k in ['original', 'reference', 'endogenous', 'final']} + regional_net = {} # {year: Series(region -> net TWh, rep WY)} + wy_slices = {} # {year: {item: DataFrame(rep-WY hours x regions)}} + issues = [] + + for t in years: + load_year = load.loc[t] # hourly, all weather years + ref_t = reference.loc[t] if t in reference.index else pd.Series(0.0, index=regions) + endo = map_endogenous( + os.path.join(handoff, f'reeds_data_{t}.gdx'), t, h_dt_szn, regions, tz) + # align endogenous to load_year's index (rep periods broadcast to all WYs) + endo = endo.reindex(load_year.index).fillna(0) + + orig = load_year.add(ref_t, axis=1) # reconstruct pre-removal load + final = load_year.add(endo) # what PRAS/RA sees + + # ---- correctness checks ---- + for name, df in [('original', orig), ('reference', None), + ('endogenous', endo), ('final', final)]: + if df is not None and df.isnull().any().any(): + issues.append(f'{t}: NaNs in {name} ' + f'({int(df.isnull().sum().sum())} cells)') + miss = [r for r in regions if r not in endo.columns] + if miss: + issues.append(f'{t}: endogenous missing regions {miss}') + if (final < 0).any().any(): + issues.append(f'{t}: final load < 0 somewhere (unexpected)') + # magnitude relationship: reference removed vs endogenous added + ref_twh = ref_t.sum() * 8760 / 1e6 + endo_twh = endo.loc[endo.index.get_level_values('datetime').year + == REP_WEATHER_YEAR].sum().sum() / 1e6 + + # ---- aggregates (rep weather year for a coherent annual comparison) ---- + wy = REP_WEATHER_YEAR + def wyslice(df): + return df.loc[df.index.get_level_values('datetime').year == wy] \ + .reset_index('year', drop=True) if 'year' in df.index.names \ + else df.loc[df.index.year == wy] + o_wy, f_wy, e_wy = wyslice(orig), wyslice(final), wyslice(endo) + r_wy = o_wy * 0 + ref_t # flat reference over rep-WY hours + annual['original'][t] = o_wy.sum().sum() / 1e6 + annual['reference'][t] = r_wy.sum().sum() / 1e6 + annual['endogenous'][t] = e_wy.sum().sum() / 1e6 + annual['final'][t] = f_wy.sum().sum() / 1e6 + regional_net[t] = (e_wy.sum() - r_wy.sum()) / 1e6 # region -> net TWh + if t in csv_years: + wy_slices[t] = {'original': o_wy, 'reference': r_wy, 'endogenous': e_wy, + 'final': f_wy} + print(f' {t}: reference={ref_twh:8.1f} TWh | ' + f'endogenous={endo_twh:8.1f} TWh | ' + f'net={(endo_twh - ref_twh):+8.1f} TWh') + + annual = pd.DataFrame(annual) + + # ---- report ---- + print('\n=== correctness checks ===') + if not issues: + print(' PASS: no NaNs, all regions present, final load >= 0.') + else: + for i in issues: + print(' ISSUE:', i) + if (annual['reference'] == 0).all(): + print(' *** FINDING: reference load is ZERO for every year -> the 2018 ' + 'industrial baseline is NOT being removed, so FINITO endogenous load ' + 'is added on top of load that still contains it (double-counting). ' + 'Check finito/inputs_case/load_finito.csv. ***') + + # ---- CSV exports (hourly, regional, rep weather year) ---- + for t in csv_years: + for item in ('original', 'reference', 'endogenous'): + fn = os.path.join(outdir, f'{item}_wy{REP_WEATHER_YEAR}_y{t}.csv') + wy_slices[t][item].round(3).to_csv(fn) + print(f'\nWrote hourly regional CSVs (wy{REP_WEATHER_YEAR}) to {outdir}') + annual.round(2).to_csv(os.path.join(outdir, 'annual_system_twh.csv')) + + if MAKE_PLOTS: + make_plots(annual, regional_net, wy_slices, spotlight, outdir) + print('Wrote charts to', outdir) + + return annual, regional_net, wy_slices + + +#%% Charts +# FINITO load is ~1-2% of total, so anywhere we'd overlay a FINITO component on +# total load we instead use a big+small two-panel pattern (shared x-axis, same +# units) that zooms the component onto its own readable scale -- never a 2nd y-axis. +def _legend(ax): + ax.legend(frameon=False, fontsize=9, loc='best') + + +def make_plots(annual, regional_net, wy_slices, spotlight, outdir): + wy = REP_WEATHER_YEAR + + # 1) system-wide annual: totals (top) + FINITO components (bottom) ----------- + fig, (a1, a2) = plt.subplots(2, 1, figsize=(7, 6), sharex=True, + gridspec_kw={'height_ratios': [2, 1]}) + for ax in (a1, a2): + _style_axes(ax) + for item in ['original', 'final']: + a1.plot(annual.index, annual[item], color=COLORS[item], linewidth=2, + marker='o', markersize=5, label=item, zorder=3) + for item in ['endogenous', 'reference']: + a2.plot(annual.index, annual[item], color=COLORS[item], linewidth=2, + marker='o', markersize=5, label=item, zorder=3) + _legend(a1) + _legend(a2) + a1.set_title(f'System-wide annual load (weather year {wy})', + color=INK, fontsize=11, loc='left') + a1.set_ylabel('total load, TWh/yr', color=INK, fontsize=9) + a2.set_ylabel('FINITO, TWh/yr', color=INK, fontsize=9) + a2.set_xlabel('model year', color=INK, fontsize=9) + a2.set_ylim(bottom=0) + fig.tight_layout() + fig.savefig(os.path.join(outdir, '1_annual_system.png'), dpi=140) + plt.close(fig) + + # 2) net FINITO adjustment (endogenous - reference), by year ---------------- + net = annual['endogenous'] - annual['reference'] + fig, ax = plt.subplots(figsize=(7, 4.2)) + _style_axes(ax) + ax.axhline(0, color=MUTED, linewidth=1) + ax.bar(net.index, net.values, width=3, + color=[DIVERGE_POS if v >= 0 else DIVERGE_NEG for v in net.values], + zorder=3) + ax.set_title('Net FINITO load adjustment (endogenous − reference)', + color=INK, fontsize=11, loc='left') + ax.set_ylabel('TWh/yr', color=INK, fontsize=9) + ax.set_xlabel('model year', color=INK, fontsize=9) + fig.tight_layout() + fig.savefig(os.path.join(outdir, '2_net_adjustment.png'), dpi=140) + plt.close(fig) + + if spotlight in wy_slices: + sl = wy_slices[spotlight] + # 3) load-duration: totals (top) + endogenous own scale (bottom) -------- + fig, (a1, a2) = plt.subplots(2, 1, figsize=(7, 6), sharex=True, + gridspec_kw={'height_ratios': [2, 1]}) + for ax in (a1, a2): + _style_axes(ax) + pct = np.arange(len(sl['original'])) / len(sl['original']) * 100 + for item in ['original', 'final']: + a1.plot(pct, np.sort(sl[item].sum(axis=1).values)[::-1] / 1e3, + color=COLORS[item], linewidth=2, label=item, zorder=3) + a2.plot(pct, np.sort(sl['endogenous'].sum(axis=1).values)[::-1] / 1e3, + color=COLORS['endogenous'], linewidth=2, label='endogenous', zorder=3) + _legend(a1) + _legend(a2) + a1.set_title(f'System load-duration curve, {spotlight} (weather year {wy})', + color=INK, fontsize=11, loc='left') + a1.set_ylabel('total, GW', color=INK, fontsize=9) + a2.set_ylabel('FINITO, GW', color=INK, fontsize=9) + a2.set_xlabel('% of hours at or above', color=INK, fontsize=9) + a2.set_ylim(bottom=0) + fig.tight_layout() + fig.savefig(os.path.join(outdir, '3_duration_curve.png'), dpi=140) + plt.close(fig) + + # 4) diurnal: totals (top) + endogenous own scale (bottom) -------------- + fig, (a1, a2) = plt.subplots(2, 1, figsize=(7, 6), sharex=True, + gridspec_kw={'height_ratios': [2, 1]}) + for ax in (a1, a2): + _style_axes(ax) + for item in ['original', 'final']: + s = sl[item].sum(axis=1) + a1.plot(s.groupby(s.index.hour).mean().index, + s.groupby(s.index.hour).mean().values / 1e3, + color=COLORS[item], linewidth=2, label=item, zorder=3) + se = sl['endogenous'].sum(axis=1) + a2.plot(se.groupby(se.index.hour).mean().index, + se.groupby(se.index.hour).mean().values / 1e3, + color=COLORS['endogenous'], linewidth=2, label='endogenous', zorder=3) + _legend(a1) + _legend(a2) + a1.set_title(f'Average diurnal system load, {spotlight} (weather year {wy})', + color=INK, fontsize=11, loc='left') + a1.set_ylabel('total, GW', color=INK, fontsize=9) + a2.set_ylabel('FINITO, GW', color=INK, fontsize=9) + a2.set_xlabel('hour of day', color=INK, fontsize=9) + a2.set_ylim(bottom=0) + fig.tight_layout() + fig.savefig(os.path.join(outdir, '4_diurnal.png'), dpi=140) + plt.close(fig) + + # 5) regional net adjustment, top-N by magnitude ------------------------ + net_r = regional_net[spotlight].reindex( + regional_net[spotlight].abs().sort_values(ascending=False).index) + net_r = net_r.head(N_SPOTLIGHT_REGIONS)[::-1] + fig, ax = plt.subplots(figsize=(7, max(4.2, 0.32 * len(net_r)))) + _style_axes(ax) + ax.axvline(0, color=MUTED, linewidth=1) + ax.barh(net_r.index, net_r.values, + color=[DIVERGE_POS if v >= 0 else DIVERGE_NEG for v in net_r.values], + zorder=3) + ax.set_title(f'Net FINITO adjustment by region, {spotlight} ' + f'(top {len(net_r)} by magnitude)', + color=INK, fontsize=11, loc='left') + ax.set_xlabel('TWh/yr (endogenous − reference)', color=INK, fontsize=9) + fig.tight_layout() + fig.savefig(os.path.join(outdir, '5_regional_net.png'), dpi=140) + plt.close(fig) + + +if __name__ == '__main__': + main() From 52cfb69a33159d19c0ec60931b44ec39b83032e2 Mon Sep 17 00:00:00 2001 From: mmowers Date: Thu, 6 Aug 2026 22:15:57 -0600 Subject: [PATCH 46/78] Add missing reference lines on finito_load_check.py --- reeds/resource_adequacy/finito_load_check.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/reeds/resource_adequacy/finito_load_check.py b/reeds/resource_adequacy/finito_load_check.py index ba52c0293..b2bec0add 100644 --- a/reeds/resource_adequacy/finito_load_check.py +++ b/reeds/resource_adequacy/finito_load_check.py @@ -321,8 +321,9 @@ def make_plots(annual, regional_net, wy_slices, spotlight, outdir): for item in ['original', 'final']: a1.plot(pct, np.sort(sl[item].sum(axis=1).values)[::-1] / 1e3, color=COLORS[item], linewidth=2, label=item, zorder=3) - a2.plot(pct, np.sort(sl['endogenous'].sum(axis=1).values)[::-1] / 1e3, - color=COLORS['endogenous'], linewidth=2, label='endogenous', zorder=3) + for item in ['endogenous', 'reference']: + a2.plot(pct, np.sort(sl[item].sum(axis=1).values)[::-1] / 1e3, + color=COLORS[item], linewidth=2, label=item, zorder=3) _legend(a1) _legend(a2) a1.set_title(f'System load-duration curve, {spotlight} (weather year {wy})', @@ -345,10 +346,11 @@ def make_plots(annual, regional_net, wy_slices, spotlight, outdir): a1.plot(s.groupby(s.index.hour).mean().index, s.groupby(s.index.hour).mean().values / 1e3, color=COLORS[item], linewidth=2, label=item, zorder=3) - se = sl['endogenous'].sum(axis=1) - a2.plot(se.groupby(se.index.hour).mean().index, - se.groupby(se.index.hour).mean().values / 1e3, - color=COLORS['endogenous'], linewidth=2, label='endogenous', zorder=3) + for item in ['endogenous', 'reference']: + s = sl[item].sum(axis=1) + a2.plot(s.groupby(s.index.hour).mean().index, + s.groupby(s.index.hour).mean().values / 1e3, + color=COLORS[item], linewidth=2, label=item, zorder=3) _legend(a1) _legend(a2) a1.set_title(f'Average diurnal system load, {spotlight} (weather year {wy})', From b2adca4a455d63086ef32529a3d00f910a02a3d1 Mon Sep 17 00:00:00 2001 From: mmowers Date: Fri, 7 Aug 2026 15:53:00 -0600 Subject: [PATCH 47/78] Fix gas price reporting for finito-linked runs --- reeds/core/terminus/report.gms | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/reeds/core/terminus/report.gms b/reeds/core/terminus/report.gms index 9df52cf95..fdfbf1f1f 100644 --- a/reeds/core/terminus/report.gms +++ b/reeds/core/terminus/report.gms @@ -575,7 +575,9 @@ repgasprice(cendiv,t)$[tmodel_new(t)$(not tfuel(t))] = sum{h, hours(h) * repgasprice_finito(cendiv,h,t) } / sum{h, hours(h) } ; -repgasprice_r(r,t)$[(Sw_GasCurve = 0 or Sw_GasCurve = 2)$tmodel_new(t)$tfuel(t)] = +*Anytime Sw_GasCurve = 0 or 2, apply repgasprice(cendiv,t) to repgasprice_r. +*Do the same for finito-linked years (not tfuel(t)). +repgasprice_r(r,t)$[((not tfuel(t)) or (Sw_GasCurve = 0 or Sw_GasCurve = 2))$tmodel_new(t)] = [sum{cendiv$r_cendiv(r,cendiv), repgasprice(cendiv,t) } ]; repgasprice_r(r,t)$[(Sw_GasCurve = 1)$tmodel_new(t)$tfuel(t)] = @@ -588,7 +590,8 @@ repgasprice_r(r,t)$[(Sw_GasCurve = 1)$tmodel_new(t)$tfuel(t)] = + smax(fuelbin$VGASBINQ_NATIONAL.l(fuelbin,t), gasbinp_national(fuelbin,t) ) ) ; -repgasprice(cendiv,t)$[((Sw_GasCurve = 1) or (Sw_FINITO_Link = 1))$tmodel_new(t)$repgasquant(cendiv,t)$tfuel(t)] = +*Now calculate the remaining repgasprice(cendiv,t) (for Sw_GasCurve = 1) +repgasprice(cendiv,t)$[(Sw_GasCurve = 1)$tmodel_new(t)$repgasquant(cendiv,t)$tfuel(t)] = sum{(i,r)$r_cendiv(r,cendiv), repgasprice_r(r,t) * repgasquant_irt(i,r,t) } / repgasquant(cendiv,t) ; repgasprice_nat(t)$[tmodel_new(t)$sum{cendiv, repgasquant(cendiv,t) }] = From 386381c022b52a9c4ee2478d415925b13cb5e85b Mon Sep 17 00:00:00 2001 From: mmowers Date: Fri, 7 Aug 2026 23:18:04 -0600 Subject: [PATCH 48/78] Convert finito electricity load from end-use to busbar --- reeds/core/solve/6_data_dump.gms | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reeds/core/solve/6_data_dump.gms b/reeds/core/solve/6_data_dump.gms index 2fb809d8f..8d1ef8937 100644 --- a/reeds/core/solve/6_data_dump.gms +++ b/reeds/core/solve/6_data_dump.gms @@ -274,7 +274,7 @@ ra_cap_loadsite(r,t)$[Sw_LoadSiteCF$val_loadsite(r)] = CAP_LOADSITE.l(r,t) ; * FINITO load $ifthene.linked_load Sw_FINITO_Link==1 * limit to representative timeslices since prep_data.py maps these to rep-period timestamps - load_finito_rt(r,h,t)$h_rep(h) = USE_ELE_FINITO.l(r,h,t) ; + load_finito_rt(r,h,t)$h_rep(h) = USE_ELE_FINITO.l(r,h,t) / (1.0 - distloss); $else.linked_load load_finito_rt(r,h,t) = 0 ; $endif.linked_load From f5babb4299a2802d08ccaeae85552aa0ecfe770a Mon Sep 17 00:00:00 2001 From: mmowers Date: Sat, 8 Aug 2026 11:04:06 -0600 Subject: [PATCH 49/78] Output annual LOAD.l --- postprocessing/bokehpivot/reeds2.py | 2 +- reeds/core/terminus/report.gms | 2 ++ reeds/core/terminus/report_params.csv | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/postprocessing/bokehpivot/reeds2.py b/postprocessing/bokehpivot/reeds2.py index 88f91503a..f8a238ded 100644 --- a/postprocessing/bokehpivot/reeds2.py +++ b/postprocessing/bokehpivot/reeds2.py @@ -3079,7 +3079,7 @@ def pre_spur(dfs, **kw): } ), ('Load (TWh)', - {'file':'load_rt', + {'file':'loadvar_rt', 'columns': ['rb', 'year', 'Load (TWh)'], 'preprocess': [ {'func': sum_over_cols, 'args': {'drop_cols': ['rb'], 'group_cols': ['year']}}, diff --git a/reeds/core/terminus/report.gms b/reeds/core/terminus/report.gms index fdfbf1f1f..c39093757 100644 --- a/reeds/core/terminus/report.gms +++ b/reeds/core/terminus/report.gms @@ -460,6 +460,8 @@ reqt_price_sys('res_marg_ann','na','ann',t)$reqt_quant_sys('res_marg_ann','na',' load_rt(r,t)$tmodel_new(t) = sum{h, hours(h) * load_exog(r,h,t) } ; +loadvar_rt(r,t)$[tmodel_new(t)] = sum{h, hours(h) * LOAD.l(r,h,t) } ; + load_stress(r,allh,t)$[tmodel_new(t)$h_stress_t(allh,t)] = LOAD.l(r,allh,t) ; co2_price(t)$tmodel_new(t) = (1 / cost_scale) * (1 / pvf_onm(t)) * eq_annual_cap.m("CO2",t) ; diff --git a/reeds/core/terminus/report_params.csv b/reeds/core/terminus/report_params.csv index 5a9bc33c9..d38c17cb1 100644 --- a/reeds/core/terminus/report_params.csv +++ b/reeds/core/terminus/report_params.csv @@ -107,6 +107,7 @@ planned_outage(i),fraction,average scheduled outage rate (h-weighted) by technol "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,,, "load_stress(r,allh,t)",MW,Timeslice load during stress periods,,, "load_frac_rt(r,t)",fraction,Fraction of LOAD in each region,,, "loadsite_cap(r,t)",MW,Capacity of flexibly sited load,,, From 99f61b88983e3bca6c2b5c802199b9c68dc12e3d Mon Sep 17 00:00:00 2001 From: bsergi Date: Tue, 11 Aug 2026 11:39:12 -0400 Subject: [PATCH 50/78] clean up old comment --- reeds/inputs.py | 1 - 1 file changed, 1 deletion(-) diff --git a/reeds/inputs.py b/reeds/inputs.py index c77279a2b..ed3ffc8da 100644 --- a/reeds/inputs.py +++ b/reeds/inputs.py @@ -948,7 +948,6 @@ def setup_finito(casedir, caseSwitches, BatchName): os._exit(1) ## Populate sets for each linked run using autopop_set.py - #autopop_args = f" -c {casedir_finito} -d {inputs_case_finito} -a {aeo_year} -s {focus_sectors} -f {caseSwitches['GSw_FixedCostSupply']} -rwf {caseSwitches['GSw_Trade_Partners']} -e {caseSwitches['GSw_ROE_EndUses']}" os.system( 'python ' + os.path.join(caseSwitches['finito_dir'], 'input_processing', 'processing', 'autopop_set.py') + f" -c {casedir_finito} -d {inputs_case_finito} --link" From 2ca82c0b516552ce0954b3153e9493020c10ae0a Mon Sep 17 00:00:00 2001 From: bsergi Date: Tue, 11 Aug 2026 14:58:00 -0400 Subject: [PATCH 51/78] generalize FINITO path in cases.csv --- cases.csv | 2 +- cases_test.csv | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cases.csv b/cases.csv index 250cdaf96..bf745ccd2 100644 --- a/cases.csv +++ b/cases.csv @@ -34,7 +34,7 @@ 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, -finito_dir,Directory with cloned FINITO repository,N/A,'/path/to/FINITO/directory', +finito_dir,Directory with cloned FINITO repository,N/A,/path/to/FINITO/directory, finito_cases_file,"extension of the FINITO cases file including all scenarios to be run, e.g., 'linked' for cases_linked.csv",N/A,linked, finito_case,Case name for FINITO in {finito_dir}/cases_linked.csv; use 'same' if the scenario name in cases_linked.csv matches the scenario name in the cases file,N/A,BAUfast, first_year_finito,First year when the FINITO module is enabled,N/A,2018, diff --git a/cases_test.csv b/cases_test.csv index 1dd0d5e5e..fdcf38c6f 100644 --- a/cases_test.csv +++ b/cases_test.csv @@ -65,5 +65,5 @@ pras_unitsize_source,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,r2x,, pras_vre_combine,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,, pras_samples,,,,,,10,10,10,,,,,,,,,,,,,,,,10,10,,,,,,,10,10 GSw_FINITO_Link,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,1 -finito_dir,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,/projects/futuregrid/mmowers/FINITO,/projects/futuregrid/mmowers/FINITO +finito_dir,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,/path/to/FINITO/directory,/path/to/FINITO/directory finito_case,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,BAUfast_reg From 9ed9a74f313be9d1cd17425253d4c7b9ebc24c7a Mon Sep 17 00:00:00 2001 From: bsergi Date: Tue, 11 Aug 2026 14:59:13 -0400 Subject: [PATCH 52/78] separate gas price reporting for supply curves and detailed fuels --- reeds/core/terminus/report.gms | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/reeds/core/terminus/report.gms b/reeds/core/terminus/report.gms index c39093757..be2d761d5 100644 --- a/reeds/core/terminus/report.gms +++ b/reeds/core/terminus/report.gms @@ -558,13 +558,20 @@ repgasprice(cendiv,t)$[(Sw_GasCurve = 2)$tmodel_new(t)$repgasquant(cendiv,t)$tfu * gas price when linked with FINITO [$2004/MMBtu] $ifthene.finitogasprice Sw_FINITO_Link == 1 -repgasprice_finito(cendiv,h,t)$[tmodel_new(t)$(not tfuel(t))] = +* approach with GSw_FixedCostSupply=1 or default supply curves +repgasprice_finito(cendiv,h,t)$[tmodel_new(t)$(not tfuel(t))$(not Sw_DetailedFuels)] = + deflator('2018') * 1/(obj_scale) * 1/(pvf_onm(t)) + * eq_supplydemand_fsc.m('NG',cendiv,t) +; + +* approach with detailed fuels representation (GSw_DetailedFuels=1) +repgasprice_finito(cendiv,h,t)$[tmodel_new(t)$(not tfuel(t))$Sw_DetailedFuels] = + deflator('2018') * 1/(obj_scale) * 1/(pvf_onm(t)) * citygate price of natural gas - deflator('2018') * [ - ( 1/(obj_scale) * 1/(pvf_onm(t)) * eq_supplydemand_fsc.m('NG',cendiv,t) ) + * [ smax{(cfp,st)$st_cendiv(st,cendiv), eq_supplydemand_cf.M(cfp,'NG',st,h,t) } / hours(h) * electric-sector markup for natural gas -* TODO: activate after FINITO pricing PR is merged (note: the markup below is cf-based and needs rework since NG is fe_fsc, not fe_cf) -* + smax{(cfp,cendiv)$[r_cendiv(r,cendiv)$gasp(cfp)$map_cf_fe(cfp,'NG')$valcft(cfp,t)],cf_markup(cfp,'NG','Electric_Power',cendiv,t)} +* 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) } ] ; $else.finitogasprice From 6ee34b8c05b2e101ad9cdabba6337ab56c3e02e5 Mon Sep 17 00:00:00 2001 From: mmowers Date: Tue, 11 Aug 2026 17:13:45 -0400 Subject: [PATCH 53/78] Remove finito_load_check.py script --- reeds/resource_adequacy/finito_load_check.py | 386 ------------------- 1 file changed, 386 deletions(-) delete mode 100644 reeds/resource_adequacy/finito_load_check.py diff --git a/reeds/resource_adequacy/finito_load_check.py b/reeds/resource_adequacy/finito_load_check.py deleted file mode 100644 index b2bec0add..000000000 --- a/reeds/resource_adequacy/finito_load_check.py +++ /dev/null @@ -1,386 +0,0 @@ -#%% Notes -""" -Diagnostic for the FINITO load coupling in a linked ReEDS-FINITO run. - -For a given run it reconstructs and compares the four load quantities that flow -through the coupling, at hourly/regional resolution, for every FINITO model year: - - 1. original - exogenous ReEDS load BEFORE FINITO reference removal - (reconstructed as load.h5 + reference; see note below) - 2. reference - flat 2018 baseline industrial load that hourly_load.py removes - (from finito/inputs_case/load_finito.csv, busbar) - 3. endogenous - FINITO's optimized industrial load added back in the model - (load_finito_rt / USE_ELE_FINITO from the handoff GDX), mapped - to hours exactly as reeds/resource_adequacy/prep_data.py does - 4. final - load PRAS/RA sees = load.h5 + endogenous - -It then (a) runs correctness checks (NaNs, region coverage, the accounting -identity, clipping evidence, base-year calibration, magnitude sanity), (b) writes -hourly regional CSVs for the representative weather year for items 1-3, and (c) -makes charts (annual system-wide for all four, net adjustment, duration curve, -diurnal shape, and auto-ranked regional spotlights). - -IMPORTANT - these are NOT a subtract-then-add-back identity: the reference removed -is a flat 2018 baseline, the endogenous load added is FINITO's shaped, optimized -demand. "original" is therefore RECONSTRUCTED from load.h5 + reference, so the -identity load.h5 == original - reference is definitional; the load-bearing checks -are on endogenous (independent, from the GDX), on `final`, and on the magnitude -relationship between reference and endogenous. - -This diagnostic REUSES the production code paths (hourly_load.get_hourly_finito_load -for the reference, and prep_data's exact endogenous mapping) so it validates the -real functions rather than a parallel reimplementation. - -Run: python reeds/resource_adequacy/finito_load_check.py -""" - -#%%### USER SWITCHES ########################################################### -# Path to the linked run to check -RUN_PATH = '/kfs2/projects/futuregrid/mmowers/ReEDS/runs/v20260722_133722_FINITO' - -# Weather year the representative periods are drawn from (GSw_HourlyWeatherYears); -# used for the hourly CSV exports and the single-year charts. -REP_WEATHER_YEAR = 2012 - -# FINITO model years to process. None = auto-detect (solved years with a handoff -# GDX that are >= first_year_finito). Or provide a list, e.g. [2030, 2050]. -MODEL_YEARS = None - -# Model year to spotlight in the single-year charts (duration curve, diurnal, -# regional bars). None = latest processed FINITO year. -SPOTLIGHT_YEAR = None - -# Model years to write hourly regional CSVs for. None = all processed years. -CSV_MODEL_YEARS = None - -# Number of regions to spotlight in the regional net-adjustment chart. -N_SPOTLIGHT_REGIONS = 12 - -# Output directory. None = /outputs/finito_load_check -OUTPUT_DIR = None - -# Whether to render charts (set False for a checks-only, CSV-only run). -MAKE_PLOTS = True -############################################################################### - -#%% Imports -import os -import sys -from pathlib import Path - -# make `reeds` importable when run as a standalone script from the repo -_REPO_ROOT = str(Path(__file__).resolve().parents[2]) -if _REPO_ROOT not in sys.path: - sys.path.insert(0, _REPO_ROOT) - -import gdxpds # noqa: E402 (import before pandas on Linux to avoid a lib conflict) -import numpy as np # noqa: E402 -import pandas as pd # noqa: E402 -import reeds # noqa: E402 -from reeds.input_processing.hourly_load import get_hourly_finito_load # noqa: E402 - -if MAKE_PLOTS: - import matplotlib as mpl - mpl.use('Agg') - import matplotlib.pyplot as plt - -# ---- Okabe-Ito, a pre-validated colorblind-safe categorical palette ---------- -# Assigned in fixed order by the role each series plays (never cycled). -COLORS = { - 'original': '#0072B2', # blue - total, pre-adjustment - 'reference': '#D55E00', # vermillion- removed - 'endogenous': '#009E73', # green - added - 'final': '#000000', # black - total, post-adjustment -} -DIVERGE_POS = '#009E73' # green - net addition -DIVERGE_NEG = '#D55E00' # vermillion - net removal -GRID = '#D9D9D9' -INK = '#222222' -MUTED = '#888888' - - -#%% Helpers -def _style_axes(ax): - ax.grid(True, color=GRID, linewidth=0.6, zorder=0) - ax.set_axisbelow(True) - for s in ('top', 'right'): - ax.spines[s].set_visible(False) - for s in ('left', 'bottom'): - ax.spines[s].set_color(MUTED) - ax.tick_params(colors=INK, labelsize=9) - return ax - - -def build_h_dt_szn(inputs_case): - """Rep-timeslice -> full multi-weather-year timestamp map (mirrors prep_data).""" - h_dt_szn = pd.read_csv(os.path.join(inputs_case, 'rep', 'h_dt_szn.csv')) - hmap_allyrs = pd.read_csv( - os.path.join(inputs_case, 'rep', 'hmap_allyrs.csv'), low_memory=False) - h_dt_szn = h_dt_szn.set_index(['year', 'hour']) - h_dt_szn['timestamp'] = pd.to_datetime( - h_dt_szn.index.map(hmap_allyrs.set_index(['year', 'hour'])['*timestamp'])) - return h_dt_szn.reset_index().set_index('timestamp') - - -def map_endogenous(gdx_file, t, h_dt_szn, ref_columns, tz): - """FINITO endogenous load (load_finito_rt) mapped to hours. - - Mirrors reeds/resource_adequacy/prep_data.py lines ~158-177 exactly. - """ - gdxreeds = gdxpds.to_dataframes(gdx_file) - lf = gdxreeds['load_finito_rt'].rename(columns={'allh': 'h', 'Value': 'load_MW'}) - lf['t'] = lf['t'].astype(int) - load_finito = lf.loc[lf.t == t].drop('t', axis=1).copy() - load_finito = pd.merge( - load_finito, h_dt_szn.reset_index(), on='h', how='outer' - )[['timestamp', 'r', 'load_MW']].rename(columns={'timestamp': 'datetime'}) - load_finito = load_finito.pivot(index=['datetime'], columns='r', values='load_MW') - load_finito.index = load_finito.index.tz_convert(tz) - load_finito = load_finito.reindex(columns=ref_columns, fill_value=0).fillna(0) - return load_finito - - -#%% Main -def main(run_path=RUN_PATH): - inputs_case = os.path.join(run_path, 'inputs_case') - outdir = OUTPUT_DIR or os.path.join(run_path, 'outputs', 'finito_load_check') - os.makedirs(outdir, exist_ok=True) - - sw = reeds.io.get_switches(run_path) - if not int(sw.GSw_FINITO_Link): - raise SystemExit('This run is not a linked FINITO run (GSw_FINITO_Link=0).') - scalars = reeds.io.get_scalars(inputs_case) - distloss = float(scalars['distloss']) - first_year_finito = int(sw.first_year_finito) - - # --- exogenous load seen by ReEDS (post-removal); this IS load.h5 ----------- - load = reeds.io.read_file(os.path.join(inputs_case, 'load.h5'), parse_timestamps=True) - tz = load.index.get_level_values('datetime').tz - regions = list(load.columns) - - # --- reference (flat 2018 baseline), busbar; mirrors remove_finito_load ----- - reference = (get_hourly_finito_load(inputs_case) / (1 - distloss)) - reference = reference.reindex(columns=regions, fill_value=0) - - h_dt_szn = build_h_dt_szn(inputs_case) - - # --- which model years to process ------------------------------------------ - handoff = os.path.join(run_path, 'handoff', 'reeds_data') - avail = sorted(int(f.split('_')[-1].split('.')[0]) - for f in os.listdir(handoff) if f.startswith('reeds_data_')) - years = MODEL_YEARS or [y for y in avail if y >= first_year_finito] - csv_years = CSV_MODEL_YEARS or years - spotlight = SPOTLIGHT_YEAR or years[-1] - - print(f'FINITO load check | run: {os.path.basename(run_path)}') - print(f' regions: {len(regions)} | distloss: {distloss} | ' - f'first_year_finito: {first_year_finito}') - print(f' model years: {years}\n') - - # accumulate lightweight aggregates for charts; keep only rep-WY slices hourly - annual = {k: {} for k in ['original', 'reference', 'endogenous', 'final']} - regional_net = {} # {year: Series(region -> net TWh, rep WY)} - wy_slices = {} # {year: {item: DataFrame(rep-WY hours x regions)}} - issues = [] - - for t in years: - load_year = load.loc[t] # hourly, all weather years - ref_t = reference.loc[t] if t in reference.index else pd.Series(0.0, index=regions) - endo = map_endogenous( - os.path.join(handoff, f'reeds_data_{t}.gdx'), t, h_dt_szn, regions, tz) - # align endogenous to load_year's index (rep periods broadcast to all WYs) - endo = endo.reindex(load_year.index).fillna(0) - - orig = load_year.add(ref_t, axis=1) # reconstruct pre-removal load - final = load_year.add(endo) # what PRAS/RA sees - - # ---- correctness checks ---- - for name, df in [('original', orig), ('reference', None), - ('endogenous', endo), ('final', final)]: - if df is not None and df.isnull().any().any(): - issues.append(f'{t}: NaNs in {name} ' - f'({int(df.isnull().sum().sum())} cells)') - miss = [r for r in regions if r not in endo.columns] - if miss: - issues.append(f'{t}: endogenous missing regions {miss}') - if (final < 0).any().any(): - issues.append(f'{t}: final load < 0 somewhere (unexpected)') - # magnitude relationship: reference removed vs endogenous added - ref_twh = ref_t.sum() * 8760 / 1e6 - endo_twh = endo.loc[endo.index.get_level_values('datetime').year - == REP_WEATHER_YEAR].sum().sum() / 1e6 - - # ---- aggregates (rep weather year for a coherent annual comparison) ---- - wy = REP_WEATHER_YEAR - def wyslice(df): - return df.loc[df.index.get_level_values('datetime').year == wy] \ - .reset_index('year', drop=True) if 'year' in df.index.names \ - else df.loc[df.index.year == wy] - o_wy, f_wy, e_wy = wyslice(orig), wyslice(final), wyslice(endo) - r_wy = o_wy * 0 + ref_t # flat reference over rep-WY hours - annual['original'][t] = o_wy.sum().sum() / 1e6 - annual['reference'][t] = r_wy.sum().sum() / 1e6 - annual['endogenous'][t] = e_wy.sum().sum() / 1e6 - annual['final'][t] = f_wy.sum().sum() / 1e6 - regional_net[t] = (e_wy.sum() - r_wy.sum()) / 1e6 # region -> net TWh - if t in csv_years: - wy_slices[t] = {'original': o_wy, 'reference': r_wy, 'endogenous': e_wy, - 'final': f_wy} - print(f' {t}: reference={ref_twh:8.1f} TWh | ' - f'endogenous={endo_twh:8.1f} TWh | ' - f'net={(endo_twh - ref_twh):+8.1f} TWh') - - annual = pd.DataFrame(annual) - - # ---- report ---- - print('\n=== correctness checks ===') - if not issues: - print(' PASS: no NaNs, all regions present, final load >= 0.') - else: - for i in issues: - print(' ISSUE:', i) - if (annual['reference'] == 0).all(): - print(' *** FINDING: reference load is ZERO for every year -> the 2018 ' - 'industrial baseline is NOT being removed, so FINITO endogenous load ' - 'is added on top of load that still contains it (double-counting). ' - 'Check finito/inputs_case/load_finito.csv. ***') - - # ---- CSV exports (hourly, regional, rep weather year) ---- - for t in csv_years: - for item in ('original', 'reference', 'endogenous'): - fn = os.path.join(outdir, f'{item}_wy{REP_WEATHER_YEAR}_y{t}.csv') - wy_slices[t][item].round(3).to_csv(fn) - print(f'\nWrote hourly regional CSVs (wy{REP_WEATHER_YEAR}) to {outdir}') - annual.round(2).to_csv(os.path.join(outdir, 'annual_system_twh.csv')) - - if MAKE_PLOTS: - make_plots(annual, regional_net, wy_slices, spotlight, outdir) - print('Wrote charts to', outdir) - - return annual, regional_net, wy_slices - - -#%% Charts -# FINITO load is ~1-2% of total, so anywhere we'd overlay a FINITO component on -# total load we instead use a big+small two-panel pattern (shared x-axis, same -# units) that zooms the component onto its own readable scale -- never a 2nd y-axis. -def _legend(ax): - ax.legend(frameon=False, fontsize=9, loc='best') - - -def make_plots(annual, regional_net, wy_slices, spotlight, outdir): - wy = REP_WEATHER_YEAR - - # 1) system-wide annual: totals (top) + FINITO components (bottom) ----------- - fig, (a1, a2) = plt.subplots(2, 1, figsize=(7, 6), sharex=True, - gridspec_kw={'height_ratios': [2, 1]}) - for ax in (a1, a2): - _style_axes(ax) - for item in ['original', 'final']: - a1.plot(annual.index, annual[item], color=COLORS[item], linewidth=2, - marker='o', markersize=5, label=item, zorder=3) - for item in ['endogenous', 'reference']: - a2.plot(annual.index, annual[item], color=COLORS[item], linewidth=2, - marker='o', markersize=5, label=item, zorder=3) - _legend(a1) - _legend(a2) - a1.set_title(f'System-wide annual load (weather year {wy})', - color=INK, fontsize=11, loc='left') - a1.set_ylabel('total load, TWh/yr', color=INK, fontsize=9) - a2.set_ylabel('FINITO, TWh/yr', color=INK, fontsize=9) - a2.set_xlabel('model year', color=INK, fontsize=9) - a2.set_ylim(bottom=0) - fig.tight_layout() - fig.savefig(os.path.join(outdir, '1_annual_system.png'), dpi=140) - plt.close(fig) - - # 2) net FINITO adjustment (endogenous - reference), by year ---------------- - net = annual['endogenous'] - annual['reference'] - fig, ax = plt.subplots(figsize=(7, 4.2)) - _style_axes(ax) - ax.axhline(0, color=MUTED, linewidth=1) - ax.bar(net.index, net.values, width=3, - color=[DIVERGE_POS if v >= 0 else DIVERGE_NEG for v in net.values], - zorder=3) - ax.set_title('Net FINITO load adjustment (endogenous − reference)', - color=INK, fontsize=11, loc='left') - ax.set_ylabel('TWh/yr', color=INK, fontsize=9) - ax.set_xlabel('model year', color=INK, fontsize=9) - fig.tight_layout() - fig.savefig(os.path.join(outdir, '2_net_adjustment.png'), dpi=140) - plt.close(fig) - - if spotlight in wy_slices: - sl = wy_slices[spotlight] - # 3) load-duration: totals (top) + endogenous own scale (bottom) -------- - fig, (a1, a2) = plt.subplots(2, 1, figsize=(7, 6), sharex=True, - gridspec_kw={'height_ratios': [2, 1]}) - for ax in (a1, a2): - _style_axes(ax) - pct = np.arange(len(sl['original'])) / len(sl['original']) * 100 - for item in ['original', 'final']: - a1.plot(pct, np.sort(sl[item].sum(axis=1).values)[::-1] / 1e3, - color=COLORS[item], linewidth=2, label=item, zorder=3) - for item in ['endogenous', 'reference']: - a2.plot(pct, np.sort(sl[item].sum(axis=1).values)[::-1] / 1e3, - color=COLORS[item], linewidth=2, label=item, zorder=3) - _legend(a1) - _legend(a2) - a1.set_title(f'System load-duration curve, {spotlight} (weather year {wy})', - color=INK, fontsize=11, loc='left') - a1.set_ylabel('total, GW', color=INK, fontsize=9) - a2.set_ylabel('FINITO, GW', color=INK, fontsize=9) - a2.set_xlabel('% of hours at or above', color=INK, fontsize=9) - a2.set_ylim(bottom=0) - fig.tight_layout() - fig.savefig(os.path.join(outdir, '3_duration_curve.png'), dpi=140) - plt.close(fig) - - # 4) diurnal: totals (top) + endogenous own scale (bottom) -------------- - fig, (a1, a2) = plt.subplots(2, 1, figsize=(7, 6), sharex=True, - gridspec_kw={'height_ratios': [2, 1]}) - for ax in (a1, a2): - _style_axes(ax) - for item in ['original', 'final']: - s = sl[item].sum(axis=1) - a1.plot(s.groupby(s.index.hour).mean().index, - s.groupby(s.index.hour).mean().values / 1e3, - color=COLORS[item], linewidth=2, label=item, zorder=3) - for item in ['endogenous', 'reference']: - s = sl[item].sum(axis=1) - a2.plot(s.groupby(s.index.hour).mean().index, - s.groupby(s.index.hour).mean().values / 1e3, - color=COLORS[item], linewidth=2, label=item, zorder=3) - _legend(a1) - _legend(a2) - a1.set_title(f'Average diurnal system load, {spotlight} (weather year {wy})', - color=INK, fontsize=11, loc='left') - a1.set_ylabel('total, GW', color=INK, fontsize=9) - a2.set_ylabel('FINITO, GW', color=INK, fontsize=9) - a2.set_xlabel('hour of day', color=INK, fontsize=9) - a2.set_ylim(bottom=0) - fig.tight_layout() - fig.savefig(os.path.join(outdir, '4_diurnal.png'), dpi=140) - plt.close(fig) - - # 5) regional net adjustment, top-N by magnitude ------------------------ - net_r = regional_net[spotlight].reindex( - regional_net[spotlight].abs().sort_values(ascending=False).index) - net_r = net_r.head(N_SPOTLIGHT_REGIONS)[::-1] - fig, ax = plt.subplots(figsize=(7, max(4.2, 0.32 * len(net_r)))) - _style_axes(ax) - ax.axvline(0, color=MUTED, linewidth=1) - ax.barh(net_r.index, net_r.values, - color=[DIVERGE_POS if v >= 0 else DIVERGE_NEG for v in net_r.values], - zorder=3) - ax.set_title(f'Net FINITO adjustment by region, {spotlight} ' - f'(top {len(net_r)} by magnitude)', - color=INK, fontsize=11, loc='left') - ax.set_xlabel('TWh/yr (endogenous − reference)', color=INK, fontsize=9) - fig.tight_layout() - fig.savefig(os.path.join(outdir, '5_regional_net.png'), dpi=140) - plt.close(fig) - - -if __name__ == '__main__': - main() From 989952ef01fe92329c6161e20f50dc5c65e60aa8 Mon Sep 17 00:00:00 2001 From: bsergi Date: Fri, 28 Aug 2026 10:52:44 -0400 Subject: [PATCH 54/78] fix some missing / to %ds% in gams --- reeds/core/solve/2_temporal_params.gms | 2 +- reeds/core/solve/3_solve_oneyear.gms | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/reeds/core/solve/2_temporal_params.gms b/reeds/core/solve/2_temporal_params.gms index 6b9c83753..a49732804 100644 --- a/reeds/core/solve/2_temporal_params.gms +++ b/reeds/core/solve/2_temporal_params.gms @@ -903,7 +903,7 @@ gasprice_adj_cendiv(cendiv,h)$(Sw_GasPriceAdjMethod = 0) = 1 ; * ----- ReEDS-FINITO temporal parameters ----- *============================================= $ifthene.linked_finito_temporal_params %GSw_FINITO_Link% ==1 -$include finito/model/linked_finito_temporal_params.gms +$include finito%ds%model%ds%linked_finito_temporal_params.gms $endif.linked_finito_temporal_params diff --git a/reeds/core/solve/3_solve_oneyear.gms b/reeds/core/solve/3_solve_oneyear.gms index c1aa2184d..d62003f29 100644 --- a/reeds/core/solve/3_solve_oneyear.gms +++ b/reeds/core/solve/3_solve_oneyear.gms @@ -293,7 +293,7 @@ $include reeds%ds%core%ds%solve%ds%5_varfix.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 +$include finito%ds%model%ds%finito_varfix.gms $endif.linked_finito_varfix *** Dump data used in calculations between solve years From e2fd61f774c886a63fee971aa53619c6fb9482d4 Mon Sep 17 00:00:00 2001 From: bsergi Date: Fri, 28 Aug 2026 11:17:46 -0400 Subject: [PATCH 55/78] move finito functions into separate script --- reeds/__init__.py | 1 + reeds/finito.py | 218 ++++++++++++++++++++ reeds/input_processing/hourly_load.py | 83 +------- reeds/input_processing/hourly_repperiods.py | 3 +- reeds/inputs.py | 54 ----- runreeds.py | 72 +------ 6 files changed, 223 insertions(+), 208 deletions(-) create mode 100644 reeds/finito.py diff --git a/reeds/__init__.py b/reeds/__init__.py index ff723dfd1..fb0b52cd8 100644 --- a/reeds/__init__.py +++ b/reeds/__init__.py @@ -5,6 +5,7 @@ from . import checks as checks from . import financials as financials +from . import finito as finito from . import inputs as inputs from . import io as io from . import log as log diff --git a/reeds/finito.py b/reeds/finito.py new file mode 100644 index 000000000..7204f5a63 --- /dev/null +++ b/reeds/finito.py @@ -0,0 +1,218 @@ +### Imports +import os +import sys +import csv +import shutil +import numpy as np +import pandas as pd +from pathlib import Path +sys.path.append(str(Path(__file__).parent.parent)) +import reeds + + +#%% =========================================================================== +### --- FUNCTIONS --- +### =========================================================================== + +# (ReEDS-FINITO) update 'df_cases' to include FINITO switches +def linked_cases(df_cases, case): + """ + Updates the cases dataframe to include FINITO-specific switches. + When a switch is duplicated in FINITO and ReEDS, then we default to + the ReEDS value. + + For the FINITO switches, the combined cases file defaults to the case-specific + 'Default Value' in cases_linked.csv first, before using the universal FINITO + 'Default Value' in cases.csv for any un-assigned switches. + """ + # check for valid finito_dir + if not os.path.isdir(df_cases[case]['finito_dir']): + raise ValueError( + f"finito_dir = {df_cases[case]['finito_dir']} is not a valid path. " + "Please ensure this path points to a cloned version of the FINITO repository. " + ) + + # define path to and read the FINITO check_inputs function + finito_check_inputs_path = os.path.join(df_cases[case]['finito_dir'], 'input_processing', 'processing') + sys.path.append(finito_check_inputs_path) + from check_inputs import check_inputs + + ### load the default values for all FINITO switches from ~\FINITO\cases.csv + df_cases_finito = pd.read_csv(os.path.join(df_cases[case]['finito_dir'],'cases.csv'), dtype=object, index_col=0) + df_cases_finito = df_cases_finito[['Choices', 'Default Value']] + + ### load the scenario-specific switches from ~\FINITO\cases_linked.csv + cases_linked_path = os.path.join(df_cases[case]['finito_dir'],f"cases_{df_cases[case]['finito_cases_file']}.csv") + df_cases_suf_finito = pd.read_csv(cases_linked_path, dtype=object, index_col=0) + ## check that case names are unique in cases_linked.csv + # grab the scenario names **exactly** as they appear in the csv file + with open(cases_linked_path, 'r', newline='') as csvfile: + reader = csv.reader(csvfile) + header = next(reader) + # find the duplicate column names and raise an error if any are found + duplicate_columns = set([x for x in header if header.count(x) > 1]) + if duplicate_columns: + raise ValueError(f"The FINITO cases_{df_cases[case]['finito_cases_file']}.csv has the following duplicate column names: {duplicate_columns}") + ### identify the FINITO case + if df_cases[case]['finito_case'] == 'same': + finito_case=case + else: + finito_case=df_cases[case]['finito_case'] + # ensures **exact** match of names between the ReEDS cases_{}.csv and the FINITO cases_linked.csv + if finito_case not in (df_cases_suf_finito.columns): + raise ValueError(f"The 'finito_case' input '{finito_case}' in the ReEDS cases file does not exist in FINITO's cases_{df_cases[case]['finito_cases_file']}.csv.") + + ### first use 'Default Value' from the FINITO cases_linked.csv to fill missing switches + if 'Default Value' in df_cases_suf_finito.columns: + df_cases_suf_finito[finito_case] = df_cases_suf_finito[finito_case].fillna(df_cases_suf_finito['Default Value']) + ### then, use 'Default Value' from the FINITO cases.csv to fill un-assigned switches + df_cases_suf_finito.drop(['Choices','Default Value'], axis='columns',inplace=True, errors='ignore') + df_cases_finito = df_cases_finito.join(df_cases_suf_finito, how='outer') + df_cases_finito[finito_case] = df_cases_finito[finito_case].fillna(df_cases_finito['Default Value']) + + #### create new dataframe for the combined ReEDS and FINITO switches + df_cases_combine = pd.concat([df_cases[case],df_cases_finito[finito_case]]) + ### drop duplicated switches, defaulting to reeds + df_cases_combine = df_cases_combine[~df_cases_combine.index.duplicated(keep='first')] + + #%% Check for incompatibility of FINITO switches + model_sectors = df_cases_finito['Default Value']['focus_sectors'].split('.') + check_inputs(case = case, df_case = df_cases_combine, model_sectors=model_sectors) + + return df_cases_combine + + +def setup_finito(casedir, caseSwitches, BatchName): + #%% Copy FINITO code folders and inputs into [casedir]/finito + # ... finito directory within the case directory + casedir_finito = os.path.join(casedir,'finito') + # ... define the inputs case directory for FINITO + inputs_case_finito = os.path.join(casedir,'finito','inputs_case') + + # copy directories + os.makedirs(casedir_finito, exist_ok=True) + shutil.copytree(os.path.join(caseSwitches['finito_dir'], 'inputs'),os.path.join(casedir,'finito', 'inputs')) + shutil.copytree(os.path.join(caseSwitches['finito_dir'], 'input_processing'),os.path.join(casedir,'finito', 'input_processing')) + shutil.copytree(os.path.join(caseSwitches['finito_dir'], 'model'),os.path.join(casedir,'finito', 'model')) + shutil.copytree(os.path.join(caseSwitches['finito_dir'], 'visualization'),os.path.join(casedir, 'finito', 'visualization')) + + # copy over the FINITO cases files + shutil.copy2(os.path.join(caseSwitches['finito_dir'], 'cases.csv'), os.path.join(casedir, 'finito')) + shutil.copy2(os.path.join(caseSwitches['finito_dir'], f"cases_{caseSwitches['finito_cases_file']}.csv"), os.path.join(casedir, 'finito')) + + #%% (GSw_Trade_PriceResponse > 0) If doing a price-responsive trade run, retrieve the reference exports/imports prices + if int(caseSwitches['GSw_Trade_PriceResponse']) > 0: + initialize_price_response_path = os.path.join(casedir_finito, 'input_processing', 'processing', 'initialize_price_response.py') + # 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) + + #%% Filter and copy all input files for each scenario + # Call FINITO copy_files.py file before starting the runs + copy_files_run = os.system( + 'python ' + os.path.join(caseSwitches['finito_dir'], 'input_processing', 'processing', 'copy_files.py') + + f" -c {casedir_finito} -d {inputs_case_finito} --link" + ) + + # Print an error message if copy_files.py encounters any issue + if copy_files_run != 0: + print("\nERROR: FINITO copy_files.py encountered an issue and did not complete successfully.") + print("Please check the console output above for details.") + print("The issue could be due to regionality, focus sector filtering, or file reading errors.") + os._exit(1) + + ## Populate sets for each linked run using autopop_set.py + os.system( + 'python ' + os.path.join(caseSwitches['finito_dir'], 'input_processing', 'processing', 'autopop_set.py') + + f" -c {casedir_finito} -d {inputs_case_finito} --link" + ) + + ## Call read_mecs_heat.py to generate heat/nonheat/feedstock ratios for FINITO Rest of Industry (ROI) + mecs_sectors = caseSwitches['focus_sectors'].replace('.', ' ') + read_mecs_path = os.path.join(caseSwitches['finito_dir'], 'input_processing', 'processing', 'mecs', 'read_mecs_heat.py') + # Collect all arguments for read_mecs_heat.py + read_mecs_args = f' -s {mecs_sectors} -d {inputs_case_finito}' + os.system('python ' + read_mecs_path + read_mecs_args) + + +#TODO: add docstrings +def get_hourly_finito_load( + inputs_case: str, +) -> pd.DataFrame: + # load reference FINITO load + inputs_case_finito = Path(inputs_case).parent / 'finito' / Path(inputs_case).name + load_finito = pd.read_csv(inputs_case_finito / "load_finito.csv") + + # reshape to match load data. with year in index and region in columns + load_finito = load_finito.melt(id_vars='r', var_name='year', value_name='load_MWh') + load_finito.year = load_finito.year.astype(int) + load_finito = load_finito.pivot(index='year', columns='r', values='load_MWh') + load_finito.columns.name = None + + # load_finito.csv is at z134 (p) resolution, so aggregate to this + # run's model regions + county2p = reeds.io.get_county2zone(GSw_ZoneSet='z134') + county2zone = reeds.io.get_county2zone(case=Path(inputs_case).parent) + p2zone = ( + pd.concat({'p': county2p, 'zone': county2zone}, axis=1) + .dropna() + .drop_duplicates() + ) + zones_per_p = p2zone.groupby('p')['zone'].nunique() + if (zones_per_p > 1).any(): + raise ValueError( + 'Cannot aggregate load_finito.csv from z134 to model regions because ' + 'these p regions span multiple model regions: ' + f'{zones_per_p.loc[zones_per_p > 1].index.tolist()}' + ) + p2zone = p2zone.set_index('p')['zone'] + 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? + hours_per_year = 8760 + load_hourly_finito = load_finito / hours_per_year + load_hourly_finito = load_hourly_finito.astype(np.float32) + + return load_hourly_finito + + +def remove_finito_load( + load_hourly: pd.DataFrame, + inputs_case: str, + distloss: float, +) -> pd.DataFrame: + + # get FINITO reference load + load_hourly_finito = get_hourly_finito_load(inputs_case) + + # Convert to busbar + load_hourly_finito = load_hourly_finito / (1 - distloss) + + # subtract FINITO reference load from ReEDS load data, + # aligning by model year (index) and region (columns) + result = load_hourly - load_hourly_finito + + # any missing region or model year in load_finito.csv shows up as NaN + if result.isnull().any().any(): + raise ValueError( + 'FINITO reference load is missing regions or years present in the ' + 'ReEDS load data; check load_finito.csv' + ) + + # Validation check: FINITO reference load should not exceed baseline load. + # If it does, clip to zero and report + negative = result < 0 + if negative.any().any(): + clipped = (-result[negative]).groupby('year').sum() + clipped = clipped.stack().loc[lambda x: x > 0].rename('clipped_MWh') + print( + 'WARNING: FINITO reference load exceeds baseline load; clipping ' + f'{clipped.sum():.0f} MWh (summed over weather years) to zero.\n' + 'Clipped MWh by (year, region):\n' + + clipped.to_string() + ) + result = result.clip(lower=0) + + return result diff --git a/reeds/input_processing/hourly_load.py b/reeds/input_processing/hourly_load.py index 760c8a153..a7cd56f15 100644 --- a/reeds/input_processing/hourly_load.py +++ b/reeds/input_processing/hourly_load.py @@ -596,86 +596,6 @@ def reaggregate_to_model_regions( return regional_load_hourly -#TODO: add docstrings -def get_hourly_finito_load( - inputs_case: str, -) -> pd.DataFrame: - # load reference FINITO load - inputs_case_finito = Path(inputs_case).parent / 'finito' / Path(inputs_case).name - load_finito = pd.read_csv(inputs_case_finito / "load_finito.csv") - - # reshape to match load data. with year in index and region in columns - load_finito = load_finito.melt(id_vars='r', var_name='year', value_name='load_MWh') - load_finito.year = load_finito.year.astype(int) - load_finito = load_finito.pivot(index='year', columns='r', values='load_MWh') - load_finito.columns.name = None - - # load_finito.csv is at z134 (p) resolution, so aggregate to this - # run's model regions - county2p = reeds.io.get_county2zone(GSw_ZoneSet='z134') - county2zone = reeds.io.get_county2zone(case=Path(inputs_case).parent) - p2zone = ( - pd.concat({'p': county2p, 'zone': county2zone}, axis=1) - .dropna() - .drop_duplicates() - ) - zones_per_p = p2zone.groupby('p')['zone'].nunique() - if (zones_per_p > 1).any(): - raise ValueError( - 'Cannot aggregate load_finito.csv from z134 to model regions because ' - 'these p regions span multiple model regions: ' - f'{zones_per_p.loc[zones_per_p > 1].index.tolist()}' - ) - p2zone = p2zone.set_index('p')['zone'] - 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? - hours_per_year = 8760 - load_hourly_finito = load_finito / hours_per_year - load_hourly_finito = load_hourly_finito.astype(np.float32) - - return load_hourly_finito - -def remove_finito_load( - load_hourly: pd.DataFrame, - inputs_case: str, - distloss: float, -) -> pd.DataFrame: - - # get FINITO reference load - load_hourly_finito = get_hourly_finito_load(inputs_case) - - # Convert to busbar - load_hourly_finito = load_hourly_finito / (1 - distloss) - - # subtract FINITO reference load from ReEDS load data, - # aligning by model year (index) and region (columns) - result = load_hourly - load_hourly_finito - - # any missing region or model year in load_finito.csv shows up as NaN - if result.isnull().any().any(): - raise ValueError( - 'FINITO reference load is missing regions or years present in the ' - 'ReEDS load data; check load_finito.csv' - ) - - # Validation check: FINITO reference load should not exceed baseline load. - # If it does, clip to zero and report - negative = result < 0 - if negative.any().any(): - clipped = (-result[negative]).groupby('year').sum() - clipped = clipped.stack().loc[lambda x: x > 0].rename('clipped_MWh') - print( - 'WARNING: FINITO reference load exceeds baseline load; clipping ' - f'{clipped.sum():.0f} MWh (summed over weather years) to zero.\n' - 'Clipped MWh by (year, region):\n' - + clipped.to_string() - ) - result = result.clip(lower=0) - - return result - #%% =========================================================================== ### --- MAIN FUNCTION --- ### =========================================================================== @@ -782,8 +702,7 @@ def main(reeds_path, inputs_case): # note that this step occurs after peakload calculation so that the latter # includes a baseline estimate of industrial load captured by FINITO if int(sw.GSw_FINITO_Link): - regional_load_hourly = remove_finito_load( - regional_load_hourly, inputs_case, scalars['distloss'] + regional_load_hourly = reeds.finito.remove_finito_load( ) ############################################# diff --git a/reeds/input_processing/hourly_repperiods.py b/reeds/input_processing/hourly_repperiods.py index dea90109d..05e78f0c9 100644 --- a/reeds/input_processing/hourly_repperiods.py +++ b/reeds/input_processing/hourly_repperiods.py @@ -36,7 +36,6 @@ import reeds from reeds.input_processing import hourly_writetimeseries from reeds.input_processing import hourly_plots -from reeds.input_processing import hourly_load ## Time the operation of this script tic = datetime.datetime.now() @@ -61,7 +60,7 @@ def get_load(inputs_case, sw, keep_modelyear=None, keep_weatheryears=[2012]): ### 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 if int(sw.GSw_FINITO_Link): - load_hourly_finito = hourly_load.get_hourly_finito_load(inputs_case) + load_hourly_finito = reeds.finito.get_hourly_finito_load(inputs_case) load = load + load_hourly_finito ### Subset to keep_modelyear if provided diff --git a/reeds/inputs.py b/reeds/inputs.py index 646f73844..8d6ba1d33 100644 --- a/reeds/inputs.py +++ b/reeds/inputs.py @@ -5,7 +5,6 @@ import yaml import hashlib import shapely -import shutil import numpy as np import pandas as pd import sklearn.cluster @@ -917,56 +916,3 @@ def validate_zoneset(GSw_ZoneSet): hierarchypath = Path(zonepath, 'hierarchy.csv') err = f'The following columns are missing from {hierarchypath}: ' + ' '.join(missing) raise KeyError(err) - -def setup_finito(casedir, caseSwitches, BatchName): - #%% Copy FINITO code folders and inputs into [casedir]/finito - # ... finito directory within the case directory - casedir_finito = os.path.join(casedir,'finito') - # ... define the inputs case directory for FINITO - inputs_case_finito = os.path.join(casedir,'finito','inputs_case') - - # copy directories - os.makedirs(casedir_finito, exist_ok=True) - shutil.copytree(os.path.join(caseSwitches['finito_dir'], 'inputs'),os.path.join(casedir,'finito', 'inputs')) - shutil.copytree(os.path.join(caseSwitches['finito_dir'], 'input_processing'),os.path.join(casedir,'finito', 'input_processing')) - shutil.copytree(os.path.join(caseSwitches['finito_dir'], 'model'),os.path.join(casedir,'finito', 'model')) - shutil.copytree(os.path.join(caseSwitches['finito_dir'], 'visualization'),os.path.join(casedir, 'finito', 'visualization')) - - # copy over the FINITO cases files - shutil.copy2(os.path.join(caseSwitches['finito_dir'], 'cases.csv'), os.path.join(casedir, 'finito')) - shutil.copy2(os.path.join(caseSwitches['finito_dir'], f"cases_{caseSwitches['finito_cases_file']}.csv"), os.path.join(casedir, 'finito')) - - #%% (GSw_Trade_PriceResponse > 0) If doing a price-responsive trade run, retrieve the reference exports/imports prices - if int(caseSwitches['GSw_Trade_PriceResponse']) > 0: - initialize_price_response_path = os.path.join(casedir_finito, 'input_processing', 'processing', 'initialize_price_response.py') - # 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) - - #%% Filter and copy all input files for each scenario - # Call FINITO copy_files.py file before starting the runs - copy_files_run = os.system( - 'python ' + os.path.join(caseSwitches['finito_dir'], 'input_processing', 'processing', 'copy_files.py') + - f" -c {casedir_finito} -d {inputs_case_finito} --link" - ) - - # Print an error message if copy_files.py encounters any issue - if copy_files_run != 0: - print("\nERROR: FINITO copy_files.py encountered an issue and did not complete successfully.") - print("Please check the console output above for details.") - print("The issue could be due to regionality, focus sector filtering, or file reading errors.") - os._exit(1) - - ## Populate sets for each linked run using autopop_set.py - os.system( - 'python ' + os.path.join(caseSwitches['finito_dir'], 'input_processing', 'processing', 'autopop_set.py') + - f" -c {casedir_finito} -d {inputs_case_finito} --link" - ) - - ## Call read_mecs_heat.py to generate heat/nonheat/feedstock ratios for FINITO Rest of Industry (ROI) - mecs_sectors = caseSwitches['focus_sectors'].replace('.', ' ') - read_mecs_path = os.path.join(caseSwitches['finito_dir'], 'input_processing', 'processing', 'mecs', 'read_mecs_heat.py') - # Collect all arguments for read_mecs_heat.py - read_mecs_args = f' -s {mecs_sectors} -d {inputs_case_finito}' - os.system('python ' + read_mecs_path + read_mecs_args) diff --git a/runreeds.py b/runreeds.py index 9d06e93ba..4eb8e9bd2 100644 --- a/runreeds.py +++ b/runreeds.py @@ -104,7 +104,7 @@ def create_case_lists(df_cases:pd.DataFrame, BatchName:str, single:str=''): # (ReEDS-FINITO) Combine the cases files for the linked model if int(case_out.loc['GSw_FINITO_Link']) == 1 : # add the FINITO switches to the caseSwitches - case_out=linked_cases(df_cases,case) + case_out=reeds.finito.linked_cases(df_cases,case) #exclude certain switches that don't need to be passed to GAMS for i,v in case_out.items(): @@ -779,74 +779,6 @@ def setup_window( OPATH.writelines( "python valuestreams.py" + '\n') -# (ReEDS-FINITO) update 'df_cases' to include FINITO switches -def linked_cases(df_cases,case): - """ - Updates the cases dataframe to include FINITO-specific switches. - When a switch is duplicated in FINITO and ReEDS, then we default to - the ReEDS value. - - For the FINITO switches, the combined cases file defaults to the case-specific - 'Default Value' in cases_linked.csv first, before using the universal FINITO - 'Default Value' in cases.csv for any un-assigned switches. - """ - # check for valid finito_dir - if not os.path.isdir(df_cases[case]['finito_dir']): - raise ValueError( - f"finito_dir = {df_cases[case]['finito_dir']} is not a valid path. " - "Please ensure this path points to a cloned version of the FINITO repository. " - ) - - # define path to and read the FINITO check_inputs function - finito_check_inputs_path = os.path.join(df_cases[case]['finito_dir'], 'input_processing', 'processing') - sys.path.append(finito_check_inputs_path) - from check_inputs import check_inputs - - ### load the default values for all FINITO switches from ~\FINITO\cases.csv - df_cases_finito = pd.read_csv(os.path.join(df_cases[case]['finito_dir'],'cases.csv'), dtype=object, index_col=0) - df_cases_finito = df_cases_finito[['Choices', 'Default Value']] - - ### load the scenario-specific switches from ~\FINITO\cases_linked.csv - cases_linked_path = os.path.join(df_cases[case]['finito_dir'],f"cases_{df_cases[case]['finito_cases_file']}.csv") - df_cases_suf_finito = pd.read_csv(cases_linked_path, dtype=object, index_col=0) - ## check that case names are unique in cases_linked.csv - # grab the scenario names **exactly** as they appear in the csv file - with open(cases_linked_path, 'r', newline='') as csvfile: - reader = csv.reader(csvfile) - header = next(reader) - # find the duplicate column names and raise an error if any are found - duplicate_columns = set([x for x in header if header.count(x) > 1]) - if duplicate_columns: - raise ValueError(f"The FINITO cases_{df_cases[case]['finito_cases_file']}.csv has the following duplicate column names: {duplicate_columns}") - ### identify the FINITO case - if df_cases[case]['finito_case'] == 'same': - finito_case=case - else: - finito_case=df_cases[case]['finito_case'] - # ensures **exact** match of names between the ReEDS cases_{}.csv and the FINITO cases_linked.csv - if finito_case not in (df_cases_suf_finito.columns): - raise ValueError(f"The 'finito_case' input '{finito_case}' in the ReEDS cases file does not exist in FINITO's cases_{df_cases[case]['finito_cases_file']}.csv.") - - ### first use 'Default Value' from the FINITO cases_linked.csv to fill missing switches - if 'Default Value' in df_cases_suf_finito.columns: - df_cases_suf_finito[finito_case] = df_cases_suf_finito[finito_case].fillna(df_cases_suf_finito['Default Value']) - ### then, use 'Default Value' from the FINITO cases.csv to fill un-assigned switches - df_cases_suf_finito.drop(['Choices','Default Value'], axis='columns',inplace=True, errors='ignore') - df_cases_finito = df_cases_finito.join(df_cases_suf_finito, how='outer') - df_cases_finito[finito_case] = df_cases_finito[finito_case].fillna(df_cases_finito['Default Value']) - - #### create new dataframe for the combined ReEDS and FINITO switches - df_cases_combine = pd.concat([df_cases[case],df_cases_finito[finito_case]]) - ### drop duplicated switches, defaulting to reeds - df_cases_combine = df_cases_combine[~df_cases_combine.index.duplicated(keep='first')] - - #%% Check for incompatibility of FINITO switches - model_sectors = df_cases_finito['Default Value']['focus_sectors'].split('.') - check_inputs(case = case, df_case = df_cases_combine, model_sectors=model_sectors) - - return df_cases_combine - - #%% =========================================================================== ### --- PROCEDURE --- ### =========================================================================== @@ -1328,7 +1260,7 @@ def write_batch_script( ## Set up FINITO if running linked model if int(caseSwitches['GSw_FINITO_Link'])==1: - reeds.inputs.setup_finito(casedir, caseSwitches, BatchName) + reeds.finito.setup_finito(casedir, caseSwitches, BatchName) ###### Replace files according to 'file_replacements' in cases. Ignore quotes in input text. # << is used to separate the file that is to be replaced from the file that is used From 2d863128380106ea1e2f8c4e9406c1b3b15f3225 Mon Sep 17 00:00:00 2001 From: bsergi Date: Fri, 28 Aug 2026 11:25:32 -0400 Subject: [PATCH 56/78] cleaning up the finito python module --- reeds/finito.py | 148 +++++++++++++++++++++++++++++++----------------- 1 file changed, 97 insertions(+), 51 deletions(-) diff --git a/reeds/finito.py b/reeds/finito.py index 7204f5a63..8b2778a0c 100644 --- a/reeds/finito.py +++ b/reeds/finito.py @@ -1,11 +1,17 @@ +""" +Functions relevant to setting up a linked ReEDS-FINITO run +""" + ### Imports import os -import sys -import csv import shutil +import subprocess +import sys +from pathlib import Path + import numpy as np import pandas as pd -from pathlib import Path + sys.path.append(str(Path(__file__).parent.parent)) import reeds @@ -33,113 +39,153 @@ def linked_cases(df_cases, case): ) # define path to and read the FINITO check_inputs function - finito_check_inputs_path = os.path.join(df_cases[case]['finito_dir'], 'input_processing', 'processing') + finito_check_inputs_path = os.path.join( + df_cases[case]['finito_dir'], 'input_processing', 'processing') sys.path.append(finito_check_inputs_path) from check_inputs import check_inputs ### load the default values for all FINITO switches from ~\FINITO\cases.csv - df_cases_finito = pd.read_csv(os.path.join(df_cases[case]['finito_dir'],'cases.csv'), dtype=object, index_col=0) + df_cases_finito = pd.read_csv( + os.path.join(df_cases[case]['finito_dir'], 'cases.csv'), dtype=object, index_col=0) df_cases_finito = df_cases_finito[['Choices', 'Default Value']] ### load the scenario-specific switches from ~\FINITO\cases_linked.csv - cases_linked_path = os.path.join(df_cases[case]['finito_dir'],f"cases_{df_cases[case]['finito_cases_file']}.csv") + cases_linked_path = os.path.join( + df_cases[case]['finito_dir'], f"cases_{df_cases[case]['finito_cases_file']}.csv") df_cases_suf_finito = pd.read_csv(cases_linked_path, dtype=object, index_col=0) ## check that case names are unique in cases_linked.csv # grab the scenario names **exactly** as they appear in the csv file - with open(cases_linked_path, 'r', newline='') as csvfile: - reader = csv.reader(csvfile) - header = next(reader) + header = pd.read_csv(cases_linked_path, header=None).iloc[0].values # find the duplicate column names and raise an error if any are found - duplicate_columns = set([x for x in header if header.count(x) > 1]) + duplicate_columns = {x for x in header if list(header).count(x) > 1} if duplicate_columns: - raise ValueError(f"The FINITO cases_{df_cases[case]['finito_cases_file']}.csv has the following duplicate column names: {duplicate_columns}") + raise ValueError( + f"The FINITO cases_{df_cases[case]['finito_cases_file']}.csv has the " + f"following duplicate column names: {duplicate_columns}" + ) ### identify the FINITO case if df_cases[case]['finito_case'] == 'same': - finito_case=case + finito_case = case else: - finito_case=df_cases[case]['finito_case'] + finito_case = df_cases[case]['finito_case'] # ensures **exact** match of names between the ReEDS cases_{}.csv and the FINITO cases_linked.csv if finito_case not in (df_cases_suf_finito.columns): - raise ValueError(f"The 'finito_case' input '{finito_case}' in the ReEDS cases file does not exist in FINITO's cases_{df_cases[case]['finito_cases_file']}.csv.") + raise ValueError( + f"The 'finito_case' input '{finito_case}' in the ReEDS cases file does not " + f"exist in FINITO's cases_{df_cases[case]['finito_cases_file']}.csv." + ) ### first use 'Default Value' from the FINITO cases_linked.csv to fill missing switches if 'Default Value' in df_cases_suf_finito.columns: - df_cases_suf_finito[finito_case] = df_cases_suf_finito[finito_case].fillna(df_cases_suf_finito['Default Value']) + df_cases_suf_finito[finito_case] = ( + df_cases_suf_finito[finito_case].fillna(df_cases_suf_finito['Default Value']) + ) ### then, use 'Default Value' from the FINITO cases.csv to fill un-assigned switches - df_cases_suf_finito.drop(['Choices','Default Value'], axis='columns',inplace=True, errors='ignore') + df_cases_suf_finito.drop( + ['Choices', 'Default Value'], axis='columns', inplace=True, errors='ignore') df_cases_finito = df_cases_finito.join(df_cases_suf_finito, how='outer') - df_cases_finito[finito_case] = df_cases_finito[finito_case].fillna(df_cases_finito['Default Value']) + df_cases_finito[finito_case] = ( + df_cases_finito[finito_case].fillna(df_cases_finito['Default Value']) + ) #### create new dataframe for the combined ReEDS and FINITO switches - df_cases_combine = pd.concat([df_cases[case],df_cases_finito[finito_case]]) + df_cases_combine = pd.concat([df_cases[case], df_cases_finito[finito_case]]) ### drop duplicated switches, defaulting to reeds df_cases_combine = df_cases_combine[~df_cases_combine.index.duplicated(keep='first')] #%% Check for incompatibility of FINITO switches model_sectors = df_cases_finito['Default Value']['focus_sectors'].split('.') - check_inputs(case = case, df_case = df_cases_combine, model_sectors=model_sectors) + check_inputs(case=case, df_case=df_cases_combine, model_sectors=model_sectors) return df_cases_combine def setup_finito(casedir, caseSwitches, BatchName): #%% Copy FINITO code folders and inputs into [casedir]/finito + finito_dir = Path(caseSwitches['finito_dir']) # ... finito directory within the case directory - casedir_finito = os.path.join(casedir,'finito') + casedir_finito = Path(casedir, 'finito') # ... define the inputs case directory for FINITO - inputs_case_finito = os.path.join(casedir,'finito','inputs_case') + inputs_case_finito = Path(casedir, 'finito', 'inputs_case') # copy directories - os.makedirs(casedir_finito, exist_ok=True) - shutil.copytree(os.path.join(caseSwitches['finito_dir'], 'inputs'),os.path.join(casedir,'finito', 'inputs')) - shutil.copytree(os.path.join(caseSwitches['finito_dir'], 'input_processing'),os.path.join(casedir,'finito', 'input_processing')) - shutil.copytree(os.path.join(caseSwitches['finito_dir'], 'model'),os.path.join(casedir,'finito', 'model')) - shutil.copytree(os.path.join(caseSwitches['finito_dir'], 'visualization'),os.path.join(casedir, 'finito', 'visualization')) + casedir_finito.mkdir(parents=True, exist_ok=True) + shutil.copytree(finito_dir / 'inputs', casedir_finito / 'inputs') + shutil.copytree(finito_dir / 'input_processing', casedir_finito / 'input_processing') + shutil.copytree(finito_dir / 'model', casedir_finito / 'model') + shutil.copytree(finito_dir / 'visualization', casedir_finito / 'visualization') # copy over the FINITO cases files - shutil.copy2(os.path.join(caseSwitches['finito_dir'], 'cases.csv'), os.path.join(casedir, 'finito')) - shutil.copy2(os.path.join(caseSwitches['finito_dir'], f"cases_{caseSwitches['finito_cases_file']}.csv"), os.path.join(casedir, 'finito')) + shutil.copy2(finito_dir / 'cases.csv', casedir_finito) + shutil.copy2(finito_dir / f"cases_{caseSwitches['finito_cases_file']}.csv", casedir_finito) #%% (GSw_Trade_PriceResponse > 0) If doing a price-responsive trade run, retrieve the reference exports/imports prices if int(caseSwitches['GSw_Trade_PriceResponse']) > 0: - initialize_price_response_path = os.path.join(casedir_finito, 'input_processing', 'processing', 'initialize_price_response.py') - # 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) + initialize_price_response_path = ( + casedir_finito / 'input_processing' / 'processing' / 'initialize_price_response.py' + ) + subprocess.run( + [ + 'python', str(initialize_price_response_path), + '-c', str(casedir), + '-b', str(BatchName), + '-cr', str(caseSwitches['GSw_Trade_PriceResponse_RefScen']), + '-l', str(caseSwitches['GSw_FINITO_Link']), + ], + check=True, + ) #%% Filter and copy all input files for each scenario # Call FINITO copy_files.py file before starting the runs - copy_files_run = os.system( - 'python ' + os.path.join(caseSwitches['finito_dir'], 'input_processing', 'processing', 'copy_files.py') + - f" -c {casedir_finito} -d {inputs_case_finito} --link" - ) + copy_files_run = subprocess.run( + [ + 'python', str(finito_dir / 'input_processing' / 'processing' / 'copy_files.py'), + '-c', str(casedir_finito), + '-d', str(inputs_case_finito), + '--link', + ], + check=False, + ) - # Print an error message if copy_files.py encounters any issue - if copy_files_run != 0: - print("\nERROR: FINITO copy_files.py encountered an issue and did not complete successfully.") - print("Please check the console output above for details.") - print("The issue could be due to regionality, focus sector filtering, or file reading errors.") - os._exit(1) + # Raise an error if copy_files.py encounters any issue + if copy_files_run.returncode != 0: + raise RuntimeError( + "FINITO copy_files.py encountered an issue and did not complete successfully. " + "Please check the console output above for details. " + "The issue could be due to regionality, focus sector filtering, or file reading errors." + ) ## Populate sets for each linked run using autopop_set.py - os.system( - 'python ' + os.path.join(caseSwitches['finito_dir'], 'input_processing', 'processing', 'autopop_set.py') + - f" -c {casedir_finito} -d {inputs_case_finito} --link" + subprocess.run( + [ + 'python', str(finito_dir / 'input_processing' / 'processing' / 'autopop_set.py'), + '-c', str(casedir_finito), + '-d', str(inputs_case_finito), + '--link', + ], + check=True, ) ## Call read_mecs_heat.py to generate heat/nonheat/feedstock ratios for FINITO Rest of Industry (ROI) mecs_sectors = caseSwitches['focus_sectors'].replace('.', ' ') - read_mecs_path = os.path.join(caseSwitches['finito_dir'], 'input_processing', 'processing', 'mecs', 'read_mecs_heat.py') - # Collect all arguments for read_mecs_heat.py - read_mecs_args = f' -s {mecs_sectors} -d {inputs_case_finito}' - os.system('python ' + read_mecs_path + read_mecs_args) + read_mecs_path = ( + finito_dir / 'input_processing' / 'processing' / 'mecs' / 'read_mecs_heat.py' + ) + subprocess.run( + ['python', str(read_mecs_path), '-s', mecs_sectors, '-d', str(inputs_case_finito)], + check=True, + ) -#TODO: add docstrings def get_hourly_finito_load( inputs_case: str, ) -> pd.DataFrame: + """ + Load FINITO's reference annual industrial demand (load_finito.csv), aggregate it + to this run's model regions, and spread it evenly across all hours. + + Returns an hourly load dataframe (year index, model-region columns, MW). + """ # load reference FINITO load inputs_case_finito = Path(inputs_case).parent / 'finito' / Path(inputs_case).name load_finito = pd.read_csv(inputs_case_finito / "load_finito.csv") From 948bf47b0a2f1ff201b28d1eef38941d6fa98396 Mon Sep 17 00:00:00 2001 From: bsergi Date: Fri, 28 Aug 2026 17:38:30 -0400 Subject: [PATCH 57/78] clean up FINITO switches in cases_test.csv --- cases_test.csv | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/cases_test.csv b/cases_test.csv index 3d486639b..3ffb4f1bd 100644 --- a/cases_test.csv +++ b/cases_test.csv @@ -65,6 +65,5 @@ pras_scheduled_outage,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,0, pras_unitsize_source,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,r2x, pras_vre_combine,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1, pras_samples,,,,,,10,10,10,,,,,,,,,,,,,,,,10,10,,,,,,,, -GSw_FINITO_Link,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1 -finito_dir,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,/path/to/FINITO/directory -finito_case,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,BAUfast_reg \ No newline at end of file +FINITO,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1 +FINITO_case,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,BAUfast_reg \ No newline at end of file From c9469cc4fb0b32a88504e36d59f24d9b6133b7a6 Mon Sep 17 00:00:00 2001 From: bsergi Date: Fri, 28 Aug 2026 17:42:26 -0400 Subject: [PATCH 58/78] reorganization of FINITO switches --- cases.csv | 10 +++--- helpers/restart_runs.py | 2 +- reeds/core/setup/b_inputs.gms | 4 +-- reeds/core/solve/3_solve_oneyear.gms | 2 +- reeds/finito.py | 46 ++++++++++++++++++---------- reeds/input_processing/copy_files.py | 5 --- reeds/inputs.py | 8 +++++ runreeds.py | 7 ++--- 8 files changed, 50 insertions(+), 34 deletions(-) diff --git a/cases.csv b/cases.csv index 0ae04e674..0a47fccda 100644 --- a/cases.csv +++ b/cases.csv @@ -33,11 +33,6 @@ coalscen,"Coal price scenario (inputs\fuelprices\{coalscen}.csv). The options fo 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, -finito_dir,Directory with cloned FINITO repository,N/A,/path/to/FINITO/directory, -finito_cases_file,"extension of the FINITO cases file including all scenarios to be run, e.g., 'linked' for cases_linked.csv",N/A,linked, -finito_case,Case name for FINITO in {finito_dir}/cases_linked.csv; use 'same' if the scenario name in cases_linked.csv matches the scenario name in the cases file,N/A,BAUfast, -first_year_finito,First year when the FINITO module is enabled,N/A,2018, geodiscov,Annual discover rates for new geothermal sites,BAU; TI,BAU, geohydrosupplycurve,Geohydro Supply Curve,ATB_2023,ATB_2023, egssupplycurve,EGS Supply Curve,ATB_2023; reV,reV, @@ -61,6 +56,11 @@ depreciation_schedules_suffix,File suffix for depreciation schedules,default,def financials_sys_suffix,File suffix for the system-wide system discount rate,ATB(2023|2024),ATB2024, financials_tech_suffix,File suffix for technology-specific financial assumptions (see inputs\financials_tech),ATB(2023|2023_CRP20|2024),ATB2024, financials_trans_suffix,File suffix for transmission financial assumptions,default; 30ITC_0pen_2022_2031,default, +FINITO,Switch to enable running linked ReEDS-FINITO model (mapped to GSw_FINITO_Link),0; 1,0, +FINITO_cases_file,"Filename suffix of the FINITO cases file that includes the scenarios to be run (e.g., 'linked' for cases_linked.csv)",N/A,linked, +FINITO_case,FINITO case for this run; must be a column in the files specified by FINITO_cases_file. Use 'same' if the ReEDS run name matches the FINITO case name,N/A,same, +FINITO_dir,Directory with cloned FINITO repository,N/A,{reeds_path}/../FINITO, +FINITO_first_year,First year when the FINITO module is to be run,N/A,2018, incentives_suffix,File suffix for incentive definition,ira; obbba; obbba_conservative; annual; biennial; ira_45q_45v_extension; none,obbba, inflation_suffix,File suffix for historical inflation schedule,default,default, ivt_suffix,File suffix for ivt csv file,default; small; step,default, diff --git a/helpers/restart_runs.py b/helpers/restart_runs.py index d353d2aef..a3ad06170 100644 --- a/helpers/restart_runs.py +++ b/helpers/restart_runs.py @@ -101,7 +101,7 @@ sw = reeds.io.get_switches(case) if int(sw.GSw_FINITO_Link): f_copy = re.sub("^finito/", "" , f, flags=re.IGNORECASE) - shutil.copy(os.path.join(sw.finito_dir,f_copy), os.path.join(case,f)) + shutil.copy(os.path.join(sw.FINITO_dir,f_copy), os.path.join(case,f)) else: shutil.copy(os.path.join(reeds_path,f), os.path.join(case,f)) shutil.copy(f, Path(case, f)) diff --git a/reeds/core/setup/b_inputs.gms b/reeds/core/setup/b_inputs.gms index 7cb3124d7..52284b819 100644 --- a/reeds/core/setup/b_inputs.gms +++ b/reeds/core/setup/b_inputs.gms @@ -967,11 +967,11 @@ tprev(t,tt)$[tmodel_new(t)$tmodel_new(tt)$(tt.valmindiff(t))] = no ; -* If FINITO linkage is on, remove all modeled years from tfuel after first_year_finito +* If FINITO linkage is on, remove all modeled years from tfuel after FINITO_first_year * as the FINITO supply curves will be used instead of those in ReEDS; * otherwise, all modeled years use ReEDS supply curves and are eligible for tfuel tfuel(t)$[tmodel_new(t)]=yes; -tfuel(t)$[tmodel_new(t)$Sw_FINITO_Link$(t.val>=%first_year_finito%)] = no ; +tfuel(t)$[tmodel_new(t)$Sw_FINITO_Link$(t.val>=%FINITO_first_year%)] = no ; * In order to fill all necessary dimensions of upgrade techs parameters, we require * Sw_UpgradeYear in ban(i) to be a modeled year and thus we compute as either diff --git a/reeds/core/solve/3_solve_oneyear.gms b/reeds/core/solve/3_solve_oneyear.gms index d62003f29..debddab24 100644 --- a/reeds/core/solve/3_solve_oneyear.gms +++ b/reeds/core/solve/3_solve_oneyear.gms @@ -26,7 +26,7 @@ tmodel("%cur_year%") = yes ; * (ReEDS-FINITO) also reset t_finito $ifthene.linked_finito_time %GSw_FINITO_Link% == 1 t_finito(t) = no ; -t_finito(t)$[tmodel(t)$(t.val>=first_year_finito)$(t.val<=endyear)] = yes ; +t_finito(t)$[tmodel(t)$(t.val>=%FINITO_first_year%)$(t.val<=endyear)] = yes ; $endif.linked_finito_time diff --git a/reeds/finito.py b/reeds/finito.py index 8b2778a0c..550ecf8cb 100644 --- a/reeds/finito.py +++ b/reeds/finito.py @@ -19,9 +19,22 @@ #%% =========================================================================== ### --- FUNCTIONS --- ### =========================================================================== +def update_FINITO_switches(case, sw, new_switches): + """ + Creates any intermediate switches needed for FINITO + """ + new_switches[case]['GSw_FINITO_Link'] = str(int(sw['FINITO'])) + ## If the FINITO path is set relative to ReEDS, fill it out here + if '{reeds_path}' in sw['FINITO_dir']: + new_switches[case]['FINITO_dir'] = str( + Path(sw['FINITO_dir'].format(reeds_path=reeds.io.reeds_path)).resolve() + ) + else: + new_switches[case]['FINITO_dir'] = sw['FINITO_dir'] + + return new_switches -# (ReEDS-FINITO) update 'df_cases' to include FINITO switches -def linked_cases(df_cases, case): +def setup_linked_cases(df_cases, case): """ Updates the cases dataframe to include FINITO-specific switches. When a switch is duplicated in FINITO and ReEDS, then we default to @@ -31,27 +44,28 @@ def linked_cases(df_cases, case): 'Default Value' in cases_linked.csv first, before using the universal FINITO 'Default Value' in cases.csv for any un-assigned switches. """ - # check for valid finito_dir - if not os.path.isdir(df_cases[case]['finito_dir']): + # check for valid FINITO directory + finito_model = Path(df_cases[case]['FINITO_dir']) / 'model' / 'finito_model.gms' + if not (os.path.isdir(df_cases[case]['FINITO_dir']) and finito_model.exists()): raise ValueError( - f"finito_dir = {df_cases[case]['finito_dir']} is not a valid path. " + f"FINITO_dir = {df_cases[case]['FINITO_dir']} is not a valid path. " "Please ensure this path points to a cloned version of the FINITO repository. " - ) + ) # define path to and read the FINITO check_inputs function finito_check_inputs_path = os.path.join( - df_cases[case]['finito_dir'], 'input_processing', 'processing') + df_cases[case]['FINITO_dir'], 'input_processing', 'processing') sys.path.append(finito_check_inputs_path) from check_inputs import check_inputs ### load the default values for all FINITO switches from ~\FINITO\cases.csv df_cases_finito = pd.read_csv( - os.path.join(df_cases[case]['finito_dir'], 'cases.csv'), dtype=object, index_col=0) + os.path.join(df_cases[case]['FINITO_dir'], 'cases.csv'), dtype=object, index_col=0) df_cases_finito = df_cases_finito[['Choices', 'Default Value']] ### load the scenario-specific switches from ~\FINITO\cases_linked.csv cases_linked_path = os.path.join( - df_cases[case]['finito_dir'], f"cases_{df_cases[case]['finito_cases_file']}.csv") + df_cases[case]['FINITO_dir'], f"cases_{df_cases[case]['FINITO_cases_file']}.csv") df_cases_suf_finito = pd.read_csv(cases_linked_path, dtype=object, index_col=0) ## check that case names are unique in cases_linked.csv # grab the scenario names **exactly** as they appear in the csv file @@ -60,19 +74,19 @@ def linked_cases(df_cases, case): duplicate_columns = {x for x in header if list(header).count(x) > 1} if duplicate_columns: raise ValueError( - f"The FINITO cases_{df_cases[case]['finito_cases_file']}.csv has the " + f"The FINITO cases_{df_cases[case]['FINITO_cases_file']}.csv has the " f"following duplicate column names: {duplicate_columns}" ) ### identify the FINITO case - if df_cases[case]['finito_case'] == 'same': + if df_cases[case]['FINITO_case'] == 'same': finito_case = case else: - finito_case = df_cases[case]['finito_case'] + finito_case = df_cases[case]['FINITO_case'] # ensures **exact** match of names between the ReEDS cases_{}.csv and the FINITO cases_linked.csv if finito_case not in (df_cases_suf_finito.columns): raise ValueError( - f"The 'finito_case' input '{finito_case}' in the ReEDS cases file does not " - f"exist in FINITO's cases_{df_cases[case]['finito_cases_file']}.csv." + f"The 'FINITO_case' input '{finito_case}' in the ReEDS cases file does not " + f"exist in FINITO's cases_{df_cases[case]['FINITO_cases_file']}.csv." ) ### first use 'Default Value' from the FINITO cases_linked.csv to fill missing switches @@ -102,7 +116,7 @@ def linked_cases(df_cases, case): def setup_finito(casedir, caseSwitches, BatchName): #%% Copy FINITO code folders and inputs into [casedir]/finito - finito_dir = Path(caseSwitches['finito_dir']) + finito_dir = Path(caseSwitches['FINITO_dir']) # ... finito directory within the case directory casedir_finito = Path(casedir, 'finito') # ... define the inputs case directory for FINITO @@ -117,7 +131,7 @@ def setup_finito(casedir, caseSwitches, BatchName): # copy over the FINITO cases files shutil.copy2(finito_dir / 'cases.csv', casedir_finito) - shutil.copy2(finito_dir / f"cases_{caseSwitches['finito_cases_file']}.csv", casedir_finito) + shutil.copy2(finito_dir / f"cases_{caseSwitches['FINITO_cases_file']}.csv", casedir_finito) #%% (GSw_Trade_PriceResponse > 0) If doing a price-responsive trade run, retrieve the reference exports/imports prices if int(caseSwitches['GSw_Trade_PriceResponse']) > 0: diff --git a/reeds/input_processing/copy_files.py b/reeds/input_processing/copy_files.py index 907674161..9fd7ed4d4 100644 --- a/reeds/input_processing/copy_files.py +++ b/reeds/input_processing/copy_files.py @@ -1311,11 +1311,6 @@ def main(reeds_path, inputs_case): scalar_csv_to_txt(os.path.join(inputs_case,'gswitches.csv')) source_deflator_map = get_source_deflator_map(reeds_path) - - ### 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) - scalar_csv_to_txt(os.path.join(os.path.dirname(inputs_case),'finito','inputs','scalars.csv')) # Copy non-region files write_non_region_files(non_region_files, sw, inputs_case, regions_and_agglevel, source_deflator_map) diff --git a/reeds/inputs.py b/reeds/inputs.py index 8d6ba1d33..d9399cdf2 100644 --- a/reeds/inputs.py +++ b/reeds/inputs.py @@ -124,7 +124,15 @@ def add_intermediate_switches(dfcases:pd.DataFrame) -> pd.DataFrame: int(sw['numbins_upv']), 15, )) + ## ReEDS-FINITO switches + new_switches = reeds.finito.update_FINITO_switches(case, sw, new_switches) + dfcases_out = pd.concat([dfcases, pd.DataFrame(new_switches)]) + # if there are duplicates, defer to the new switch + dfcases_out = dfcases_out[~dfcases_out.index.duplicated(keep='last')] + # sort by switch name + dfcases_out = dfcases_out.sort_index() + return dfcases_out diff --git a/runreeds.py b/runreeds.py index 4eb8e9bd2..a0bb15614 100644 --- a/runreeds.py +++ b/runreeds.py @@ -103,8 +103,7 @@ def create_case_lists(df_cases:pd.DataFrame, BatchName:str, single:str=''): case_out = df_cases[case].copy() # (ReEDS-FINITO) Combine the cases files for the linked model if int(case_out.loc['GSw_FINITO_Link']) == 1 : - # add the FINITO switches to the caseSwitches - case_out=reeds.finito.linked_cases(df_cases,case) + case_out=reeds.finito.setup_linked_cases(df_cases,case) #exclude certain switches that don't need to be passed to GAMS for i,v in case_out.items(): @@ -1410,14 +1409,14 @@ def write_batch_script( + toLogGamsString + f"--fname={batch_case}" + f" --GSw_calc_powfrac={caseSwitches['GSw_calc_powfrac']}" - + f" --first_year_finito={caseSwitches['first_year_finito']} \n" + + f" --FINITO_first_year={caseSwitches['FINITO_first_year']} \n" ) OPATH.writelines(writescripterrorcheck("report.gms")) if not LINUXORMAC and int(caseSwitches['GSw_FINITO_Link']) != 1: OPATH.writelines("endlocal\n") OPATH.writelines(f'python {logger}\n') - ### (ReEDS-FINITO) calls FINITO reporting + ### (ReEDS-FINITO) call FINITO reporting if int(caseSwitches['GSw_FINITO_Link'])==1: OPATH.writelines( 'gams ' From 9f79b1cbed02bdd01b55f71c67fb03e68eb0d71b Mon Sep 17 00:00:00 2001 From: bsergi Date: Tue, 8 Sep 2026 20:48:02 -0400 Subject: [PATCH 59/78] avoid hardcoded FINITO dollaryear --- reeds/core/setup/d_objective.gms | 2 +- reeds/core/terminus/report.gms | 8 ++++---- runreeds.py | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/reeds/core/setup/d_objective.gms b/reeds/core/setup/d_objective.gms index 5285c118c..536d8995d 100644 --- a/reeds/core/setup/d_objective.gms +++ b/reeds/core/setup/d_objective.gms @@ -25,7 +25,7 @@ eq_ObjFn.. Z =e= cost_scale * ( * economy-wide costs from FINITO: deflate from $2018 to $2004 * and remove any FINITO scaling $ifthene.linked_objective Sw_FINITO_Link==1 - + deflator('2018') / obj_scale * sum{t$[t_finito(t)], Z_finito(t) } + + deflator('%FINITO_dollaryear%') / obj_scale * sum{t$[t_finito(t)], Z_finito(t) } $endif.linked_objective ) ; diff --git a/reeds/core/terminus/report.gms b/reeds/core/terminus/report.gms index 198a93108..f3ea327de 100644 --- a/reeds/core/terminus/report.gms +++ b/reeds/core/terminus/report.gms @@ -523,7 +523,7 @@ repbioprice(r,t)$[tmodel_new(t)$tfuel(t)] = max{0, smax{bioclass$BIOUSED.l(biocl $ifthene.finitobioprice Sw_FINITO_Link == 1 * here we take the weighted average of prices across biomass products used for power repbioprice(r,t)$[tmodel_new(t)$(not tfuel(t))$sum{(i,v,bs), USE_BS_REEDS.l(i,v,bs,r,t) }] = - 1/(obj_scale) * 1/(pvf_onm(t)) * deflator('2018') * + 1/(obj_scale) * 1/(pvf_onm(t)) * deflator('%FINITO_dollaryear%') * sum{(i,v,bs), USE_BS_REEDS.l(i,v,bs,r,t) * eq_supplydemand_bs.m(bs,r,t) } / sum{(i,v,bs), USE_BS_REEDS.l(i,v,bs,r,t) } ; @@ -579,13 +579,13 @@ repgasprice(cendiv,t)$[(Sw_GasCurve = 2)$tmodel_new(t)$repgasquant(cendiv,t)$tfu $ifthene.finitogasprice Sw_FINITO_Link == 1 * approach with GSw_FixedCostSupply=1 or default supply curves repgasprice_finito(cendiv,h,t)$[tmodel_new(t)$(not tfuel(t))$(not Sw_DetailedFuels)] = - deflator('2018') * 1/(obj_scale) * 1/(pvf_onm(t)) + deflator('%FINITO_dollaryear%') * 1/(obj_scale) * 1/(pvf_onm(t)) * eq_supplydemand_fsc.m('NG',cendiv,t) ; * approach with detailed fuels representation (GSw_DetailedFuels=1) repgasprice_finito(cendiv,h,t)$[tmodel_new(t)$(not tfuel(t))$Sw_DetailedFuels] = - deflator('2018') * 1/(obj_scale) * 1/(pvf_onm(t)) + deflator('%FINITO_dollaryear%') * 1/(obj_scale) * 1/(pvf_onm(t)) * citygate price of natural gas * [ smax{(cfp,st)$st_cendiv(st,cendiv), eq_supplydemand_cf.M(cfp,'NG',st,h,t) } / hours(h) * electric-sector markup for natural gas @@ -1825,7 +1825,7 @@ error_check('z') = ( * account for costs from FINITO: deflate from $2018 to $2004, * 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 ) + + cost_scale * ( Z_finito.l(t)$t_finito(t) * deflator('%FINITO_dollaryear%') / obj_scale ) $endif.linked_objective } ) / z.l ; diff --git a/runreeds.py b/runreeds.py index a0bb15614..d4fe5b0f1 100644 --- a/runreeds.py +++ b/runreeds.py @@ -1409,7 +1409,7 @@ def write_batch_script( + toLogGamsString + f"--fname={batch_case}" + f" --GSw_calc_powfrac={caseSwitches['GSw_calc_powfrac']}" - + f" --FINITO_first_year={caseSwitches['FINITO_first_year']} \n" + + f" --FINITO_dollaryear={caseSwitches['FINITO_dollaryear']} \n" ) OPATH.writelines(writescripterrorcheck("report.gms")) if not LINUXORMAC and int(caseSwitches['GSw_FINITO_Link']) != 1: From b69ddcc840940755872c97a320951cd8aafdffdd Mon Sep 17 00:00:00 2001 From: bsergi Date: Tue, 8 Sep 2026 20:53:35 -0400 Subject: [PATCH 60/78] fix missing arguments --- reeds/finito.py | 2 +- reeds/input_processing/hourly_load.py | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/reeds/finito.py b/reeds/finito.py index 550ecf8cb..c97354be1 100644 --- a/reeds/finito.py +++ b/reeds/finito.py @@ -247,7 +247,7 @@ def remove_finito_load( # get FINITO reference load load_hourly_finito = get_hourly_finito_load(inputs_case) - # Convert to busbar + # Convert to busbar using ReEDS assumption for distribution losses load_hourly_finito = load_hourly_finito / (1 - distloss) # subtract FINITO reference load from ReEDS load data, diff --git a/reeds/input_processing/hourly_load.py b/reeds/input_processing/hourly_load.py index a7cd56f15..518253eb7 100644 --- a/reeds/input_processing/hourly_load.py +++ b/reeds/input_processing/hourly_load.py @@ -702,7 +702,10 @@ def main(reeds_path, inputs_case): # note that this step occurs after peakload calculation so that the latter # includes a baseline estimate of industrial load captured by FINITO if int(sw.GSw_FINITO_Link): - regional_load_hourly = reeds.finito.remove_finito_load( + regional_load_hourly = reeds.finito.remove_finito_load( + regional_load_hourly, + inputs_case, + scalars['distloss'] ) ############################################# From 053ab537bc4476f40b359404b155f820990df11d Mon Sep 17 00:00:00 2001 From: bsergi Date: Tue, 8 Sep 2026 20:54:00 -0400 Subject: [PATCH 61/78] load sw in function instead of passing as an argument --- reeds/input_processing/hourly_repperiods.py | 6 +++--- reeds/reedsplots.py | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/reeds/input_processing/hourly_repperiods.py b/reeds/input_processing/hourly_repperiods.py index 05e78f0c9..3c87f3890 100644 --- a/reeds/input_processing/hourly_repperiods.py +++ b/reeds/input_processing/hourly_repperiods.py @@ -51,7 +51,7 @@ techs_min_vre = ['upv', 'wind-ons'] #%%### Functions -def get_load(inputs_case, sw, keep_modelyear=None, keep_weatheryears=[2012]): +def get_load(inputs_case, keep_modelyear=None, keep_weatheryears=[2012]): """ """ ### Subset to modeled regions @@ -59,6 +59,7 @@ def get_load(inputs_case, sw, keep_modelyear=None, keep_weatheryears=[2012]): ### 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) if int(sw.GSw_FINITO_Link): load_hourly_finito = reeds.finito.get_hourly_finito_load(inputs_case) load = load + load_hourly_finito @@ -335,7 +336,6 @@ def main( print("Collecting 8760 load data") load = get_load( inputs_case=inputs_case, - sw=sw, keep_modelyear=(int(sw['GSw_HourlyClusterYear']) if int(sw['GSw_HourlyClusterYear']) in modelyears else max(modelyears)), @@ -494,7 +494,7 @@ def main( and (sw['GSw_PRM_StressSeedLoadLevel'].lower() not in ['false','none']) ): ## Get load for all model and weather years - load_allyears = get_load(inputs_case, sw, keep_weatheryears='all').loc[modelyears] + load_allyears = get_load(inputs_case, keep_weatheryears='all').loc[modelyears] ## Add descriptive index load_allyears = load_allyears.merge( timestamps[['year', 'yperiod', 'h_of_period']], left_on='datetime', right_index=True) diff --git a/reeds/reedsplots.py b/reeds/reedsplots.py index 1ba2b28a0..c1771be29 100644 --- a/reeds/reedsplots.py +++ b/reeds/reedsplots.py @@ -5054,7 +5054,6 @@ def plot_seed_stressperiods( ### Recalculate peak load days since we dropped duplicates above load_allyears = hourly_repperiods.get_load( os.path.join(case, 'inputs_case'), - sw, keep_weatheryears='all').loc[years] timestamps = pd.read_csv(os.path.join(case, 'inputs_case', 'rep', 'timestamps.csv')) resource_adequacy_years = [int(y) for y in sw.resource_adequacy_years.split('_')] From 23e0c9b3ad5ce894030fd0796d4db06dfe0e0466 Mon Sep 17 00:00:00 2001 From: bsergi Date: Tue, 8 Sep 2026 21:36:07 -0400 Subject: [PATCH 62/78] add FINITO_first_year to call to solve_oneyear.gms --- reeds/inputs.py | 1 + 1 file changed, 1 insertion(+) diff --git a/reeds/inputs.py b/reeds/inputs.py index d9399cdf2..99c7b3939 100644 --- a/reeds/inputs.py +++ b/reeds/inputs.py @@ -377,6 +377,7 @@ def solvestring_sequential( 'GSw_StartMarkets', 'GSw_ValStr', 'GSw_FINITO_Link', + 'FINITO_first_year', 'solver', 'debug', 'startyear', From 4a299e886313101255c03cf066355bd556a2c53b Mon Sep 17 00:00:00 2001 From: bsergi Date: Tue, 8 Sep 2026 21:36:21 -0400 Subject: [PATCH 63/78] formatting --- reeds/core/setup/b_inputs.gms | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reeds/core/setup/b_inputs.gms b/reeds/core/setup/b_inputs.gms index 52284b819..3a7a24f81 100644 --- a/reeds/core/setup/b_inputs.gms +++ b/reeds/core/setup/b_inputs.gms @@ -952,7 +952,7 @@ tfix(t) = no ; stfeas(st) = no ; tprev(t,tt) = no ; tsolved(t) = no ; -tfuel(t)=no; +tfuel(t) = no ; *============================== * Year specification From 5db2ab2e51db3ea076c72a9bc938277491af1506 Mon Sep 17 00:00:00 2001 From: bsergi Date: Wed, 9 Sep 2026 09:38:47 -0400 Subject: [PATCH 64/78] add full FINITO case to cases_examples.csv --- cases_examples.csv | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/cases_examples.csv b/cases_examples.csv index c13ee1bdc..2640794af 100644 --- a/cases_examples.csv +++ b/cases_examples.csv @@ -1,18 +1,20 @@ -,Default Value,ERCOT_rep_days,ERCOT_rep_wek,ERCOT_full_year,ERCOT_cap_credit,USA_20_rep_days,USA_69_regions,USA_54_regions,USA_FasterSolve,NorthDakota_county,NY_VT_mixed,ERCOT_county -ignore,1,,,,,,,,,,, -endyear,,2032,2032,2032,2032,,,,,2032,2032,2032 -GSw_Region,,interconnect/texas,interconnect/texas,interconnect/texas,interconnect/texas,,,,,st/ND,st/NY.VT,interconnect/texas -GSw_ZoneSet,,,,,,,z70,z54,z54,z3109,PJMcounty,z3109 -GSw_GasCurve,,2,2,2,2,,,,,2,2,2 -GSw_HourlyType,,,wek,year,,,,,,,, -GSw_OpRes,,,,0,,,,,0,,, -GSw_HourlyNumClusters,,,,,,20,,,20,,, -GSw_PRM_CapCredit,,,,,1,,,,,,, -GSw_StartCost,,,,,,,,,0,,, -GSw_LfillGas,,,,,,,,,0,,, -GSw_Geothermal,,,,,,,,,0,,, -GSw_Biopower,,,,,,,,,0,,, -GSw_Nuclear,,,,,,,,,0,,, -GSw_HourlyChunkLengthRep,,,,,,,,,4,,, -GSw_HourlyChunkLengthStress,,,,,,,,,4,,, -yearset,,,,,,,,,2010..2050..5,,, +,Default Value,ERCOT_rep_days,ERCOT_rep_wek,ERCOT_full_year,ERCOT_cap_credit,USA_20_rep_days,USA_69_regions,USA_54_regions,USA_FasterSolve,NorthDakota_county,NY_VT_mixed,ERCOT_county,FINITO +ignore,1,,,,,,,,,,,, +endyear,,2032,2032,2032,2032,,,,,2032,2032,2032, +GSw_Region,,interconnect/texas,interconnect/texas,interconnect/texas,interconnect/texas,,,,,st/ND,st/NY.VT,interconnect/texas, +GSw_ZoneSet,,,,,,,z70,z54,z54,z3109,PJMcounty,z3109,z54 +GSw_GasCurve,,2,2,2,2,,,,,2,2,2, +GSw_HourlyType,,,wek,year,,,,,,,,, +GSw_OpRes,,,,0,,,,,0,,,, +GSw_HourlyNumClusters,,,,,,20,,,20,,,, +GSw_PRM_CapCredit,,,,,1,,,,,,,, +GSw_StartCost,,,,,,,,,0,,,, +GSw_LfillGas,,,,,,,,,0,,,, +GSw_Geothermal,,,,,,,,,0,,,, +GSw_Biopower,,,,,,,,,0,,,, +GSw_Nuclear,,,,,,,,,0,,,, +GSw_HourlyChunkLengthRep,,,,,,,,,4,,,, +GSw_HourlyChunkLengthStress,,,,,,,,,4,,,, +yearset,,,,,,,,,,,,, +FINITO,,,,,,,,,,,,,1 +FINITO_case,,,,,,,,,,,,,BAU From fd2c0a2661b1363749fb07f4d2bb1291e699de15 Mon Sep 17 00:00:00 2001 From: bsergi Date: Wed, 9 Sep 2026 11:22:33 -0400 Subject: [PATCH 65/78] Sw_DetailedFuels --> Sw_DetailedNG --- reeds/core/terminus/report.gms | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/reeds/core/terminus/report.gms b/reeds/core/terminus/report.gms index 84f272570..d1d8c67eb 100644 --- a/reeds/core/terminus/report.gms +++ b/reeds/core/terminus/report.gms @@ -578,13 +578,13 @@ repgasprice(cendiv,t)$[(Sw_GasCurve = 2)$tmodel_new(t)$repgasquant(cendiv,t)$tfu * gas price when linked with FINITO [$2004/MMBtu] $ifthene.finitogasprice Sw_FINITO_Link == 1 * approach with GSw_FixedCostSupply=1 or default supply curves -repgasprice_finito(cendiv,h,t)$[tmodel_new(t)$(not tfuel(t))$(not Sw_DetailedFuels)] = +repgasprice_finito(cendiv,h,t)$[tmodel_new(t)$(not tfuel(t))$(not Sw_DetailedNG)] = deflator('%FINITO_dollaryear%') * 1/(obj_scale) * 1/(pvf_onm(t)) * eq_supplydemand_fsc.m('NG',cendiv,t) ; * approach with detailed fuels representation (GSw_DetailedFuels=1) -repgasprice_finito(cendiv,h,t)$[tmodel_new(t)$(not tfuel(t))$Sw_DetailedFuels] = +repgasprice_finito(cendiv,h,t)$[tmodel_new(t)$(not tfuel(t))$Sw_DetailedNG] = deflator('%FINITO_dollaryear%') * 1/(obj_scale) * 1/(pvf_onm(t)) * citygate price of natural gas * [ smax{(cfp,st)$st_cendiv(st,cendiv), eq_supplydemand_cf.M(cfp,'NG',st,h,t) } / hours(h) From 1fa486f068b4d24755fd992b705be0162887390b Mon Sep 17 00:00:00 2001 From: bsergi Date: Thu, 10 Sep 2026 09:17:38 -0400 Subject: [PATCH 66/78] add FINITO dollaryear to cases file --- cases.csv | 1 + 1 file changed, 1 insertion(+) diff --git a/cases.csv b/cases.csv index a90b13409..990e443bc 100644 --- a/cases.csv +++ b/cases.csv @@ -60,6 +60,7 @@ FINITO,Switch to enable running linked ReEDS-FINITO model (mapped to GSw_FINITO_ FINITO_cases_file,"Filename suffix of the FINITO cases file that includes the scenarios to be run (e.g., 'linked' for cases_linked.csv)",N/A,linked, FINITO_case,FINITO case for this run; must be a column in the files specified by FINITO_cases_file. Use 'same' if the ReEDS run name matches the FINITO case name,N/A,same, FINITO_dir,Directory with cloned FINITO repository,N/A,{reeds_path}/../FINITO, +FINITO_dollaryear,[$] real dollar year for FINITO; do not change until all inputs and reporting are adjusted appropriately,2018,2018 FINITO_first_year,First year when the FINITO module is to be run,N/A,2018, incentives_suffix,File suffix for incentive definition,ira; obbba; obbba_conservative; annual; biennial; ira_45q_45v_extension; none,obbba, inflation_suffix,File suffix for historical inflation schedule,default,default, From bdbcfd7273df95391ba7be41a5a797553e8995cc Mon Sep 17 00:00:00 2001 From: bsergi Date: Sat, 12 Sep 2026 14:18:22 -0400 Subject: [PATCH 67/78] initial pass at user guide section for ReEDS-FINITO --- .github/PULL_REQUEST_TEMPLATE.md | 2 +- docs/source/user_guide.md | 58 ++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index a8de87f0c..028618038 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -85,7 +85,7 @@ Include additional illustrative plots describing input data, methods, testing, a - + diff --git a/docs/source/user_guide.md b/docs/source/user_guide.md index eff586c19..30f8ce0ee 100644 --- a/docs/source/user_guide.md +++ b/docs/source/user_guide.md @@ -734,6 +734,64 @@ MCS Example: Total transmission capacity by interface. For additional information on using Hourlize, you can watch the training video: [Hourlize wind/solar resource preprocessing tutorial](https://nrel-my.sharepoint.com/:v:/r/personal/bsergi_nrel_gov/Documents/Misc/Recordings/Hourlize%20wind_solar%20resource%20preprocessing%20tutorial-20240212_150245-Meeting%20Recording.mp4?csf=1&web=1&e=vIds6r&nav=eyJyZWZlcnJhbEluZm8iOnsicmVmZXJyYWxBcHAiOiJTdHJlYW1XZWJBcHAiLCJyZWZlcnJhbFZpZXciOiJTaGFyZURpYWxvZy1MaW5rIiwicmVmZXJyYWxBcHBQbGF0Zm9ybSI6IldlYiIsInJlZmVycmFsTW9kZSI6InZpZXcifX0%3D) +## ReEDS-FINITO + +The Fuels and Industry Integrated Optimization Model (FINITO) provides a representation of the U.S. energy system with a bottom-up, technology-rich representation of the industrial sector. +ReEDS and FINITO can be linked to provide integrated modeling of the power sector with economy-wide energy supply and demand dynamics. +When linked, ReEDS and FINITO are formulated as a single optimization problem. + +### Setup + +FINITO can be cloned from https://github.com/NatLabRockies/FINITO (Note: this repository is currently private and only available within NLR). +By default ReEDS will look for a copy of the FINITO in the parent directory, but this can be configured by setting a value for `FINITO_dir` in your cases file. + +ReEdS switches controlling ReEDS-FINITO linkage: +- `FINITO` (default 0): Turn off/on the linked model. +Passes its value to `GSw_FINITO_Link` which is used to activate the linkage in the model. +- `FINITO_cases_file` (default 'linked'): Filename suffix of the FINITO cases file to use. +- `FINITO_case` (default 'same'): Name of the case in `FINITO_cases_file` to use for the ReEDS run; used to determine FINITO switch settings. +When set to 'same' it will assume the same case name as used in ReEDS. + +A linked model run is launched using the same call to `runreeds.py` as a normal ReEDS run. + +All ReEDS and FINITO switches are combined into a single group of switches. +As FINITO can also be run as a standalone model, some switches in the FINITO cases files have the same name as switches in ReEDS. +For any switches with conflicting values, a linked run will always use the value specified from ReEDS, using the default in the ReEDS `cases.csv` file as a fallback for any unspecfied switches. + +### Linkage points + +**Load balance** +- Electricity demand from FINITO's industrial focus sectors (set by the FINITO `focus_sectors` switch) and from converted fuel processing is added to the ReEDS load balancing equation via `USE_ELE_FINITO`. +- The load projection input data in ReEDS accounts for demand from sectors represented endogenously in FINITO. +To avoid double-counting, in a linked run the estimated 'reference' electricity demand for covered sectors is substracted from the load data processed for ReEDS in `hourly_load.py` (see the `finito.remove_finito_load` function) +- Due to lack of granular sector electricity demand data, the current approach takes annual demand estimates and allocates to hourly profiles assuming constant demand, and the broadcasts those across all weather years. This approach is a coarse assumption that enables running the linked model with detailed hourly sectoral, and future work aims to improve the temporal and interannual characterization of industrial loads. +- When itereating with PRAS, the demand represented by `USE_ELE_FINITO` is added back to the original load file. Values from each representative timeslice are mapped back to their corresponding 'actual' hour for the modeled year, with the data being repeated for other weather years. Currently all load is assumed to be inflexible; future work aims to explore characterizing the flexibility of this load in PRAS. +- The FINITO switch `GSw_ConstantProduction` can be used to enforce flat hourly production (and therefore electricity demand) across all hours represented by the model. + +**Fuel supply curves** +- When linked, ReEDS defers to FINITO for representation of hydrocarbon and biomass supply curves for solve years with FINITO activated. This representation supersedes any settings specified by fuel price switches in ReEDS (e.g., `ngscen`, `GSw_GasCurve`). +- Consumption of fuel equivalents in ReEDS is accounted for in FINITO by the `USE_FE_REEDS` variable. +- The activation of the ReEDS supply curves is controlled by the `tfuel` set, which is populated for any years using the ReEDS version. In a standalone run `tfuel` will include all solve years; in a linked run it will only includes t < `FINITO_first_year`. +- When linked, the output reporting in `report.gms` utilizes the FINITO marginals for calculating prices on the relevant quantities. +- The fuel supply curves can be adjusted by scenario by the `GSw_supply_scen` in FINITO, which includes scenarios from the AEO (e.g., Reference, HOG, LOG). +- Setting `GSw_DetailedNG=1` in FINITO enables explicit representation of natural gas production and interstate pipeline transport. +- Projections for non-power sector demand are scenario based and can be toggled using `GSw_demand_scen`. + +**Hydrogen** +- WHen linked FINITO defers to ReEDS for the representation of the production and transport of hydrogen. +- FINITO focus sector hydrogen demand from future conversion to hydrogen processes is tracked in ReEDS by `USE_H2_FINITO`. +- Note that when linked, FINITO does not represent existing industrial hydrogen demand; to account for this demand, when linked `GSw_H2_Demand_Case` should be set to `FINITO`. + +**CO2 representation** +- The link model relies on ReEDS for the representation of CO2 transport and storage. +- Total CO2 captured from FINITO is represented by `CAPTURE_CO2EM`. +- In the absence of explicit CO2 transport and storage modeling (i.e., `GSw_CO2_Detail=0`), the FINITO switches `GSw_CO2Cost_opt` and `GSw_CO2Cost_value` should be used to provide a cost for transport and sequestration. + +**Other linkages** +- Functions for processing related the linked model are located in the `reeds/finito.py` module. +- Costs represented by FINITO are captured by `Z_finito`, which is rescaled and converted from `FINITO_dollaryear` before being added to costs from ReEDS in `eq_ObjFn`. +- Select outputs are saved by FINITO to a `finito_reeds_outputs_%case%.gdx` file, which is processed by `report_dump.py` to save csv/h5 outputs alongside other ReEDS results. + ## Troubleshooting From b6bcf537844eca9876e87292a3d1d0de9d96c7e0 Mon Sep 17 00:00:00 2001 From: Brian Sergi Date: Wed, 16 Sep 2026 14:26:11 -0600 Subject: [PATCH 68/78] clean up call to read_mecs_heat.py --- reeds/finito.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/reeds/finito.py b/reeds/finito.py index c97354be1..59a422c74 100644 --- a/reeds/finito.py +++ b/reeds/finito.py @@ -181,12 +181,13 @@ def setup_finito(casedir, caseSwitches, BatchName): ) ## Call read_mecs_heat.py to generate heat/nonheat/feedstock ratios for FINITO Rest of Industry (ROI) - mecs_sectors = caseSwitches['focus_sectors'].replace('.', ' ') - read_mecs_path = ( - finito_dir / 'input_processing' / 'processing' / 'mecs' / 'read_mecs_heat.py' - ) subprocess.run( - ['python', str(read_mecs_path), '-s', mecs_sectors, '-d', str(inputs_case_finito)], + [ + 'python', str(finito_dir / 'input_processing' / 'processing' / 'mecs' / 'read_mecs_heat.py'), + '-c', str(casedir_finito), + '-d', str(inputs_case_finito), + '--link' + ], check=True, ) From 181bc1b118ebf7c9bd30baf6f459f05b26e7bf4e Mon Sep 17 00:00:00 2001 From: Brian Sergi Date: Fri, 18 Sep 2026 07:59:04 -0600 Subject: [PATCH 69/78] set default FINITO case to 10-year timesteps --- cases_examples.csv | 1 + 1 file changed, 1 insertion(+) diff --git a/cases_examples.csv b/cases_examples.csv index 6310a6297..a5427c020 100644 --- a/cases_examples.csv +++ b/cases_examples.csv @@ -1,6 +1,7 @@ ,Default Value,ERCOT_rep_days,ERCOT_rep_wek,ERCOT_full_year,ERCOT_cap_credit,USA_20_rep_days,USA_69_regions,USA_54_regions,USA_FasterSolve,NorthDakota_county,NY_VT_mixed,ERCOT_county,FINITO ignore,1,,,,,,,,,,,, endyear,2035,,,,,,,,,,,, +yearset,,,,,,,,,,,,,2010..2050..10 GSw_Region,,interconnect/texas,interconnect/texas,interconnect/texas,interconnect/texas,,,,,st/ND,st/NY.VT,interconnect/texas, GSw_ZoneSet,,,,,,,z70,z54,z54,z3109,PJMcounty,z3109,z54 GSw_GasCurve,,2,2,2,2,,,,,2,2,2, From ed729714343ef5a24492350b54ccbb501e5a4b6b Mon Sep 17 00:00:00 2001 From: bsergi Date: Fri, 25 Sep 2026 10:16:33 -0400 Subject: [PATCH 70/78] Update FINITO cases with recommend H2 settings --- cases_examples.csv | 5 ++++- cases_test.csv | 4 +++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/cases_examples.csv b/cases_examples.csv index a5427c020..89b3a52ba 100644 --- a/cases_examples.csv +++ b/cases_examples.csv @@ -17,4 +17,7 @@ GSw_Nuclear,,,,,,,,,0,,,, GSw_HourlyChunkLengthRep,,,,,,,,,4,,,, GSw_HourlyChunkLengthStress,,,,,,,,,4,,,, FINITO,,,,,,,,,,,,,1 -FINITO_case,,,,,,,,,,,,,BAU \ No newline at end of file +FINITO_case,,,,,,,,,,,,,BAU +GSw_H2,,,,,,,,,,,,,1 +GSw_H2_Demand_Case,,,,,,,,,,,,,BAU +GSw_H2_SMR,,,,,,,,,,,,,1 \ No newline at end of file diff --git a/cases_test.csv b/cases_test.csv index 43959f767..c0d8d4a40 100644 --- a/cases_test.csv +++ b/cases_test.csv @@ -66,4 +66,6 @@ pras_unitsize_source,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,r2x, pras_vre_combine,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1, pras_samples,,,,,,10,10,10,,,,,,,,,,,,,,,,10,10,,,,,,,, FINITO,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1 -FINITO_case,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,BAUfast_reg \ No newline at end of file +FINITO_case,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,BAUfast_reg +GSw_H2_Demand_Case,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,BAU +GSw_H2_SMR,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1 \ No newline at end of file From 64ae1897c72fb7d13cdf5bc155d5d648e68268d7 Mon Sep 17 00:00:00 2001 From: bsergi Date: Fri, 25 Sep 2026 10:18:17 -0400 Subject: [PATCH 71/78] A few other default settings for FINITO cases --- cases.csv | 2 +- cases_examples.csv | 9 +++++---- cases_test.csv | 7 ++++--- reeds/core/setup/d_objective.gms | 2 +- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/cases.csv b/cases.csv index 990e443bc..25335907c 100644 --- a/cases.csv +++ b/cases.csv @@ -60,7 +60,7 @@ FINITO,Switch to enable running linked ReEDS-FINITO model (mapped to GSw_FINITO_ FINITO_cases_file,"Filename suffix of the FINITO cases file that includes the scenarios to be run (e.g., 'linked' for cases_linked.csv)",N/A,linked, FINITO_case,FINITO case for this run; must be a column in the files specified by FINITO_cases_file. Use 'same' if the ReEDS run name matches the FINITO case name,N/A,same, FINITO_dir,Directory with cloned FINITO repository,N/A,{reeds_path}/../FINITO, -FINITO_dollaryear,[$] real dollar year for FINITO; do not change until all inputs and reporting are adjusted appropriately,2018,2018 +FINITO_dollaryear,[$] real dollar year for FINITO; do not change until all inputs and reporting are adjusted appropriately,2018,2018, FINITO_first_year,First year when the FINITO module is to be run,N/A,2018, incentives_suffix,File suffix for incentive definition,ira; obbba; obbba_conservative; annual; biennial; ira_45q_45v_extension; none,obbba, inflation_suffix,File suffix for historical inflation schedule,default,default, diff --git a/cases_examples.csv b/cases_examples.csv index 89b3a52ba..972dd8921 100644 --- a/cases_examples.csv +++ b/cases_examples.csv @@ -7,17 +7,18 @@ GSw_ZoneSet,,,,,,,z70,z54,z54,z3109,PJMcounty,z3109,z54 GSw_GasCurve,,2,2,2,2,,,,,2,2,2, GSw_HourlyType,,,wek,year,,,,,,,,, GSw_OpRes,,,,0,,,,,0,,,, -GSw_HourlyNumClusters,,,,,,20,,,20,,,, +GSw_HourlyNumClusters,,,,,,20,,,20,,,,20 GSw_PRM_CapCredit,,,,,1,,,,,,,, GSw_StartCost,,,,,,,,,0,,,, GSw_LfillGas,,,,,,,,,0,,,, GSw_Geothermal,,,,,,,,,0,,,, GSw_Biopower,,,,,,,,,0,,,, GSw_Nuclear,,,,,,,,,0,,,, -GSw_HourlyChunkLengthRep,,,,,,,,,4,,,, -GSw_HourlyChunkLengthStress,,,,,,,,,4,,,, +GSw_HourlyChunkLengthRep,,,,,,,,,4,,,,4 +GSw_HourlyChunkLengthStress,,,,,,,,,4,,,,4 FINITO,,,,,,,,,,,,,1 FINITO_case,,,,,,,,,,,,,BAU GSw_H2,,,,,,,,,,,,,1 GSw_H2_Demand_Case,,,,,,,,,,,,,BAU -GSw_H2_SMR,,,,,,,,,,,,,1 \ No newline at end of file +GSw_H2_SMR,,,,,,,,,,,,,1 +GSw_GasPriceAdjMethod,,,,,,,,,,,,,0 \ No newline at end of file diff --git a/cases_test.csv b/cases_test.csv index c0d8d4a40..6dcab1d68 100644 --- a/cases_test.csv +++ b/cases_test.csv @@ -42,8 +42,8 @@ GSw_CO2_Detail,,,,,,,1,,,,,,,,,,,,,,,1,,,,,,,,,,, GSw_DAC,,,,,,,1,,,,,,,,,,,,,,,1,,,,,,,,,,, GSw_NoFossilOffsetCDR,,,,,,,1,,,,,,,,,,,,,,,1,,,,,,,,,,, GSw_Biopower,,,,,,,,,,,,,,,,,,,,,,,0,,0,,,,,,,, -GSw_HourlyChunkLengthRep,,,,,,,,,,,,,,,,,,,,,,,6,4,4,,,,,,,, -GSw_HourlyChunkLengthStress,,,,,,,,,,,,,,,,,,,,,,,6,4,4,,,,,,,, +GSw_HourlyChunkLengthRep,,,,,,,,,,,,,,,,,,,,,,,6,4,4,,,,,,,,4 +GSw_HourlyChunkLengthStress,,,,,,,,,,,,,,,,,,,,,,,6,4,4,,,,,,,,4 GSw_LfillGas,,,,,,,,,,,,,,,,,,,,,,,0,,0,,,,,,,, GSw_Nuclear,,,,,,,,,,,,,,,,,,,,,,,0,,0,,,,,,,, GSw_OpRes,,,,,,,,,,,,,,,,,,,,,,,0,,0,,,,,,,, @@ -68,4 +68,5 @@ pras_samples,,,,,,10,10,10,,,,,,,,,,,,,,,,10,10,,,,,,,, FINITO,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1 FINITO_case,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,BAUfast_reg GSw_H2_Demand_Case,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,BAU -GSw_H2_SMR,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1 \ No newline at end of file +GSw_H2_SMR,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1 +GSw_GasPriceAdjMethod,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,0 \ No newline at end of file diff --git a/reeds/core/setup/d_objective.gms b/reeds/core/setup/d_objective.gms index 536d8995d..648bffa71 100644 --- a/reeds/core/setup/d_objective.gms +++ b/reeds/core/setup/d_objective.gms @@ -22,7 +22,7 @@ Variable Z "--$-- total cost of operations and investment, scale varie eq_ObjFn.. Z =e= cost_scale * ( * electricity and H2 costs sum{t$tmodel(t), Z_inv(t) + Z_op(t) } -* economy-wide costs from FINITO: deflate from $2018 to $2004 +* economy-wide costs from FINITO: deflate from FINITO_dollaryear to $2004 * and remove any FINITO scaling $ifthene.linked_objective Sw_FINITO_Link==1 + deflator('%FINITO_dollaryear%') / obj_scale * sum{t$[t_finito(t)], Z_finito(t) } From b6b55f541f4c782797b6940ba246eac56d434f84 Mon Sep 17 00:00:00 2001 From: bsergi Date: Fri, 25 Sep 2026 10:20:28 -0400 Subject: [PATCH 72/78] Add switch compatability for FINITO runs --- reeds/finito.py | 36 +++++++++++++++++++++++++++++++++++- runreeds.py | 7 ++++++- 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/reeds/finito.py b/reeds/finito.py index 59a422c74..97c3a92c0 100644 --- a/reeds/finito.py +++ b/reeds/finito.py @@ -34,7 +34,41 @@ def update_FINITO_switches(case, sw, new_switches): return new_switches -def setup_linked_cases(df_cases, case): +def check_FINITO_switch_compatability(sw): + """ + Checks for switch compatability for linked ReEDS-FINITO runs + """ + + ## gas settings + if int(sw['GSw_GasPriceAdjMethod']) > 0: + raise ValueError( + f"GSw_GasPriceAdjMethod = {sw['GSw_GasPriceAdjMethod']} is not compatible with the " + "linked ReEDS-FINITO model (FINITO = 1)" + ) + print( + "Note: for ReEDS-FINITO runs (FINITO = 1) natural gas prices are\n" + "derived from FINITO constraints and will override the settings for\n" + "'ngscen' and 'GSw_GasCurve' for years >= FINITO_first_year.\n" + ) + + # H2 settings + if int(sw['GSw_H2']) == 0: + raise ValueError( + "GSw_H2 > 0 is required for ReEDS-FINITO runs (FINITO = 1)" + ) + + if int(sw['GSw_H2_SMR']) == 0 or sw['GSw_H2_Demand_Case'] == 'none': + print( + "Note: for ReEDS-FINITO runs (FINITO = 1) it is recommended to run with exogenous\n" + f"H2 demand (GSw_H2_Demand_Case != 'none'; current setting = '{sw['GSw_H2_Demand_Case']}')\n" + f"and with steam methane reforming (GSw_H2_SMR = 1; current setting = {sw['GSw_H2_SMR']}).\n" + ) + confirm = str(input('\nProceed? y/[n]: ') or 'n') + if confirm.lower() not in ['y', 'yes']: + sys.exit(0) + + +def setup_linked_FINITO_cases(df_cases, case): """ Updates the cases dataframe to include FINITO-specific switches. When a switch is duplicated in FINITO and ReEDS, then we default to diff --git a/runreeds.py b/runreeds.py index d4fe5b0f1..3317ec1a2 100644 --- a/runreeds.py +++ b/runreeds.py @@ -103,7 +103,7 @@ def create_case_lists(df_cases:pd.DataFrame, BatchName:str, single:str=''): case_out = df_cases[case].copy() # (ReEDS-FINITO) Combine the cases files for the linked model if int(case_out.loc['GSw_FINITO_Link']) == 1 : - case_out=reeds.finito.setup_linked_cases(df_cases,case) + case_out=reeds.finito.setup_linked_FINITO_cases(df_cases,case) #exclude certain switches that don't need to be passed to GAMS for i,v in case_out.items(): @@ -552,6 +552,11 @@ def check_compatibility(sw): ) raise ModuleNotFoundError(err) + ## ReEDS-FINITO switches + if int(sw['GSw_FINITO_Link'])==1: + reeds.finito.check_FINITO_switch_compatability(sw) + + # function to stop the model after input processing def stop_after_input_processing(OPATH, reeds_path, casedir, caseSwitches): comment('Exit after input_processing', OPATH) From f08a3323b1659d34ce0143dcacfb42df9491dfd2 Mon Sep 17 00:00:00 2001 From: bsergi Date: Fri, 25 Sep 2026 10:32:40 -0400 Subject: [PATCH 73/78] clean natural gas price reporting with FINITO --- reeds/core/terminus/report.gms | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/reeds/core/terminus/report.gms b/reeds/core/terminus/report.gms index d1d8c67eb..4f87643e4 100644 --- a/reeds/core/terminus/report.gms +++ b/reeds/core/terminus/report.gms @@ -577,22 +577,23 @@ repgasprice(cendiv,t)$[(Sw_GasCurve = 2)$tmodel_new(t)$repgasquant(cendiv,t)$tfu * gas price when linked with FINITO [$2004/MMBtu] $ifthene.finitogasprice Sw_FINITO_Link == 1 + +$ifthene.finitodetailedNG Sw_DetailedNG == 0 * approach with GSw_FixedCostSupply=1 or default supply curves -repgasprice_finito(cendiv,h,t)$[tmodel_new(t)$(not tfuel(t))$(not Sw_DetailedNG)] = +repgasprice_finito(cendiv,h,t)$[tmodel_new(t)$(not tfuel(t))] = deflator('%FINITO_dollaryear%') * 1/(obj_scale) * 1/(pvf_onm(t)) * eq_supplydemand_fsc.m('NG',cendiv,t) ; - +$else.finitodetailedNG * approach with detailed fuels representation (GSw_DetailedFuels=1) -repgasprice_finito(cendiv,h,t)$[tmodel_new(t)$(not tfuel(t))$Sw_DetailedNG] = +repgasprice_finito(cendiv,h,t)$[tmodel_new(t)$(not tfuel(t))] = deflator('%FINITO_dollaryear%') * 1/(obj_scale) * 1/(pvf_onm(t)) * citygate price of natural gas - * [ smax{(cfp,st)$st_cendiv(st,cendiv), eq_supplydemand_cf.M(cfp,'NG',st,h,t) } / hours(h) -* electric-sector markup for natural gas -* 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) } - ] + * [ smax{(cfp,st)$st_cendiv(st,cendiv), eq_supplydemand_cf.M(cfp,'NG',st,h,t) } / hours(h) ] ; + +$endif.finitodetailedNG + $else.finitogasprice repgasprice_finito(cendiv,h,t)$[tmodel_new(t)$(not tfuel(t))] = 0 ; $endif.finitogasprice From 298d830332466022048c815d5b5e90b5c04250b7 Mon Sep 17 00:00:00 2001 From: bsergi Date: Fri, 25 Sep 2026 10:35:08 -0400 Subject: [PATCH 74/78] clean out some TODO items --- reeds/core/setup/c_model.gms | 1 - reeds/finito.py | 3 +-- reeds/resource_adequacy/prep_data.py | 2 -- 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/reeds/core/setup/c_model.gms b/reeds/core/setup/c_model.gms index 16203a843..12b272aa2 100644 --- a/reeds/core/setup/c_model.gms +++ b/reeds/core/setup/c_model.gms @@ -3555,7 +3555,6 @@ eq_h2_demand(p,t)$[(sameas(p,"H2"))$tmodel(t)$(yeart(t)>=h2_demand_start)$(Sw_H2 } * hydrogen demand from industry when linked with FINITO: demand [MMBtu/yr] * conversion [metric tons-H2/MMBtu-H2] -* TODO: should we disable exogenous H2 demand when linked? $ifthene.linked_h2_nat Sw_FINITO_Link==1 + [sum{(r,h)$h_rep(h), hours(h) * USE_H2_FINITO(r,h,t) * h2_metric_tons_per_mmbtu }]$t_finito(t) $endif.linked_h2_nat diff --git a/reeds/finito.py b/reeds/finito.py index 97c3a92c0..aa8169500 100644 --- a/reeds/finito.py +++ b/reeds/finito.py @@ -264,8 +264,7 @@ def get_hourly_finito_load( p2zone = p2zone.set_index('p')['zone'] 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? + # allocate annual load to hours, assuming flat demand (see h_weight_finito) hours_per_year = 8760 load_hourly_finito = load_finito / hours_per_year load_hourly_finito = load_hourly_finito.astype(np.float32) diff --git a/reeds/resource_adequacy/prep_data.py b/reeds/resource_adequacy/prep_data.py index 92f172b7d..53edf0ec2 100644 --- a/reeds/resource_adequacy/prep_data.py +++ b/reeds/resource_adequacy/prep_data.py @@ -159,8 +159,6 @@ def main(t, casedir, iteration=0): # down select to relevant model year load_finito = load_finito_rt.loc[load_finito_rt.t==t].drop('t', axis=1).copy() - #TODO: why so much shifting? validate the dynamics here with FINITO team - #TODO: also check against reference quantity # map from rep day to actual hour # since we don't have multi-year profiles for FINITO load From 65c09310194f1ace5d599c6ab4424a6a708d39aa Mon Sep 17 00:00:00 2001 From: bsergi Date: Fri, 25 Sep 2026 10:59:37 -0400 Subject: [PATCH 75/78] reorder dataframe pivot before merge for speed up --- reeds/resource_adequacy/prep_data.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/reeds/resource_adequacy/prep_data.py b/reeds/resource_adequacy/prep_data.py index 53edf0ec2..ab3eee17f 100644 --- a/reeds/resource_adequacy/prep_data.py +++ b/reeds/resource_adequacy/prep_data.py @@ -159,15 +159,16 @@ def main(t, casedir, iteration=0): # down select to relevant model year load_finito = load_finito_rt.loc[load_finito_rt.t==t].drop('t', axis=1).copy() + load_finito = load_finito.pivot(index=['h'], columns='r', values='load_MW') # map from rep day to actual hour # since we don't have multi-year profiles for FINITO load # we assume they repeat across all weather years - load_finito = pd.merge(load_finito, h_dt_szn.reset_index(), on='h', how='outer')[['timestamp', 'r', 'load_MW']] - load_finito = load_finito.rename(columns={'timestamp':'datetime'}) - # add model year back in - #load_finito = load_finito.assign(year=t) - load_finito = load_finito.pivot(index=['datetime'], columns='r', values='load_MW') + load_finito = pd.merge( + load_finito.reset_index(), + h_dt_szn.reset_index()[['h', 'timestamp']], + on='h', how='outer').drop('h', axis=1) + load_finito = load_finito.rename(columns={'timestamp':'datetime'}).set_index('datetime') # convert timezone and fill any missing columns load_finito.index = load_finito.index.tz_convert(load_year.index.tz) From 2fc9e5ab40caadb2e7631cbec7c909dc6af5a994 Mon Sep 17 00:00:00 2001 From: bsergi Date: Fri, 25 Sep 2026 11:21:26 -0400 Subject: [PATCH 76/78] clean up copying of FINITO files with restart_runs.py --- helpers/restart_runs.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/helpers/restart_runs.py b/helpers/restart_runs.py index a3ad06170..cb51a692c 100644 --- a/helpers/restart_runs.py +++ b/helpers/restart_runs.py @@ -101,10 +101,9 @@ sw = reeds.io.get_switches(case) if int(sw.GSw_FINITO_Link): f_copy = re.sub("^finito/", "" , f, flags=re.IGNORECASE) - shutil.copy(os.path.join(sw.FINITO_dir,f_copy), os.path.join(case,f)) + shutil.copy(Path(sw.FINITO_dir,f_copy), Path(case,f)) else: - shutil.copy(os.path.join(reeds_path,f), os.path.join(case,f)) - shutil.copy(f, Path(case, f)) + shutil.copy(f, Path(case, f)) if copy_reeds: shutil.copytree(Path(reeds_path, 'reeds'), Path(case, 'reeds'), dirs_exist_ok=True) From 56c32a345c640dde44e843ee19408d21fca94001 Mon Sep 17 00:00:00 2001 From: bsergi Date: Fri, 25 Sep 2026 15:03:40 -0400 Subject: [PATCH 77/78] tfuel indent and comment cleanup --- reeds/core/setup/b_inputs.gms | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/reeds/core/setup/b_inputs.gms b/reeds/core/setup/b_inputs.gms index 2e5ed607d..cb91bb69d 100644 --- a/reeds/core/setup/b_inputs.gms +++ b/reeds/core/setup/b_inputs.gms @@ -947,7 +947,7 @@ set tmodel(t) "years to include in the model", tprev(t,tt) "previous modeled tt from year t", stfeas(st) "states to include in the model", tsolved(t) "years that have solved", - tfuel(t) "years that use ReEDS fuel supply curve module (otherwise uses supply curves in FINITO)" ; + tfuel(t) "years that use ReEDS fuel costs (years not included here use FINITO costs)" ; *following parameters get re-defined when the solve years have been declared parameter mindiff(t) "minimum difference between t and all other tt that are in tmodel(t)" ; @@ -978,7 +978,7 @@ tprev(t,tt)$[tmodel_new(t)$tmodel_new(tt)$(t.val-tt.val<>mindiff(t))] = no ; * If FINITO linkage is on, remove all modeled years from tfuel after FINITO_first_year * as the FINITO supply curves will be used instead of those in ReEDS; * otherwise, all modeled years use ReEDS supply curves and are eligible for tfuel -tfuel(t)$[tmodel_new(t)]=yes; +tfuel(t)$[tmodel_new(t)] = yes ; tfuel(t)$[tmodel_new(t)$Sw_FINITO_Link$(t.val>=%FINITO_first_year%)] = no ; * In order to fill all necessary dimensions of upgrade techs parameters, we require From af4fa74e6b4fc96dd051a779f3ccfdc8dcc01256 Mon Sep 17 00:00:00 2001 From: bsergi Date: Fri, 25 Sep 2026 21:03:44 -0400 Subject: [PATCH 78/78] revised gas switch compability check --- reeds/finito.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/reeds/finito.py b/reeds/finito.py index aa8169500..be06d1e2d 100644 --- a/reeds/finito.py +++ b/reeds/finito.py @@ -39,24 +39,23 @@ def check_FINITO_switch_compatability(sw): Checks for switch compatability for linked ReEDS-FINITO runs """ - ## gas settings + ## natural gas settings if int(sw['GSw_GasPriceAdjMethod']) > 0: raise ValueError( - f"GSw_GasPriceAdjMethod = {sw['GSw_GasPriceAdjMethod']} is not compatible with the " - "linked ReEDS-FINITO model (FINITO = 1)" - ) - print( - "Note: for ReEDS-FINITO runs (FINITO = 1) natural gas prices are\n" - "derived from FINITO constraints and will override the settings for\n" - "'ngscen' and 'GSw_GasCurve' for years >= FINITO_first_year.\n" - ) + "ReEDS-FINITO runs are only compatible with GSw_GasPriceAdjMethod = 0 " + f"(current setting = {sw['GSw_GasPriceAdjMethod']})" + ) + if int(sw['GSw_GasCurve']) != 2: + raise ValueError( + "ReEDS-FINITO runs are only compatible with GSw_GasCurve = 2 " + f"(current setting = {sw['GSw_GasCurve']})" + ) - # H2 settings + ## H2 settings if int(sw['GSw_H2']) == 0: raise ValueError( "GSw_H2 > 0 is required for ReEDS-FINITO runs (FINITO = 1)" ) - if int(sw['GSw_H2_SMR']) == 0 or sw['GSw_H2_Demand_Case'] == 'none': print( "Note: for ReEDS-FINITO runs (FINITO = 1) it is recommended to run with exogenous\n"