Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
1b9f88d
renamed outputs in converter openloop controllers
elenya-grant Mar 26, 2026
2b3d0c1
updated tests and examples with updated naming
elenya-grant Mar 26, 2026
2af6329
Merge remote-tracking branch 'h2i_upstream/develop' into dispatch/con…
elenya-grant Mar 27, 2026
c59a20e
Merge branch 'develop' into dispatch/converter_ol_sync
kbrunik Mar 31, 2026
eab6d09
added performance model outputs to the converter control strategies
elenya-grant Apr 1, 2026
1e54ed2
udpated plant configs in test_openloop_controllers.py
elenya-grant Apr 1, 2026
1852db4
Merge branch 'develop' into dispatch/converter_ol_sync
johnjasa Apr 1, 2026
b1b62c9
moved converter control strategies to separate file
elenya-grant Apr 1, 2026
a97292c
Merge branch 'dispatch/converter_ol_sync' of github.com:elenya-grant/…
elenya-grant Apr 6, 2026
2d6b0c5
moved converter control strategies to demand folder
elenya-grant Apr 6, 2026
0e6b3e8
updated import paths for demand components
elenya-grant Apr 6, 2026
2986dff
removed commodity_set_point as output
elenya-grant Apr 6, 2026
1f71c9d
Merge remote-tracking branch 'h2i_upstream/develop' into dispatch/con…
elenya-grant Apr 7, 2026
9672ecc
renamed demand files
elenya-grant Apr 7, 2026
7ad7b59
renamed demand classes and configs
elenya-grant Apr 7, 2026
9ace0ad
Merge branch 'develop' into dispatch/converter_ol_sync
kbrunik Apr 8, 2026
8444cdf
renamed demand component to be performance model instead of control s…
elenya-grant Apr 8, 2026
2093b81
moved output calculations to shared method in baseclass
elenya-grant Apr 8, 2026
1510bd2
Merge branch 'dispatch/converter_ol_sync' of github.com:elenya-grant/…
elenya-grant Apr 8, 2026
d116ad8
removed the word controller from docstrings
elenya-grant Apr 8, 2026
7c5b9e5
draft update to docs
elenya-grant Apr 8, 2026
f8a55fb
updated demand docs
elenya-grant Apr 10, 2026
1fb531a
updated changelog
elenya-grant Apr 10, 2026
d95950d
merged changelog
elenya-grant Apr 10, 2026
8b46552
Merge branch 'develop' into dispatch/converter_ol_sync
johnjasa Apr 10, 2026
8397cb0
removed commented out code and other doc updates
elenya-grant Apr 10, 2026
18ed019
Merge remote-tracking branch 'h2i_upstream/develop' into dispatch/con…
elenya-grant Apr 10, 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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@
data between functions in a module. [PR 590](https://github.com/NatLabRockies/H2Integrate/pull/590)
- Adds `H2IntegrateModel.state` as an `IntEnum` to handle setup and run status checks.
[PR 590](https://github.com/NatLabRockies/H2Integrate/pull/590)
- Reclassified open-loop converter control strategies as demand components and updated output naming convention to align with output naming convention in storage performance models [PR 631](https://github.com/NatLabRockies/H2Integrate/pull/631).
- The `FlexibleDemandOpenLoopConverterController` has been renamed to `FlexibleDemandComponent`
- The `DemandOpenLoopConverterController` has been renamed to `GenericDemandComponent`
- Modified CI setup so Windows is temporarily disabled and also so unit, regression, and integration tests are run in separate jobs to speed up testing and provide more information on test failures. [PR 668](https://github.com/NatLabRockies/H2Integrate/pull/668)

## 0.7.2 [April 9, 2026]
Expand Down
3 changes: 3 additions & 0 deletions docs/_toc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ parts:
- file: control/open-loop_controllers
- file: control/pyomo_controllers
- file: control/controller_demonstrations
- caption: Demand
chapters:
- file: demand/demand_components
- caption: Finance Models
chapters:
- file: finance_models/financial_analyses
Expand Down
3 changes: 1 addition & 2 deletions docs/control/control_overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ The first approach, [open-loop control](#open-loop-control), assumes no feedback
Supported controllers:
- [`SimpleStorageOpenLoopController`](#pass-through-controller)
- [`DemandOpenLoopStorageController`](#demand-open-loop-storage-controller)
- [`DemandOpenLoopConverterController`](#demand-open-loop-converter-controller)
- [`FlexibleDemandOpenLoopConverterController`](#flexible-demand-open-loop-converter-controller)



(pyomo-control-framework)=
Expand Down
90 changes: 0 additions & 90 deletions docs/control/open-loop_controllers.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,93 +26,3 @@ An example of an N2 diagram for a system using the open-loop control framework f
For examples of how to use the `DemandOpenLoopStorageController` open-loop control framework, see the following:
- `examples/14_wind_hydrogen_dispatch/`
- `examples/19_simple_dispatch/`

## Open-Loop Converter Controllers

Open-loop converter controllers define rule-based logic for meeting commodity demand profiles without using dynamic system feedback. These controllers operate independently at each timestep.

This page documents two core controller types:
1. Demand Open-Loop Converter Controller — meets a fixed demand profile.
2. Flexible Demand Open-Loop Converter Controller — adjusts demand up or down within flexible bounds.

(demand-open-loop-converter-controller)=
### Demand Open-Loop Converter Controller
The `DemandOpenLoopConverterController` allocates commodity input to meet a defined demand profile. It does not contain energy storage logic, only **instantaneous** matching of supply and demand.

The controller computes each value per timestep:
- Unmet demand (non-zero when supply < demand, otherwise 0.)
- Unused commodity (non-zero when supply > demand, otherwise 0.)
- Delivered output (commodity supplied to demand sink)

This provides a simple baseline for understanding supply–demand balance before adding complex controls.

#### Configuration
The controller is defined within the `tech_config` and requires these inputs.

| Field | Type | Description |
| ----------------- | -------------- | ------------------------------------- |
| `commodity_name` | `str` | Commodity name (e.g., `"hydrogen"`). |
| `commodity_units` | `str` | Units (e.g., `"kg/h"`). |
| `demand_profile` | scalar or list | Timeseries demand or constant demand. |

```yaml
control_strategy:
model: DemandOpenLoopConverterController
model_inputs:
control_parameters:
commodity_name: hydrogen
commodity_units: kg/h
demand_profile: [10, 10, 12, 15, 14]
```
For an example of how to use the `DemandOpenLoopConverterController` open-loop control framework, see the following:
- `examples/23_solar_wind_ng_demand`

(flexible-demand-open-loop-converter-controller)=
### Flexible Demand Open-Loop Converter Controller
The `FlexibleDemandOpenLoopConverterController` extends the fixed-demand controller by allowing the actual demand to flex up or down within defined bounds. This is useful for demand-side management scenarios where:
- Processes can defer demand (e.g., flexible industrial loads)
- The system requires demand elasticity without dynamic optimization

The controller computes:
- Flexible demand (clamped within allowable ranges)
- Unmet flexible demand
- Unused commodity
- Delivered output

Everything remains open-loop no storage, no intertemporal coupling.

For an example of how to use the `FlexibleDemandOpenLoopConverterController` open-loop control framework, see the following:
- `examples/23_solar_wind_ng_demand`

The flexible demand component takes an input commodity production profile, the maximum demand profile, and various constraints (listed below), and creates a "flexible demand profile" that follows the original input commodity production profile while satisfying varying constraint.
Please see the figure below for an example of how the flexible demand profile can vary from the original demand profile based on the input commodity production profile and the ramp rates.
The axes are unlabeled to allow for generalization to any commodity and unit type.

| ![Flexible Demand Example](figures/flex_demand_fig.png) |
|-|


#### Configuration
The flexible demand controller is defined within the `tech_config` with the following parameters:

| Field | Type | Description |
| ------------------- | -------------- | -------------------------------------------- |
| `commodity_name` | `str` | Commodity name. |
| `commodity_units` | `str` | Units for all values. |
| `demand_profile` | scalar or list | Default (nominal) demand profile. |
| `turndown_ratio` | float | Minimum fraction of baseline demand allowed. |
| `ramp_down_rate_fraction` | float | Maximum ramp-down rate per timestep expressed as a fraction of baseline demand. |
| `ramp_up_rate_fraction` | float | Maximum ramp-up rate per timestep expressed as a fraction of baseline demand. |
| `min_utilization` | float | Minimum total fraction of baseline demand that must be met over the entire simulation. |

```yaml
model_inputs:
control_parameters:
commodity_name: hydrogen
commodity_units: kg/h
demand_profile: [10, 12, 10, 8]
turndown_ratio: 0.1
ramp_down_rate_fraction: 0.5
ramp_up_rate_fraction: 0.5
min_utilization: 0
```
90 changes: 90 additions & 0 deletions docs/demand/demand_components.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# Demand components

- [`GenericDemandComponent`](#generic-demand-component)
- [`FlexibleDemandComponent`](#flexible-demand-component)

Demand components define rule-based logic for meeting commodity demand profiles without using dynamic system feedback. These components operate independently at each timestep.

This page documents two core demand types:
1. Generic Demand Component — meets a fixed demand profile.
2. Flexible Demand Component — adjusts demand up or down within flexible bounds.

(generic-demand-component)=
### Generic Demand Component
The `GenericDemandComponent` allocates commodity input to meet a defined demand profile. It does not contain energy storage logic, only **instantaneous** matching of supply and demand.

The demand component computes each value per timestep:
- Unmet demand (non-zero when supply < demand, otherwise 0.)
- Unused commodity (non-zero when supply > demand, otherwise 0.)
- Delivered output (commodity supplied to demand sink)

This provides a simple baseline for understanding supply–demand balance.

#### Configuration
The demand is defined within the `tech_config` and requires these inputs.

| Field | Type | Description |
| ----------------- | -------------- | ------------------------------------- |
| `commodity_name` | `str` | Commodity name (e.g., `"hydrogen"`). |
| `commodity_units` | `str` | Units (e.g., `"kg/h"`). |
| `demand_profile` | scalar or list | Timeseries demand or constant demand. |

```yaml
performance_model:
model: GenericDemandComponent
model_inputs:
performance_parameters:
commodity_name: hydrogen
commodity_units: kg/h
demand_profile: [10, 10, 12, 15, 14]
```
For an example of how to use the `GenericDemandComponent` framework, see the following:
- `examples/23_solar_wind_ng_demand`

(flexible-demand-component)=
### Flexible Demand Component
The `FlexibleDemandComponent` extends the generic demand component by allowing the actual demand to flex up or down within defined bounds. This is useful for demand-side management scenarios where:
- Processes can defer demand (e.g., flexible industrial loads)
- The system requires demand elasticity without dynamic optimization

The component computes:
- Flexible demand (clamped within allowable ranges)
- Unmet flexible demand
- Unused commodity
- Delivered output

For an example of how to use the `FlexibleDemandComponent` demand component, see the following:
- `examples/23_solar_wind_ng_demand`

The flexible demand component takes an input commodity production profile, the maximum demand profile, and various constraints (listed below), and creates a "flexible demand profile" that follows the original input commodity production profile while satisfying varying constraint.
Please see the figure below for an example of how the flexible demand profile can vary from the original demand profile based on the input commodity production profile and the ramp rates.
The axes are unlabeled to allow for generalization to any commodity and unit type.

| ![Flexible Demand Example](figures/flex_demand_fig.png) |
|-|


#### Configuration
The flexible demand component is defined within the `tech_config` with the following parameters:

| Field | Type | Description |
| ------------------- | -------------- | -------------------------------------------- |
| `commodity_name` | `str` | Commodity name. |
| `commodity_units` | `str` | Units for all values. |
| `demand_profile` | scalar or list | Default (nominal) demand profile. |
| `turndown_ratio` | float | Minimum fraction of baseline demand allowed. |
| `ramp_down_rate_fraction` | float | Maximum ramp-down rate per timestep expressed as a fraction of baseline demand. |
| `ramp_up_rate_fraction` | float | Maximum ramp-up rate per timestep expressed as a fraction of baseline demand. |
| `min_utilization` | float | Minimum total fraction of baseline demand that must be met over the entire simulation. |

```yaml
model_inputs:
performance_parameters:
commodity_name: hydrogen
commodity_units: kg/h
demand_profile: [10, 12, 10, 8]
turndown_ratio: 0.1
ramp_down_rate_fraction: 0.5
ramp_up_rate_fraction: 0.5
min_utilization: 0
```
9 changes: 6 additions & 3 deletions docs/user_guide/model_overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ Below summarizes the available performance, cost, and financial models for each
- [Storage Models](#storage-models)
- [Basic Operations](#basic-operations)
- [Control Models](#control-models)
- [DemandModels](#demand-models)

(resource-models)=
## Resource models
Expand Down Expand Up @@ -284,8 +285,10 @@ Below summarizes the available performance, cost, and financial models for each
- `'SimpleStorageOpenLoopController'`: open-loop control; manages resource flow based on demand and input commodity
- `'DemandOpenLoopStorageController'`: open-loop control; manages resource flow based on demand and storage constraints
- `'HeuristicLoadFollowingController'`: open-loop control that works on a time window basis to set dispatch commands; uses Pyomo
- Converter Controllers:
- `'DemandOpenLoopConverterController'`: open-loop control; manages resource flow based on demand constraints
- `'FlexibleDemandOpenLoopConverterController'`: open-loop control; manages resource flow based on demand and flexibility constraints
- Optimized Dispatch:
- `'OptimizedDispatchController'`: optimization-based dispatch using Pyomo

(demand-models)=
## Demand Models
- `'GenericDemandComponent'`: manages resource flow based on demand constraints
- `'FlexibleDemandComponent'`: manages resource flow based on demand and flexibility constraints
4 changes: 2 additions & 2 deletions examples/19_simple_dispatch/run_wind_battery.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,14 @@
)
ax[1].plot(
range(start_hour, end_hour),
model.prob.get_val("battery.electricity_unused_commodity", units="MW")[start_hour:end_hour],
model.prob.get_val("battery.unused_electricity_out", units="MW")[start_hour:end_hour],
linestyle=":",
label="Unused Electricity commodity (MW)",
linewidth=2,
)
ax[1].plot(
range(start_hour, end_hour),
model.prob.get_val("battery.electricity_unmet_demand", units="MW")[start_hour:end_hour],
model.prob.get_val("battery.unmet_electricity_demand_out", units="MW")[start_hour:end_hour],
linestyle=":",
label="Electricity Unmet Demand (MW)",
linewidth=2,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,10 +91,10 @@ technologies:
variable_opex_per_mwh: 0.0 # $/MWh
cost_year: 2023
electrical_load_demand:
control_strategy:
model: FlexibleDemandOpenLoopConverterController
performance_model:
model: FlexibleDemandComponent
model_inputs:
control_parameters:
performance_parameters:
commodity: electricity
commodity_rate_units: kW
rated_demand: 100000
Expand Down
2 changes: 1 addition & 1 deletion examples/23_solar_wind_ng_demand/plant_config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ technology_interconnections:
# connect NG feedstock to NG plant
- [combiner, electrical_load_demand, [electricity_out, electricity_in]]
# subtract wind and solar from demand
- [electrical_load_demand, natural_gas_plant, [electricity_unmet_demand, electricity_demand]]
- [electrical_load_demand, natural_gas_plant, [unmet_electricity_demand_out, electricity_demand]]
# give remaining load demand to natural gas plant
- [combiner, fin_combiner, electricity, cable]
- [natural_gas_plant, fin_combiner, electricity, cable]
Expand Down
6 changes: 3 additions & 3 deletions examples/23_solar_wind_ng_demand/tech_config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -91,10 +91,10 @@ technologies:
variable_opex_per_mwh: 0.0 # $/MWh
cost_year: 2023
electrical_load_demand:
control_strategy:
model: DemandOpenLoopConverterController
performance_model:
model: GenericDemandComponent
model_inputs:
control_parameters:
performance_parameters:
commodity: electricity
commodity_rate_units: kW
demand_profile: 100000 # 100 MW
Expand Down
12 changes: 6 additions & 6 deletions examples/24_solar_battery_grid/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@ The grid performance model handles:
- `electricity_in`: Power flowing INTO the grid (selling to grid) - limited by interconnection size
- `electricity_out`: Power flowing OUT OF the grid (buying from grid) - limited by interconnection size
- `electricity_sold`: Actual electricity sold (up to interconnection limit)
- `electricity_unmet_demand`: Demand that couldn't be met due to interconnection limit
- `electricity_excess`: Electricity that couldn't be sold due to interconnection limit
- `unmet_electricity_demand_out`: Demand that couldn't be met due to interconnection limit
- `unused_electricity_out`: Electricity that couldn't be sold due to interconnection limit

**Cost Model:**
- CapEx: Based on interconnection size ($/kW) plus fixed costs
Expand All @@ -72,16 +72,16 @@ Solar → Battery → [Grid Buy (purchases) | Grid Sell (sales)]

- Solar generates electricity
- Battery stores excess and follows 100 MW demand profile
- Grid Buy purchases electricity when battery cannot meet demand (via `electricity_unmet_demand` → `electricity_demand` connection)
- Grid Sell accepts excess electricity when battery has surplus (via `electricity_unused_commodity` → `electricity_in` connection)
- Grid Buy purchases electricity when battery cannot meet demand (via `unmet_electricity_demand_out` → `electricity_demand` connection)
- Grid Sell accepts excess electricity when battery has surplus (via `unused_electricity_out` → `electricity_in` connection)

### Technology Interconnections

```yaml
technology_interconnections: [
["solar", "battery", "electricity", "cable"],
["battery", "grid_buy", ["electricity_unmet_demand", "electricity_demand"]],
["battery", "grid_sell", ["electricity_unused_commodity", "electricity_in"]]
["battery", "grid_buy", ["unmet_electricity_demand_out", "electricity_demand"]],
["battery", "grid_sell", ["unused_electricity_out", "electricity_in"]]
]
```

Expand Down
Loading
Loading