Skip to content

Conversation

@genevievestarke
Copy link
Collaborator

@genevievestarke genevievestarke commented Dec 9, 2025

Bring Pyomo load following dispatch capabilities into H2I

This feature would enable dispatch optimization using one storage technology and multiple generation technologies (using the combiner) to determine optimal storage operation to follow a load while minimizing costs. This feature adds the optimization problem formulation, framework, and solver for the optimization in Pyomo. To do this, a hybrid_rule.py file was added to aggregate the storage and production variables to create one problem for the optimizer. The added files are

  • OptimizedDispatchController class in pyomo_controllers.py
  • The hybrid_rule.py file: Aggregates optimization pieces from the various components and formulates the model for the optimization. This file collects the objective function, initializes model parameters, and updates time series parameters.
  • The generic_converter_opt.py file: Houses the variables, parameters, constraints, and objective function for the generator technology in the optimization model.
  • The pyomo_storage_rule_min_operating_cost.py file: houses the storage and system variables, parameters, constraints, and objective function. Ideally the system parameters would be in a separate file, perhaps, but have been combined for efficiency in the first implementation.
  • The controller_opt_problem_state.py file: Formulates the optimization model for the solver

The below is an example of how and where the dispatch is run using a battery for electricity storage:
image

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:

  • Get general feedback on framework of added pyomo pieces
  • Apply any framework feedback and update code so that heuristic dispatch works as expected
  • Update example to accurately describe run
  • Update doc strings in methods
  • Write tests for new pyomo optimization
  • Make docs page?
  • Maybe: address where dispatch inputs should live for generator technologies
  • Maybe: make an example with multiple generation technologies, using the combiner to connect to dispatch

Type of Reviewer Feedback Requested (on Draft PR)

Structural feedback:
This dispatch model differs significantly from the heuristic block formulation in H2I. The dispatch for the generation + storage system is aggregated in hybrid_rule.py, which pulls from generic_converter_opt.py and pyomo_storage_rule_min_operating_cost.py, all three of which are classes, and thus outside of the OpenMDAO input/output framework. This was implemented because the previous dispatch_rule framework was difficult to aggregate in the way that was needed to formulate the optimization problem. I would like feedback on this structure, and whether we could make it more OpenMDAO-friendly.

Implementation feedback:

Other feedback:
The inputs to the dispatch are all defined in the storage technology config, including the cost_per_production term, which describes the cost of the production technologies. I think this should be defined under the production technologies in the config, so any feedback on how to pull this into the battery dispatch would be appreciated!

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 has been updated to describe the changes made in this PR

Section 3: Related Issues

This resolves #386

Section 4: Impacted Areas of the Software

Section 4.1: New Files

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

Section 4.2: Modified Files

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

Section 5: Additional Supporting Information

Section 6: Test Results, if applicable

Section 7 (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

jaredthomas68 and others added 15 commits October 2, 2025 09:16
             {build-extensions,convert,download-extensions,help,install-extras,model-viewer,run,solve,test-solvers}
             ...

This is the main driver for the Pyomo optimization software.

options:
  -h, --help            show this help message and exit
  --version             show program's version number and exit

subcommands:
  {build-extensions,convert,download-extensions,help,install-extras,model-viewer,run,solve,test-solvers}
    build-extensions    Build compiled extension modules
    convert             Convert a Pyomo model to another format
    download-extensions
                        Download compiled extension modules
    help                Print help information.
    install-extras      Install "extra" packages that Pyomo can leverage.
    model-viewer        Run the Pyomo model viewer
    run                 Execute a command from the Pyomo bin (or Scripts)
                        directory.
    solve               Optimize a model
    test-solvers        Test Pyomo solvers

-------------------------------------------------------------------------
Pyomo supports a variety of modeling and optimization capabilities,
which are executed either as subcommands of 'pyomo' or as separate
commands.  Use the 'help' subcommand to get information about the
capabilities installed with Pyomo.  Additionally, each subcommand
supports independent command-line options.  Use the -h option to
print details for a subcommand.  For example, type

   pyomo solve -h

to print information about the `solve` subcommand. branch that needs to be saved for later
Merging in current pyomo opt branch
@genevievestarke genevievestarke added the ready for review This PR is ready for input from folks label Dec 31, 2025
@genevievestarke
Copy link
Collaborator Author

This pull request is ready for an overall framework review!

@jaredthomas68 jaredthomas68 self-requested a review January 6, 2026 22:38
@elenya-grant elenya-grant self-requested a review January 6, 2026 22:55
Copy link
Collaborator

@elenya-grant elenya-grant left a comment

Choose a reason for hiding this comment

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

This is super cool new functionality! Thanks for adding this into H2I!

I think eventually (not in this PR) that we may need to consider a framework specific to handling supervisory or plant-level control (rather than technology-level control). But - I think this is a great starting point!

Most of my comments are high-level or are small things that could be updated later. I think that making this flexible to different timesteps, rounding precision, and checking that this can be used for non-electricity commodities would be good future steps to take but not necessarily required at this moment!

I'll review again, but wanted to provide my initial feedback!

Copy link
Collaborator

Choose a reason for hiding this comment

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

It doesn't look PyomoControlOptions is being used anywhere?

Copy link
Collaborator

Choose a reason for hiding this comment

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

I almost think it'd be nice to split out these controllers into separate files. Like PyomoControllerBaseConfig and PyomoControllerBaseClass (maybe even SolverOptions if that's used across the Pyomo controllers) could be moved into h2integrate/control/control_strategies/controller_baseclass.py. Then separate files for SimpleBatteryControllerHeuristic, HeuristicLoadFollowingControllerConfig and HeuristicLoadFollowingController, and OptimizedDispatchControllerConfig and OptimizedDispatchController. This could be a separate clean-up thing though and is non-blocking at the moment. But - if you wanted to move OptimizedDispatchControllerConfig and OptimizedDispatchController to a separate file now, that'd also seem good to me :)

# )

self.source_techs = source_techs
self.options = dispatch_options
Copy link
Collaborator

Choose a reason for hiding this comment

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

The only option used in dispatch_options is the time_weighting_factor. Could we replace dispatch_options with time_weighting_factor and then remove the options attribute altogether?

self.arcs = []

self.block_set_name = block_set_name
self.round_digits = 4
Copy link
Collaborator

Choose a reason for hiding this comment

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

could we make round_digits an input that can be user-set at the config level (it could default to 4 if desired)

Copy link
Collaborator

Choose a reason for hiding this comment

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

new test for the OptimizedDispatchController would be nice!

}

self.n_control_window = self.config.n_control_window
self.n_horizon_window = self.config.n_control_window
Copy link
Collaborator

Choose a reason for hiding this comment

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

why is self.n_control_window and self.n_horizon_window both being set to self.config.n_control_window? Rather than having these as attributes, could we just always access them from the config? It also doesn't seem like self.config.n_horizon_window is used anywhere within the Pyomo classes. I think in OptimizedDispatchController._create_dispatch_optimization_model() the forecast horizon should be set as:

model.forecast_horizon = pyomo.Set(
            doc="Set of time periods in time horizon",
            initialize=range(self.config.n_control_window),
        )

* refactored DispatchProblemState

* updated tech config and removed pysam options file

* minor cleanups to DispatchProblemState

* minor updates to generic_converter_opt

* initial cleanups to hybrid_rule.py

* minor cleanups to pyomo_storage_rule_min_operating_cost

* extra small cleanups to generic_converter_opt

* added storage capacities as inputs to optimized controller

* updated use of n_control_window and n_horizon_window
Copy link
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 an excellent piece of work, well done. Thank you for the effort to make it relatively clean and straight forward. My thoughts are:

  • structurally I think it is very good. I guess we will find ways to improve as we move forward, but I like where it is at generally.
  • I am concerned with the number of getter and setter methods, are they really necessary?
  • more tests are needed for added functionality
  • lots of comments and print statements need to be removed, but that is expected in a draft
  • more comments would be helpful as there are some methods with little to no comments



class PyomoDispatchGenericConverterMinOperatingCosts:
def __init__(
Copy link
Collaborator

Choose a reason for hiding this comment

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

before going to production we will need some more comments and explanation here I think

def _create_parameters(self, pyomo_model: pyo.ConcreteModel):
"""Create technology Pyomo parameters to add to the Pyomo model instance.
Method is currently passed but this can serve as a template to add parameters to the Pyomo
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm not sure what you mean by "Method is currently passed"

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This was a copy/past mistake. Fixed now!

def _create_constraints(self, pyomo_model: pyo.ConcreteModel):
"""Create technology Pyomo parameters to add to the Pyomo model instance.
Method is currently passed but this can serve as a template to add constraints to the Pyomo
Copy link
Collaborator

Choose a reason for hiding this comment

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

see above comment

# initial_soc = self._check_initial_soc(initial_soc)
# pyomo_model.initial_soc = round(initial_soc, self.round_digits)

@property
Copy link
Collaborator

Choose a reason for hiding this comment

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

I wish we could avoid having so many getter and setter methods, but I don't have any good ideas for that at the moment (non-blocking).

solver_problem_dict = {
k.lower().replace(" ", "_"): v.value for k, v in solver_results.problem._list[0].items()
}
prob_to_attr_map = {
Copy link
Collaborator

Choose a reason for hiding this comment

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

some unclear names in the solver, but this is a fairly nice solution for dealing with it

upper_bound = solver_problem_dict["upper_bound"]
lower_bound = solver_problem_dict["lower_bound"]
if upper_bound != 0.0:
gap = abs(upper_bound - lower_bound) / abs(upper_bound)
Copy link
Collaborator

Choose a reason for hiding this comment

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

some comments here would be good. I think I get what you are doing, but it took me a minute

but has not been implemented yet."
)
)
# TODO: implement optimized solutions; this is where pyomo_model would be used
Copy link
Collaborator

Choose a reason for hiding this comment

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

remove comment

return dispatch_inputs


class OptimizedDispatchController(SimpleBatteryControllerHeuristic):
Copy link
Collaborator

Choose a reason for hiding this comment

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

This inheritance strikes me as odd since the parent and child are technically at the same level in their usage. If there are common elements they both need I think we should make a base class for them both to inherit from.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This is a good point. I've fixed this in the most recent update. Now it inherits from PyomoControllerBaseClass

wind_speed: 9.
model_inputs:
performance_parameters:
num_turbines: 100
Copy link
Collaborator

Choose a reason for hiding this comment

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

The wind farm rating is really high in comparison with the battery. I think the plots in the example would be more meaningful if you reduced the size of the wind farm

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Fixed!

@johnjasa johnjasa added needs modifications This PR has been reviewed, at least partially, and is ready for PR author response and removed ready for review This PR is ready for input from folks labels Jan 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

in progress needs modifications This PR has been reviewed, at least partially, and is ready for PR author response

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants