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/cases.csv b/cases.csv index 02c1b34db..f629c4845 100644 --- a/cases.csv +++ b/cases.csv @@ -56,6 +56,12 @@ 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_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, ivt_suffix,File suffix for ivt csv file,default; small; step,default, diff --git a/cases_examples.csv b/cases_examples.csv index 8ede4674f..cee594c8c 100644 --- a/cases_examples.csv +++ b/cases_examples.csv @@ -1,17 +1,25 @@ -,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,2035,,,,,,,,,,, -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,,, +,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,,,,,,,,,,,,2050 +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, +GSw_HourlyType,,,wek,year,,,,,,,,, +GSw_OpRes,,,,0,,,,,0,,,, +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,,,,4 +GSw_HourlyChunkLengthStress,,,,,,,,,4,,,,4 +FINITO,,,,,,,,,,,,,1 +FINITO_case,,,,,,,,,,,,,BAU +GSw_H2,,,,,,,,,,,,,1 +GSw_H2_Demand_Case,,,,,,,,,,,,,BAU +GSw_H2_PTC,,,,,,,,,,,,,0 +GSw_H2_SMR,,,,,,,,,,,,,1 +GSw_GasPriceAdjMethod,,,,,,,,,,,,,0 \ No newline at end of file diff --git a/cases_test.csv b/cases_test.csv index 805d5829d..6dcab1d68 100644 --- a/cases_test.csv +++ b/cases_test.csv @@ -1,67 +1,72 @@ -,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,2035,,2050,2050,2050,2030,2060,2030,,,,,,,,,,,2035,2030,2030,2060,2035,2050,2050,2050,,,,,, -yearset,,,,,,,2010..2060..10,,,,,,,,,,,,,,,2010..2060..10,2010_2015_2020..2050..3,,2010_2025_2050,,,,,,, -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,,,,,,, +,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,Pacific_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,, +endyear,2035,,2050,2050,2050,2030,2060,2030,,,,,,,,,,,2035,2030,2030,2060,2035,2050,2050,2050,,,,,,, +yearset,,,,,,,2010..2060..10,,,,,,,,,,,,,,,2010..2060..10,2010_2015_2020..2050..3,,2010_2025_2050,,,,,,,, +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,,,,,,,, +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,,,,,,,, +FINITO,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1 +FINITO_case,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,BAUfast_reg +GSw_H2_Demand_Case,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,BAU +GSw_H2_SMR,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1 +GSw_GasPriceAdjMethod,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,0 \ No newline at end of file diff --git a/docs/source/user_guide.md b/docs/source/user_guide.md index eff586c19..15c9b8ce0 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 certain energy-intensive industrial subsectors. +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 FINITO supply curves is controlled by the `tfinito_all` set, which is populated for any years running with FINITO. In a standalone ReEDS run `tfinito_all` will not include any solve years; in a linked run it will include all t >= `FINITO_first_year`. Any years not in `tfinito_all` use the ReEDS supply curve representation. +- 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 transportation and buildings sector demand are scenario based and can be toggled using `GSw_demand_scen`. Scenarios derived from both AEO and EER data are supported. + +**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 diff --git a/helpers/restart_runs.py b/helpers/restart_runs.py index f1132e2e3..fd0db7dbc 100644 --- a/helpers/restart_runs.py +++ b/helpers/restart_runs.py @@ -4,6 +4,7 @@ import shutil import subprocess import argparse +import re from glob import glob from pathlib import Path sys.path.append(str(Path(__file__).parent.parent)) @@ -53,20 +54,26 @@ #%% Get all runs dictruns = get_run_status(reeds_path, batch_name) -runs_unfinished = dictruns['running'] + dictruns['failed'] +runs_finished = dictruns['finished'] runs_failed = dictruns['failed'] runs_running = dictruns['running'] +runs_unfinished = dictruns['running'] + dictruns['failed'] ### Take a look -print('unfinished:', len(runs_unfinished)) +print('finished:', len(runs_finished)) print('running:', len(runs_running)) print('failed:', len(runs_failed)) +if include_finished: + runs_restart = runs_failed + runs_finished +else: + runs_restart = runs_failed + #%% Double check if not force: - for i in runs_failed: + for i in runs_restart: print(os.path.basename(i)) - print(f'Restarting the {len(runs_failed)} runs listed above.') + print(f'Restarting the {len(runs_restart)} runs listed above.') confirm_local = str(input('Proceed? [y]/n: ') or 'y') if confirm_local not in ['y','Y','yes','Yes','YES']: quit() @@ -83,7 +90,7 @@ writelines_srun = list() #%%### Loop through runs, figure out when they failed, and restart -for case in runs_failed: +for case in runs_restart: casename = os.path.basename(case) #%% Copy the solver settings file if desired @@ -95,7 +102,14 @@ #%% Copy additional files if desired for f in more_copyfiles: - shutil.copy(f, Path(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(Path(sw.FINITO_dir,f_copy), Path(case,f)) + else: + shutil.copy(f, Path(case, f)) if copy_reeds: shutil.copytree(Path(reeds_path, 'reeds'), Path(case, 'reeds'), dirs_exist_ok=True) @@ -146,7 +160,7 @@ # Check if we are going to run this in parallel or not hpc = True if (int(os.environ.get('REEDS_USE_SLURM',0))) else False -if hpc and len(runs_failed) > 1: +if hpc and len(runs_restart) > 1: # On HPC with multiple cases cases_per_node = int(input('Number of simultaneous runs per node [integer]: ')) else: @@ -155,7 +169,7 @@ if hpc and (cases_per_node > 1): # Write the slurm scripts for parallel runs and # submit them to the HPC - casenames = [os.path.basename(p).split(batch_name + "_", 1)[-1] for p in runs_failed] + casenames = [os.path.basename(p).split(batch_name + "_", 1)[-1] for p in runs_restart] submit_slurm_parallel_jobs( reeds_path=reeds_path, BatchName=batch_name, @@ -165,7 +179,7 @@ else: # Run each case individually - for case in runs_failed: + for case in runs_restart: casename = os.path.basename(case) callfile = os.path.join(case, f'call_{casename}.sh') sbatchfile = os.path.join(case, f'{casename}.sh') diff --git a/postprocessing/bokehpivot/reeds2.py b/postprocessing/bokehpivot/reeds2.py index ee1413e50..821cac769 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/__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/core/setup/a_createmodel.gms b/reeds/core/setup/a_createmodel.gms index 9250f30ea..19db60371 100644 --- a/reeds/core/setup/a_createmodel.gms +++ b/reeds/core/setup/a_createmodel.gms @@ -4,7 +4,18 @@ $setglobal ds / $endif.unix $include reeds%ds%core%ds%setup%ds%b_inputs.gms + +$ifthene.finito_link %GSw_FINITO_Link% == 1 +$include finito%ds%model%ds%finito_input.gms +$include finito%ds%model%ds%finito_vars_eqs.gms +$endif.finito_link + $include reeds%ds%core%ds%setup%ds%c_model.gms + +$ifthene.finito_link %GSw_FINITO_Link% == 1 +$include finito%ds%model%ds%finito_model.gms +$endif.finito_link + $include reeds%ds%core%ds%setup%ds%d_objective.gms $include reeds%ds%core%ds%setup%ds%d_mga.gms $include reeds%ds%core%ds%setup%ds%e_solveprep.gms diff --git a/reeds/core/setup/b_inputs.gms b/reeds/core/setup/b_inputs.gms index 1e1093e2a..59c19d4ed 100644 --- a/reeds/core/setup/b_inputs.gms +++ b/reeds/core/setup/b_inputs.gms @@ -946,7 +946,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", + tfinito_all(t) "years to run with linkage to 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)" ; @@ -959,7 +960,7 @@ tfix(t) = no ; stfeas(st) = no ; tprev(t,tt) = no ; tsolved(t) = no ; - +tfinito_all(t) = no ; *============================== * Year specification @@ -974,6 +975,7 @@ tprev(t,tt)$[tmodel_new(t)$tmodel_new(tt)$(tt.valmindiff(t))] = 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 diff --git a/reeds/core/setup/c_model.gms b/reeds/core/setup/c_model.gms index dde222c6b..32520935f 100644 --- a/reeds/core/setup/c_model.gms +++ b/reeds/core/setup/c_model.gms @@ -259,12 +259,12 @@ eq_interconnection_queues(tg,r,t) "--MW-- capacity deployment limit base eq_h2_storage_level_szn(h2_stor,r,actualszn,t) "--metric tons-- tracks H2 storage level by storage type and BA within and across periods" * CO2 capture and storage - eq_co2_capture(r,allh,t) "--metric tons-- accounting of CO2 captured from DAC and CCS technologies" + eq_co2_capture(r,allh,t) "--metric tons per hour-- accounting of CO2 captured from DAC and CCS technologies" eq_co2_injection_limit(cs,allh,t) "--metric tons per hour-- limit on CO2 injection for each carbon site as a rate" eq_co2_sink(r,allh,t) "--metric tons per hour-- co2 stored or used must exceed co2 captured plus net trade" eq_co2_transport_caplimit(r,rr,allh,t) "--metric tons-- limit on interregional co2 trade" eq_co2_spurline_caplimit(r,cs,allh,t) "--metric tons-- limit on transport of CO2 from BA to carbon storage site" - eq_co2_cumul_limit(cs,t) "--cumulative metric tons-- total stored in a reservor cannot exceed capacity" + eq_co2_cumul_limit(cs,t) "--metric tons-- cumulative total stored in a reservor cannot exceed capacity" * transmission equations eq_INVTRAN_DC(r,rr,trtype,t) "--MW-- DC transmission additions are assumed to add the same capacity in both directions" @@ -394,6 +394,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 +* USE_ELE_FINITO is enduse, 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) / (1.0 - distloss))$[tfinito(t)] * (1 + prm(r,t)$h_stress(h)) +$endif.linked_load ; * --------------------------------------------------------------------------- @@ -2865,7 +2873,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)$(not tfinito_all(t))$((Sw_GasCurve=0) or (Sw_GasCurve=3))].. sum{gb,GASUSED(cendiv,gb,h,t) } @@ -2882,7 +2890,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)$(not tfinito_all(t))$(Sw_GasCurve=0)].. gaslimit(cendiv,gb,t) @@ -2893,7 +2901,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)$(not tfinito_all(t))$(Sw_GasCurve=3)].. gaslimit_nat(gb,t) @@ -2906,7 +2914,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)$(not tfinito_all(t))$(Sw_GasCurve=1)].. sum{fuelbin, VGASBINQ_REGIONAL(fuelbin,cendiv,t) } @@ -2919,7 +2927,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)$(not tfinito_all(t))$(Sw_GasCurve=1)].. sum{fuelbin,VGASBINQ_NATIONAL(fuelbin,t) } @@ -2932,7 +2940,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)$(not tfinito_all(t))$(Sw_GasCurve=1)].. Gasbinwidth_regional(fuelbin,cendiv,t) @@ -2943,7 +2951,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)$(not tfinito_all(t))$(Sw_GasCurve=1)].. Gasbinwidth_national(fuelbin,t) @@ -2957,10 +2965,10 @@ eq_gasbinlimit_national(fuelbin,t)$[tmodel(t)$(Sw_GasCurve=1)].. *============================== * -- Bioenergy Supply Curve -- *============================== +* 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)].. +eq_bioused(r,t)$[sum{(i,v)$(bio(i) or cofire(i)), valgen(i,v,r,t) }$tmodel(t)$(not tfinito_all(t))].. sum{bioclass, BIOUSED(bioclass,r,t) } @@ -2979,7 +2987,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)$(not tfinito_all(t))].. biosupply(usda_region,bioclass,"cap") @@ -3549,6 +3557,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_gen(i)$h_rep(h)], GEN(i,v,r,h,t) * hours(h) * h2_combustion_intensity * heat_rate(i,v,r,t) } + +* hydrogen demand from industry when linked with 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 }]$tfinito(t) +$endif.linked_h2_nat ; * --------------------------------------------------------------------------- @@ -3580,6 +3593,11 @@ eq_h2_demand_regional(r,h,t) + sum{(i,v)$[valgen(i,v,r,t)$h2_gen(i)], GEN(i,v,r,h,t) * h2_combustion_intensity * heat_rate(i,v,r,t) } + +* when linked include regional 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 ]$tfinito(t) +$endif.linked_h2_reg ; * --------------------------------------------------------------------------- @@ -3801,12 +3819,11 @@ eq_h2_ptc_creditgen(i,v,r,h,t)$[valgen_h2ptc(i,v,r,t) * -- CO2 transport and storage -- *================================= - +* [metric tons per hour] eq_co2_capture(r,h,t) - $[tmodel(t) - $Sw_CO2_Detail - $(yeart(t)>=co2_detail_startyr) - $h_rep(h)].. + $[Sw_CO2_Detail + $tmodel(t) + $(yeart(t)>=co2_detail_startyr)].. CO2_CAPTURED(r,h,t) @@ -3822,12 +3839,21 @@ 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 + +* capture from industry when linked with FINITO [metric_tons-CO2/hr]: +$ifthene.linked_co2_capture Sw_FINITO_Link==1 + + [CAPTURE_CO2EM(r,h,t) / co2_scale]$[tfinito(t)] +$endif.linked_co2_capture ; * --------------------------------------------------------------------------- -eq_co2_transport_caplimit(r,rr,h,t)$[co2_routes(r,rr)$Sw_CO2_Detail - $tmodel(t)$(yeart(t)>=co2_detail_startyr)].. +* [metric tons per hour] +eq_co2_transport_caplimit(r,rr,h,t) + $[Sw_CO2_Detail + $co2_routes(r,rr) + $tmodel(t) + $(yeart(t)>=co2_detail_startyr)].. *capacity computed as cumulative investments of co2 pipelines up to the current year sum{tt$[(yeart(tt)<=yeart(t))$(tmodel(tt) or tfix(tt)) @@ -3842,7 +3868,12 @@ 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)].. +* [metric tons per hour] +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)], @@ -3851,11 +3882,20 @@ 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) +* (ReEDS-FINITO) extraction of CO2 [metric tons per hour] +* 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) ]$[tfinito(t)] +$endif.linked_co2_spurline_caplimit ; * --------------------------------------------------------------------------- -eq_co2_sink(r,h,t)$[tmodel(t)$Sw_CO2_Detail$(yeart(t)>=co2_detail_startyr)].. +* [metric tons per hour] +eq_co2_sink(r,h,t) + $[Sw_CO2_Detail + $tmodel(t) + $(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) } @@ -3870,11 +3910,27 @@ 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) } + +* (ReEDS-FINITO) extraction - use of CO2 [metric tons per hour] +$ifthene.linked_co2_sink Sw_FINITO_Link==1 + + (1 / co2_scale) * [ sum{cs$[csfeas(cs)$r_cs(r,cs)], +* extraction from all cs sites in r + + EXTRACT_CO2_CS(cs,r,h,t)} +* total use of CO2, equivalent to supply + - USE_CO2(r,h,t) + ]$[tfinito(t)] +$endif.linked_co2_sink + ; * --------------------------------------------------------------------------- -eq_co2_injection_limit(cs,h,t)$[Sw_CO2_Detail$tmodel(t)$(yeart(t)>=co2_detail_startyr)$csfeas(cs)].. +* [metric tons per hour] +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) @@ -3883,11 +3939,21 @@ 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) } + +* (ReEDS-FINITO) extraction of CO2 for use [metric tons per hour] +$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) }$[tfinito(t)] +$endif.linked_co2_injection_limit ; * --------------------------------------------------------------------------- -eq_co2_cumul_limit(cs,t)$[tmodel(t)$Sw_CO2_Detail$(yeart(t)>=co2_detail_startyr)$csfeas(cs)].. +* [metric tons] +eq_co2_cumul_limit(cs,t) + $[Sw_CO2_Detail + $csfeas(cs) + $tmodel(t) + $(yeart(t)>=co2_detail_startyr)].. *capacity by co2 bin for injections co2_storage_limit(cs) @@ -3897,8 +3963,16 @@ eq_co2_cumul_limit(cs,t)$[tmodel(t)$Sw_CO2_Detail$(yeart(t)>=co2_detail_startyr) *cumulative amount stored over time sum{(r,h,tt) $[(yeart(tt)<=yeart(t))$(tmodel(tt) or tfix(tt))$(yeart(tt)>=co2_detail_startyr) - $r_cs(r,cs)$h_rep(h)], + $r_cs(r,cs)], yearweight(tt) * hours(h) * CO2_STORED(r,cs,h,tt) } + +* (ReEDS-FINITO) 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)$(not tfinito_all(tt))], + yearweight(tt) * hours(h) * EXTRACT_CO2_CS(cs,r,h,tt) }$[tfinito(t)] +$endif.linked_co2_storage_cumul_limit ; * --------------------------------------------------------------------------- diff --git a/reeds/core/setup/d_objective.gms b/reeds/core/setup/d_objective.gms index 498988760..69d5e7982 100644 --- a/reeds/core/setup/d_objective.gms +++ b/reeds/core/setup/d_objective.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 FINITO_dollaryear to $2004 +* and remove any FINITO scaling +$ifthene.linked_objective Sw_FINITO_Link==1 + + (deflator('%FINITO_dollaryear%') / cost_scale_finito + * sum{t$[tfinito(t)], Z_finito(t) } ) +$endif.linked_objective + ) +; *======================================================= * -- Investment component of the objective function -- @@ -224,7 +234,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)$[valgen(i,v,r,t)$heat_rate(i,v,r,t) +* Note on FINITO linkage: fuel costs for years in tfinito_all accounted for in +* 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)$[[not tfinito_all(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_gen(i)) or h2_gen(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) } @@ -235,47 +249,47 @@ 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)$[(not tfinito_all(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)$[(not tfinito_all(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) * gasprice_adj_r(r,h) } *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)$[(not tfinito_all(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) * gasprice_adj_cendiv(cendiv,h) } + + sum{(cendiv,gb)$[(not tfinito_all(t))], sum{h, hours(h) * GASUSED(cendiv,gb,h,t) * gasprice_adj_cendiv(cendiv,h) } * gasprice(cendiv,gb,t) }$(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)$[(not tfinito_all(t))], hours(h) * GASUSED(cendiv,gb,h,t) * gasadder_cd(cendiv,t,h) * gasprice_adj_cendiv(cendiv,h) + gasprice_nat_bin(gb,t) }$(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)$(not tfinito_all(t))], gasmultterm(cendiv,t) * cendiv_weights(r,cendiv) * hours(h) * heat_rate(i,v,r,t) * GEN(i,v,r,h,t) * gasprice_adj_r(r,h) } *second - adjustments based on changes from last year's consumption at the regional and national level - + sum{(fuelbin,cendiv), + + sum{(fuelbin,cendiv)$(not tfinito_all(t)), gasbinp_regional(fuelbin,cendiv,t) * VGASBINQ_REGIONAL(fuelbin,cendiv,t) } - + sum{(fuelbin), + + sum{(fuelbin)$(not tfinito_all(t)), gasbinp_national(fuelbin,t) * VGASBINQ_NATIONAL(fuelbin,t) } )$[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)$[(not tfinito_all(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) } diff --git a/reeds/core/solve/2_temporal_params.gms b/reeds/core/solve/2_temporal_params.gms index 706e7b65d..be72c1824 100644 --- a/reeds/core/solve/2_temporal_params.gms +++ b/reeds/core/solve/2_temporal_params.gms @@ -894,6 +894,14 @@ gasprice_adj_r(r,h)$(Sw_GasPriceAdjMethod = 0) = 1 ; gasprice_adj_cendiv(cendiv,h)$(Sw_GasPriceAdjMethod = 0) = 1 ; +*============================================= +* ----- ReEDS-FINITO temporal parameters ----- +*============================================= +$ifthene.linked_finito_temporal_params %GSw_FINITO_Link% ==1 +$include finito%ds%model%ds%linked_finito_temporal_params.gms +$endif.linked_finito_temporal_params + + *============================================= * -- Round parameters for GAMS -- *============================================= diff --git a/reeds/core/solve/3_solve_oneyear.gms b/reeds/core/solve/3_solve_oneyear.gms index b002325dc..f5f8fd693 100644 --- a/reeds/core/solve/3_solve_oneyear.gms +++ b/reeds/core/solve/3_solve_oneyear.gms @@ -23,6 +23,13 @@ tload(t) = no ; tmodel(t) = no ; tmodel("%cur_year%") = yes ; +* (ReEDS-FINITO) also reset tfinito +$ifthene.linked_finito_time %GSw_FINITO_Link% == 1 +tfinito(t) = no ; +tfinito(t)$[tmodel(t)$tfinito_all(t)] = yes ; +$endif.linked_finito_time + + $log 'Solving sequential case for...' $log ' Case: %case%' $log ' Year: %cur_year%' @@ -283,6 +290,12 @@ $include reeds%ds%core%ds%solve%ds%4_post_solve_adjustments.gms tfix("%cur_year%") = yes ; $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%ds%model%ds%finito_varfix.gms +$endif.linked_finito_varfix + *** Dump data used in calculations between solve years $include reeds%ds%core%ds%solve%ds%6_data_dump.gms diff --git a/reeds/core/solve/6_data_dump.gms b/reeds/core/solve/6_data_dump.gms index 651bc72e0..45c6daa4c 100644 --- a/reeds/core/solve/6_data_dump.gms +++ b/reeds/core/solve/6_data_dump.gms @@ -8,6 +8,7 @@ This file creates a gdx file with all of the data necessary for the resource ade - 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" @@ -158,6 +160,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_gen(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 ]$tfinito(t) +$endif.linked_h2_reg_report ) ; @@ -255,15 +261,25 @@ 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, converted from enduse to busbar +$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) / (1.0 - distloss); +$else.linked_load + load_finito_rt(r,h,t) = 0 ; +$endif.linked_load + + *============================ * Extra consumption data *============================ @@ -353,6 +369,7 @@ execute_unload 'handoff%ds%reeds_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/reeds/core/terminus/report.gms b/reeds/core/terminus/report.gms index 6affe4ce4..8232208c1 100644 --- a/reeds/core/terminus/report.gms +++ b/reeds/core/terminus/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"/ @@ -479,6 +479,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) ; @@ -514,21 +516,32 @@ 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) - +repbioprice(r,t)$[tmodel_new(t)$(not tfinito_all(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)$(tfinito_all(t))$sum{(i,v,bs), USE_BS_REEDS.l(i,v,bs,r,t) }] = + 1/(cost_scale_finito) * 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) } +; +$endif.finitobioprice + + * 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) } ; +bioused_out(bioclass,r,t)$[tmodel_new(t)$(not tfinito_all(t))] = BIOUSED.l(bioclass,r,t) / bio_energy_content ; +bioused_usda(bioclass,usda_region,t)$[tmodel_new(t)$(not tfinito_all(t))] = sum{r$r_usda(r,usda_region), bioused_out(bioclass,r,t) } ; * 1e9 converts from MMBtu to Quads -repgasquant_gb(cendiv,gb,t)$[(Sw_GasCurve = 0 or Sw_GasCurve = 3)$tmodel_new(t)] = +repgasquant_gb(cendiv,gb,t)$[(Sw_GasCurve = 0 or Sw_GasCurve = 3)$tmodel_new(t)$(not tfinito_all(t))] = sum{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)] = +repgasquant(cendiv,t)$[(Sw_GasCurve = 0 or Sw_GasCurve = 3)$tmodel_new(t)$(not tfinito_all(t))] = sum{gb, repgasquant_gb(cendiv,gb,t) }; -repgasquant(cendiv,t)$[(Sw_GasCurve = 1 or Sw_GasCurve = 2)$tmodel_new(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)], @@ -550,20 +563,53 @@ 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 and apply annual consumption-weighted gas price multipliers -repgasprice(cendiv,t)$[(Sw_GasCurve = 0)$tmodel_new(t)$repgasquant(cendiv,t)] = +repgasprice(cendiv,t)$[(Sw_GasCurve = 0)$tmodel_new(t)$repgasquant(cendiv,t)$(not tfinito_all(t))] = smax{gb$[repgasquant_gb(cendiv,gb,t)], gasprice(cendiv,gb,t) * sum{h, gasprice_adj_cendiv(cendiv,h) * GASUSED.l(cendiv,gb,h,t) * hours(h) / (repgasquant_gb(cendiv,gb,t) * 1e9) } } ; -repgasprice(cendiv,t)$[(Sw_GasCurve = 2)$tmodel_new(t)$repgasquant(cendiv,t)] = +repgasprice(cendiv,t)$[(Sw_GasCurve = 2)$tmodel_new(t)$repgasquant(cendiv,t)$(not tfinito_all(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) * gasprice_adj_r(r,h) } / (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)] = +* 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)$(tfinito_all(t))] = + deflator('%FINITO_dollaryear%') * 1/(cost_scale_finito) * 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)$(tfinito_all(t))] = + deflator('%FINITO_dollaryear%') * 1/(cost_scale_finito) * 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) ] +; + +$endif.finitodetailedNG + +$else.finitogasprice + repgasprice_finito(cendiv,h,t)$[tmodel_new(t)$(tfinito_all(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)$(tfinito_all(t))] = + sum{h, hours(h) * repgasprice_finito(cendiv,h,t) } / sum{h, hours(h) } +; + +*Anytime Sw_GasCurve = 0 or 2, apply repgasprice(cendiv,t) to repgasprice_r. +*Do the same for finito-linked years (tfinito_all(t)). +repgasprice_r(r,t)$[(tfinito_all(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)$(not tfinito_all(t))] = ( sum{(h,cendiv), gasmultterm(cendiv,t) * cendiv_weights(r,cendiv) * hours(h) * gasprice_adj_r(r,h) } / sum{h, hours(h) } @@ -573,7 +619,8 @@ repgasprice_r(r,t)$[(Sw_GasCurve = 1)$tmodel_new(t)] = + smax(fuelbin$VGASBINQ_NATIONAL.l(fuelbin,t), gasbinp_national(fuelbin,t) ) ) ; -repgasprice(cendiv,t)$[(Sw_GasCurve = 1)$tmodel_new(t)$repgasquant(cendiv,t)] = +*Now calculate the remaining repgasprice(cendiv,t) (for Sw_GasCurve = 1) +repgasprice(cendiv,t)$[(Sw_GasCurve = 1)$tmodel_new(t)$repgasquant(cendiv,t)$(not tfinito_all(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) }] = @@ -585,14 +632,16 @@ repgasprice_nat(t)$[tmodel_new(t)$sum{cendiv, repgasquant(cendiv,t) }] = *======================================== 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)$(not tfinito_all(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]], @@ -620,11 +669,20 @@ gascost_cendiv(cendiv,t)$tmodel_new(t) = )$[Sw_GasCurve = 1]; +* cost of natural gas - linked with FINITO ('tfinito_all' indicates years using FINITO supply curves) +gascost_cendiv(cendiv,t)$[tmodel_new(t)$(tfinito_all(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)$(not tfinito_all(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)$[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) } @@ -1377,12 +1435,15 @@ systemcost_techba("op_fuelcosts_objfn",i,r,t)$tmodel_new(t) = + sum{(v,h)$[valgen(i,v,r,t)$heat_rate(i,v,r,t) $(not gas(i))$(not bio(i))$(not cofire(i)) $((not h2_gen(i)) or h2_gen(i)$[(Sw_H2=0) or h_stress(h)])], - hours(h) * heat_rate(i,v,r,t) * fuel_price(i,r,t) * GEN.l(i,v,r,h,t) } + hours(h) * heat_rate(i,v,r,t) * fuel_price(i,r,t) * GEN.l(i,v,r,h,t) + }$(not tfinito_all(t)) + *cofire coal consumption - cofire bio consumption already accounted for in accounting of BIOUSED + sum{(v,h)$[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.l(i,v,r,h,t) } + * fuel_price("coal-new",r,t) * GEN.l(i,v,r,h,t) + }$(not tfinito_all(t)) *cost of natural gas fuel + sum{cendiv$r_cendiv(r,cendiv), gascost_cendiv(cendiv,t) * gasshare_techba(i,r,cendiv,t) } @@ -1765,6 +1826,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)$tfinito(t) * deflator('%FINITO_dollaryear%') / cost_scale_finito ) +$endif.linked_objective } ) / z.l ; @@ -1989,6 +2055,10 @@ 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 *======================================== diff --git a/reeds/core/terminus/report_dump.py b/reeds/core/terminus/report_dump.py index 0cc45f479..22352d345 100644 --- a/reeds/core/terminus/report_dump.py +++ b/reeds/core/terminus/report_dump.py @@ -68,6 +68,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 @@ -277,6 +281,19 @@ 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") + finito_gdx = os.path.join(outputs_path, f"finito_reeds_outputs_{os.path.basename(case)}.gdx") + try: + finito_outputs = gdxpds.to_dataframes(finito_gdx) + dict_out.update(finito_outputs) + print("Finished loading FINITO outputs gdx") + except Exception as err: + print(err) + print(f"Error loading FINITO outputs in {finito_gdx}, skipping.") + + write_dfdict( dfdict=dict_out, outputs_path=outputs_path, diff --git a/reeds/core/terminus/report_params.csv b/reeds/core/terminus/report_params.csv index e30647243..20600a25a 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 total for LOAD variable,,, "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,,, @@ -145,6 +146,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 from FINITO supply curves,,, "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/reeds/finito.py b/reeds/finito.py new file mode 100644 index 000000000..c1d585df6 --- /dev/null +++ b/reeds/finito.py @@ -0,0 +1,306 @@ +""" +Functions relevant to setting up a linked ReEDS-FINITO run +""" + +### Imports +import os +import shutil +import subprocess +import sys +from pathlib import Path + +import numpy as np +import pandas as pd + +sys.path.append(str(Path(__file__).parent.parent)) +import reeds + + +#%% =========================================================================== +### --- 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 + +def check_FINITO_switch_compatability(sw): + """ + Checks for switch compatability for linked ReEDS-FINITO runs + """ + + ## natural gas settings + if int(sw['GSw_GasPriceAdjMethod']) > 0: + raise ValueError( + "ReEDS-FINITO runs are only compatible with GSw_GasPriceAdjMethod = 0 " + f"(current setting = {sw['GSw_GasPriceAdjMethod']})" + ) + + ## 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 + 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 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. " + "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 + 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 = {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"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 " + 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']) + ) + ### 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_dir = Path(caseSwitches['FINITO_dir']) + # ... finito directory within the case directory + casedir_finito = Path(casedir, 'finito') + # ... define the inputs case directory for FINITO + inputs_case_finito = Path(casedir, 'finito', 'inputs_case') + + # copy directories + 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(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 = ( + 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 = subprocess.run( + [ + 'python', str(finito_dir / 'input_processing' / 'processing' / 'copy_files.py'), + '-c', str(casedir_finito), + '-d', str(inputs_case_finito), + '--link', + ], + check=False, + ) + + # 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 + 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) + subprocess.run( + [ + 'python', str(finito_dir / 'input_processing' / 'processing' / 'mecs' / 'read_mecs_heat.py'), + '-c', str(casedir_finito), + '-d', str(inputs_case_finito), + '--link' + ], + check=True, + ) + + +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") + + # 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 (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) + + 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 using ReEDS assumption for distribution losses + 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 5c4eefdbb..518253eb7 100644 --- a/reeds/input_processing/hourly_load.py +++ b/reeds/input_processing/hourly_load.py @@ -596,7 +596,6 @@ def reaggregate_to_model_regions( return regional_load_hourly - #%% =========================================================================== ### --- MAIN FUNCTION --- ### =========================================================================== @@ -697,6 +696,19 @@ 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 = reeds.finito.remove_finito_load( + regional_load_hourly, + inputs_case, + scalars['distloss'] + ) + + ############################################# # -- DR Shed Load Modifications -- # ############################################# diff --git a/reeds/input_processing/hourly_repperiods.py b/reeds/input_processing/hourly_repperiods.py index db3aacb09..92e1c83d4 100644 --- a/reeds/input_processing/hourly_repperiods.py +++ b/reeds/input_processing/hourly_repperiods.py @@ -36,6 +36,7 @@ import reeds from reeds.input_processing import hourly_writetimeseries from reeds.input_processing import hourly_plots + ## Time the operation of this script tic = datetime.datetime.now() @@ -55,6 +56,14 @@ def get_load(inputs_case, keep_modelyear=None, keep_weatheryears=[2012]): """ ### Subset to modeled regions load = reeds.io.read_file(os.path.join(inputs_case,'load.h5')) + + ### When running the linked model (GSw_FINITO_Link=1) we add reference load estimates + ### for FINITO load back in to use when selecting representative periods + sw = reeds.io.get_switches(inputs_case) + if int(sw.GSw_FINITO_Link): + load_hourly_finito = reeds.finito.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() @@ -677,10 +686,13 @@ def main( description='Create the necessary 8760 and capacity factor data for hourly resolution') parser.add_argument('reeds_path', help='ReEDS directory') parser.add_argument('inputs_case', help='ReEDS/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 = reeds.io.reeds_path @@ -690,10 +702,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) @@ -708,6 +721,7 @@ def main( sw=sw, reeds_path=reeds_path, inputs_case=inputs_case, periodtype='rep', make_plots=1, + logging=logging ) ############################################ diff --git a/reeds/input_processing/hourly_writetimeseries.py b/reeds/input_processing/hourly_writetimeseries.py index 38eebf141..3e9bcb312 100644 --- a/reeds/input_processing/hourly_writetimeseries.py +++ b/reeds/input_processing/hourly_writetimeseries.py @@ -200,6 +200,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')).unstack(level=0) load_in.columns = load_in.columns.rename(['r','t']) diff --git a/reeds/inputs.py b/reeds/inputs.py index 256f95114..99c7b3939 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 @@ -368,6 +376,8 @@ def solvestring_sequential( 'GSw_StateCO2ImportLevel', 'GSw_StartMarkets', 'GSw_ValStr', + 'GSw_FINITO_Link', + 'FINITO_first_year', 'solver', 'debug', 'startyear', diff --git a/reeds/resource_adequacy/prep_data.py b/reeds/resource_adequacy/prep_data.py index fd63e86f1..ab3eee17f 100644 --- a/reeds/resource_adequacy/prep_data.py +++ b/reeds/resource_adequacy/prep_data.py @@ -147,8 +147,37 @@ 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')) + 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() + 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.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) + load_finito = load_finito.reindex(columns=load_year.columns, fill_value=0) + load_finito = load_finito.fillna(0) + + # add to load + 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')) recf.columns = pd.MultiIndex.from_tuples([tuple(x.split('|')) for x in recf.columns], @@ -364,7 +393,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/runreeds.py b/runreeds.py index 1572ce3ef..3317ec1a2 100644 --- a/runreeds.py +++ b/runreeds.py @@ -4,6 +4,7 @@ import reeds import os +import sys import git import queue import threading @@ -99,12 +100,17 @@ 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(): - #exclude certain switches that don't need to be passed to GAMS + 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_FINITO_cases(df_cases,case) + + #exclude certain switches that don't need to be passed to GAMS + 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 @@ -546,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) @@ -1251,6 +1262,10 @@ def write_batch_script( os.makedirs(os.path.join(casedir,'handoff','reeds_data'), exist_ok=True) os.makedirs(os.path.join(casedir,'handoff','PRAS'), exist_ok=True) + ## Set up FINITO if running linked model + if int(caseSwitches['GSw_FINITO_Link'])==1: + 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 # || is used to separate multiple replacements. @@ -1398,12 +1413,43 @@ 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" --FINITO_dollaryear={caseSwitches['FINITO_dollaryear']} \n" ) OPATH.writelines(writescripterrorcheck("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') + + ### (ReEDS-FINITO) call FINITO reporting + 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" + ) + # (ReEDS-FINITO) calls FINITO postprocessing + 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' + ) + if not LINUXORMAC: + OPATH.writelines("endlocal\n") + + ### save reporting outputs to h5 and/or csv files OPATH.writelines(f"python {Path('reeds','core','terminus','report_dump.py')} {casedir} -c\n") OPATH.writelines(writescripterrorcheck('report_dump.py')+'\n')