Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
2c2aaaa
Breakout simulation code
genevievestarke Mar 6, 2026
d6ee5f0
Remove duplicate code
genevievestarke Mar 7, 2026
14be9dd
Fix testing
genevievestarke Mar 9, 2026
8db8332
Update doc strings and docs
genevievestarke Mar 9, 2026
673dad9
Merge branch 'develop' into cleanup_pyomo_controllers
johnjasa Mar 9, 2026
d79ee6a
Merge branch 'develop' into cleanup_pyomo_controllers
johnjasa Mar 9, 2026
4ecac61
added docstring for heuristic controller config
elenya-grant Mar 9, 2026
e7c9604
Merge branch 'cleanup_pyomo_controllers' of github.com:genevievestark…
elenya-grant Mar 9, 2026
a58c135
fixed issue that I made with the HeuristicLoadFollowingControllerConfig
elenya-grant Mar 9, 2026
1d0b0d3
Merge branch 'develop' into cleanup_pyomo_controllers
johnjasa Mar 11, 2026
81743ec
Update docs/control/pyomo_controllers.md
genevievestarke Mar 11, 2026
c7817fa
Make initialize_parameters() functions the same for both controllers
genevievestarke Mar 11, 2026
dd7f078
Apply suggestion from @jaredthomas68
genevievestarke Mar 11, 2026
39643da
Fix doc string format
genevievestarke Mar 11, 2026
172bced
Merge branch 'develop' into cleanup_pyomo_controllers
johnjasa Mar 11, 2026
f04fae4
Merge branch 'develop' into cleanup_pyomo_controllers
jaredthomas68 Mar 11, 2026
821bf69
Merge branch 'develop' into cleanup_pyomo_controllers
jaredthomas68 Mar 11, 2026
9658808
Merge branch 'develop' into cleanup_pyomo_controllers
johnjasa Mar 12, 2026
e1c226e
Initial changes for enabling grid charging for load following
genevievestarke Mar 13, 2026
7ffe919
Merge branch 'develop' into feature/pyomo_grid_charging
genevievestarke Mar 17, 2026
136277d
Merge branch 'develop' into feature/pyomo_grid_charging
genevievestarke Mar 20, 2026
b657a44
Updates to parameter names and logic
genevievestarke Mar 20, 2026
c6b51a7
Update grid inputs with openmdao structure
genevievestarke Mar 23, 2026
21810aa
Update constraints
genevievestarke Mar 25, 2026
c4af280
Update constraints
genevievestarke Mar 25, 2026
496e4bb
merged in develop
elenya-grant Mar 25, 2026
b75f63f
Merge branch 'develop' into feature/pyomo_grid_charging
johnjasa Mar 26, 2026
6d5b517
Add tests for commodity buying
genevievestarke Mar 27, 2026
d24e7ac
Merge branch 'develop' into feature/pyomo_grid_charging
johnjasa Mar 30, 2026
d78036a
Update doc strings
genevievestarke Mar 30, 2026
65f8383
Update pyomo rules for min operating cost
genevievestarke Mar 30, 2026
369eca9
Address PR comments
genevievestarke Mar 30, 2026
f56aaf8
Address PR comments
genevievestarke Mar 30, 2026
2152fd0
Fix mentions of grid
genevievestarke Mar 30, 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
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,51 @@
# Create an H2Integrate model
model = H2IntegrateModel("pyomo_optimized_dispatch.yaml")

# --- Parameters ---
amplitude = 0.9 # Amplitude of the sine wave
frequency = 0.05 # Frequency of the sine wave in Hz
duration = 8760.0 # Duration of the signal in seconds
sampling_rate = 1 # Number of samples per second (Fs)

# Noise parameters
noise_mean = 0.0
noise_std_dev = 0.1 # Standard deviation controls the noise intensity

# --- Generate the Time Vector ---
# Create a time array from 0 to duration with a specific sampling rate
t = np.linspace(1.0, duration, int(sampling_rate * duration), endpoint=True)

# --- Generate the Pure Sine Wave Signal ---
# Formula: y(t) = A * sin(2 * pi * f * t)
pure_signal = amplitude * np.sin(2.0 * np.pi * frequency * t)

# --- Generate the Random Gaussian Noise ---
# Create noise with the same shape as the time vector
rng = np.random.default_rng()
noise = rng.normal(loc=noise_mean, scale=noise_std_dev, size=t.shape)

# --- Create the Noisy Signal ---
noisy_signal = (pure_signal + noise) * 0.04 + 0.04 * np.ones(len(t))

commodity_met_value_profile = np.ones(8760) * 1
commodity_buy_price_profile = noisy_signal

demand_profile = np.ones(8760) * 100.0


# TODO: Update with demand module once it is developed
model.setup()
model.prob.set_val("battery.electricity_demand", demand_profile, units="MW")
# model.prob.set_val("battery.demand_met_value", commodity_met_value_profile, units="USD/kW")
model.prob.set_val("battery.electricity_buy_price", commodity_buy_price_profile, units="USD/kW")

# Run the model
model.run()

model.post_process()

# Plot the results
fig, ax = plt.subplots(2, 1, sharex=True, figsize=(8, 6))
fig, ax = plt.subplots(3, 1, sharex=True, figsize=(8, 6))

start_hour = 0
end_hour = 200
Expand Down Expand Up @@ -61,10 +92,12 @@
)
ax[1].plot(
range(start_hour, end_hour),
model.prob.get_val("battery.battery_electricity", units="MW")[start_hour:end_hour],
model.prob.get_val("battery.battery_electricity_out", units="MW")[start_hour:end_hour],
linestyle="-.",
label="Battery Electricity Out (MW)",
)
print(min(model.prob.get_val("battery.electricity_out", units="MW")))
print(min(model.prob.get_val("battery.battery_electricity_out", units="MW")))
ax[1].plot(
range(start_hour, end_hour),
demand_profile[start_hour:end_hour],
Expand All @@ -73,7 +106,14 @@
)
ax[1].set_ylim([-1e2, 2.5e2])
ax[1].set_ylabel("Electricity Hourly (MW)")
ax[1].set_xlabel("Timestep (hr)")

ax[2].plot(
range(start_hour, end_hour),
model.prob.get_val("battery.electricity_buy_price", units="USD/MW")[start_hour:end_hour],
label="Grid Purchase Price ($/MW)",
)
ax[2].set_ylabel("Grid Purchase Price ($/MW)")
ax[2].set_xlabel("Timestep (hr)")

plt.legend(ncol=2, frameon=False)
plt.tight_layout()
Expand Down
7 changes: 5 additions & 2 deletions examples/30_pyomo_optimized_dispatch/tech_config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,11 @@ technologies:
opex_fraction: 0.25 # 0.25% of capex per year from 2024 ATB
control_parameters:
cost_per_charge: 0.03 # in $/kW, cost to charge the storage (note that charging is incentivized)
cost_per_discharge: 0.05 # in $/kW, cost to discharge the storage
commodity_met_value: 0.1 # in $/kW, penalty for not meeting the desired load demand
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I like this name change!

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Agreed!

cost_per_discharge: 0.07 # in $/kW, cost to discharge the storage
demand_met_value: 0.1 # in $/kW, penalty for not meeting the desired load demand
cost_per_production: 0.0 # in $/kW, cost to use the incoming produced commodity (i.e. electricity from wind)
time_weighting_factor: 0.995 # This parameter discounts each subsequent time step incrementally in the future in the horizon window by this amount
n_control_window: 24 # in timesteps (currently hours), The length of time that the control is applied to in the rolling window optimization
commodity_import_limit: 207500 # rating of the wind farm
allow_commodity_buying: true
commodity_buy_price: 0.4
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,12 @@ def initialize_parameters(self, inputs: dict, dispatch_inputs: dict):
Args:
inputs (dict):
Dictionary of numpy arrays (length = self.n_timesteps) containing at least:
f"{commodity}_in" : Available generated commodity profile.
f"{commodity}_demand" : Demanded commodity output profile.
f"{commodity}_in" : Available generated commodity profile.
f"{commodity}_demand" : Demanded commodity output profile.
"demand_met_value" : Variable weight for meeting the load
if allow_commodity_buying:
f"{commodity}_buy_price" : Variable cost of buying commodity for charging
(e.g. could be a grid price)
dispatch_inputs (dict): Dictionary of the dispatch input parameters from config

"""
Expand Down Expand Up @@ -183,18 +187,26 @@ def _create_constraints(self, pyomo_model: pyo.ConcreteModel):

# Update time series parameters for next optimization window
def update_time_series_parameters(
self, commodity_in: list, commodity_demand: list, updated_initial_soc: float
self,
time_update_inputs: dict,
updated_initial_soc: float,
):
"""Updates the pyomo optimization problem with parameters that change with time

Args:
commodity_in (list): List of generated commodity in for this time slice.
commodity_demand (list): The demanded commodity for this time slice.
demand_met_value (list): List of variable value of meeting the provided load
updated_initial_soc (float): The updated initial state of charge for storage
technologies for the current time slice.
if allow_commodity_buying:
commodity_buy_price (list): List of variable cost of buying commodity for charging
(e.g. could be a grid price)
"""
self.time_duration = [1.0] * len(self.blocks.index_set())
self.available_production = [commodity_in[t] for t in self.blocks.index_set()]
self.available_production = [
time_update_inputs[f"{self.commodity_name}_in"][t] for t in self.blocks.index_set()
]

# Objective functions
def min_operating_cost_objective(self, hybrid_blocks, tech_name: str):
Expand Down
8 changes: 2 additions & 6 deletions h2integrate/control/control_rules/plant_dispatch_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,9 +189,7 @@ def arc_rule(m, t):

pyo.TransformationFactory("network.expand_arcs").apply_to(self.model)

def update_time_series_parameters(
self, commodity_in=list, commodity_demand=list, updated_initial_soc=float
):
def update_time_series_parameters(self, time_update_inputs=dict, updated_initial_soc=float):
"""
Updates the pyomo optimization problem with parameters that change with time

Expand All @@ -206,9 +204,7 @@ def update_time_series_parameters(
for tech in self.source_techs:
name = tech + "_rule"
pyomo_block = self.tech_dispatch_models.__getattribute__(name)
pyomo_block.update_time_series_parameters(
commodity_in, commodity_demand, updated_initial_soc
)
pyomo_block.update_time_series_parameters(time_update_inputs, updated_initial_soc)

def create_min_operating_cost_expression(self):
"""
Expand Down
Loading
Loading