Skip to content

Converter PLM dispatch - #773

Open
jaredthomas68 wants to merge 63 commits into
NatLabRockies:developfrom
jaredthomas68:control-converter
Open

Converter PLM dispatch#773
jaredthomas68 wants to merge 63 commits into
NatLabRockies:developfrom
jaredthomas68:control-converter

Conversation

@jaredthomas68

@jaredthomas68 jaredthomas68 commented May 28, 2026

Copy link
Copy Markdown
Collaborator

Converter PLM control

This PR adds peak load management dispatch for converters. It handles two load profiles, one representing an upstream (grid) load and the other representing either a local load that must be met or a price profile. The converter will be dispatched when either of the demand profiles exceeds their respective peak limits. If a price profile is given, then dispatching will only occur when the price exceeds the provided cutoff.

This PR also provides minor updates to the fuel cell model to determine VarOpEx.

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:

  • Add tests
  • Finish example
  • Add documentation

Type of Reviewer Feedback Requested (on Draft PR)

Ready for any and all feedback. Let's get this PR polished and merged.

Structural feedback:
Looking for high-level feedback at this point from any who wish to give it.

Implementation feedback:

Other feedback:

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

Section 5: Impacted Areas of the Software

Section 5.1: New Files

  • plm_openloop_converter_controller.py
    • PeakLoadManagementHeuristicOpenLoopConverterControllerConfig: New config plm converter control
    • PeakLoadManagementHeuristicOpenLoopConverterController.setup
    • PeakLoadManagementHeuristicOpenLoopConverterController.compute: Computes a per-timestep {commodity}_command_value that peak-shaves using primary/upstream cutoffs, clipped to instantaneous demand and rated production.
  • init.py
    • Exports the new PeakLoadManagementHeuristicOpenLoopConverterController
  • run_peak_load_management.py, 33_plm_converter_heuristic.yaml, tech_config.yaml, plant_config.yaml, driver_config.yaml
    • New converter example that dispatches a fuel cell against local and upstream demand profiles and plots the peak-shaving result.

Section 5.2: Modified Files

  • h2integrate_model.py

    • _process_model: Adds a NonlinearBlockGS/DirectSolver to the tech group when a controller sets _reads_performance_outputs, so the controller/performance data cycle converges instead of using stale defaults.
  • h2_fuel_cell.py

    • LinearH2FuelCellPerformanceConfig/compute: Adds uptime_hours_until_eol and computes a replacement_schedule from cumulative "on" hours for use by the finance model.
    • H2FuelCellCostConfig/setup/compute: Adds variable_opex_per_kwh and an annual_electricity_produced input to output VarOpEx.
  • test_h2_fuel_cell.py

    • Adds subtests for the replacement schedule (integer/non-integer/multiple-per-year cases) and variable OpEx, and converts the cost test to a unit test.
  • test_all_examples.py

    • Repoints the storage test to 33_peak_load_management/plm_storage and adds test_plm_converter_heuristic_example covering the new converter example.
  • model_baseclasses.py

    • CostModelBaseClass: Removed a stray blank line (whitespace only).
  • .pre-commit-config.yaml: adjusted excluded files for demand_profile.yaml rename

  • Renamed demand profile files (.../demand_profiles/demand_profile.yaml, demand_profile_upstream.yaml)

    • Files relocated under the reorganized example folders; contents unchanged (rename only).

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

@jaredthomas68
jaredthomas68 requested a review from johnjasa May 28, 2026 21:03
@jaredthomas68 jaredthomas68 mentioned this pull request May 29, 2026
51 tasks
@jaredthomas68
jaredthomas68 requested a review from kbrunik June 1, 2026 22:01

@kbrunik kbrunik left a comment

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.

Looking pretty good! Remarkable how much simpler the code is when you don't have to track state of charge! I know there was already some planned work that still needs to happen, so happy to re-review at that point

A few high-level notes for the PR:

  1. Be sure to update the changelog
  2. Add some basic tests to make sure the dispatch is occurring as expected (i.e., the tech is turns off after peak is "managed")
  3. Write a doc page for the converter PLM

Comment thread library/demand_profiles/demand_profile.yaml
Comment thread library/demand_profiles/demand_profile_upstream.yaml
Comment thread examples/xx_peak_load_management_converter/driver_config.yaml Outdated
control_parameters:
commodity: electricity
commodity_rate_units: kW
system_capacity_rate: 1000.0

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.

Remove from control section, example didn't run with it included.

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 removed system_capacity_rates, I think the others should stay there.

Comment thread examples/xx_peak_load_management_converter/run_peak_load_management.py Outdated
Comment thread examples/xx_peak_load_management_converter/tech_config.yaml Outdated

"""

system_capacity_kw: int | float = field()

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.

@elenya-grant and @johnjasa, I was discussing with Jared about how to make this control strategy tech agnostic. Right now it's hardcoded to have system_capacity_kw in the config. I'm wondering if y'all have ideas of what the best path forward is for this to work with all dispatchable converters.

We have access to the standardized commodity_rate_units but there's no standardization across how we define system capacity, which makes this challenging.

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.

well system capacity is a standard output of performance models (rated_{commodity}_production). I get confused on the order that models are added, but if the performance model is created in the tech subgroup before the controller, then the performance model would output the rated_{commodity}_production and that could then be an input to the PLM converter controller.

This is a good question though. I think the best thing (if possible) would be to have rated_{commodity}_production as an openmdao input.

Also - I don't think the capacity input in the config or the openmdao inputs should be standardized for a few reasons:

  1. In the config, it seems more acceptable to include units in config parameters to make it more clear to users what units that parameter should be in. The units are not included in OpenMDAO input names.
  2. Some technologies do not have a single capacity-related input. Some technologies have an input that would instead be a multiplier on some number of units (like number of electrolyzer clusters and cluster capacity OR number of wind turbines and wind turbine capacity).

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.

@elenya-grant, thank you for the idea. I successfully implemented the switch to rated_{commodity}_production to make the model technology agnositc. It does require a non-linear solver to resolve the output of the performance model into an input of the controller, which takes 3 iterations to converge. This should be fine as long as the performance model in question is fast-running. I also like how it opens up a possible path for more realistic control interactions between the performance and tech control models. We should probably discuss this more, somewhat related to #711 in that this solution adds a feedback loop in the tech model between the performance and tech control.

jaredthomas68 and others added 13 commits June 9, 2026 13:15
* varopex

* refurb schedule

* update fuel cell schedule calc to allow for non-integer multiples of eol and timesteps

* extend liftime for schedule test

* Update h2integrate/converters/hydrogen/h2_fuel_cell.py

---------

Co-authored-by: Jared Thomas <jaredthomas68@gmail.com>
Co-authored-by: Jared Thomas <jaredthomas68@users.noreply.github.com>
demand_profile_upstream_peak_cutoff (int | float | None): Threshold applied to
``demand_profile_upstream``. Units depend on
``demand_profile_upstream_kind``.
demand_profile_upstream_kind (str): Interpretation mode for

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.

For my own edification, you would have to use the demand_profile_upstream if you wanted to dispatch based on price only? and if that was the case would the demand profile basically be a dummy profile? An example of this case might be helpful to show the functionality or more explanation of this in the docs with perhaps an example tech config.

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.

@jaredthomas68 did you make any changes to this PR based on this question? Do we need to specify demand_profile_upstream even doing a price only dispatch?

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 actually think it is necessary to show an example in the docs of an example tech config of how to use price dispatch only, I think that will be a primary use case for this dispatch strategy and it would be helpful to show them how to set the demand_profile so that they aren't erroneously dispatching the tech unrelated to the price signal.

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'll add an example. You populate the demand profile upstream with price when in price mode. I can see that being confusing.

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.

Example added for price mode, and corresponding tests

@jaredthomas68
jaredthomas68 requested review from johnjasa and removed request for johnjasa August 11, 2026 19:27
@jaredthomas68
jaredthomas68 requested review from johnjasa and removed request for johnjasa August 12, 2026 17:02
@johnjasa
johnjasa marked this pull request as draft August 12, 2026 18:54
@johnjasa
johnjasa marked this pull request as ready for review August 12, 2026 18:54
@jaredthomas68
jaredthomas68 requested review from johnjasa and removed request for johnjasa August 14, 2026 22:17
@jaredthomas68
jaredthomas68 requested review from johnjasa and removed request for johnjasa August 14, 2026 23:54
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.

5 participants