Demand Response Optimization for Battery Energy Storage (Stage 2) - #808
Demand Response Optimization for Battery Energy Storage (Stage 2)#808abhineet-gupta wants to merge 5 commits into
Conversation
96a279f to
bb33d74
Compare
johnjasa
left a comment
There was a problem hiding this comment.
This is great, thanks for the follow-on PR, @abhineet-gupta! I love how you updated the code, examples, tests, and docs. Really slick stuff with some good solver generalizations as well.
I've left some questions and suggestions, nothing huge but I do think some of the clarifications will be useful for users approaching this code.
There was a problem hiding this comment.
Could you please add definitions or explanations for the shaded gold regions in the figure or legends?
| dependencies: | ||
| - python=3.11 | ||
| - glpk | ||
| - highspy |
There was a problem hiding this comment.
Thanks for adding the highspy req here! Could you please add it to the conda install command within the install.md doc page too please?
| ) | ||
|
|
||
| PeakLoadManagementOptimizedStorageController.glpk_solve_call(model) | ||
| PeakLoadManagementOptimizedStorageController.pyomosolver_solve_call(model) |
There was a problem hiding this comment.
I like this generalization away from glpk to the pyomosolver! Thanks
| """Compute the cost a G$T charges to a CoOp based on the current grid price. | ||
|
|
||
| Args: | ||
| lmp (float): Current grid price (locational marginal price), e.g., in $/MWh. | ||
|
|
||
| Returns: | ||
| float: Cost charged by the G$T in the same units as `lmp`. |
There was a problem hiding this comment.
Should this be G&T instead of G$T? Maybe I'm just not familiar with the initialism
| Returns: | ||
| float: Cost charged by the G$T in the same units as `lmp`. | ||
| """ | ||
| return 1.05 * lmp + 20 |
There was a problem hiding this comment.
Should these multiplicative and offset coefficients be input or settable in some way? Or is it okay that they're hardcoded here? Will they always be these values?
|
|
||
| @staticmethod | ||
| def glpk_solve_call( | ||
| def pyomosolver_solve_call( |
There was a problem hiding this comment.
My own curiosity: why move away from GLPK here to use highs? Is it more robust, faster, better for this problem, or something else? Should we avoid using GLPK in other contexts within H2I?
| ``` | ||
|
|
||
| # Optimized Demand Response Controller | ||
| ## Optimized Demand Response Controller |
There was a problem hiding this comment.
Suggestion: the actual class file has PeakLoadManagement in the name; I'd suggest keeping the same naming convention here in the docs, and maybe even referencing or linking to the class itself so users can dig deeper.
| object.__setattr__(self.config, "max_charge_rate", float(inputs["max_charge_rate"][0])) | ||
| if "storage_capacity" in inputs: | ||
| object.__setattr__(self.config, "max_capacity", float(inputs["storage_capacity"][0])) | ||
|
|
There was a problem hiding this comment.
Is this just no longer necessary? Can we still optimize the max_charge_rate and storage_capacity values at the OM level and they'll get updated? It looks like yes as they're used on line 250, but I'm asking to double-check!
vijay092
left a comment
There was a problem hiding this comment.
Thank you for working on this! I had some minor comments.
|
|
||
| **Given:** | ||
| - $\lambda_t$ := `supervisory_signal`: price, demand, or price $\times$ demand time series at timestep $t$ | ||
| - $\lambda_t$ := `lmp_signal`: electricity price time series at timestep $t$ |
There was a problem hiding this comment.
I'd call this a supervisory signal, since it could be either LMP or demand- essentially any external signal that the battery needs to respond to.
|
|
||
| $$ | ||
| \max_{u_t, v_t,p_{d,t}, p_{c,t}} \quad \gamma \cdot \Delta t \sum_{t \in \mathcal{T}} p_{d,t} | ||
| \min_{u_{gt,t},u_{coop,t},v_t,p^d_{gt,t},p^d_{coop,t},pc_t,\text{SOC}_t,p_{gt2coop,t}} \quad |
There was a problem hiding this comment.
Could you confirm if the units are uniform in the first and second term? Incentive is $ / kwh and LMP is $/MWh?
| Returns: | ||
| float: Cost charged by the G$T in the same units as `lmp`. | ||
| """ | ||
| return 1.05 * lmp + 20 |
| discharge_gt = pyomo.value(model.discharge_gt[t]) # type: ignore[index] | ||
| discharge_coop = pyomo.value(model.discharge_coop[t]) # type: ignore[index] | ||
| with subtests.test(f"No simultaneous charge, discharge_gt or discharge_coop at t={t}"): | ||
| assert not (charge > 0.5 and discharge_gt > 0.5 and discharge_coop > 0.5) |
There was a problem hiding this comment.
This allows two conditions to be > 0.5 which should not happen, right?
|
|
||
| # Incentive revenue is earned for every kWh discharged. | ||
| # Power transmitted to CoOp | ||
| m.p_tocoop = pyomo.Var( |
There was a problem hiding this comment.
p_tocoop is unbounded above which might cause issues later on. Good to add an upper bound.
| u_dicharge_coop = controller.discharge_coop_bin_history | ||
| v_charge = controller.charge_bin_history | ||
| p_dicharge_gt = controller.p_discharge_gt_history | ||
| p_dicharge_coop = controller.p_discharge_coop_history |
| length ``n_control_window_hours`` per solve. | ||
| lmp_signal (list[float]): Locational Marginal Pricing (LMP) | ||
| forecast time series. | ||
| demand_signal (list[float]): Consumer demand forcast time series |
| ) | ||
|
|
||
| # Objective is maximizing incentive revenue is earned for every kWh discharged and | ||
| # minimzing the cost of energy for the CoOp. |
| @@ -304,13 +306,34 @@ def pyomo_dispatch_solver( | |||
| # Solve the optimzation problem | |||
| with subtests.test("Discharge never above max_charge_rate"): | ||
| assert np.all(discharge <= 1.0 + 1e-4) | ||
|
|
||
| with subtests.test("SOC at t=0 equal to init_soc_fraction"): |
Demand Response Optimization for Battery Energy Storage (Stage 2)
This PR is a continuation of PR 679.
It adds a Pyomo optimization based controller for the BESS system.
The controller optimizes the battery dispatch to minimize the Co-Op's expenditure while allowing demand response capabilities based on G&T requirements during peak window.
Section 1: Type of Contribution
Section 2: Draft PR Checklist
TODO:
Type of Reviewer Feedback Requested (on Draft PR)
Structural feedback:
Do you agree with the terminology used in describing this approach in documentation.
Implementation feedback:
Is this the correct way to add highs solver for pyomo to github workflow. It has already been added to
environment.yml.Other feedback:
Section 3: General PR Checklist
docs/files are up-to-date, or added when necessaryCHANGELOG.md"A complete thought. [PR XYZ]((https://github.com/NatLabRockies/H2Integrate/pull/XYZ)", where
XYZshould be replaced with the actual number.Section 4: Related Issues
Section 5: Impacted Areas of the Software
Section 5.1: New Files
Section 5.2: Modified Files
h2integrate/control/control_strategies/storage/plm_optimized_storage_controller.pyPeakLoadManagementOptimizedStorageControllerSection 6: Additional Supporting Information
Section 7: Test Results, if applicable
Section 8 (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.ymlgenerate_class_hierarchy.pyto update the class hierarchy diagram indocs/developer_guide/class_structure.md