Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
173 commits
Select commit Hold shift + click to select a range
0abe06f
added test to highlight error
elenya-grant Apr 9, 2026
f65d00f
fixed bug of charging with unavailable commodity
elenya-grant Apr 9, 2026
255b7c3
added subtests for charging less than available
elenya-grant Apr 9, 2026
ade5a20
added test for technology naming and updated logic for using_feedback…
elenya-grant Apr 9, 2026
27ca0a9
Merge remote-tracking branch 'h2i_upstream/develop' into dispatch/sto…
elenya-grant Apr 9, 2026
85eda16
Merge remote-tracking branch 'h2i_upstream/develop' into dispatch/sto…
elenya-grant Apr 13, 2026
696ffe6
Merge branch 'develop' into dispatch/storage_fixes
johnjasa Apr 14, 2026
aacc334
merged in develop
elenya-grant Apr 28, 2026
a2fe782
other fixes from merge
elenya-grant Apr 28, 2026
3d0e272
Add system_level_control to the CI
johnjasa Apr 28, 2026
d13d25a
added control classifiers to key technology models
elenya-grant Apr 28, 2026
c51d33d
added curtailable component
elenya-grant Apr 28, 2026
0e9dd0b
added logic in h2imodel to add curtailment component to curtailment c…
elenya-grant Apr 28, 2026
0cedd82
Working through start of system level controller
johnjasa Apr 28, 2026
6a51fe1
Merge pull request #713 from elenya-grant/draft_control_setup
johnjasa Apr 29, 2026
0aff134
Added initial system level control example
johnjasa Apr 29, 2026
9ff6fae
Fixed SLC example
johnjasa Apr 29, 2026
08f8790
Reordering SLC settings
johnjasa Apr 29, 2026
d84566f
Merge branch 'develop' of https://github.com/NREL/H2Integrate into sy…
johnjasa Apr 29, 2026
733c636
Updating the SLC example
johnjasa Apr 29, 2026
d91e8ae
Added curtailment
johnjasa Apr 29, 2026
fc6bd5f
WIP: adding battery example for SLC
johnjasa Apr 29, 2026
efd3093
Improving plotting for battery SLC example
johnjasa Apr 29, 2026
59f9b16
Moved combiner and changed battery model
johnjasa Apr 29, 2026
0fdfde2
Changed example so wind power is sometimes curtailed after charging b…
johnjasa Apr 29, 2026
2e428b3
updated plotting script
johnjasa Apr 29, 2026
237df21
added marker to curtailment component test
elenya-grant Apr 29, 2026
cb9d053
added super basic SLC tests to make sure examples dont break
elenya-grant Apr 29, 2026
1ef9743
fixed example tests
elenya-grant Apr 29, 2026
748be32
Minor name clarifications within SLC
johnjasa Apr 29, 2026
f8294c6
Adding a notion of price-considering SLCs
johnjasa Apr 29, 2026
3bcbc2f
WIP: working on marginal costs for SLC
johnjasa Apr 29, 2026
6dff236
Updating SLC example to be simpler
johnjasa Apr 29, 2026
c2123f8
Removing prior SLC file
johnjasa Apr 30, 2026
9df4fd7
Consolidating methods for SLC
johnjasa Apr 30, 2026
9567318
Moving SLC strategy definitions
johnjasa Apr 30, 2026
8910d36
moved solver options to a config class
elenya-grant May 1, 2026
553ed20
minor fixes
elenya-grant May 1, 2026
5ca4d15
Merging
johnjasa May 4, 2026
2a741ba
Multi-commodity systems for SLC (#717)
elenya-grant May 4, 2026
8b6f2c1
added framework to interface with storage controllers and example
elenya-grant May 5, 2026
23a251a
fix so curtailment only applied if using SLC
elenya-grant May 5, 2026
b271026
made slc_config an input option
elenya-grant May 5, 2026
c4d95bb
actually make it so slc get option of slc conig and fixed bug in appl…
elenya-grant May 5, 2026
8de0ff1
added check for slc in apply_curtailment() method
elenya-grant May 5, 2026
7a4752a
updated so slc_config is an input option and added control config cla…
elenya-grant May 5, 2026
5881e07
Adding marginal_cost calcs
johnjasa May 5, 2026
3cf8cd5
Merging
johnjasa May 5, 2026
d084f69
replaced lists of tech control classifiers with the dictionary
elenya-grant May 5, 2026
6010b5c
merged in upstream
elenya-grant May 5, 2026
d520335
merging
johnjasa May 6, 2026
a11e37c
Directly connected demand profile
johnjasa May 6, 2026
db9c9c0
Shared commodity sell price as input from finance subgroups
johnjasa May 6, 2026
c101048
Merging
johnjasa May 6, 2026
887873f
Fixed bad set-point behavior
johnjasa May 6, 2026
93a69c0
Uploading complex profit maximization
johnjasa May 6, 2026
1085a68
Backmerging, all examples in folder 35 run
johnjasa May 6, 2026
e4a94bd
tried to add helper methods to slc baseclass
elenya-grant May 6, 2026
9a5cfdc
added marginal cost to cost models that dont inherit costmodelbaseconfig
elenya-grant May 6, 2026
afb0af3
updated cost model baseclass handling of marginal cost
elenya-grant May 6, 2026
a6a841d
updated so that demand is properly output from slc for storage contro…
elenya-grant May 6, 2026
f06736e
merged in upstream
elenya-grant May 6, 2026
9984c70
fixed example 33
elenya-grant May 6, 2026
18732aa
Merge branch 'system_level_control' into marginal_costs
kbrunik May 6, 2026
fb219d5
Minor typographical changes
johnjasa May 6, 2026
4ee228b
Merge branch 'system_level_control' into slc/storge_control_interfacing
johnjasa May 6, 2026
9108d5d
Merge pull request #731 from elenya-grant/slc/storge_control_interfacing
johnjasa May 6, 2026
770e6e3
Merging
johnjasa May 7, 2026
f2e023b
Added control classifiers to all technologies
johnjasa May 7, 2026
a56a57f
updated baseclass methods and demand following controller
elenya-grant May 7, 2026
0f5e5e7
updated cost control strategies and cleaned up baseclass
elenya-grant May 7, 2026
916f369
Merge pull request #732 from johnjasa/marginal_costs
johnjasa May 7, 2026
494475f
Merge pull request #735 from elenya-grant/slc/clean_up_methods
johnjasa May 7, 2026
e8b0319
updated find_converter_tech method
elenya-grant May 7, 2026
8fd6a76
started adding logic for multi-commods
elenya-grant May 7, 2026
2432793
connected feedstocks
elenya-grant May 7, 2026
e565bc3
Merging
johnjasa May 7, 2026
4bbab17
removed control classifier from demand components
elenya-grant May 7, 2026
909af6d
Merge branch 'system_level_control' of https://github.com/NREL/H2Inte…
johnjasa May 7, 2026
ae3b8d1
moved logic from compute to method in demand following
elenya-grant May 7, 2026
add6a4e
merged in upstream
elenya-grant May 7, 2026
0f7d6b9
updated find_converter_techs and get_upstream_techs_for_commodity
elenya-grant May 7, 2026
be846a0
bugfix
elenya-grant May 7, 2026
1f2e5cc
Ard control classifier
kbrunik May 7, 2026
8f77755
Updating for changes to slc_config
johnjasa May 7, 2026
aac36d4
Merging
johnjasa May 7, 2026
883c0b9
ard.
kbrunik May 7, 2026
66c655e
Merge branch 'system_level_control' of https://github.com/NREL/H2Inte…
kbrunik May 7, 2026
b6b1415
test
kbrunik May 7, 2026
f46e7c8
updated _find_feedstock_techs to use self.feedstock_comps
elenya-grant May 7, 2026
b34bb91
updated some doc strings in slc baseclass
elenya-grant May 7, 2026
0caae7a
Merging
johnjasa May 8, 2026
4fb8990
docs wip
kbrunik May 8, 2026
31711d4
Minor refactor to demand following control
johnjasa May 8, 2026
db182b5
Merge branch 'system_level_control' of https://github.com/NREL/H2Inte…
johnjasa May 8, 2026
9bae204
docs
kbrunik May 8, 2026
a50ba5a
Merge branch 'system_level_control' of https://github.com/NREL/H2Inte…
kbrunik May 8, 2026
b04ece2
docs
kbrunik May 8, 2026
66e1153
Additional refactoring of demand following control
johnjasa May 8, 2026
df47e4f
Merge branch 'system_level_control' of https://github.com/NREL/H2Inte…
johnjasa May 8, 2026
ebb9abb
Refactoring some SLC base methods
johnjasa May 8, 2026
b427be8
Added complex profit max example to test
johnjasa May 8, 2026
59b1676
updated location of cost_per_tech
elenya-grant May 8, 2026
3a20fcf
update SLC add method
johnjasa May 8, 2026
503c606
update SLC add method
johnjasa May 8, 2026
4deb91d
Refactoring some SLC base methods
johnjasa May 8, 2026
2b7fed5
Expanding SLC docstrings
johnjasa May 8, 2026
da11a52
refactored test_slc_controllers
elenya-grant May 8, 2026
92336b1
Merge remote-tracking branch 'h2i_upstream/system_level_control' into…
elenya-grant May 8, 2026
6eeac71
docs update
kbrunik May 10, 2026
9beec63
more docs
kbrunik May 11, 2026
f763302
updated no_battery example
elenya-grant May 11, 2026
12f0b11
cleaned up other slc examples
elenya-grant May 11, 2026
6af2397
removed SLC baseconfig since its unused
elenya-grant May 11, 2026
dee1925
updated docstring in baseclass
elenya-grant May 11, 2026
37be44e
docs added
kbrunik May 11, 2026
dc7bbf8
Merge branch 'system_level_control' of https://github.com/NREL/H2Inte…
kbrunik May 11, 2026
416dc5d
minor update to demand following and updated profast to handle zero cf
elenya-grant May 11, 2026
7deabec
added start of demand following docs
elenya-grant May 11, 2026
c8ecead
added draft of demand following docs
elenya-grant May 11, 2026
b6003fe
Adding to SLC docs
johnjasa May 12, 2026
2b4f835
small doc mods
kbrunik May 12, 2026
e8cf31f
minor update to doc page
elenya-grant May 12, 2026
654a008
merge ci.yml
elenya-grant May 12, 2026
0263a58
minor clarification in doc
elenya-grant May 12, 2026
85c8679
Reconfiguring the connection for varopex
johnjasa May 13, 2026
26b5e74
Merge branch 'develop' into system_level_control
johnjasa May 13, 2026
ecf4273
Shifting to five control classifiers
johnjasa May 14, 2026
4fbd901
small doc change
kbrunik May 14, 2026
4deb083
connected storage duration
elenya-grant May 14, 2026
ecec21f
Merge branch 'develop' into system_level_control
johnjasa May 19, 2026
8c968a4
added more subtests to test_slc_examples for the two single-commodity…
elenya-grant May 19, 2026
f08382c
Fixing example dir
johnjasa May 20, 2026
1d51984
fixed failing test
elenya-grant May 20, 2026
784e937
Checkpointing progress on all controllers for techs
johnjasa May 21, 2026
83577e1
Adding passthrough controller file
johnjasa May 22, 2026
8dbd7bd
Updating tests
johnjasa May 22, 2026
b2f13c0
Added comments to passthrough controller
johnjasa May 22, 2026
67c43b9
Merging
johnjasa May 22, 2026
dfe4cf4
Fixing import statements
johnjasa May 22, 2026
270b4d8
Fixing tests and examples
johnjasa May 22, 2026
4f5b911
Merge branch 'develop' into system_level_control
johnjasa Jun 1, 2026
f4b8401
Partially addressing Jared's PR feedback
johnjasa Jun 1, 2026
3286f5e
Merge branch 'system_level_control' of https://github.com/NREL/H2Inte…
johnjasa Jun 1, 2026
585c87c
Renaming per PR feedback
johnjasa Jun 1, 2026
181445a
Added notes in the docs that every tech group has a controller
johnjasa Jun 1, 2026
fc89753
Merge branch 'develop' of https://github.com/NREL/H2Integrate into HEAD
johnjasa Jun 3, 2026
28a0374
Updating docs based on PR feedback
johnjasa Jun 3, 2026
010800d
Apply suggestions from code review
johnjasa Jun 3, 2026
5c7f447
Updating adding a new tech doc page based on PR review
johnjasa Jun 3, 2026
d1da302
Merging
johnjasa Jun 3, 2026
2705b18
Merge branch 'system_level_control' of https://github.com/NREL/H2Inte…
johnjasa Jun 3, 2026
fa9ef61
Updated control classifier docs
johnjasa Jun 3, 2026
74cf077
Removing now-defunct storage_techs_to_control parameter
johnjasa Jun 3, 2026
60a9355
Updating SLC plots and docstrings
johnjasa Jun 4, 2026
0022143
Adding curtailment to more techs
johnjasa Jun 4, 2026
d38dc79
Updated SLC battery example regression value
johnjasa Jun 5, 2026
9ecca8d
Adding notes that just one demand component is allowed now
johnjasa Jun 8, 2026
8767b4a
Dramatically refactoring, renaming controller based things throughout
johnjasa Jun 9, 2026
f592e92
Added command value to electrolyzer
johnjasa Jun 9, 2026
b88db1d
Docs build correctly locally
johnjasa Jun 9, 2026
520a376
Updating tests and components for new command_value paradigm
johnjasa Jun 10, 2026
e209414
Updating tests for set-point changes; simplifying sql timeseries test
johnjasa Jun 10, 2026
38abfc2
Promoting controller parameters up a level
johnjasa Jun 10, 2026
68bc172
Reverting naming changes
johnjasa Jun 10, 2026
c4be629
Reverting final changes from naming
johnjasa Jun 10, 2026
d3133d9
doc figures
kbrunik Jun 10, 2026
de3d2f3
Dramatic renaming for control nomenclature
johnjasa Jun 11, 2026
fbe1086
Merge branch 'system_level_control' of https://github.com/NREL/H2Inte…
johnjasa Jun 11, 2026
754fca6
doc figures update
kbrunik Jun 11, 2026
74ebb58
PR feedback
kbrunik Jun 11, 2026
5a11de0
Reverting errant storage control logic
johnjasa Jun 11, 2026
34c4903
inconsequential change to trigger CI
johnjasa Jun 11, 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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## Unreleased

- **Controller signal naming overhaul**: Standardized controller input/output names across the codebase. SLC outputs are now `{tech}_{commodity}_set_point` (was `{tech}_{commodity}_demand`); technology-level controllers take `{commodity}_set_point` as input and emit `{commodity}_command_value` as output (was `{commodity}_demand`/`{commodity}_set_point`). Storage performance models in feedback mode receive `{commodity}_set_point`; in open-loop mode they receive `{commodity}_command_value`. The SLC's own input (`{commodity}_demand`) and demand component I/O remain unchanged.
- Change commodity in DRI and EAF model from pig iron to sponge iron based on likely carbon content [PR 670](https://github.com/NatLabRockies/H2Integrate/pull/670)
- Bugfix for round-trip efficiency handling when calling `check_inputs` around `StoragePerformanceModel` [PR 684](https://github.com/NatLabRockies/H2Integrate/pull/684)
- Bugfix. Include nuclear in electricity producing tech list and improve error message for zero-length electricity producing techs in model when electricity is specified as the commodity. [PR 685](https://github.com/NatLabRockies/H2Integrate/pull/685)
Expand Down
18 changes: 14 additions & 4 deletions docs/_toc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,20 @@ parts:
- file: resource/tidal_resource
- caption: Control
chapters:
- file: control/control_overview
- file: control/open-loop_controllers
- file: control/pyomo_controllers
- file: control/controller_demonstrations
- file: control/system_level_control/system_level_control
sections:
- file: control/system_level_control/system_level_control_base
- file: control/system_level_control/control_classifier
- file: control/system_level_control/controllers
sections:
- file: control/system_level_control/slc_demand_following
- file: control/system_level_control/slc_profit_max
- file: control/system_level_control/slc_cost_min
- file: control/technology_level_control/technology_control_overview
sections:
- file: control/technology_level_control/open-loop_controllers
- file: control/technology_level_control/pyomo_controllers
- file: control/technology_level_control/controller_demonstrations
- caption: Demand
chapters:
- file: demand/demand_components
Expand Down
82 changes: 82 additions & 0 deletions docs/control/system_level_control/control_classifier.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# System Level Control Technology Performance Classifiers

To enable a generic system level control framework we need to classify each technology based on how the model that is included in H2I can operate within the system.

```{note}
While in real life there are a lot of controllable parameters allowing for ramping production up or down for a particular technology (e.g., wind or solar curtailment), the model of that technology in H2I might not be capable of the same response behavior to input signals.
These classifications are for specific H2I dispatch formulations and are based on how the models in H2I are implemented, **not always** on how the actual physical subsystem might operate.
This is a useful and necessary distinction that delineates different model capabilities clearly.
```

We have identified five key classifiers that are able to represent the different behaviors that we can expect from the models. Each performance model includes a parameter setting the classifier `_control_classifier`.

Classifier | Meaning | Example Technology Models
-- | -- | --
fixed | Always produces commodity and cannot be controlled or reduced; does not receive a set-point | classical nuclear
flexible | Resource-driven; can only be *reduced* (curtailed) below the resource-determined maximum via a set-point | wind, solar
dispatchable | Can modulate production within bounds in response to a set-point | grid, electrolyzer, NG turbine
storage | Can modulate consumption/production within bounds while tracking SOC | battery, h2 storage, any storage
feedstock | Are not directly controlled, but useful for SLC to make dispatch decisions | feedstocks

To add a classifier for a particular model it would look something like this in the class:

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.

This is in case someone is adding a new model, correct? All of the control classifiers have been set for current models?

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.

This is correct, yep! I'll add a note in the adding_a_new_technology.md page to make this more clear

```{python}
_control_classifier = "flexible"
```

```{note}
**Flexible vs. dispatchable.** Both classifiers receive a `{commodity}_set_point` from the system-level controller, so the distinction is about *what the set-point can do*. A flexible model is a strictly more restricted case of a dispatchable one: the set-point can only *cap* the output below whatever the underlying resource (sun, wind, etc.) makes available. A dispatchable model, by contrast, can be ramped up or down anywhere within its operating bounds in direct response to the set-point.
```

## Fixed
A fixed performance model represents anything that always produces at its rated capacity and cannot be controlled or reduced by the system level controller. The SLC reads the output from a fixed technology and subtracts it from the demand, but does not send a set-point back to the technology. A good example of this is a classical nuclear plant model: it produces a constant output that the rest of the system must accommodate.

## Flexible
A flexible performance model represents anything whose production is determined by an external resource (e.g., wind speed, solar irradiance) and that can only be *reduced* below that resource-determined maximum and never increased above it. The system-level controller sends a `{commodity}_set_point` that acts as an upper bound: when the resource-driven output exceeds the set-point, output is curtailed down to the set-point; otherwise, output is left at the resource-driven value. A good example is the PVWatts PySAM solar plant in H2I; its performance is a function of the input solar resource, and we cannot tell the sun to shine more, but we can curtail the panel output below the available solar production.

In other words, flexible is a strictly more restricted case of [dispatchable](#dispatchable): a dispatchable model can be ramped both up and down in response to a set-point, while a flexible model can only be ramped down.

To simplify the implementation of applying this curtailment we added a method, `apply_curtailment()`, to the `PerformanceBaseClass`.

```{figure} figures/curtailable.png
:width: 70%
:align: center
```

### Apply curtailment based on set_point
Within the `compute()` method in the performance model you can apply the curtailment using the `apply_curtailment()` method.
```
self.apply_curtailment(outputs)
```
which applies curtailment to `{commodity}_out` based on `{commodity}_set_point`. This adds `uncurtailed_{commodity}_out` and `{commodity}_out` as outputs from the performance model.

(dispatchable)=
## Dispatchable
A dispatchable performance model represents anything that can be ramped both *up and down* within its operating bounds in response to a `{commodity}_set_point` from the system-level controller. Unlike a [flexible](#flexible) model, the set-point for a dispatchable model can request any production level within the model's rated capacity (and minimum load, if applicable), and the model will produce at that level. Examples include a grid connection, an electrolyzer, or a natural-gas turbine.

There aren't additional special methods to handle this because the set-point response is internal to each performance model.

```{figure} figures/dispatchable.png
:width: 70%
:align: center
```

## Storage
Storage is a unique control classifier because it assumes that within the model that energy isn't created or destroyed (minus some efficiency losses). While it's technically "dispatchable" in that it can receive and change its performance based on a set point, its handling within H2I is unique because it's attached to storage performance models, which is handled differently than converter performance models. A converter model only has positive (or zero) `{commodity}_out`, whereas a storage model can have positive or negative `{commodity}_out`.

There are two types of cases for the storage control classifier:
1. **with a storage controller**
When the storage performance model is controlled with a storage-level controller (open-loop or feedback controlled), the system-level controller outputs combined demand, that is always positive to the storage-level controller. The demand is `{commodity}_in` from the technologies upstream of the storage that output the same commodity to the storage performance model and the `remaining_demand`.
Comment thread
jaredthomas68 marked this conversation as resolved.

2. **without a storage controller**
The system-level controller outputs set points to the storage performance model which can be considered charge (negative) and discharge (positive) commands (storage-level set points) to the storage performance model, directly.


```{figure} figures/storage.png
:width: 85%
:align: center
```

## Feedstock
Feedstocks represent commodity *inputs* to the controllable system: they are consumed by other technologies but their availability is not itself something the controller can adjust. Although feedstocks themselves cannot be dispatched, knowing how much of each feedstock is available is valuable information for more advanced controllers, since feedstock supply can constrain what the controllable technologies are actually able to produce.

For example, consider an ammonia plant that consumes both hydrogen and nitrogen. If the nitrogen feedstock supply is insufficient to meet the ammonia demand, the ammonia output is capped by the nitrogen availability regardless of how much hydrogen and electricity are produced. A controller that is aware of the nitrogen feedstock can recognize that the ammonia demand cannot be met, and can adjust the set-points for the hydrogen and electricity technologies accordingly (e.g., avoiding over-production of hydrogen that would otherwise go unused). This is why feedstocks are classified separately rather than being ignored by the controller: they are uncontrollable, but they are not irrelevant.
15 changes: 15 additions & 0 deletions docs/control/system_level_control/controllers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Control Strategies
There are several simple control strategies already implemented in the SLC paradigm. While fairly simplistic, they are meant to illustrate how information can be passed from different blocks/components (converters, storage, feedstocks, demand, etc.) and models (performance, cost, finance) to use within the SLC.

The current control strategies are:
1. [Demand Following](#slc-demand-following)
2. [Profit Maximization](#slc-profit-max)
3. [Cost Minimization](#slc-cost-min)

```{note}
The strategies currently implemented are experimental and will likely require further development for specific analyses.
```

All control strategies inherit `SystemLevelControlBase`, which is a base class that has common setup logic shared by all system-level control strategies.

See additional information, which is more developer focused, about the [`SystemLevelControlBase`](#slc-base).
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
67 changes: 67 additions & 0 deletions docs/control/system_level_control/slc_cost_min.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
---
jupytext:
text_representation:
extension: .md
format_name: myst
format_version: 0.13
jupytext_version: 1.18.1
kernelspec:
display_name: Python 3.11.13 ('h2i_env')
language: python
name: python3
---

(slc-cost-min)=
# Cost Minimization System Level Controller

The cost minimization controller, `CostMinimizationControl`, meets demand at minimum variable cost using merit-order dispatch.
Unlike the {ref}`demand following controller <slc-demand-following>`, which splits demand evenly across dispatchable technologies, this controller dispatches the cheapest technologies first.
Comment thread
vijay092 marked this conversation as resolved.

## Dispatch Logic

The controller follows a three-step dispatch process:

1. **Flexible technologies** run at their available capacity (assumed zero marginal cost). Their output is subtracted from the demand.
2. **Storage technologies** absorb any surplus (charging) or provide the deficit (discharging). Residual demand is split evenly across storage technologies producing the demanded commodity.
3. **Dispatchable technologies** are dispatched by cheapest marginal cost first, each up to its rated capacity, until the remaining demand is met.

## Marginal Cost Configuration

Marginal costs are specified per dispatchable technology in the `cost_per_tech` dictionary under `system_level_control.control_parameters` in the plant config. Each entry can be:

| Value | Description |
| --- | --- |
| Numeric (e.g. `0.05`) | Constant marginal cost in `$/(commodity_amount_units)` |
| `"buy_price"` | Uses the technology's configured purchase price |
| `"VarOpEx"` | Derives marginal cost from the technology's variable operating expenditure divided by total production |
| `"feedstock"` | Sums upstream feedstock `VarOpEx` values and divides by the technology's total production |

```{note}
The dispatch order is determined by sorting dispatchable technologies by their **mean** marginal cost across all timesteps (cheapest first).
```

### Example Configuration

```yaml
system_level_control:

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'm wondering if the system level control should be called "dispatch" and the local control called "control", where actual control decisions are made. I'm not sure we won't have true control at the system level or dispatch at the tech level, so this is just a thought to gather comments from others. It would simplify naming conventions: dispatch making set point decisions, control making path decisions to achieve those set points.

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 could see that delineation being useful, primarily to be more precise when discussing system-level control vs. tech-level control. That being said, I do like how broad and inclusive "control" is as a term. @elenya-grant and @kbrunik and others, what do you think of Jared's suggested delineation here?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I like the use of system_level as a descriptor. I don't have strong feelings whether it's called system_level_dispatch or system_level_control. I like @jaredthomas68 definition of "dispatch make set point decisions" and controllers trying to achieve those set point (although I'm struggling to understand what the output(s) of the controllers would be). If we go with that definition - then do all existing storage "controllers" in H2I fall under the "dispatch" definition?

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 agree that it would clarify things to go with system_level_dispatch. In my head, the apply_curtailment method for flexible technologies is a good example of technology-level control.

control_strategy: CostMinimizationControl
control_parameters:
cost_per_tech:
natural_gas_plant: feedstock
```

## Inputs and Outputs

In addition to the standard inputs inherited from `SystemLevelControlBase`, the cost minimization controller adds marginal cost inputs based on the `cost_per_tech` configuration (see above).

The base inputs for technologies classified as `flexible`, `dispatchable`, and `storage` are:

- `f"{tech_name}_{tech_output_commodity}_out"`
- `f"{tech_name}_rated_{tech_output_commodity}_production"`
- `f"{tech_name}_{tech_output_commodity}_demand"`

## Limitations

- Greedy dispatch: The merit-order approach is greedy - it does not look ahead across timesteps to optimize total cost over the simulation horizon.
- Even splitting across storage: Residual demand is split evenly across storage technologies regardless of capacity or state of charge.
- Demand is always met: Unlike the {ref}`profit maximization controller <slc-profit-max>`, this controller always attempts to meet demand regardless of cost.
102 changes: 102 additions & 0 deletions docs/control/system_level_control/slc_demand_following.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
---
jupytext:
text_representation:
extension: .md
format_name: myst
format_version: 0.13
jupytext_version: 1.18.1
kernelspec:
display_name: Python 3.11.13 ('h2i_env')
language: python
name: python3
---

(slc-demand-following)=
# Demand Following System Level Controller

The demand following controller, `DemandFollowingControl`, aims to fully meet the demand and does not have any inputs related to cost.

The N2 diagram below shows an example system using the demand following controller with wind, natural gas, and battery storage technologies.

```{code-cell} ipython3
:tags: [remove-input]

from h2integrate.core.h2integrate_model import H2IntegrateModel
import openmdao.api as om
import os

import html
from pathlib import Path
from h2integrate import EXAMPLE_DIR
from IPython.display import HTML, display

os.chdir(EXAMPLE_DIR / "35_system_level_control/battery_with_controller/")

h2i_model = H2IntegrateModel("wind_ng_demand.yaml")
h2i_model.setup()

om.n2(
h2i_model.prob,
outfile="h2i_n2.html",
display_in_notebook=False,
show_browser=False,
)

n2_html = "h2i_n2.html"
n2_srcdoc = html.escape(Path(n2_html).read_text(encoding="utf-8"))
display(
HTML(
f'<div style="width:100%; height:600px; overflow:auto; margin:0; padding:0; border:0;">'
f'<iframe srcdoc="{n2_srcdoc}" '
'style="display:block; width:200%; height:600px; border:0; margin:0; padding:0; background:transparent;" '
'loading="lazy"></iframe>'
'</div>'
)
)
```
## Dispatch Logic

The demand is satisfied in a fixed three-step priority order, and each step's shortfall or surplus is passed to the next:

1. **Curtailable techs** run at their available capacity. Their total output is subtracted from the demand, which may drive the residual demand negative (surplus).

2. **Storage techs** receive the residual demand (which may be positive or negative). When residual demand is positive the storage is commanded to discharge; when negative it is commanded to charge. If multiple storage techs produce the demanded commodity, the residual demand is
split **evenly** across them (each receives ``demand / n_storage``).

3. **Dispatchable techs** cover any remaining positive demand after storage. The remaining demand (floored at zero) is split **evenly** across all dispatchable techs that produce the demanded commodity (each receives ``remaining_demand / n_dispatchable``).

### Example Configuration

```yaml
system_level_control:
control_strategy: DemandFollowingControl
solver_options: # solver options for resolving feedback
solver_name: gauss_seidel
max_iter: 20
convergence_tolerance: 1.0e-6
```

## Inputs and Outputs

The inputs for technologies classified as `curtailable`, `dispatchable`, and `storage` are:

- `f"{tech_name}_{tech_output_commodity}_out"`
- `f"{tech_name}_rated_{tech_output_commodity}_production"`
- `f"{tech_name}_{tech_output_commodity}_demand"`

The inputs for technologies classified as `feedstock` are:
- `f"{tech_name}_{commodity}_out"`

## Systems with Heterogeneous Commodities

The `DemandFollowingControl` controller can be used in hybrid systems where technologies produce different commodities.
For example, in a system where an electrolyzer produces hydrogen and the demand commodity is hydrogen, the controller can set the electricity-generating *curtailable* technologies' set-points to meet the hydrogen demand.

This framework provides a starting point for hybrid energy system control but is intended to be extended with more sophisticated strategies for complex multi-commodity systems.

## Limitations

- No cost awareness: The controller dispatches technologies purely to meet demand without considering operational costs, commodity prices, or economic optimization.
- Even splitting across storage: When multiple storage technologies produce the demanded commodity, the residual demand is divided evenly among them (`demand / n_storage`), regardless of differences in capacity, state of charge, or efficiency.
- Even splitting across dispatchable technologies: Similarly, any remaining demand after storage dispatch is split evenly across all dispatchable technologies (`remaining_demand / n_dispatchable`), without accounting for marginal costs or capacity constraints.
- Fixed priority order: The dispatch order (curtailable → storage → dispatchable) is fixed in the current implementation.
Comment thread
jaredthomas68 marked this conversation as resolved.
Loading
Loading