Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
91f1223
Remove period length from ramping constraints since ramp rates are di…
esrawli Apr 16, 2026
22047dd
Replace periodLength parameters with new indexed parameters
esrawli Apr 16, 2026
e670b24
Run black
esrawli Apr 16, 2026
98b19d7
Rename PeriodLength parameters in each stage to avoid confusion when …
esrawli Apr 16, 2026
44cfd15
Update period length parameters names in test and run black
esrawli Apr 16, 2026
46afec6
Improve period structure handling to allow integer inputs for number …
esrawli Apr 16, 2026
c27a645
Remove argument from class since it not needed anymore
esrawli Apr 16, 2026
13ec972
Remove len_reps since it is no longer needed or used
esrawli Apr 16, 2026
bfa512b
Merge branch 'IDAES:main' into indexing-period-lengths
esrawli Apr 22, 2026
47b397a
Add new file "utils" that includes functions to generate the period s…
esrawli Apr 29, 2026
807f490
Improve period structure in class to read number of periods and their…
esrawli Apr 29, 2026
cd0bcbf
Merge remote-tracking branch 'github-idaesgtep-esrawli/main' into ind…
esrawli Apr 29, 2026
e894155
Update test to call scalars from data file and not as an argument in …
esrawli Apr 29, 2026
e7877a1
Move new arguments from expansion class to data class and update driv…
esrawli Apr 29, 2026
0b0baf0
Add consistency check for the duration of dispatch periods and update…
esrawli Apr 29, 2026
570b10d
Add test for dispatch duration consistency check
esrawli Apr 29, 2026
1db3276
Add consistency check for sum of commitment duration and update test …
esrawli Apr 30, 2026
730ba25
Add new duration for representative period to avoid touching consiste…
esrawli Apr 30, 2026
7017099
Add header
esrawli Apr 30, 2026
2e6632f
Use Pyomo’s TempfileManager to create a temporary directory and .json…
esrawli May 1, 2026
34a7a29
Add functions that expand the period structure to a dictionary and th…
esrawli May 1, 2026
069625f
remove duration for commitment and dispatch as an argument in the cla…
esrawli May 1, 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
18 changes: 10 additions & 8 deletions gtep/driver_esr.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,16 @@

# Add data
data_path = "./data/5bus"
data_object = ExpansionPlanningData()
data_object = ExpansionPlanningData(
stages=2,
num_reps=2,
num_commit=6,
num_dispatch=4,
duration_representative_period=6,
save_period_structure_file=False,
period_structure_json_file=None,
# period_structure_json_file="period_structure_from_gtep.json",
)
data_object.load_prescient(data_path)

# [ESR WIP: Add bus and cost data files to be used on the
Expand Down Expand Up @@ -52,15 +61,8 @@

# Populate and create GTEP model
mod_object = ExpansionPlanningModel(
stages=2,
data=data_object,
cost_data=data_processing_object,
num_reps=2,
len_reps=1,
num_commit=6,
num_dispatch=4,
# [ESR: in min by default, for now]
duration_dispatch=15,
)

mod_object.config["include_investment"] = True
Expand Down
26 changes: 18 additions & 8 deletions gtep/gtep_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,23 +35,33 @@ def __init__(
len_reps=1,
num_commit=24,
num_dispatch=1,
duration_dispatch=60,
duration_representative_period=24,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Should this br 24 * units.hour?

save_period_structure_file=False,
period_structure_json_file=None,
):
"""Initialize generation & expansion planning data object.

:param stages: integer number of investment periods
:param num_reps: integer number of representative periods per investment period
:param len_reps: (for now integer) length of each representative period (in hours)
:param num_commit: integer number of commitment periods per representative period
:param num_dispatch: integer number of dispatch periods per commitment period
:param duration_dispatch: (for now integer) duration of each dispatch period (in minutes)
:param: stages: integer number of investment periods
:param: num_reps: integer number of representative periods per investment period
:param: num_commit: integer number of commitment periods per representative period
:param: num_dispatch: integer number of dispatch periods per commitment period
:param: duration_representative_period: duration of each representative period
(in hours)
:param: save_period_structure_file: (optional) If True, saves the generated
period structure as a JSON file in the data directory. Default is False.
:param: period_structure_json_file: (optional) Path to a JSON file in the data
directory specifying the period structure. Overrides scalar/list arguments
if provided. Default is None.

"""
self.stages = stages
self.num_reps = num_reps
self.len_reps = len_reps
self.num_commit = num_commit
self.num_dispatch = num_dispatch
self.duration_dispatch = duration_dispatch
self.duration_representative_period = duration_representative_period
self.save_period_structure_file = save_period_structure_file
self.period_structure_json_file = period_structure_json_file

def load_prescient(
self,
Expand Down
155 changes: 121 additions & 34 deletions gtep/gtep_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import json
import numpy as np
import re
import os

import pyomo.environ as pyo
from pyomo.environ import units as u
Expand All @@ -52,6 +53,13 @@
import gtep.model_library.storage as stor
import gtep.model_library.transmission as transm

from gtep.utils import (
_set_period_structure_dict,
check_period_structure_consistency,
)

curr_dir = os.path.dirname(os.path.abspath(__file__))
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This is fragile (and does not appear to be used). Remove?


# Define what a USD is for pyomo units purposes. This will be set to a
# base year and we will do NPV calculations based on automatic Pyomo
# unit transformations.
Expand Down Expand Up @@ -99,17 +107,64 @@ def __init__(
:return: Pyomo model for full GTEP
"""

self.config = _get_model_config()
self.timer = TicTocTimer()

self.stages = data.stages
self.formulation = formulation
self.data = data
self.cost_data = cost_data
self.num_reps = data.num_reps
self.len_reps = data.len_reps
self.num_commit = data.num_commit
self.num_dispatch = data.num_dispatch
self.duration_dispatch = data.duration_dispatch
self.config = _get_model_config()
self.timer = TicTocTimer()
self.save_period_structure_file = data.save_period_structure_file
self.period_structure_json_file = data.period_structure_json_file

# Calculate commitment and dispatch period durations using
# provided scalars for the duration of representative period
# and number of commitment and dispatch periods. Note: We are
# assuming that all periods within their parent are of equal
# length.
duration_commitment = data.duration_representative_period / data.num_commit
duration_dispatch = pyo.value(
pyo.units.convert(
(duration_commitment / data.num_dispatch) * u.hours,
to_units=u.minutes,
)
)

# Generate the period structure dictionary from provided
# scalars, or load it from a .json file if specified. In
# either case, the period structure is returned as a
# dictionary.
period_dict = _set_period_structure_dict(
data.num_reps,
data.num_commit,
data.num_dispatch,
data.duration_representative_period,
duration_commitment,
duration_dispatch,
self.save_period_structure_file,
self.period_structure_json_file,
)

# Assign period structure attributes from the dictionary
self.num_reps = period_dict.get("number_representative", data.num_reps)
self.num_commit = period_dict["number_commitment"]
self.num_dispatch = period_dict["number_dispatch"]
self.duration_representative_period = period_dict[
"duration_representative_period"
]
self.duration_commitment = period_dict["duration_commitment"]
self.duration_dispatch = period_dict["duration_dispatch"]

# Run a consistency check on commitment and dispatch
# durations.
check_period_structure_consistency(
self.num_reps,
self.num_commit,
self.num_dispatch,
self.duration_representative_period,
self.duration_commitment,
self.duration_dispatch,
)

_add_common_configs(self.config)
_add_investment_configs(self.config)
Expand Down Expand Up @@ -151,15 +206,18 @@ def create_model(self):
m, self.stages, rep_per=[i for i in range(1, self.num_reps + 1)]
)

comps.add_model_parameters(
comps.add_model_parameters(m)

create_stages(
m,
self.stages,
self.num_commit,
self.num_dispatch,
self.duration_representative_period,
self.duration_commitment,
self.duration_dispatch,
)

create_stages(m, self.stages)

obj_comp.create_objective_function(m)

self.model = m
Expand Down Expand Up @@ -202,8 +260,24 @@ def report_large_coefficients(self, outfile, magnitude_cutoff=1e5):
with open(outfile, "w") as fil:
json.dump(really_bad_var_coef_list, fil)

return (
num_commit_dict,
num_dispatch_dict,
duration_rep_dict,
duration_commit_dict,
duration_dispatch_dict,
)


def create_stages(m, stages):
def create_stages(
m,
stages,
num_commit,
num_dispatch,
duration_representative_period,
duration_commitment,
duration_dispatch,
):
"""This method constructs the block structure for the Generation
and Transmission Expansion Planning (GTEP) model. It creates
investment, representative period, and commitment blocks for each
Expand Down Expand Up @@ -247,6 +321,13 @@ def create_stages(m, stages):
# representative_period.
for representative_period in b_inv.representativePeriods:
b_rep = b_inv.representativePeriod[representative_period]

b_rep.representativePeriodLength = pyo.Param(
initialize=duration_representative_period[representative_period],
within=pyo.PositiveReals,
units=u.hr,
)

b_rep.representative_date = m.data.representative_dates[
representative_period - 1
]
Expand All @@ -258,28 +339,33 @@ def create_stages(m, stages):
# b_rep.day = int(broken_date[2])
b_rep.currentPeriod = representative_period

# [ESR NOTE: Include commitment blocks regardless of the
# value of include_commitment. When include_commitment is
# False, generators are assumed to be always online, and
# Include commitment blocks regardless of the value of
# include_commitment. When False, generators are on and
# operational costs are determined solely by dispatch
# decisions. No redispatch for now.]
# if m.config["include_commitment"] or m.config["include_redispatch"]:
b_rep.commitmentPeriods = pyo.RangeSet(
m.numCommitmentPeriods[representative_period]
)
# decisions.
n_commit = num_commit[representative_period]
b_rep.commitmentPeriods = pyo.RangeSet(n_commit)
b_rep.commitmentPeriod = pyo.Block(b_rep.commitmentPeriods)

# --.--.--.--.--.--.----.--.--.--.--.--.----.--.--.--.--.--.--
# Add commitment Block and all its equations and
# constraints
for commitment_period in b_rep.commitmentPeriods:
b_comm = b_rep.commitmentPeriod[commitment_period]

b_comm.commitmentPeriodLength = pyo.Param(
initialize=duration_commitment[representative_period][
commitment_period
],
within=pyo.PositiveReals,
units=u.hr,
)

b_comm.commitmentPeriod = commitment_period

if m.config["include_redispatch"]:
b_comm.dispatchPeriods = pyo.RangeSet(
m.numDispatchPeriods[b_rep.currentPeriod]
)
n_dispatch = num_dispatch[representative_period][commitment_period]
b_comm.dispatchPeriods = pyo.RangeSet(n_dispatch)
b_comm.dispatchPeriod = pyo.Block(b_comm.dispatchPeriods)

# [TODO: update properties for this time period!]
Expand All @@ -299,21 +385,22 @@ def create_stages(m, stages):

# [TODO: This feels REALLY inelegant and
# bad. Check a better way of declaring these.]
for period in b_comm.dispatchPeriods:
b_comm.dispatchPeriod[period].periodLength = pyo.Param(
initialize=1,
for dispatch_period in b_comm.dispatchPeriods:
b_disp = b_comm.dispatchPeriod[dispatch_period]
b_disp.dispatchPeriodLength = pyo.Param(
initialize=duration_dispatch[representative_period][
commitment_period
][dispatch_period],
within=pyo.PositiveReals,
# units=u.minutes,
units=u.minutes,
)

disp.add_dispatch_variables(
b_comm.dispatchPeriod[period],
period,
m.dispatchPeriodLength,
)
disp.add_dispatch_constraints(
b_comm.dispatchPeriod[period], period
b_disp,
dispatch_period,
b_disp.dispatchPeriodLength,
)
disp.add_dispatch_constraints(b_disp, dispatch_period)

# =.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.

Expand All @@ -338,8 +425,8 @@ def create_stages(m, stages):
rep_period.add_representative_period_variables(b_rep, representative_period)

if m.config["include_commitment"]:
# These logical constraint ensure the state
# disjuncts stay consistent.
# These logical constraints ensure the state disjuncts
# stay consistent.
rep_period.add_representative_period_logical_constraints(
b_rep, representative_period
)
Expand Down
1 change: 0 additions & 1 deletion gtep/gtep_solution.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@ def load_from_model(self, gtep_model):
self.formulation = gtep_model.formulation # None (???)
self.data = gtep_model.data # ModelData object
self.num_reps = gtep_model.num_reps # int
self.len_reps = gtep_model.len_reps # int
self.num_commit = gtep_model.num_commit # int
self.num_dispatch = gtep_model.num_dispatch # int

Expand Down
7 changes: 3 additions & 4 deletions gtep/model_library/commitment.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,6 @@ def add_commitment_parameters(b, commitment_period, investmentStage):

m = b.model()

b.commitmentPeriodLength = pyo.Param(
within=pyo.PositiveReals, default=1, units=u.hr
)
b.carbonTax = pyo.Param(default=0)

# [ESR: Corrected to be in the commitment block "b", not in main
Expand Down Expand Up @@ -174,7 +171,9 @@ def renewable_curtailment_cost(b):
for com_per in b.representativePeriod[rep_per].commitmentPeriods:
renewableCurtailmentRep += (
m.weights[rep_per]
* m.commitmentPeriodLength
* b.representativePeriod[rep_per]
.commitmentPeriod[com_per]
.commitmentPeriodLength
* b.representativePeriod[rep_per]
.commitmentPeriod[com_per]
.renewableCurtailmentCommitment # in MW
Expand Down
29 changes: 1 addition & 28 deletions gtep/model_library/components.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ def add_model_sets(m, stages, rep_per=["a", "b"], com_per=2, dis_per=2):
)


def add_model_parameters(m, num_commit, num_dispatch, duration_dispatch):
def add_model_parameters(m):
"""Creates and labels all the parameters in the GTEP model. This
method ties input data directly to the model.

Expand All @@ -118,33 +118,6 @@ def add_model_parameters(m, num_commit, num_dispatch, duration_dispatch):
# configuration arg and not hardcoded values.]
m.years = [2025, 2030, 2035]

# Add parameters related to the representative periods for the
# different stages
m.representativePeriodLength = pyo.Param(
m.representativePeriods, within=pyo.PositiveReals, default=24, units=u.hr
)
m.numCommitmentPeriods = pyo.Param(
m.representativePeriods,
within=pyo.PositiveIntegers,
default=2,
initialize=num_commit,
)
m.numDispatchPeriods = pyo.Param(
m.representativePeriods,
within=pyo.PositiveIntegers,
default=2,
initialize=num_dispatch,
)
m.commitmentPeriodLength = pyo.Param(
within=pyo.PositiveReals, default=1, units=u.hr
)

# [TODO: Index by dispatch period? Certainly index by
# commitment period.]
m.dispatchPeriodLength = pyo.Param(
within=pyo.PositiveReals, initialize=duration_dispatch, units=u.minutes
)

# Add power-related parameters
m.thermalCapacity = pyo.Param(
m.thermalGenerators,
Expand Down
Loading
Loading