Enable load following optimization dispatch with Pyomo#407
Enable load following optimization dispatch with Pyomo#407genevievestarke wants to merge 82 commits intoNatLabRockies:developfrom
Conversation
{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
…in operating costs framework
…e/H2Integrate into feature/pyomo_opt
|
This pull request is ready for an overall framework review! |
kbrunik
left a comment
There was a problem hiding this comment.
Thanks for continuing to iterate on this! I think there's still a bit more clean up and adding documentation that would be helpful before bringing it in. If you need help with some of it let me know and we can tackle it together.
In regards to the diagram in the PR body -- how does the hybrid rule class and it's outputs get incorporated into the overall run/dispatch?
| demand_profile = np.ones(8760) * 100.0 | ||
|
|
||
|
|
||
| # TODO: Update with demand module once it is developed |
There was a problem hiding this comment.
Do you want to address this TODO? I thought you could set the demand in the tech config?
There was a problem hiding this comment.
Currently, you have to set up the problem, then assign the battery demand signal to the battery model, then run the model. #385 is an issue to address this
|
|
||
|
|
||
| class PyomoDispatchGenericConverterMinOperatingCosts: | ||
| def __init__( |
There was a problem hiding this comment.
I agree I think it would be helpful to explain what the parameters in those lines are
h2integrate/control/control_rules/converters/generic_converter_min_operating_cost.py
Show resolved
Hide resolved
|
|
||
|
|
||
| @define | ||
| class OptimizedDispatchControllerConfig(PyomoControllerBaseConfig): |
There was a problem hiding this comment.
Could you add a docstring with all of these parameters defined?
| - `examples/18_pyomo_heuristic_wind_battery_dispatch` | ||
|
|
||
| (optimized-load-following-controller)= | ||
| ## Optimized Load Following Controller |
There was a problem hiding this comment.
After further reviewing the PR, I think there's some additions to the doc page that would really help with overall understanding.
- Adding something similar to the diagram in the PR body to help explain the mechanics of the interactions.
- A bit more about "Arcs", "Ports", "parameters" and "constraints" that are used within the context of the optimized load following controller
- It's a little confusing what "system-level" is within the context of the work because everything seems to be housed within the storage model. Explaining that might help users understand what's going on.
- I know we talked about making a note about users setting weights and how monkeying around with those can change how the Pyomo solver runs. I still think that would be a helpful addition
There was a problem hiding this comment.
Some responses to the comments above:
- We can add some figures to the docs, happy to get some feedback on which parts of the diagram need to be clearer!
- "parameters" are values that the model needs to run the optimization (e.g. available production of the commodity to be used for storage), but that are not changed in the optimization like the "variables" are. "Constraints" define constraints on the variables, sometimes involving the parameters, that are enforced in the pyomo optimization. "Ports" define names of the variables that can be used to create external links between pyomo modules. The pyomo dispatch is implemented in a modular structure, where the dispatch for each plant technology is defined individually, and then all of the dispatch models are collected into a single optimization problem for the pyomo solver by
hybrid_rule. The ports define what variables are passed from the individual technology dispatch models to thehybrid_ruleproblem. "Arcs" are what actually form the connection between the ports. This means that the storage dispatch pyomo model has a port forsystem_production,hybrid_rulehas a port forsystem_production, and an arc connects these two endpoints to connect this variable across the pyomo models. - In this PR, I've used "system-level" to define things that apply to the whole system (i.e. commodity load demand, etc.), and "storage" to define things that are specific to the storage technology (i.e. maximum charge rate, SOC, etc.). These are both included in the storage pyomo model at the moment. In HOPP, these were separated into 'storage' and 'grid' pyomo models, but the grid paradigm doesn't work with H2I at the moment, so they were combined into the storage pyomo model. We could separate these out into two different models (maybe "storage" and "system"). They are not defined in
hybrid_rulebecausehybrid_rulecurrently only works as an aggregator of models and only includes things that you would need access to all the individual pyomo models to know. - There are some thoughts about the weight settings in the body of the PR. I can distill those down to what would be helpful to have as a note in the docs.
…state.py Apply comment suggestions Co-authored-by: John Jasa <john.jasa@nrel.gov>
…state.py Apply comment suggestions Co-authored-by: John Jasa <john.jasa@nrel.gov>
Apply comment suggestions Co-authored-by: John Jasa <john.jasa@nrel.gov>
Co-authored-by: kbrunik <102193481+kbrunik@users.noreply.github.com>
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.pyfile was added to aggregate the storage and production variables to create one problem for the optimizer. The added files areOptimizedDispatchControllerclass inpyomo_controllers.pyhybrid_rule.pyfile: 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.generic_converter_opt.pyfile: Houses the variables, parameters, constraints, and objective function for the generator technology in the optimization model.pyomo_storage_rule_min_operating_cost.pyfile: 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.controller_opt_problem_state.pyfile: Formulates the optimization model for the solverThe below is an example of how and where the dispatch is run using a battery for electricity storage:

What's changed?
For the optimized dispatch, the control and dispatch functions are no longer separate (see figure above). This also means that the controller needs access to the dispatch parameters. Now all dispatch and controller classes have access to the
shared_parameterssection in the input yaml, meaning that parameters likecommodity_namethat were defined in multiple places before should now be defined once in theshared_parameterssection. This affects the pyomo heuristic dispatch, as well. Most control parameters are then defined in theshared_parameters, as well, because there is a large overlap with the performance variables. Control/dispatch specific variables can be defined in thecontrol_parameterssection. This include:cost_per_charge: in $/kW, cost to charge the storage (note that charging is incentivized)cost_per_discharge: in $/kW, cost to discharge the storagecommodity_met_value: in $/kW, penalty for not meeting the desired load demandcost_per_production: in $/kW, cost to use the incoming produced commodity (i.e. electricity from wind)time_weighting_factor: This parameter discounts each subsequent time step incrementally in the future in the horizon window by the defined amount (defaults to 0.995)tech_name: Name that the technology will be defined as in the dispatchNote that the
tech_namevariable must match with the names given in thetech_to_dispatch_connectionsin the plant_config file. TODO: should we eliminate thetech_namevariable and make it the same as the defined technology name? Why is it different?We have exposed the cost values to the user in this implementation, which was not the case for HOPP. This is good for visibility, but the weights are fussy. If your dispatch is taking an abnormally long time (or even longer than a minute), you should check to make sure your weights make sense, or change them to see if that makes a difference. Some rules of thumb and thoughts:
commodity_met_valueto be the largest because this is what drives meeting the load for load followingcost_per_chargeequal tocost_per_discharge. This will confuse the optimizer. In the objective function, charging the battery is incentivized and discharging is disincentivized, meaning that if they are the same value, they can cause the optimizer to oscillate the battery because they "cancel each other out" in value in the objective function. I usually setcost_per_chargea little bit less thancost_per_dischargeto not discourage charging the battery instead of meeting the load by discharging.cost_per_productionas not that important if the energy is already produced (i.e. from wind for example). I tend to set this to 0 to not discourage charging the batterycost_per_chargeandcost_per_dischargetwo orders of magnitude lower thancommodity_met_valueso that the charging the discharging of the battery don't interfere with the load very much. You can put them closer together, but it will increase the time that the optimization takes.This pull request does not enable the optimal dispatch of a storage technology that can charge from the grid. This implementation currently only involves incoming electricity from upstream (that could be bought using grid component). It also only allows one incoming electricity stream and does not apply optimal dispatch of that stream back through the upstream technologies (no feedback). The dispatch can handle more than one generation technology, but the incoming electricity must be combined using an H2I combiner before going to the storage component, and the
cost_per_production, which is defined in the storage technology section, needs to include the cost of production for all production technologies. This could be done using the following:The demand is also still set externally after the setup step. Ideally, the dispatch will be integrated with the load demand framework that already exists in H2I, and the demand can be defined that way.
Section 1: Type of Contribution
Section 2: Draft PR Checklist
TODO:
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 fromgeneric_converter_opt.pyandpyomo_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_productionterm, 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
docs/files are up-to-date, or added when necessaryCHANGELOG.mdhas been updated to describe the changes made in this PRSection 3: Related Issues
This resolves #386
Section 4: Impacted Areas of the Software
Section 4.1: New Files
path/to/file.extensionmethod1: What and why something was changed in one sentence or less.Section 4.2: Modified Files
path/to/file.extensionmethod1: 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
docs/developer_guide/coding_guidelines.mdattrsclass to define theConfigto load in attributes for the modelBaseConfigorCostModelBaseConfiginitialize()method,setup()method,compute()methodCostModelBaseClasssupported_models.pycreate_financial_modelinh2integrate_model.pytest_all_examples.pydocs/user_guide/model_overview.mddocs/section<model_name>.mdis added to the_toc.yml