diff --git a/cases.csv b/cases.csv index ab6b600bc..ed410bdf5 100644 --- a/cases.csv +++ b/cases.csv @@ -224,7 +224,9 @@ GSw_MethaneLeakageScen,Fractional leakage of methane from upstream natural gas p GSw_MGA_CostDelta,MGA: Fraction by which to allow objective function to increase when using Modeling to Generate Alternatives (MGA is turned off if set to 0; a common choice for MGA is 0.01),float,0, GSw_MGA_Direction,MGA: Directionality of second optimization,(min|max),min, GSw_MGA_Objective,MGA: Objective for MGA (uses GSw_MGA_SubObjective to specify technology subset if set to capacity),(capacity|generation|transmission|rasharing|co2|employment),capacity, -GSw_MGA_SubObjective,MGA: Technology subset to minimize or maximize the capacity or generation of (only used for GSw_MGA_Objective=(capacity or generation)),(battery|ccs|coal|dac|fossil|gas|gentech|geo|h2_combustion|h2_gen|hydro|nuclear|ofswind|onswind|pv|re|storage|upv|vre|wind),gentech, +GSw_MGA_SubObjective,MGA: Technology subset to minimize or maximize the capacity or generation of (only used for GSw_MGA_Objective=(capacity or generation); must be a column of tech-subset-table.csv),(battery|bio|ccs|coal|dac|fossil|gas|gentech|geo|h2_combustion|h2_gen|hydro|nuclear|ofswind|onswind|psh|pv|re|storage|upv|vre|wind),gentech, +GSw_MGA_RV_runs,"MGA: Number of samples drawn for the random vector weights that are applied to variables specified in GSw_MGA_Objective and GSw_MGA_SubObjective. Only active when GSw_MGA_CostDelta != 0; set to an integer N>0 to run N ReEDS simulations with sampling (0 disables sampling).",int,0, +GSw_MGA_RV_region,"MGA: regionality to use with random vector method (active when GSw_MGA_RV_runs>=1)",r; nercr; transreg; transgrp; cendiv; st; interconnect; country; usda_region,r, GSw_MinCF,Turn on/off regional min CF constraint (applied at i/r level),0; 1,1, GSw_Mingen,Turn on/off min-gen constraints by r/h/szn,0; 1,0, GSw_MingenFixed,Turn on/off fixed min-gen constraints,0; 1,1, @@ -393,4 +395,4 @@ resource_adequacy_years,_-delimited years to include in resource adequacy calcul MCS_runs,Number of Monte Carlo simulation runs. Set to 0 to disable MCS. Set to an integer N > 0 to run N ReEDS simulations with sampling.,int,0, MCS_dist,Name of the `.yaml` file defining the MCS distributions. Use 'default' or a string beginning with 'user' to reference `inputs/userinput/mcs_distributions_{MCS_dist}.yaml`,N/A,default, MCS_dist_groups,Define the distribution groups to use in a Monte Carlo Simulation. Each entry should refer to a key from `inputs/userinput/mcs_distributions_{MCS_dist}.yaml` with multiple entries separated by a dot (e.g. Example1.Example2.Example3),N/A,tech, -MCS_lhs,Option to use random sampling (0) or latin hypercube sampling (1) for Monte Carlo simulation,0; 1,0, +MCS_lhs,Option to use random sampling (0) or latin hypercube sampling (1) for Monte Carlo simulation (also applies to MGA random vector method when not using discrete sample values),0; 1,0, diff --git a/cases_test.csv b/cases_test.csv index f3a122adc..d52d2b771 100644 --- a/cases_test.csv +++ b/cases_test.csv @@ -1,66 +1,67 @@ -,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 -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, -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..2050..5,2010_2025_2050,2010..2050..5,,,,, -GSw_ZoneSet,,,,,,,z54,z3109,,,,,,,z3109,z3109,z3109,PJMcounty,,,,z54,z134,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_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,,,,,, -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_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,,,,,, +,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_MGA_RV,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/NY.NJ,,,,,st/MA.RI.CT.NY.NJ.PA.OH, +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..2050..5,2010_2025_2050,2010..2050..5,,,,,, +GSw_ZoneSet,,,,,,,z54,z3109,,,,,,,z3109,z3109,z3109,PJMcounty,,,,z54,z134,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_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,,,,,,, +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_PRM_StressThresholdMetrics,,,,,,,,,,,,,,,,,,,,,,,,,,NEUE/LOLH/LOLE/LOLD/duration/depth,,,,,, +GSw_DRShed,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,, +GSw_MGA_CostDelta,,,,,,,,,,,,,,,,,,,,,,,,,,,,0.01,0.01,,, +GSw_MGA_RV_runs,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,,, +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,,,,,,, \ No newline at end of file diff --git a/docs/source/user_guide.md b/docs/source/user_guide.md index cb456a7ce..eff586c19 100644 --- a/docs/source/user_guide.md +++ b/docs/source/user_guide.md @@ -631,8 +631,17 @@ Options are the column names in the `inputs/tech-subset-table.csv` file. Users familiar with GAMS can add alternative objective functions to the `d_mga.gms` file and associated options to the `GSw_MGA_Objective` switch in `cases.csv`. +By default the MGA min/max is applied to the sum of the variable across all regions being modeled. +The MGA approach also supports an option to randomly sample of a vector of weights to apply to the regional values of the variable being optimized. +This method can be used to characterize the uncertainty in the regional distribution of the results. +Weights are sampled as discrete values from {-1,1} to allow for simultaneous minimization and maximization. +The MGA random vector capability is controlled by the following switches: +- `GSw_MGA_RV_runs` (default `0`): Number of random samples of weight vectors to draw; corresponds to the number of runs. +- `GSw_MGA_RV_region` (default `r`): Regionality level (specified by hierarchy file) over which to sample the random weights. +Note that this capability is currently only supported when `GSw_MGA_Objective = (capacity or generation)`. +Weights for each run are stored in the `mga_weights` parameter. ## Uncertainty Plots diff --git a/reeds/core/setup/b_inputs.gms b/reeds/core/setup/b_inputs.gms index 0ac065810..f4b5a2ad3 100644 --- a/reeds/core/setup/b_inputs.gms +++ b/reeds/core/setup/b_inputs.gms @@ -81,6 +81,8 @@ $gdxin set land(r) "land-based (not offshore) zones" ; land(r)$[not offshore(r)] = yes ; + + sets *The following two sets: *ban - will remove the technology from being considered, anywhere @@ -5356,26 +5358,6 @@ z_rep_op(t) = 0 ; *==================================== * --- Employment Factors --- *==================================== -* Employment factors of construction and operation of power plants -$onempty -Table employment_factor_plant(i,jtype) "--job-years/MW (construction), job-years/MW-year (fom) or job-years/MWh (vom)-- employment factors of power plants by technology and job type" -$offlisting -$ondelim -$include inputs_case%ds%employment_factor_plant.csv -$offdelim -$onlisting -; -$offempty - -* Employment factors of transmission deployment and flow -parameter employment_factor_inter_transmission(jtype) "--job-years/$ (construction) -- construction employment factors of transmission lines" -/ -$offlisting -$ondelim -$include inputs_case%ds%employment_factor_inter_transmission.csv -$offdelim -$onlisting -/ ; * If upgrade techs, construction employment factor is adjusted by upgrade ratio * calculated as the ratio of the difference in capital costs between the initial @@ -5400,6 +5382,31 @@ employment_factor_plant(i,"construction") = employment_factor_plant(i,"construction") * upgrade_ratio(i) ; $endif.upgrade_ef +*==================================== +* --- MGA Random Vector Weights --- +*==================================== + +$ifthene.mgaobj ((sameas(%GSw_MGA_Objective%,capacity))or(sameas(%GSw_MGA_Objective%,generation))) + +parameter mga_weights(r,i_subtech) "--unitless-- weight to assign to given MGA subobjective by region" ; + +$ifthene.mga_rv (%GSw_MGA_RV_runs%>=1) +parameter mga_weights_in(r,i_subtech) +/ +$offlisting +$ondelim +$include inputs_case%ds%mga_weights.csv +$offdelim +$onlisting +/ ; +mga_weights(r,i_subtech) = mga_weights_in(r,i_subtech) ; +$else.mga_rv +mga_weights(r,i_subtech) = 1 ; +$endif.mga_rv + +$endif.mgaobj + + *================================================================================================ *== h- and szn-dependent sets and parameters (declared here, populated in 2_temporal_params) === *================================================================================================ diff --git a/reeds/core/setup/d_mga.gms b/reeds/core/setup/d_mga.gms index 2b7b0c313..e8724c8c8 100644 --- a/reeds/core/setup/d_mga.gms +++ b/reeds/core/setup/d_mga.gms @@ -21,7 +21,8 @@ eq_MGA_Objective$Sw_MGA.. $[tmodel(t) $valcap(i,v,r,t) $%GSw_MGA_SubObjective%(i)], - CAP(i,v,r,t) + CAP(i,v,r,t) + * sum{i_subtech$i_subsets(i,i_subtech), mga_weights(r,i_subtech)} } ; @@ -38,6 +39,7 @@ eq_MGA_Objective$Sw_MGA.. $valgen(i,v,r,t) $%GSw_MGA_SubObjective%(i)], GEN(i,v,r,h,t) * hours(h) + * sum{i_subtech$i_subsets(i,i_subtech), mga_weights(r,i_subtech)} } ; diff --git a/reeds/input_processing/copy_files.py b/reeds/input_processing/copy_files.py index 3ceacd5c2..93b37bc68 100644 --- a/reeds/input_processing/copy_files.py +++ b/reeds/input_processing/copy_files.py @@ -1005,8 +1005,24 @@ def write_miscellaneous_files( ef_trans = pd.read_csv( os.path.join(reeds_path,'inputs','employment','employment_factor_inter_transmission.csv'), index_col=0) - ef_trans[ef_trans.index == sw['GSw_EmploymentFactor']].T.round(8).to_csv( - os.path.join(inputs_case,'employment_factor_inter_transmission.csv'),header=False) + ef_trans = ef_trans[ef_trans.index == sw['GSw_EmploymentFactor']].T.round(8) + ef_trans.index.name = 'jtype' + ef_trans = ef_trans.rename(columns={sw['GSw_EmploymentFactor']:'Value'}) + reeds.io.write_to_inputs_h5( + ef_trans, 'employment_factor_inter_transmission', inputs_case, gamstype='parameter', + comment='--job-years/$ (construction)-- construction employment factors of transmission lines', + ) + + # Plant employment factors: + employment_factor_plant = pd.read_csv( + os.path.join(inputs_case, 'employment_factor_plant.csv'), index_col=0 + ).rename_axis('i').reset_index().melt(id_vars='i', var_name='jtype', value_name='Value') + + reeds.io.write_to_inputs_h5( + employment_factor_plant, 'employment_factor_plant', inputs_case, gamstype='parameter', + comment='--job-years/MW (construction), job-years/MW-year (fom) or job-years/MWh (vom)-- ' + 'employment factors of power plants by technology and job type', + ) # Add this_year to years_until_endogenous to generate the tech-specific firstyear parameter scalars = reeds.io.get_scalars(full=True) @@ -1341,10 +1357,10 @@ def main(reeds_path, inputs_case): # ---- Set up logger ---- tic = datetime.datetime.now() - log = reeds.log.makelog( - scriptname=__file__, - logpath=os.path.join(inputs_case,'..','gamslog.txt'), - ) + # log = reeds.log.makelog( + # scriptname=__file__, + # logpath=os.path.join(inputs_case,'..','gamslog.txt'), + # ) print('Starting copy_files.py') main(reeds_path, inputs_case) diff --git a/reeds/input_processing/mcs_sampler.py b/reeds/input_processing/mcs_sampler.py index ae9e804ef..c399a1f71 100644 --- a/reeds/input_processing/mcs_sampler.py +++ b/reeds/input_processing/mcs_sampler.py @@ -1807,7 +1807,7 @@ def write_samples( #%% =========================================================================== ### --- MAIN PROCEDURE --- ### =========================================================================== -def main( +def main_mcs( reeds_path: str, inputs_case: str, n_samples: int = 1, @@ -1872,6 +1872,148 @@ def main( # Write Samples write_samples(sample_group, samples_dict, aux_files) +def get_mga_rv_subsets(reeds_path: str, subobjective: str) -> List[str]: + """ + Derive the relevant subcategory (or subcategories) for a given GSw_MGA_SubObjective. + If a subcategory is a superset of other categories, this function breaks them up into + their constituents after filtering for categories that are valid options + for GSw_MGA_SubObjective. + + Args: + reeds_path (str): path to the ReEDS directory. + subobjective (str): value of GSw_MGA_SubObjective. + + Returns: + List[str]: list of subcategory to use for MGA randome vector + """ + # Parse valid tech categories for GSw_MGA_SubObjective from default cases file + cases = pd.read_csv( + os.path.join(reeds_path, 'cases.csv'), header=None, index_col=0 + ) + choices_str = cases.loc['GSw_MGA_SubObjective', 2] + valid_options = [s.strip('()') for s in choices_str.split('|')] + + # Read tech-subset-table.csv to get tech categories + tech_subset = pd.read_csv( + os.path.join(reeds_path, 'inputs', 'tech-subset-table.csv'), index_col=0 + ) + # Map each valid option to its uppercase column name if it exists in the table + col_map = {opt: opt.upper() for opt in valid_options if opt.upper() in tech_subset.columns} + + # Build a mapping of technologies associated with each tech group + tech_sets = { + opt: frozenset(tech_subset.index[tech_subset[col].fillna('') == 'YES']) + for opt, col in col_map.items() + } + + # Identify aggregated options: X is aggregated if any other option Y has tech_sets[Y] ⊂ tech_sets[X] + aggregated_options = { + x for x in tech_sets + for y in tech_sets + if x != y and tech_sets[y] < tech_sets[x] + } + + # If the subobjective is not an aggregated category, return it + if subobjective not in aggregated_options: + return [subobjective] + + # If it is an aggregated category, break into subcategories + subobjective_techs = tech_sets[subobjective] + candidates = [ + opt for opt, techs in tech_sets.items() + if opt != subobjective + and opt not in aggregated_options + and techs & subobjective_techs # at least one technology in common + ] + # drop CCS if gas or coal are already included + candidates_set = set(candidates) + if 'ccs' in candidates_set and ('coal' in candidates_set or 'gas' in candidates_set): + subsets = sorted(opt for opt in candidates if opt != 'ccs') + else: + subsets = sorted(candidates) + + return subsets + +def main_mga_rv( + reeds_path: str, + inputs_case: str, + sw: dict, + n_samples: int = 1, + lhs_sampling: int = 1, + seed: int = 0, + discrete: bool = True, +): + + # get dimensions based on number of regions and subojectives + + ## regions + # get list of valid regions (val_r generated in copy_files.py) + val_r = list(reeds.io.read_input(inputs_case, 'r').squeeze(1)) + + # option to draw samples based on aggregated regions (samples will be mapped back to r regions) + hierarchy = reeds.io.get_hierarchy(GSw_ZoneSet=sw['GSw_ZoneSet']).reset_index() + hierarchy_val_r = hierarchy.loc[hierarchy.r.isin(val_r)] + sampling_regions = hierarchy_val_r[sw['GSw_MGA_RV_region']].unique() + + ## objective (assumes sw.GSw_MGA_Objective in ['capacity', 'generation'] based on + ## check in runreeds.check_compatibility() + ## if the subobjective is an aggregated category, break it up into leaf-level subcategories + ## derived from tech-subset-table.csv; otherwise just use the subobjective as the group + subsets = get_mga_rv_subsets(reeds_path, sw.GSw_MGA_SubObjective) + + ## sample weights for each subojective group and sampling region + dimensions = len(subsets) * len(sampling_regions) + + # setup output + runs_folder_name = Path(reeds.io.standardize_case(inputs_case)).name + mga_run_number = int((runs_folder_name.split('_')[-1]).replace('R', '')) + region_labels = np.repeat(sampling_regions, len(subsets)) + subset_labels = np.tile(subsets, len(sampling_regions)) + + # sample using LHS or random approach + if lhs_sampling: + # lhs requires drawing all samples simultaneously, so rather than using + # a run-specific seed we draw for all runs at once using the global seed value + lhs_sampler = scipy.stats.qmc.LatinHypercube(d=dimensions, seed=seed) + # lhs_samples are arranged n x d (n = samples, d = dimensions) + lhs_samples_cdf = lhs_sampler.random(n=n_samples) + if discrete: + # bin CDF samples in discrete choices (-1 or 1 with equal probability) + lhs_samples = np.where(lhs_samples_cdf < 0.5, -1, 1) + else: + # translate CDF samples into weights using uniform distribution (-1 to 1 to allow for simultaneous min/max) + lhs_samples = scipy.stats.uniform.ppf(lhs_samples_cdf, loc=-1, scale=2) + + # record the lhs sampling matrix in each run folder + lhs_samples_out = pd.DataFrame(lhs_samples.round(6)).T + lhs_samples_out.columns = [f"R{i:0>4}" for i in range(1, n_samples + 1)] + lhs_samples_out.index = [f"{region_labels[i]}_{subset_labels[i]}" for i in range(len(region_labels))] + lhs_samples_out.index.name = 'dimension' + lhs_samples_out.to_csv(os.path.join(inputs_case, "mga_rv_latin_hypercube_samples.csv")) + + # get the weights for this specific run (-1 to adjust for zero indexing) + mga_weights_raw = lhs_samples[mga_run_number - 1] + else: + # set random seed using the global seed + MGA run number to allow reproducibility + np.random.seed(seed + mga_run_number) + # get the weights for this specific run (-1 to 1 to allow for simultaneous min/max) + if discrete: + mga_weights_raw = np.random.choice([-1,1], dimensions) + else: + mga_weights_raw = np.random.uniform(-1, 1, dimensions) + + # save vector of weights for this run, mapped back to r regions and rounded to 6 decimal places + mga_weights = pd.DataFrame({ + sw['GSw_MGA_RV_region']: region_labels, + 'i_subtech': subset_labels, + 'weight': mga_weights_raw.round(6) + }) + if sw['GSw_MGA_RV_region'] != 'r': + mga_weights = pd.merge(mga_weights, hierarchy_val_r[[sw['GSw_MGA_RV_region'], 'r']], on=sw['GSw_MGA_RV_region']) + mga_weights = mga_weights.rename(columns={'r':'*r'})[['*r','i_subtech','weight']] + mga_weights = mga_weights.sort_values(by=['*r', 'i_subtech'], ascending=True) + mga_weights.to_csv(os.path.join(inputs_case, "mga_weights.csv"), index=False) + if __name__ == '__main__' and not hasattr(sys, 'ps1'): parser = argparse.ArgumentParser(description='Copy files needed for this run') @@ -1902,17 +2044,24 @@ def main( sw = reeds.io.get_switches(inputs_case) MCS_runs = int(sw.get('MCS_runs', 0)) MCS_lhs = int(sw.get('MCS_lhs', 0)) + GSw_MGA_RV_runs = int(sw.get('GSw_MGA_RV_runs', 0)) # get global seed from scalars (used to set the seed for a batch of runs) scalars = reeds.io.get_scalars() seed = int(scalars['MCS_seed']) if MCS_runs >= 1: - print('Starting mcs_sampler.py') - main(reeds_path, inputs_case, n_samples=MCS_runs, lhs_sampling=MCS_lhs, seed=seed) + print('Starting Monte Carlo sampling with mcs_sampler.py') + main_mcs(reeds_path, inputs_case, n_samples=MCS_runs, lhs_sampling=MCS_lhs, seed=seed) else: print('MCS_runs switch is set to 0 or not found. No Monte Carlo sampling will be performed') + if GSw_MGA_RV_runs >= 1: + print('Starting random vector sampling for MGA with mcs_sampler.py') + main_mga_rv(reeds_path, inputs_case, sw, n_samples=GSw_MGA_RV_runs, lhs_sampling=MCS_lhs, seed=seed) + else: + print('GSw_MGA_RV_runs switch is set to 0 or not found. No MGA random vector sampling will be performed') + # Final log/timing update. reeds.log.toc( tic=tic, @@ -1920,3 +2069,4 @@ def main( process='input_processing/mcs_sampler.py', path=os.path.join(os.path.dirname(inputs_case)) ) + diff --git a/reeds/input_processing/runfiles.csv b/reeds/input_processing/runfiles.csv index a03a63204..adbd2f763 100644 --- a/reeds/input_processing/runfiles.csv +++ b/reeds/input_processing/runfiles.csv @@ -264,4 +264,4 @@ wst_climate.csv,inputs/sets/wst_climate.csv,1,ignore,ignore,,,,,0,,,set,wst_clim yearafter.csv,inputs/sets/yearafter.csv,1,ignore,ignore,,,,,,,,set,yearafter,set to loop over for the final year calculation, Project.toml,Project.toml,1,ignore,ignore,,,,,,,,,,, gamslice.txt,gamslice.txt,0,ignore,ignore,,,,,,,,,,, -runreeds.py,runreeds.py,1,ignore,ignore,,,,,,,,,,, +runreeds.py,runreeds.py,1,ignore,ignore,,,,,,,,,,, \ No newline at end of file diff --git a/reeds/inputs.py b/reeds/inputs.py index bb0d47e32..256f95114 100644 --- a/reeds/inputs.py +++ b/reeds/inputs.py @@ -249,19 +249,20 @@ def parse_cases( print("Please change the delimeter in the GSw_Region switch from ',' to '.'") quit() - # If doing a Monte Carlo run, modify dfcases by adding new columns - # for each scenario run. Also validate the distribution file. + # If doing a Monte Carlo or MGA Random Vector run, modify dfcases by adding new columns + # for each scenario run. For Monte Carlo we also validate the distribution file. warned_about_cluster_alg = False - if 'MCS_runs' in dfcases.index: + if 'MCS_runs' in dfcases.index or 'GSw_MGA_RV_runs' in dfcases.index: for c in dfcases.columns: if ( c not in ['Description','Default Value','Choices'] - and (int(dfcases.loc['MCS_runs',c]) > 0) + and ((int(dfcases.loc['MCS_runs',c]) > 0) or (int(dfcases.loc['GSw_MGA_RV_runs',c]) > 0)) and (not int(dfcases.loc['ignore',c])) ): # Warn user if the hourly clustering algorithm is not fixed for Monte Carlo runs if ( - not dfcases.at['GSw_HourlyClusterAlgorithm', c].startswith('user') + int(dfcases.loc['MCS_runs',c]) > 0 + and not dfcases.at['GSw_HourlyClusterAlgorithm', c].startswith('user') and not warned_about_cluster_alg ): print(f"\n[Warning] Case Column: '{c}'") @@ -286,23 +287,29 @@ def parse_cases( reeds.io.reeds_path, 'inputs', 'userinput', 'mcs_distributions_{}.yaml'.format(sw.MCS_dist) ) - mcs_sampler.general_mcs_dist_validation(reeds.io.reeds_path, mcs_dist_path, sw) - - # c (column) is a case with monte carlo runs. - # replicate this column N (NumMonteCarloRuns) times - NumMonteCarloRuns = int(dfcases.loc['MCS_runs',c]) + if int(dfcases[c].MCS_runs) > 0: + mcs_sampler.general_mcs_dist_validation(reeds.io.reeds_path, mcs_dist_path, sw) + numruns = int(dfcases[c].MCS_runs) + run_type = 'MC' + else: + numruns = int(dfcases[c].GSw_MGA_RV_runs) + run_type = 'R' + + # c (column) is a case with monte carlo or MGA random vector runs. + # replicate this column N times NewColumnNames = [ - f"{c}_MC{i:0>4}" - for i in range(1, NumMonteCarloRuns + 1) + f"{c}_{run_type}{i:0>4}" + for i in range(1, numruns + 1) ] - # Each new column is a copy of the original column with name c_{MC1,MC2,...} - dfcases_MC = pd.DataFrame( - data=np.array([dfcases[c].values]*NumMonteCarloRuns).T, + # Each new column is a copy of the original column with name + # c_{MCS1,MCS2,...} or c_{R1,R2,...} + dfcases_all = pd.DataFrame( + data=np.array([dfcases[c].values]*numruns).T, index=dfcases.index, columns=NewColumnNames, ) - dfcases = pd.concat([dfcases, dfcases_MC], axis=1) + dfcases = pd.concat([dfcases, dfcases_all], axis=1) # drop the original column dfcases.drop(c, axis=1, inplace=True) @@ -355,6 +362,7 @@ def solvestring_sequential( 'GSw_HourlyWrapLevel', 'GSw_MGA_CostDelta', 'GSw_MGA_Direction', + 'GSw_MGA_RV_runs', 'GSw_PVB_Dur', 'GSw_SkipRAyear', 'GSw_StateCO2ImportLevel', diff --git a/runreeds.py b/runreeds.py index 7e1db8ba8..1572ce3ef 100644 --- a/runreeds.py +++ b/runreeds.py @@ -290,6 +290,12 @@ def check_compatibility(sw): f"GSw_Region={sw['GSw_Region']}, GSw_GasCurve={sw['GSw_GasCurve']}" ) + if (int(sw['MCS_runs']) > 1 and int(sw['GSw_MGA_RV_runs']) > 1): + raise ValueError( + 'Running Monte Carlo analysis (MCS_runs > 1) and random vector \n' + 'MGA sampling (GSw_MGA_RV_runs > 1) simultaneously is not yet supported.' + ) + reeds.inputs.validate_zoneset(sw['GSw_ZoneSet']) ### Parsed string switches @@ -490,6 +496,11 @@ def check_compatibility(sw): ) raise ValueError(err) + if (sw['GSw_MGA_Objective'] not in ['capacity', 'generation']) and int(sw['GSw_MGA_RV_runs']) > 0: + raise NotImplementedError( + f"GSw_MGA_Objective='{sw['GSw_MGA_Objective']}' is not yet supported for MGA random vector sampling." + ) + ### Dependent model availability if ( ((int(sw['pras']) == 2) or int(sw['GSw_PRM_StressIterateMax']))