Skip to content

Headroom/reserve rate connections#755

Draft
cfrontin wants to merge 6 commits into
NatLabRockies:developfrom
cfrontin:headroom_reserves
Draft

Headroom/reserve rate connections#755
cfrontin wants to merge 6 commits into
NatLabRockies:developfrom
cfrontin:headroom_reserves

Conversation

@cfrontin
Copy link
Copy Markdown
Collaborator

@cfrontin cfrontin commented May 18, 2026

Headroom/reserve accounting for energy generation and storage

For certain types of analyses, there is value in redundant power generation, where power that could be supplied at short notice but not currently online is valuable if not required.
One major place this becomes very important is for powering large loads like datacenters off the grid, where grid service reliability levels have to be recreated with on-site power.
This PR is an attempt to add "headroom" power outputs, which represent offline power (or other rate flow) capacity that could be used to provide redundancy, peak absorption, etc.

Headroom is different for different technologies: for a grid interconnect, say, it's simply the difference between the rated power output and the current power output.
For a natural gas plant, it's the power in excess of the current output that is limited by either the natural gas supply or the rated output of the capital equipment, whichever is less.

For storage it's a bit more complicated. The internal headroom is:

  • no less than zero
  • no more than the discharge rating
  • no more than the current available SOC divided by the storage dt

But then, this internal headroom must have the battery output subtracted (recall: discharge positive, charge negative), since, on one hand, active discharge counts against the capacity & rating that is offline-but-available and, on the other, active charge is excess power generation that could be diverted to power delivery, and therefore represents a form of headroom provided in some sense indirectly by the battery above and beyond its discharge capability.

Section 1: Type of Contribution

  • Feature Enhancement
    • Framework
    • New Model
    • Updated Model
    • Tools/Utilities
    • Other (please describe):
  • Bug Fix
  • Documentation Update
  • CI Changes
  • Other (please describe):

Section 2: Draft PR Checklist

  • Open draft PR
  • Describe the feature that will be added
  • Fill out TODO list steps
  • Describe requested feedback from reviewers on draft PR
  • Complete Section 7: New Model Checklist (if applicable)

TODO:

  • clean up pre-commit formatting feedback

Type of Reviewer Feedback Requested (on Draft PR)

Structural feedback:

  • noted above, battery charge flows are accounted as a battery-induced headroom, but there's probably cases where charging flows are not necessarily delivered through to overall system output loads when not used for charging; is there a better handling for this?

Implementation feedback:

  • where should these features ultimately live in terms of the abstraction spectrum?
    • I made these changes at the level of the technologies, at the lowest level of specialization rather than the base classes
    • I didn't want to go too far up the abstraction chain, and when I explored doing so there were some ambiguities about how to define things (e.g. _out vs. _sold suffixes in grid aren't necessarily generalizable in higher levels of abstraction
  • I use headroom to describe available but offline rate output from different technologies: is this the right terminology?
    • I find this useful but it's inspired largely by music amplification engineering (how much can i turn this amp up before it reaches rated power somewhere in the signal chain and distorts the signal) and may have a more appropriate energy and power nomenclature
    • it was the preferred terminology in the storage model before I made any changes; see this line

Other feedback:
N/A

Section 3: General PR Checklist

  • PR description thoroughly describes the new feature, bug fix, etc.
  • Added tests for new functionality or bug fixes
  • Tests pass (If not, and this is expected, please elaborate in the Section 6: Test Results)
  • Documentation
    • Docstrings are up-to-date
    • Related docs/ files are up-to-date, or added when necessary
    • Documentation has been rebuilt successfully
    • Examples have been updated (if applicable)
  • CHANGELOG.md
    • At least one complete sentence has been provided to describe the changes made in this PR
    • After the above, a hyperlink has been provided to the PR using the following format:
      "A complete thought. [PR XYZ]((https://github.com/NatLabRockies/H2Integrate/pull/XYZ)", where
      XYZ should be replaced with the actual number.

Section 4: Related Issues

N/A.

Section 5: Impacted Areas of the Software

Section 5.1: New Files

  • path/to/file.extension
    • method1: What and why something was changed in one sentence or less.

Section 5.2: Modified Files

  • path/to/file.extension
    • method1: What and why something was changed in one sentence or less.

Section 6: Additional Supporting Information

Section 7: Test Results, if applicable

Section 8 (Optional): New Model Checklist

  • Model Structure:
    • Follows established naming conventions outlined in docs/developer_guide/coding_guidelines.md
    • Used attrs class to define the Config to load in attributes for the model
      • If applicable: inherit from BaseConfig or CostModelBaseConfig
    • Added: initialize() method, setup() method, compute() method
      • If applicable: inherit from CostModelBaseClass
  • Integration: Model has been properly integrated into H2Integrate
    • Added to supported_models.py
    • If a new commodity_type is added, update create_financial_model in h2integrate_model.py
  • Tests: Unit tests have been added for the new model
    • Pytest-style unit tests
    • Unit tests are in a "test" folder within the folder a new model was added to
    • If applicable add integration tests
  • Example: If applicable, a working example demonstrating the new model has been created
    • Input file comments
    • Run file comments
    • Example has been tested and runs successfully in test_all_examples.py
  • Documentation:
    • Write docstrings using the Google style
    • Model added to the main models list in docs/user_guide/model_overview.md
      • Model documentation page added to the appropriate docs/ section
      • <model_name>.md is added to the _toc.yml
    • Run generate_class_hierarchy.py to update the class hierarchy diagram in docs/developer_guide/class_structure.md

@elenya-grant elenya-grant self-requested a review May 19, 2026 15:29
)

self.add_output(
"electricity_headroom_sold",
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 think this naming convention is a little confusing. the current name makes it sound like this is electricity that has already been sold. Maybe electricity_headroom_to_sell or something would make sense?

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 think _to_sell makes sense

Copy link
Copy Markdown
Collaborator

@jaredthomas68 jaredthomas68 left a comment

Choose a reason for hiding this comment

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

I think this is fine and could come in with a few changes

np.testing.assert_array_almost_equal(actual_in, electricity_in)

# The headroom should be the difference between electricity_in and the rating
headroom = prob.get_val("grid.electricity_headroom_sold")
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 think headroom is a good name. Could refer to https://docs.nrel.gov/docs/fy19osti/73590.pdf for discussion of the different forms headroom might take.

)

self.add_output(
"electricity_headroom_sold",
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 think _to_sell makes sense

val=0.0,
shape=n_timesteps,
units="kW",
units=self.commodity_rate_units,
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.

The units of electricity_sold needs to be kW because it is interconnected with sell price in the cost model and comes from the same input in the config. That said, you could add the unit conversion in the cost model to make it work.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants