Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
4940d6a
a few hourlize bug fixes
bsergi Aug 4, 2026
15695b8
remove old crs parameter
bsergi Aug 4, 2026
3dd92b8
add script to automate copying of reV data to ReEDS Supply_Curve_Data…
bsergi Aug 4, 2026
27bfc08
integrate reV folder copying into hourlize run
bsergi Aug 4, 2026
b019cf0
address datetime index as bytes bug
bsergi Aug 4, 2026
25ae346
remove some settings that are no longer used
bsergi Aug 4, 2026
59bc345
get cases from rev_paths file, remove cases.json files
bsergi Aug 4, 2026
1439453
additional bug fixes
bsergi Aug 5, 2026
89ef12c
add option to select for ATB technology with geothermal
bsergi Aug 5, 2026
dd4cc43
add atb_scenario to EGS config
bsergi Aug 5, 2026
15c9d66
add option to exclude techs when running hourlize
bsergi Aug 5, 2026
fe13e4b
updated rev_paths file
bsergi Aug 5, 2026
24241ca
updated supply curves with hourlize
bsergi Aug 5, 2026
8fcd354
new exogenous capacity and prescribed builds (temporary--will be remo…
bsergi Aug 5, 2026
c51499d
Merge remote-tracking branch 'origin/main' into bs/supply_curves
bsergi Aug 7, 2026
c2dfe47
save mean_resource_temp for EGS
bsergi Aug 7, 2026
c2467bf
remove existing_capacity references
bsergi Aug 7, 2026
e337efc
updated supply curve files
bsergi Aug 7, 2026
ab5a9a7
add function to check status of hourlize runs
bsergi Aug 7, 2026
debbc5e
skip runs with 'none' listed for original rev folder
bsergi Aug 7, 2026
e2dbdd6
track original rev folder and date updated in config
bsergi Aug 7, 2026
a55adef
add back cf to egs
bsergi Aug 13, 2026
6b7ad1b
supply curve metadata update
bsergi Aug 13, 2026
be3b446
Merge remote-tracking branch 'origin/main' into bs/supply_curves
bsergi Aug 13, 2026
267961a
Merge branch 'main' into bs/supply_curves
bsergi Aug 17, 2026
fdfe393
Merge remote-tracking branch 'origin/main' into bs/supply_curves
bsergi Aug 18, 2026
6a1bd16
add script to generate supply curve maps for docs
bsergi Aug 18, 2026
7a3db49
Merge remote-tracking branch 'origin/main' into bs/supply_curves
bsergi Aug 18, 2026
cdf8f16
Merge branch 'main' into bs/supply_curves
bsergi Aug 18, 2026
50186a6
move supply_curve_plots.py into plotting_scripts folder; add presenta…
bsergi Aug 18, 2026
8e57156
Merge branch 'bs/supply_curves' of github.com:ReEDS-Model/ReEDS into …
bsergi Aug 18, 2026
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
4 changes: 2 additions & 2 deletions docs/source/figs/docs/supplycurve-upv.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions docs/source/figs/docs/supplycurve-wind-ofs.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions docs/source/figs/docs/supplycurve-wind-ons.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 0 additions & 3 deletions docs/source/figs/docs/supplycurve-windofs.png

This file was deleted.

3 changes: 0 additions & 3 deletions docs/source/figs/docs/supplycurve-windons.png

This file was deleted.

12 changes: 6 additions & 6 deletions docs/source/model_documentation.md

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.

We should probably recreate Fig29. for EGS technical potential since the supply curve is updated? Or since reV supply curves for geohydro are forthcoming so we could update it for both technologies together later.

Original file line number Diff line number Diff line change
Expand Up @@ -1030,16 +1030,16 @@ Capacity factors for wind plants coming online from 2010 through 2023 are taken
Available land-based wind resources and site-specific cost and performance are based on {cite}`lopezRenewableEnergyTechnical2025`, using outputs of the reV model {cite}`maclaurinRenewableEnergyPotential2021`.
The Reference Access case includes more than 49,000 potential wind sites, totaling more than 9,400 gigawatts (GW).
Limited Access and Open Access supply curves are also available.
Available resource for the three access cases and associated average capacity factors are shown in {numref}`figure-supplycurve-windons`.
Available resource for the three access cases and associated average capacity factors are shown in {numref}`figure-supplycurve-wind-ons`.
In ReEDS, each wind site is characterized with a supply curve cost, which comprises transmission spur line and reinforcement upgrade costs as well as site-specific capital cost adjustments based on region, land cost, and site capacity (to account for economies of scale).
See the [Interzonal Transmission](#interzonal-transmission) section for more discussion of the interconnection supply curves for accessing the wind resource.

The individual wind sites are grouped into 10 resource classes based on *k*-means clustering of average annual capacity factors.
Distinct wind generation profiles are represented in ReEDS for each region and class, based on capacity-weighted averages of all sites of that region and class.
Sites are also grouped into a flexible number of supply curve cost bins in ReEDS, with 10 bins used by default for each ReEDS region and class.

```{figure} figs/docs/supplycurve-windons.png
:name: figure-supplycurve-windons
```{figure} figs/docs/supplycurve-wind-ons.png
:name: figure-supplycurve-wind-ons

Land-based wind resource availability and capacity factor for the three siting scenarios included in ReEDS.
```
Expand All @@ -1063,11 +1063,11 @@ The offshore technology selection is made using the Offshore Wind Cost Model, wh
See also {cite}`lopezRenewableEnergyTechnical2025` for more information on the development of the resource supply curves.

Resource availability varies across different siting access cases: The Reference Access case has 4,064 sites totaling 2.97 terawatts (TW), the Open Access case has 4,524 sites totaling 3.534 TW, and the Limited Access case with 3,166 sites totals 2.212 TW.
Modeled site-level capacity factor and resource availability are shown in {numref}`figure-supplycurve-windofs`.
Modeled site-level capacity factor and resource availability are shown in {numref}`figure-supplycurve-wind-ofs`.
Additional details regarding offshore wind resource modeling can be found in {cite}`lopezRenewableEnergyTechnical2025`.

```{figure} figs/docs/supplycurve-windofs.png
:name: figure-supplycurve-windofs
```{figure} figs/docs/supplycurve-wind-ofs.png
:name: figure-supplycurve-wind-ofs

Offshore wind resource availability by siting access case for the CONUS
```
Expand Down
170 changes: 170 additions & 0 deletions docs/source/plotting_scripts/supply_curve_plots.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.

This is very handy!

Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
'''
Generates maps of the supply curves capacity.
Can be used to re-create documentation figures or to generate a summary
figure for a presentation.
'''

import sys
import numpy as np
import pandas as pd
from pathlib import Path
import matplotlib as mpl
import matplotlib.pyplot as plt
from matplotlib import patheffects as pe
import geopandas as gpd
import shapely
import argparse
import traceback
import cmocean

reeds_path = Path(__file__).resolve().parents[3]
sys.path.append(str(reeds_path))
import reeds
from reeds import plots
from postprocessing import input_plots

plots.plotparams()

savepath = reeds_path / 'docs' / 'source' / 'figs' / 'docs'
savepath.mkdir(parents=True, exist_ok=True)

def saveit(savename, fig=None):
outpath = savepath / (savename.lower().replace(' ', '-') + '.png')
(fig or plt.gcf()).savefig(outpath, bbox_inches='tight')
print(outpath)


def add_capacity_total(ax, df, fontsize=14):
ax.annotate(
f'{df.capacity.sum() / 1e6:.0f} TW', (0.08, 0.10),
xycoords='axes fraction', ha='left', va='bottom', fontsize=fontsize, zorder=1e8,
)


def plot_docs():
for tech in ['upv', 'wind-ons', 'wind-ofs']:
print(f"plotting supply curve for {tech}")

fig, axs = plt.subplots(
2, 2, figsize=(13, 10.5),
gridspec_kw={'hspace': 0.08, 'wspace': 0.04},
)
panels = [
('open', 'capacity', 'Open access'),
('reference', 'capacity', 'Reference access'),
('limited', 'capacity', 'Limited access'),
('open', 'cf', ''),
]

for ax, (access, column, title) in zip(axs.flat, panels):
f, ax, df, col = next(input_plots.map_supplycurves(
access=access,
tech=tech,
cols_out=column,
draw_stats=False,
title=title,
title_fontsize=16,
title_fontweight='bold',
cbar_ticklabel_fontsize=12,
cbar_title_fontsize=14,
cbar_labelpad=2.6,
f=fig,
ax=ax,
))
if col == 'capacity':
add_capacity_total(ax, df)
if col == 'cf':
capacity_factor_cbar_ax = fig.axes[-2]

capacity_factor_ax = axs[1, 1]
separator = {'color': '0.55', 'lw': 1.5, 'clip_on': False, 'zorder': 1e9}
capacity_factor_ax.plot([0, 1], [1, 1], transform=capacity_factor_ax.transAxes, **separator)
capacity_factor_position = capacity_factor_ax.get_position()
fig.canvas.draw()
caption_bottom = min(
text.get_window_extent(fig.canvas.get_renderer())
.transformed(fig.transFigure.inverted()).y0
for text in capacity_factor_cbar_ax.texts
)
fig.add_artist(mpl.lines.Line2D(
[capacity_factor_position.x0, capacity_factor_position.x0],
[caption_bottom - 0.005, capacity_factor_position.y1],
transform=fig.transFigure, **separator,
))

saveit(f"supplycurve {tech}", fig=fig)
plt.close(fig)


def plot_presentation():
access_cases = ['open', 'reference', 'limited']
technologies = {
'upv': 'Utility-scale PV',
'wind-ons': 'Land-based wind',
}
row_labels = ['Open\naccess', 'Reference\naccess', 'Limited\naccess']

fig = plt.figure(figsize=(6.5, 5.6))
grid = fig.add_gridspec(
4, 2, height_ratios=[1, 1, 1, 0.06],
left=0.16, right=0.98, bottom=0.09, top=0.94,
hspace=0.08, wspace=0.06,
)
map_axes = np.empty((len(access_cases), len(technologies)), dtype=object)

for col, (tech, tech_label) in enumerate(technologies.items()):
colorbar_mappable = None
for row, (access, row_label) in enumerate(zip(access_cases, row_labels)):
ax = fig.add_subplot(grid[row, col])
map_axes[row, col] = ax
f, ax, df, _ = next(input_plots.map_supplycurves(
access=access,
tech=tech,
cols_out='capacity',
draw_colorbar=False,
draw_stats=False,
f=fig,
ax=ax,
))
add_capacity_total(ax, df, fontsize=12)
if row == 0:
ax.set_title(tech_label, fontsize=12, fontweight='bold', pad=2)
if col == 0:
ax.annotate(
row_label, (-0.08, 0.5), xycoords='axes fraction',
ha='right', va='center', fontsize=10, clip_on=False,
)
colorbar_mappable = ax.collections[-1]

colorbar_ax = fig.add_subplot(grid[-1, col])
colorbar = fig.colorbar(colorbar_mappable, cax=colorbar_ax, orientation='horizontal')
colorbar.ax.xaxis.set_major_formatter(
mpl.ticker.FuncFormatter(lambda value, _: f'{value / 1e3:g}')
)
colorbar.ax.tick_params(labelsize=9, pad=1)
colorbar.set_label('Capacity [GW]', fontsize=10, fontweight='bold', labelpad=2)

saveit('supplycurve-capacity-summary', fig=fig)
plt.close(fig)


def main():
parser = argparse.ArgumentParser(description='Generate supply-curve availability maps.')
parser.add_argument(
'--mode', choices=['docs', 'presentation'], default='docs',
help='Plot layout to generate.',
)
args = parser.parse_args()

try:
if args.mode == 'docs':
plot_docs()
else:
plot_presentation()
except Exception:
print(traceback.format_exc())
raise


if __name__ == '__main__':
main()
113 changes: 113 additions & 0 deletions hourlize/copy_rev_folders.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
"""
Copy reV supply curve source folders to the shared ReEDS supply curve directory.

Source: original_rev_folder (from rev_paths.csv)
Destination: /kfs2/shared-projects/reeds/Supply_Curve_Data/{sc_path}/reV/{rev_case}

Usage:
python copy_rev_folders.py
python copy_rev_folders.py --tech wind-ons upv
python copy_rev_folders.py --sc_path ONSHORE/2025_08_31_NewSites
python copy_rev_folders.py --overwrite # skip overwrite prompts
"""
import argparse
import os
import subprocess
import time

import pandas as pd


DEST_BASE = '/kfs2/shared-projects/reeds/Supply_Curve_Data'
DEFAULT_CSV = os.path.join(
os.path.dirname(os.path.abspath(__file__)),
'..', 'inputs', 'supply_curve', 'rev_paths.csv',
)


def main(rev_paths_csv, techs=None, sc_paths=None, overwrite=False):
t0 = time.perf_counter()
# load rev paths file
df = pd.read_csv(rev_paths_csv)

# subset runs as specified
if techs:
df = df.loc[df['tech'].isin(techs)]
if sc_paths:
df = df.loc[df['sc_path'].isin(sc_paths)]
if df.empty:
print('No rows match the given filters.')
return

# iterate over rows to run
for _, row in df.iterrows():
src = row['original_rev_folder']
label = f"{row['tech']} / {row['access_case']}"

if pd.isna(src) or str(src).strip() == '' or str(src) == 'none':
print(f'[SKIP] {label}: original_rev_folder not specified.')
continue

src = str(src).strip()
if not os.path.isabs(src):
print(f'[SKIP] {label}: source path is not absolute: {src!r}')
continue

if not os.path.exists(src):
print(f'[SKIP] {label}: source does not exist: {src}')
continue

dst = os.path.join(DEST_BASE, row['sc_path'], 'reV', row['rev_case'])

print(f'\n[{label}]')
print(f' src: {src}')
print(f' dst: {dst}')

# check if folder exists and follow overwrite procedure
if os.path.exists(dst):
if overwrite:
print(' Destination exists — overwriting (--overwrite).')
else:
answer = input(' Destination already exists. Overwrite? [y/N] ').strip().lower()
if answer != 'y':
print(' Skipped.')
continue

# copy folder using rysnc
os.makedirs(dst, exist_ok=True)
t0_row = time.perf_counter()
print(' Copying...')
subprocess.run(
['rsync', '-a', '--progress', src.rstrip('/') + '/', dst],
check=True,
)
elapsed_row = time.perf_counter() - t0_row
print(f' Done. ({elapsed_row:.1f}s)')

elapsed = time.perf_counter() - t0
print(f'\nTotal time: {elapsed:.1f}s')


if __name__ == '__main__':

# command line arguments
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
'--csv', default=DEFAULT_CSV,
help='Path to rev_paths.csv (default: inputs/supply_curve/rev_paths.csv)',
)
parser.add_argument(
'--techs', '-t', nargs='+', metavar='TECH',
help='Filter by one or more techs. E.g. --tech wind-ons upv',
)
parser.add_argument(
'--sc_paths', nargs='+', metavar='SC_PATH',
help='Filter by one or more sc_paths. E.g. --sc_path ONSHORE/2025_08_31_NewSites',
)
parser.add_argument(
'--overwrite', '-o', action='store_true',
help='Force overwrite of existing destinations without prompting',
)
args = parser.parse_args()

main(args.csv, args.techs, args.sc_paths, args.overwrite)
46 changes: 0 additions & 46 deletions hourlize/inputs/configs/cases.json

This file was deleted.

6 changes: 0 additions & 6 deletions hourlize/inputs/configs/cases_egs.json

This file was deleted.

Loading
Loading