From 2f84026c8ac3b5f82b5954d22549be61854a6131 Mon Sep 17 00:00:00 2001 From: jcarag Date: Tue, 23 Jun 2026 13:56:41 -0600 Subject: [PATCH 1/3] Run climateprep.py before hydcf.py --- runreeds.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runreeds.py b/runreeds.py index e0628622..7615d966 100644 --- a/runreeds.py +++ b/runreeds.py @@ -1302,6 +1302,7 @@ def write_batch_script( for s in [ 'copy_files', 'mcs_sampler', + 'climateprep', 'hydcf', 'h2_storage', 'calc_financial_inputs', @@ -1310,7 +1311,6 @@ def write_batch_script( 'writesupplycurves', 'writedrshift', 'plantcostprep', - 'climateprep', 'hourly_load', 'recf', 'forecast', From 7298b5ddc213e4e75fdb9ebc00f1f8a443e88580 Mon Sep 17 00:00:00 2001 From: jcarag Date: Wed, 24 Jun 2026 14:51:44 -0600 Subject: [PATCH 2/3] Apply climate adjustments to hydropower CFs in input processing: - Add procedure for applying climate adjustments in hydcf.py - Modify climateprep.py to output hydadjsea.csv as climate_hydadjsea.csv in long format (deletes temp_hydadjsea.csv) - Remove procedures for temp_hydadjsea.csv from hourly_writetimeseries.py - Remove instances of climate_hydadjsea.csv and climate_hydro_* parameters from b_inputs.gms and 2_temporal_params.gms - Add entries for hydadjann.csv and hydadjsea.csv back into runfiles.csv --- reeds/core/setup/b_inputs.gms | 18 ----- reeds/core/solve/2_temporal_params.gms | 23 ------- reeds/input_processing/climateprep.py | 24 +++---- .../hourly_writetimeseries.py | 10 +-- reeds/input_processing/hydcf.py | 67 +++++++++++++++++++ reeds/input_processing/runfiles.csv | 2 + 6 files changed, 82 insertions(+), 62 deletions(-) diff --git a/reeds/core/setup/b_inputs.gms b/reeds/core/setup/b_inputs.gms index 90d45c9d..313d4886 100644 --- a/reeds/core/setup/b_inputs.gms +++ b/reeds/core/setup/b_inputs.gms @@ -4109,24 +4109,6 @@ fuel_price(i,r,t)$[sum{f$fuel2tech(f,i),1}$(not fuel_price(i,r,t))] = fuel_price(i,r,t)$upgrade(i) = sum{ii$upgrade_to(i,ii), fuel_price(ii,r,t) } ; -*===================================================== -* -- Climate impacts on nondispatchable hydropower -- -*===================================================== - -$ifthen.climatehydro %GSw_ClimateHydro% == 1 - -* declared over allt to allow for external data files that extend beyond end_year -* Written by climateprep.py -table climate_hydro_annual(r,allt) "annual dispatchable hydropower availability" -$offlisting -$ondelim -$include inputs_case%ds%climate_hydadjann.csv -$offdelim -$onlisting -; -$endif.climatehydro - - *===================================================== * -- Climate impacts on nondispatchable hydropower -- *===================================================== diff --git a/reeds/core/solve/2_temporal_params.gms b/reeds/core/solve/2_temporal_params.gms index 5c0c7ab5..7c732630 100644 --- a/reeds/core/solve/2_temporal_params.gms +++ b/reeds/core/solve/2_temporal_params.gms @@ -472,29 +472,6 @@ $onlisting / ; $offempty -$ifthen.climatehydro %GSw_ClimateHydro% == 1 - -* Written by climateprep.py -table climate_hydro_seasonal(r,allszn,allt) "annual/seasonal nondispatchable hydropower availability" -$offlisting -$ondelim -$include inputs_case%ds%%temporal_inputs%%ds%climate_hydadjsea.csv -$offdelim -$onlisting -; - -* adjust cf_hyd based on annual/seasonal climate multipliers -* non-dispatchable hydro gets new seasonal profiles as well as annually-varying CF -* dispatchable hydro keeps the original seasonal profiles; only annual CF changes. Reflects the assumption -* that reservoirs will be utilized in the same seasonal pattern even if seasonal inflows change. -cf_hyd(i,szn,r,t)$[hydro_nd(i)$(yeart(t)>=Sw_ClimateStartYear)] = - sum{allt$att(allt,t), cf_hyd(i,szn,r,t) * climate_hydro_seasonal(r,szn,allt) } ; - -cf_hyd(i,szn,r,t)$[hydro_d(i)$(yeart(t)>=Sw_ClimateStartYear)] = - sum{allt$att(allt,t), cf_hyd(i,szn,r,t) * climate_hydro_annual(r,allt) } ; - -$endif.climatehydro - *created by reeds/input_processing/writecapdat.py parameter cap_hyd_szn_adj(i,allszn,r) "--fraction-- seasonal max capacity adjustment for dispatchable hydro" / diff --git a/reeds/input_processing/climateprep.py b/reeds/input_processing/climateprep.py index e7c33970..f099e9af 100644 --- a/reeds/input_processing/climateprep.py +++ b/reeds/input_processing/climateprep.py @@ -181,21 +181,19 @@ def readwrite( ## Drop extra data after the model end year .loc[:,:endyear] ) - - # Files indexed by month undergo additional processing in hourly_writetimeseries. Create - # intermediate filenames for these files and melt them back to long format - file_prefix = 'temp' if 'month' in dfout.index.names else 'climate' - keepindex = False if file_prefix == 'temp' else True - if file_prefix == 'temp': - dfout = pd.melt( - dfout.reset_index(), id_vars=index[infile], value_vars=dfout.columns, - var_name='t', value_name='Value' - ) - + # Melt to long format + dfout = pd.melt( + dfout.reset_index(), id_vars=index[infile], value_vars=dfout.columns, + var_name='t', value_name='Value' + ) + # ClimateWater files indexed by month undergo additional processing in hourly_writetimeseries - + # Create intermediate filenames for these files + file_prefix = 'temp' if infile in ['UnappWaterMult','UnappWaterSeaAnnDistr'] else 'climate' # Write it to output folder dfout.round(decimals).to_csv(os.path.join(inputs_case, f'{file_prefix}_{infile}.csv'), - index=keepindex) - + index=False) + + return dfout diff --git a/reeds/input_processing/hourly_writetimeseries.py b/reeds/input_processing/hourly_writetimeseries.py index 8ac35efb..21102785 100644 --- a/reeds/input_processing/hourly_writetimeseries.py +++ b/reeds/input_processing/hourly_writetimeseries.py @@ -232,7 +232,6 @@ def format_climate_inputs(filename, inputs_case, szn_month_weights): szn_month_weights """ climate_index = { - 'temp_hydadjsea': ['r','season','t'], 'temp_UnappWaterMult': ['wst','r','season','t'], 'temp_UnappWaterSeaAnnDistr': ['wst','r','season','t'] } @@ -1105,10 +1104,8 @@ def main(sw, reeds_path, inputs_case, periodtype='rep', make_plots=1, logging=Tr .rename(columns={"season": "szn"}) ) - ### Import and format monthly climate_{hydadjsea/UnappWaterMult/UnappWaterSeaAnnDistr}.csv + ### Import and format monthly climate_{UnappWaterMult/UnappWaterSeaAnnDistr}.csv climate_files = {} - if int(sw.GSw_ClimateHydro): - climate_files['temp_hydadjsea'] = format_climate_inputs('temp_hydadjsea', inputs_case, szn_month_weights) if int(sw.GSw_ClimateWater): for file in ['temp_UnappWaterMult', 'temp_UnappWaterSeaAnnDistr']: climate_files[file] = format_climate_inputs(file, inputs_case, szn_month_weights) @@ -1532,10 +1529,7 @@ def main(sw, reeds_path, inputs_case, periodtype='rep', make_plots=1, logging=Tr "peak_h": [pd.DataFrame(columns=["*r", "h", "t", "MW"]), True, False], } - # Add climate inputs based on GSw_Climate* switch selection - if int(sw.GSw_ClimateHydro): - ## Climate-adjusted annual/seasonal nondispatchable hydropower availability - write['climate_hydadjsea'] = [climate_files['temp_hydadjsea'], True, True] + # Add climate inputs to write dictionary based on GSw_ClimateWater if int(sw.GSw_ClimateWater): ## Climate-adjusted time-varying annual/seasonal water supply write['climate_UnappWaterMult'] = [climate_files['temp_UnappWaterMult'], True, True] diff --git a/reeds/input_processing/hydcf.py b/reeds/input_processing/hydcf.py index c62e3b20..d498255b 100644 --- a/reeds/input_processing/hydcf.py +++ b/reeds/input_processing/hydcf.py @@ -403,6 +403,60 @@ def assemble_hydcf( return hydcf +def apply_hydro_climate_adjustments( + hydcf_unadjusted: pd.DataFrame, + inputs_case: str +) -> pd.DataFrame: + """ + Applies climate adjustment factors to hydropower capacity factors, if applicable + + Args: + hydcf_unadjusted: Monthly regional CFs prior to climate adjustments + inputs_case: Path to the inputs case directory. + Returns: + pd.DataFrame + """ + # Exit function if climate adjustments to hydropower are turned OFF, otherwise continue + sw = reeds.io.get_switches(inputs_case) + if not int(sw.GSw_ClimateHydro): + return + + # Get sets for dispatchable/non-dispatchable hydro from tech subset table + tech_subsets = pd.read_csv( + os.path.join(inputs_case, 'tech-subset-table.csv'), + index_col=0 + ) + hydro_d = set(tech_subsets.loc[tech_subsets['HYDRO_D']=='YES'].index) + hydro_nd = set(tech_subsets.loc[tech_subsets['HYDRO_ND']=='YES'].index) + + # Separate data into dispatchable vs non-dispatchable hydropower + hydcf_d = hydcf_unadjusted[hydcf_unadjusted.index.isin(hydro_d, level='*i')].reset_index().copy() + hydcf_nd = hydcf_unadjusted[hydcf_unadjusted.index.isin(hydro_nd, level='*i')].reset_index().copy() + assert len(hydcf_d)+len(hydcf_nd) == len(hydcf_unadjusted), "At least 1 hydro tech is unaccounted for from hydcf.csv" + + # Read hydropower CF climate adjustments and melt to long form + adj_factors_ann = pd.read_csv( + os.path.join(inputs_case, 'climate_hydadjann.csv'), + dtype={'r':str,'t':int} + ).rename(columns={'Value':'Factor'}) + adj_factors_sea = pd.read_csv( + os.path.join(inputs_case, 'climate_hydadjsea.csv'), + dtype={'r':str,'t':int,'month':str} + ).rename(columns={'Value':'Factor'}) + + # Apply adjustment factors + hydcf_d = pd.merge(hydcf_d, adj_factors_ann, how='left', on=['r','t']) + hydcf_d['value_adj'] = hydcf_d['value'] * hydcf_d['Factor'] + hydcf_d = hydcf_d.drop(columns=['value','Factor']).rename(columns={'value_adj':'value'}) + hydcf_nd = pd.merge(hydcf_nd, adj_factors_sea, how='left', on=['r','month','t']) + hydcf_nd['value_adj'] = hydcf_nd['value'] * hydcf_nd['Factor'] + hydcf_nd = hydcf_nd.drop(columns=['value','Factor']).rename(columns={'value_adj':'value'}) + + # Reassemble hydcf + hydcf = pd.concat([hydcf_d,hydcf_nd],axis=0) + + breakpoint() + return hydcf #%% =========================================================================== ### --- MAIN FUNCTION --- @@ -431,6 +485,11 @@ def main(reeds_path, inputs_case): future_monthly_regional_cf, inputs_case ) + hydcf = apply_hydro_climate_adjustments( + hydcf, + inputs_case + ) + hydcf.to_csv(os.path.join(inputs_case, 'hydcf.csv')) @@ -455,6 +514,12 @@ def main(reeds_path, inputs_case): reeds_path = args.reeds_path inputs_case = args.inputs_case + # #%% Settings for testing + # reeds_path = reeds.io.reeds_path + # inputs_case = os.path.join( + # reeds_path,'runs', + # 'InstantiateRepo_USA_defaults','inputs_case') + #%% Set up logger log = reeds.log.makelog( scriptname=__file__, @@ -468,3 +533,5 @@ def main(reeds_path, inputs_case): path=os.path.join(inputs_case,'..')) print('Finished hydcf.py') + +# %% diff --git a/reeds/input_processing/runfiles.csv b/reeds/input_processing/runfiles.csv index 6ca5b199..351ede7b 100644 --- a/reeds/input_processing/runfiles.csv +++ b/reeds/input_processing/runfiles.csv @@ -113,6 +113,8 @@ heat_rate_mult.csv,inputs/waterclimate/heat_rate_mult.csv,int(sw.GSw_WaterMain) heat_rate_penalty_spin.csv,inputs/plant_characteristics/heat_rate_penalty_spin.csv,1,ignore,ignore,,,,,0,,,,,, hintage_char.csv,inputs/sets/hintage_char.csv,1,ignore,ignore,,,,,,,,set,hintage_char,characteristics available in hintage_data, hyd_add_upg_cap.csv,inputs/supply_curve/hyd_add_upg_cap.csv,int(sw.GSw_HydroCapEnerUpgradeType) == 2,sum,hydroexist,r,"i,rscbin,wide",,1,0,,,,,, +hydadjann.csv,inputs/climate/{climatescen}/hydadjann.csv,int(sw.GSw_ClimateHydro) != 0,mean,uniform,r,t,,0,0,,,,,, +hydadjsea.csv,inputs/climate/{climatescen}/hydadjsea.csv,int(sw.GSw_ClimateHydro) != 0,mean,uniform,r,"month,t",,0,0,,,,,, hyd_fom.csv,inputs/hydro/hyd_fom.csv,1,mean,uniform,wide,i,,1,0,,,,,, hydcap.csv,inputs/supply_curve/hydcap.csv,1,sum,geosize,wide,"tech,class",,1,0,,,,,, hydcapadj.csv,inputs/hydro/SeaCapAdj_hy.csv,1,mean,uniform,r,"*i,month",,0,0,,,,,, From 9a417d416321f61021b0cad89969629fb2b1ad9f Mon Sep 17 00:00:00 2001 From: jcarag Date: Wed, 24 Jun 2026 15:03:08 -0600 Subject: [PATCH 3/3] Remove breakpoint --- reeds/input_processing/hydcf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reeds/input_processing/hydcf.py b/reeds/input_processing/hydcf.py index d498255b..d39f0ac7 100644 --- a/reeds/input_processing/hydcf.py +++ b/reeds/input_processing/hydcf.py @@ -455,7 +455,7 @@ def apply_hydro_climate_adjustments( # Reassemble hydcf hydcf = pd.concat([hydcf_d,hydcf_nd],axis=0) - breakpoint() + return hydcf #%% ===========================================================================