Implement daily temperature-based gas price adjustments #65
Conversation
…f get_degree_days
kodiobika
left a comment
There was a problem hiding this comment.
Thanks for your work on this! It's looking great so far -- mostly just some stylistic suggestions
|
@patrickbrown4 @wesleyjcole I'm in the middle of creating new comparison reports, but the code is ready for re-review whenever you get a chance, thanks! |
Oop actually there's a bug in |
Should be resolved now |
patrickbrown4
left a comment
There was a problem hiding this comment.
Thanks for the revisions! A couple small comments but otherwise looks good to me.
There was a problem hiding this comment.
I could remake this figure for the 90 zones, but we'll probably end up wanting to redo the layout (both of the figure and the discussion in the docs) to illustrate the different possible resolutions, so I think it can wait for a followup PR
|
|
||
| The natural gas fuel prices also include a seasonal price adjustor, making winter prices higher than the natural gas prices seen during the other seasons of the year. | ||
| The natural gas fuel prices also include time-based price adjustors. | ||
| One option is a seasonal price adjustor, which makes winter prices higher than the natural gas prices seen during the other seasons of the year CONUS-wide. |
There was a problem hiding this comment.
| One option is a seasonal price adjustor, which makes winter prices higher than the natural gas prices seen during the other seasons of the year CONUS-wide. | |
| The default option is a seasonal price adjustor, which makes winter prices higher than the natural gas prices seen during the other seasons of the year CONUS-wide. |
| *scale back to $ / mmbtu and apply annual consumption-weighted gas price multipliers | ||
| gas_consumption(cendiv,gb,t) = sum{h, GASUSED.l(cendiv,gb,h,t) } ; |
There was a problem hiding this comment.
Since gas_scale is used here, and from my read of eq_gasused, it looks like we need to multiply by gas_scale to get the units in MMBtu:
ReEDS/reeds/core/terminus/report.gms
Lines 506 to 507 in 80d1ec0
There was a problem hiding this comment.
Oh that's weird, I actually took the units from the description of GASUSED in c_model.gms (MMBtu/hour). I think this implies that the units of either GASUSED or repgasquant must be labelled incorrectly, right? Regarding gas_consumption, my understanding is the units end up not mattering (except for documentation purposes in report_params.csv) since we only use it to
- check if consumption is non-zero for a given gas bin (in the
gb$[gas_consumption(cendiv,gb,t)]conditional) and - calculate consumption weights for each hour (
GASUSED.l(cendiv,gb,h,t) / gas_consumption(cendiv,gb,t))
Does that sound right to you? And assuming it's correct that the units of gas_consumption don't matter, I think we should end up with whatever units for repgasprice we had before (but will double-check), which is labelled as $/mmbtu but now I'm not sure what they actually are
There was a problem hiding this comment.
It's probably safest to be sure about the units for gas_consumption, since if it's included as an output parameter (which it will be if it's defined in report_params.csv), someone will probably use it someday.
I missed it before but I think you're right that it's currently off; since GASUSED is in [MMBtu/hour], it should be multiplied by hours when taking the sum.
The gas_scale makes things weird; it's a 1e6 scaling factor but is only to keep the gas variables to a similar scale as the other model variables. So:
GASUSED=heat_rate[MMBtu/MWh] *GEN[MW] /gas_scale[1e6] = 1e-6*[MMBtu/hour]repgasquant=GASUSED(1e-6*[MMBtu/hour]) *hours[hour] *gas_scale(1e6) * 1e-9 [quad/MMBtu] = [quad]gas_consumption=GASUSED(1e-6*[MMBtu/hour]) *hours[hour] = 1e-6*[MMBtu]
So if you add * gas_scale to the definition of gas_consumption, you'll get gas_consumption in [MMBtu], as stated in report_params.csv:
gas_consumption(cendiv,gb,t) = sum{h, GASUSED.l(cendiv,gb,h,t) * hours(h) * gas_scale } ;- [MMBtu] = 1e-6*[MMBtu/hour] * [hour] * 1e6 = [MMBtu]
For repgasprice, note that gasprice is [$/MMBtu] multiplied by gas_scale in b_inputs.gms, so its values are on the order of 1e6. So if you do it as:
repgasprice=gasprice(1e6*[$/MMBtu]) *GASUSED(1e-6*[MMBtu/hour]) *hours[hour] /gas_consumption[MMBtu]
(after gas_consumption has been descaled to [MMBtu]), then you should just get [$/MMBtu], without the use of gas_scale in the repgasprice equation.
It does look like the hours is currently in the wrong place in the repgasprice equation; if GASUSED is [MMBtu/hour] and gas_consumption is [MMBtu] then it should be in the numerator instead of the denominator.
At least that's my current take; should take another look on Monday!
| "flex_load_out(flex_type,r,allh,t)",MWh,flexible load consumed in each timeslice,,, | ||
| forced_outage(i),fraction,average forced outage rate (h-weighted; r-unweighted) by technology during representative periods,,, | ||
| planned_outage(i),fraction,average scheduled outage rate (h-weighted) by technology during representative periods,,, | ||
| "gas_consumption(cendiv,gb,t)",MMBtu,annual gas used by census division and gas bin,,, |
There was a problem hiding this comment.
See note on report.gms about double-checking the use of gas_scale to make sure the units are in MMBtu
There was a problem hiding this comment.
Thanks for adding these!
I don't have a good system for remembering to do this. Maybe we should add another checkbox to "Details to double-check" in the PR template. (Not here, just thinking out loud.)
| csp_sm_3,1.3,"--fraction-- solar multiple for csp3" | ||
| csp_sm_4,1.0,"--fraction-- solar multiple for csp4" | ||
| csp_sm_baseline,2.4,"--fraction-- baseline solar multiple to normalize other CSP solar multiples by" | ||
| degree_days_base_temperature,18.33,"--temperature in degrees Celsius to use as the baseline temperature for calculating degree days" |
There was a problem hiding this comment.
Could add a note on the source:
| degree_days_base_temperature,18.33,"--temperature in degrees Celsius to use as the baseline temperature for calculating degree days" | |
| degree_days_base_temperature,18.33,"--temperature in degrees Celsius to use as the baseline temperature for calculating degree days (65°F from https://www.eia.gov/energyexplained/units-and-calculators/degree-days.php)" |
| cdd_hourly = (temp_hourly - base_temp).clip(lower=0) | ||
| cdd_daily = cdd_hourly.resample('D').mean() | ||
|
|
||
| return hdd_daily, cdd_daily |
There was a problem hiding this comment.
Slight preference to instead return a {'hdd':hdd_daily, 'cdd':cdd_daily} dict so the outputs are self-documenting and to help avoid mixups
| smax{gb$[gas_consumption(cendiv,gb,t)], | ||
| gasprice(cendiv,gb,t) | ||
| * sum{h, gasprice_adj_cendiv(cendiv,h) * GASUSED.l(cendiv,gb,h,t) / gas_consumption(cendiv,gb,t) / hours(h) } | ||
| } / gas_scale ; |
There was a problem hiding this comment.
If gas_scale is added above, it'd be removed here
| *scale back to $ / mmbtu | ||
| *scale back to $ / mmbtu and apply annual consumption-weighted gas price multipliers | ||
| gas_consumption(cendiv,gb,t) = sum{h, GASUSED.l(cendiv,gb,h,t) } ; | ||
| repgasprice(cendiv,t)$[(Sw_GasCurve = 0)$tmodel_new(t)$repgasquant(cendiv,t)] = |
There was a problem hiding this comment.
Just to double check, do these all look right (expected order of magnitude) in the test runs you did? (No need to add a bunch of plots to the PR, a quick scan of the output file is enough)
I've added a reminder to #34 to add an output plot for gas price (eventually, not here)
Summary
This PR calculates daily gas price multipliers based on a linear regression fitting heating and cooling degree days on daily deviations from annual regional gas prices (the parameters of which are in
inputs/fuelprices/gasreg_price_adj_regression_parameters.csv). The user can choose between these daily regional multipliers, the existing national wintertime markup, and no adjustment.Technical details
gasreghierarchy level, which represents the regions at which the degree day / price deviation regressions were fitted. These are mostly just census divisions, with the exceptions being "California" and "Northwest", which are components of the "Pacific" cendiv, and "Southwest" and "Mountain", which are components of the "Mountain" cendiv.gasreg-level gas price multipliers are calculated inreeds/input_processing/fuelcostprep.pyby calculating state-level daily degree days for the weather years of the given run and aggregating them via population-weighted average. To create multipliers at therlevel, thegasreg-level multipliers are copied to their constituent zones. To create multipliers at thecendivlevel, thegasreg-level multipliers are aggregated via population-weighted average.get_daily_gas_price_multipliersfunction to hourly_writetimeseries.py to get a GAMS compatible csv with representative hourly NG price multipliers. In this function, the multipliers for each gasreg are renormalized so that the average across the representative periods is 1, ensuring the year-round average gas price doesn't change.Additional changes
GSw_GasPriceAdjMethodwhich controls whether the daily price multipliers are applied, the national wintertime markup is applied, or no adjustment is applied.main, the wintertime markup of 1.054 (szn_adj_gas_winterinscalars.csv) mentioned in the model documentation is only being applied whenGSw_GasCurve = 1. A separate wintertime multiplier of 1.04 (gasprice_ref_frac_adderinscalars.csv) is being applied whenGSw_GasCurve = 3. No multiplier is applied whenGSw_GasCurve = 0 or 2. In this PR, thegasprice_ref_frac_addermultiplier is deleted and theszn_adj_gasmultiplier is applied in everyGSw_GasCurvescenario (assumingGSw_GasPriceAdjMethod = 1).main, the wintertime markup only increases the wintertime price but doesn't make corresponding adjustments to maintain the pre-markup year-round average price. As a result, the year-round natural gas price is being erroneously inflated. In this PR, the seasonal multipliers are renormalized so that wintertime prices are higher than nonwinter prices while the year-round average price remains unchanged.Switches added/removed/changed
GSw_GasPriceAdjMethod(0 = no adjustment, 1 = national wintertime markup, 2 = daily adjustments based on regional temperatures)Validation, testing, and comparison report(s)
Results comparing
mainand this branch using differentGSw_GasPriceAdjMethodandGSw_GasCurvevalues are below.GSw_GasCurve = 1- More gas is built due to the fact that the wintertime markup is now renormalized so that the year-round average gas price remains unchanged (previously we only increased the wintertime gas price without a commensurate decrease in the non-wintertime price, which increased the year-round average price). The non-adjustment and seasonal adjustment cases see up to a ~30 TWh increase in gas generation, while the daily price adjustment case sees up to a ~60 TWh increase.GSw_GasPriceAdjMethod = 1: results-0707_Main_USA_defaults,0707_NatGas_USA_defaults.pptxGSw_GasPriceAdjMethod = 2: results-0707_Main_USA_defaults,0707_NatGas_USA_DailyAdj.pptxGSw_GasPriceAdjMethod = 0: results-0707_Main_USA_defaults,0707_NatGas_USA_NoAdj.pptxGSw_GasCurve = 2- Differences range from no change to more gas buildout. The non-adjustment case results in no changes, which makes sense because no multiplier was being applied initially. The seasonal adjustment case has slightly lower gas generation in early years and slightly higher gas and wind generation replacing solar in later years. The daily adjustment case sees up to a ~50 TWh increase in gas generation.GSw_GasPriceAdjMethod = 1: results-0707_Main_USA_GasCurve2,0707_NatGas_USA_GasCurve2.pptxGSw_GasPriceAdjMethod = 2: results-0707_Main_USA_GasCurve2,0707_NatGas_USA_GasCurve2_DailyAdj.pptxGSw_GasPriceAdjMethod = 0: results-0707_Main_USA_GasCurve2,0707_NatGas_USA_GasCurve2_NoAdj.pptxGSw_GasCurve = 3- Differences are fairly minimal across the board. The largest change is in the non-adjustment case, where 2040 sees a ~6 TWh change in generation, which is mostly a shift from gas-cc to gas-ct. The other cases just have changes on the scale of rounding errors.GSw_GasPriceAdjMethod = 1: results-0707_Main_USA_GasCurve3,0707_NatGas_USA_GasCurve3.pptxGSw_GasPriceAdjMethod = 2: results-0707_Main_USA_GasCurve3,0707_NatGas_USA_GasCurve3_DailyAdj.pptxGSw_GasPriceAdjMethod = 0: results-0707_Main_USA_GasCurve3,0707_NatGas_USA_GasCurve3_NoAdj.pptxGSw_GasCurve = 0- Differences range from no change to more gas buildout. The non-adjustment case results in no changes, which makes sense because no multiplier was being applied initially. The seasonal adjustment case sees up to a ~20 TWh increase in gas generation, and the daily adjustment case sees up to a ~70 TWh increase.GSw_GasPriceAdjMethod = 1: results-0707_Main_USA_GasCurve0,0707_NatGas_USA_GasCurve0.pptxGSw_GasPriceAdjMethod = 2: results-0707_Main_USA_GasCurve0,0707_NatGas_USA_GasCurve0_DailyAdj.pptxGSw_GasPriceAdjMethod = 0: results-0707_Main_USA_GasCurve0,0707_NatGas_USA_GasCurve0_NoAdj.pptxChecklist for author
Details to double-check
cases_test.csvstill workGeneral information to guide review
Did you use LLM tools (chatbot or copilot) in the preparation of this PR? If so, describe how
Tag points of contact here if you would like additional review of the relevant parts of the model