Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
148 changes: 51 additions & 97 deletions reeds/core/terminus/report.gms
Original file line number Diff line number Diff line change
Expand Up @@ -122,34 +122,53 @@ $include autocode%ds%report_params.gms
h(h)$[not h_rep(h)] = no ;
szn(szn)$[not szn_rep(szn)] = no ;

*=================================================
* -- CAPACITY ABOVE INTERCONNECTION QUEUE LIMIT --
*=================================================

cap_above_limit(tg,r,t)$tmodel_new(t) = CAP_ABOVE_LIM.l(tg,r,t) ;

*=====================
* -- CO2 Reporting --
*=====================

CO2_CAPTURED_out(r,h,t)$tmodel_new(t) = CO2_CAPTURED.l(r,h,t) ;
CO2_CAPTURED_out_ann(r,t)$tmodel_new(t) = sum(h,hours(h) * CO2_CAPTURED.l(r,h,t) );
CO2_STORED_out(r,cs,h,t)$[tmodel_new(t)$csfeas(cs)] = CO2_STORED.l(r,cs,h,t) ;
CO2_STORED_out_ann(r,cs,t)$[tmodel_new(t)$csfeas(cs)] = sum(h,hours(h) * CO2_STORED.l(r,cs,h,t) );
CO2_TRANSPORT_INV_out(r,rr,t)$tmodel_new(t) = CO2_TRANSPORT_INV.l(r,rr,t) ;
CO2_SPURLINE_INV_out(r,cs,t)$[tmodel_new(t)$csfeas(cs)] = CO2_SPURLINE_INV.l(r,cs,t) ;

CO2_FLOW_out(r,rr,h,t)$[(ord(r) < ord(rr))$tmodel_new(t)] = CO2_FLOW.l(r,rr,h,t) + CO2_FLOW.l(rr,r,h,t) ;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be CO2_FLOW.l(r,rr,h,t) - CO2_FLOW.l(rr,r,h,t) ;? The sum is used in eq_co2_transport_caplimit which makes sense, but if the goal is to just get flows for r < rr region pairs but preserve direction then I would think the second term should be negative as is done with the tran_flow_rep calculation. Not sure who is the lead on CO2 these days but might be worth checking with them.

If this is a bug it might make sense to just flag this in an issue and address it in a separate PR since the goal of this one is just to do the reporting and have it match what we have. If we did want to address here I think we would just to agg='net' over in report_calcs.py.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess there is the net flow calculation below which is what I was expecting, so maybe this one is fine.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I'm not sure why it's here; the number of permutations doesn't really seem necessary, but because the net flow is also included, it seems intentional to have both. It might be used to identify hours where the pipeline capacity is binding. I figured I'd leave it for parity with the existing outputs, and it could maybe be cleaned up as part of #2.

CO2_FLOW_out_ann(r,rr,t)$[(ord(r) < ord(rr))$tmodel_new(t)] = sum{h, hours(h) * (CO2_FLOW.l(r,rr,h,t) + CO2_FLOW.l(rr,r,h,t)) } ;

CO2_FLOW_pos_out(r,rr,h,t)$[(ord(r) < ord(rr))$tmodel_new(t)] = CO2_FLOW.l(r,rr,h,t) ;
CO2_FLOW_pos_out_ann(r,rr,t)$[(ord(r) < ord(rr))$tmodel_new(t)] = sum{h, hours(h) * CO2_FLOW.l(r,rr,h,t) } ;

CO2_FLOW_neg_out(r,rr,h,t)$[(ord(r) < ord(rr))$tmodel_new(t)] = -1 * CO2_FLOW.l(rr,r,h,t) ;
CO2_FLOW_neg_out_ann(r,rr,t)$[(ord(r) < ord(rr))$tmodel_new(t)] = -1 * sum{h, hours(h) * CO2_FLOW.l(rr,r,h,t) } ;

CO2_FLOW_net_out(r,rr,h,t)$[(ord(r) < ord(rr))$tmodel_new(t)] = CO2_FLOW.l(r,rr,h,t) - CO2_FLOW.l(rr,r,h,t) ;
CO2_FLOW_net_out_ann(r,rr,t)$[(ord(r) < ord(rr))$tmodel_new(t)] = sum{h, hours(h) * (CO2_FLOW.l(r,rr,h,t) - CO2_FLOW.l(rr,r,h,t)) } ;
*=====================================
* -- Parameters moved to Python --
*=====================================

$ontext
The calculation of the following output parameters has been moved to report_calcs.py:

Interconnection queue
cap_above_limit

CO2 storage and flows
CO2_CAPTURED_out
CO2_CAPTURED_out_ann
CO2_FLOW_neg_out
CO2_FLOW_neg_out_ann
CO2_FLOW_net_out
CO2_FLOW_net_out_ann
CO2_FLOW_out
CO2_FLOW_out_ann
CO2_FLOW_pos_out
CO2_FLOW_pos_out_ann
CO2_SPURLINE_INV_out
CO2_STORED_out
CO2_STORED_out_ann
CO2_TRANSPORT_INV_out

Transmission
cap_converter_out
invtran_out
tran_cap_energy
tran_cap_grp
tran_cap_prm
tran_flow_all_rep
tran_flow_all_stress
tran_flow_rep
tran_flow_rep_ann
tran_flow_stress
tran_mi_out
tran_mi_out_detail
tran_out
tran_prm_mi_out
tran_prm_out
tran_util_ann_rep
tran_util_ann_stress
tran_util_h_rep
tran_util_h_stress
$offtext

*=========================
* LCOE
Expand Down Expand Up @@ -1774,75 +1793,6 @@ excess_load(r,h,t) = EXCESS.l(r,h,t) ;
*======================
* Transmission
*======================

invtran_out(r,rr,trtype,t)$routes_inv(r,rr,trtype,t) = INVTRAN.l(r,rr,trtype,t) ;

tran_cap_energy(r,rr,trtype,t)$routes(r,rr,trtype,t) = CAPTRAN_ENERGY.l(r,rr,trtype,t) ;
tran_cap_prm(r,rr,trtype,t)$routes(r,rr,trtype,t) = CAPTRAN_PRM.l(r,rr,trtype,t) ;
tran_cap_grp(transgrp,transgrpp,t)$trancap_init_transgroup(transgrp,transgrpp,"AC")
= CAPTRAN_GRP.l(transgrp,transgrpp,t) ;

tran_out(r,rr,trtype,t)$[(ord(r)<ord(rr))$routes(r,rr,trtype,t)] =
(tran_cap_energy(r,rr,trtype,t) + tran_cap_energy(rr,r,trtype,t)) / 2 ;

tran_prm_out(r,rr,trtype,t)$[(ord(r)<ord(rr))$routes(r,rr,trtype,t)] =
(tran_cap_prm(r,rr,trtype,t) + tran_cap_prm(rr,r,trtype,t)) / 2 ;

tran_mi_out_detail(r,rr,trtype,t)$routes(r,rr,trtype,t) = tran_out(r,rr,trtype,t) * distance(r,rr,trtype) ;

tran_mi_out(trtype,t)$tmodel_new(t) =
sum{(r,rr)$routes(r,rr,trtype,t), tran_mi_out_detail(r,rr,trtype,t) } ;
tran_prm_mi_out(trtype,t)$tmodel_new(t) =
sum{(r,rr)$routes(r,rr,trtype,t), tran_prm_out(r,rr,trtype,t) * distance(r,rr,trtype) } ;

cap_converter_out(r,t)$tmodel_new(t) = CAP_CONVERTER.l(r,t) ;

tran_flow_all_rep(r,rr,h,trtype,t)
$[tmodel_new(t)$routes(r,rr,trtype,t)] = FLOW.l(r,rr,h,t,trtype) ;

tran_flow_all_stress(r,rr,allh,trtype,t)
$[tmodel_new(t)$routes(r,rr,trtype,t)$h_stress_t(allh,t)] = FLOW.l(r,rr,allh,t,trtype) ;

tran_flow_rep(r,rr,h,trtype,t)
$[tmodel_new(t)$routes(r,rr,trtype,t)$(ord(r) < ord(rr))] =
FLOW.l(r,rr,h,t,trtype) - FLOW.l(rr,r,h,t,trtype)
;

tran_flow_stress(r,rr,allh,trtype,t)
$[tmodel_new(t)$routes(r,rr,trtype,t)$(ord(r) < ord(rr))$h_stress_t(allh,t)] =
FLOW.l(r,rr,allh,t,trtype) - FLOW.l(rr,r,allh,t,trtype)
;

tran_flow_rep_ann(r,rr,trtype,t)
$[sum{h, tran_flow_rep(r,rr,h,trtype,t)}] =
sum{h, hours(h) * tran_flow_rep(r,rr,h,trtype,t) }
;

tran_util_h_rep(r,rr,h,trtype,t)
$[tmodel_new(t)$routes(r,rr,trtype,t)$tran_cap_energy(r,rr,trtype,t)] =
FLOW.l(r,rr,h,t,trtype) / tran_cap_energy(r,rr,trtype,t)
;

tran_util_h_stress(r,rr,allh,trtype,t)
$[tmodel_new(t)$routes(r,rr,trtype,t)$tran_cap_prm(r,rr,trtype,t)$h_stress_t(allh,t)] =
FLOW.l(r,rr,allh,t,trtype) / tran_cap_prm(r,rr,trtype,t)
;

tran_util_ann_rep(r,rr,trtype,t)
$[tmodel_new(t)$routes(r,rr,trtype,t)$tran_cap_energy(r,rr,trtype,t)] =
sum{h, FLOW.l(r,rr,h,t,trtype) * hours(h) / tran_cap_energy(r,rr,trtype,t) }
/ sum{h, hours(h) }
;

tran_util_ann_stress(r,rr,trtype,t)
$[tmodel_new(t)
$routes(r,rr,trtype,t)$tran_cap_prm(r,rr,trtype,t)
$sum{allh$h_stress_t(allh,t), hours_t(allh,t)}] =
sum{allh$h_stress_t(allh,t),
FLOW.l(r,rr,allh,t,trtype) * hours_t(allh,t) / tran_cap_prm(r,rr,trtype,t) }
/ sum{allh$h_stress_t(allh,t), hours_t(allh,t) }
;

import_h_rep(r,h,t)
$[tmodel_new(t)] =
* Imports with losses
Expand Down Expand Up @@ -2152,6 +2102,10 @@ $endif.powerfrac
* Dump results
*========================================

execute_unload "outputs%ds%results.gdx"
$include reeds%ds%core%ds%terminus%ds%report_data.csv
;

* The parameter list in the following file is read from report_params.csv
* and parsed in copy_files.py
execute_unload "outputs%ds%rep_%fname%.gdx"
Expand Down
165 changes: 165 additions & 0 deletions reeds/core/terminus/report_calcs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
#%% Imports
import gdxpds
import pandas as pd
from pathlib import Path
from typing import Literal


#%% Helper functions
def get_gams_results(case):
print('Loading results.gdx')
dictin = gdxpds.to_dataframes(Path(case, 'outputs', 'results.gdx'))
## Set indices as multiindex
valcols = ['Value','Level','Marginal','Lower','Upper','Scale']
for key, df in dictin.items():
indices = [i for i in df if i not in valcols]
dictin[key] = df.set_index(indices).squeeze(1)
print('Finished loading results.gdx')
return dictin


def get_flow(df, direction:Literal['forward','reverse']='forward', value='Level'):
r_indices = ['r', 'rr']
other_indices = [i for i in df.index.names if i not in r_indices]
if direction == 'forward':
mask = df.index.get_level_values('r') < df.index.get_level_values('rr')
if isinstance(df, pd.Series):
out = df.loc[mask]
elif isinstance(df, pd.DataFrame):
out = df.loc[mask, value]
elif direction == 'reverse':
mask = df.index.get_level_values('r') > df.index.get_level_values('rr')
if isinstance(df, pd.Series):
_out = df.loc[mask]
elif isinstance(df, pd.DataFrame):
_out = df.loc[mask, value]
out = _out.rename_axis(['rr', 'r'] + other_indices).reorder_levels(r_indices + other_indices)
return out


def combine_forward_reverse(df, agg:Literal['net','simult']='net', value='Level'):
"""Combine forward (r < rr) and reverse (r > rr) into one +/- series"""
r_indices = ['r', 'rr']
other_indices = [i for i in df.index.names if i not in r_indices]
forward = get_flow(df, 'forward')
reverse = (-1 if agg == 'net' else 1) * get_flow(df, 'reverse')
return pd.concat([forward, reverse]).groupby(r_indices + other_indices).sum()


#%% Results calculations
def calc_iq(g):
"""Capacity above interconnection queue limit"""
dfs = {}
## (tg,r,t)
dfs['cap_above_limit'] = g['CAP_ABOVE_LIM'].Level
return dfs


def calc_co2_stor(g):
"""CO2 capture, transport, and storage"""
dfs = {}
## (r,h,t)
dfs['CO2_CAPTURED_out'] = g['CO2_CAPTURED'].Level
## (r,t)
dfs['CO2_CAPTURED_out_ann'] = (g['CO2_CAPTURED'].Level * g['hours']).groupby(['r','t']).sum()
## (r,cs,h,t)
dfs['CO2_STORED_out'] = g['CO2_STORED'].Level
## (r,cs,t)
dfs['CO2_STORED_out_ann'] = (g['CO2_STORED'].Level * g['hours']).groupby(['r','cs','t']).sum()
## (r,rr,t)
dfs['CO2_TRANSPORT_INV_out'] = g['CO2_TRANSPORT_INV'].Level
## (r,cs,t)
dfs['CO2_SPURLINE_INV_out'] = g['CO2_SPURLINE_INV'].Level
## (r,rr,h,t)
dfs['CO2_FLOW_out'] = combine_forward_reverse(g['CO2_FLOW'], agg='simult')
## (r,rr,t)
dfs['CO2_FLOW_out_ann'] = (dfs['CO2_FLOW_out'] * g['hours']).groupby(['r','rr','t']).sum()
## (r,rr,h,t)
dfs['CO2_FLOW_pos_out'] = get_flow(g['CO2_FLOW'], 'forward')
## (r,rr,t)
dfs['CO2_FLOW_pos_out_ann'] = (dfs['CO2_FLOW_pos_out'] * g['hours']).groupby(['r','rr','t']).sum()
## (r,rr,h,t)
dfs['CO2_FLOW_neg_out'] = -get_flow(g['CO2_FLOW'], 'reverse')
## (r,rr,t)
dfs['CO2_FLOW_neg_out_ann'] = (dfs['CO2_FLOW_neg_out'] * g['hours']).groupby(['r','rr','t']).sum()
## (r,rr,h,t)
dfs['CO2_FLOW_net_out'] = combine_forward_reverse(g['CO2_FLOW'], agg='net')
## (r,rr,t)
dfs['CO2_FLOW_net_out_ann'] = (dfs['CO2_FLOW_net_out'] * g['hours']).groupby(['r','rr','t']).sum()
return dfs


def calc_transmission(g):
"""Transmission capacity and flow"""
dfs = {}
## (r,rr,trtype,t)
dfs['invtran_out'] = g['INVTRAN'].Level
## (r,rr,trtype,t)
dfs['tran_cap_energy'] = g['CAPTRAN_ENERGY'].Level
## (r,rr,trtype,t)
dfs['tran_cap_prm'] = g['CAPTRAN_PRM'].Level
## (transgrp,transgrpp,t)
dfs['tran_cap_grp'] = g['CAPTRAN_GRP'].Level
## (r,rr,trtype,t)
dfs['tran_out'] = combine_forward_reverse(dfs['tran_cap_energy'], agg='simult') / 2
## (r,rr,trtype,t)
dfs['tran_prm_out'] = combine_forward_reverse(dfs['tran_cap_prm'], agg='simult') / 2
## (r,rr,trtype,t)
dfs['tran_mi_out_detail'] = dfs['tran_out'] * g['distance']
## (trtype,t)
dfs['tran_mi_out'] = dfs['tran_mi_out_detail'].groupby(['trtype','t']).sum()
## (trtype,t)
dfs['tran_prm_mi_out'] = (dfs['tran_prm_out'] * g['distance']).groupby(['trtype','t']).sum()
## (r,t)
dfs['cap_converter_out'] = g['CAP_CONVERTER'].Level
## (r,rr,h,trtype,t) (r,rr,allh,t,trtype)
dfs['tran_flow_all_rep'] = g['FLOW'].Level.loc[:,:,g['h_rep'].index]
## (r,rr,allh,trtype,t)
dfs['tran_flow_all_stress'] = (
g['FLOW'].Level.reset_index()
.merge(g['h_stress_t'], left_on=['allh','t'], right_on=['allh','allt'])
.set_index(['r','rr','allh','trtype','t']).Level
)
## (r,rr,h,trtype,t) (r,rr,allh,t,trtype)
dfs['tran_flow_rep'] = combine_forward_reverse(g['FLOW'], agg='net').loc[:,:,g['h_rep'].index]
## (r,rr,allh,trtype,t)
dfs['tran_flow_stress'] = (
combine_forward_reverse(g['FLOW']).reset_index()
.merge(g['h_stress_t'], left_on=['allh','t'], right_on=['allh','allt'])
.set_index(['r','rr','allh','trtype','t']).Level
)
## (r,rr,trtype,t)
dfs['tran_flow_rep_ann'] = (dfs['tran_flow_rep'] * g['hours']).groupby(['r','rr','trtype','t']).sum()
## (r,rr,h,trtype,t)
dfs['tran_util_h_rep'] = dfs['tran_flow_all_rep'] / dfs['tran_cap_energy']
## (r,rr,allh,trtype,t)
dfs['tran_util_h_stress'] = dfs['tran_flow_all_stress'] / dfs['tran_cap_prm']
## (r,rr,trtype,t)
dfs['tran_util_ann_rep'] = (
(dfs['tran_flow_all_rep'] * g['hours'] / dfs['tran_cap_energy']).groupby(['r','rr','trtype','t']).sum()
/ g['hours'].sum()
)
## NOTE: We here assume that all solve years use the same total weighting for stress

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method on main is off for years before the last year; see #146. This implementation fixes it and is self-consistent when stress periods are always weighted at 6 hours per solve year, but if #146 is fixed as suggested, we should instead use hours_h(allh,t) here to make it future-proof in case we switch to nonuniform stress period weights.

## periods and weight all stress hours the same, so we don't weight by the number of hours.
## If we switch to different weightings for different stress periods,
## should add an hours(allh,t) parameter.
## (r,rr,trtype,t)
dfs['tran_util_ann_stress'] = dfs['tran_util_h_stress'].groupby(['r','rr','trtype','t']).mean()
return dfs


#%% Procedure
def main(case):
## NOTE: If calculations slow down for large runs, consider dropping zeros upfront
## in get_gams_results() to speed up processing
dictin = get_gams_results(case)
dictout = {
**calc_iq(dictin),
**calc_co2_stor(dictin),
**calc_transmission(dictin),
}
## Drop zeros to reduce file size and match GAMS convention
for key, df in dictout.items():
_df = df.rename('Value').reset_index()
dictout[key] = _df.loc[_df.Value != 0].dropna().copy()
return dictout
24 changes: 24 additions & 0 deletions reeds/core/terminus/report_data.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
***
*** Sets and parameters
***
distance
h_rep
h_stress_t
hours
r
routes
***
*** Variables
***
CAP_ABOVE_LIM
CAP_CONVERTER
CAPTRAN_ENERGY
CAPTRAN_GRP
CAPTRAN_PRM
CO2_CAPTURED
CO2_FLOW
CO2_SPURLINE_INV
CO2_STORED
CO2_TRANSPORT_INV
FLOW
INVTRAN
12 changes: 7 additions & 5 deletions reeds/core/terminus/report_dump.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from pathlib import Path
sys.path.append(str(Path(__file__).parent.parent.parent.parent))
import reeds
from reeds.core.terminus import report_calcs


#%% Generic functions
Expand Down Expand Up @@ -70,7 +71,7 @@ def dfdict_to_h5(
_symbol_list = dfdict.keys() if symbol_list is None else symbol_list

### Check for existing file
_filepath = filepath if filepath.endswith(".h5") else filepath + ".h5"
_filepath = filepath if Path(filepath).suffix == '.h5' else str(filepath) + '.h5'
if os.path.exists(_filepath):
if overwrite:
os.remove(_filepath)
Expand Down Expand Up @@ -270,9 +271,10 @@ def postprocess_outputs(case, outputs_path=None, verbose=0):
# %%### Write results for each gdx file
### outputs gdx
print("Loading outputs gdx")
dict_out = gdxpds.to_dataframes(
os.path.join(outputs_path, f"rep_{os.path.basename(case)}.gdx")
)
dict_out = {
**gdxpds.to_dataframes(Path(outputs_path, f"rep_{os.path.basename(case)}.gdx")),
**report_calcs.main(case),
}
print("Finished loading outputs gdx")

write_dfdict(
Expand Down Expand Up @@ -305,6 +307,6 @@ def postprocess_outputs(case, outputs_path=None, verbose=0):
#%% All done
print("Completed report_dump.py")
try:
toc(tic=tic, year=0, path=case, process="report_dump.py")
reeds.log.toc(tic=tic, year=0, path=case, process="report_dump.py")
except NameError:
print("reeds/log.py not found, so not logging output")
Loading
Loading