Baseclass for Storage Performance Models#624
Baseclass for Storage Performance Models#624elenya-grant wants to merge 14 commits intoNatLabRockies:developfrom
Conversation
| # StoragePerformanceModel | ||
| # Do whatever pre-calculations are necessary, then run storage | ||
| self.dt_hr = int(self.options["plant_config"]["plant"]["simulation"]["dt"]) / ( | ||
| 60**2 |
There was a problem hiding this comment.
switching it up from the 3600 I see
| ) | ||
|
|
||
| # Input storage design parameters | ||
| if "max_charge_rate" in self.config.as_dict(): |
There was a problem hiding this comment.
I like the if statements in the setup()
| # create inputs for pyomo control model | ||
| if "tech_to_dispatch_connections" in self.options["plant_config"]: | ||
| # get technology group name | ||
| # TODO: The split below seems brittle |
There was a problem hiding this comment.
Are we worried about this TODO now that it's coming into a base class?
There was a problem hiding this comment.
I'm not sure. I can't think of a better way to do it. Any changes to this logic may also require updating the logic in the setup() method of PyomoControllerBaseClass. I personally don't know of a better way to handle this logic at the moment
There was a problem hiding this comment.
Perhaps pulling a specific entry from the split() output would be better. However, I think this should generally work as long as no two technologies have the same name, even if they are the same type (e.g. two batteries in the system should be battery_1 and battery_2 or something similar.
There was a problem hiding this comment.
I feel like @johnjasa's framing of "should be ok" spells trouble down the road. I tend to avoid this kind of maneuver unless there's a validation involved. In a separate project, I entirely removed a large series of these combined naming structures as it created some extreme brittleness in naming conventions. The other concern is it becomes less clear over time what information is actually being captured, leading to difficulty in maintainability.
There was a problem hiding this comment.
If someone has a suggestion on an alternative approach then I'm happy to try it.
There was a problem hiding this comment.
Potentially we could give each tech it's own unique name, which I think could be helpful on multiple fronts not just dispatch.
self.tech_name = self.options.get("tech_name")
Then:
using_feedback_control = False
connections = self.options["plant_config"].get("tech_to_dispatch_connections", [])
for _source_tech, intended_dispatch_tech in connections:
if intended_dispatch_tech == self.tech_name:
self.add_discrete_input("pyomo_dispatch_solver", val=dummy_function)
using_feedback_control = True
break
I think this would be a bit more work than this PR is meant to do and relates very much so to #620. I think we could leave this for another PR.
| ) | ||
| return outputs | ||
|
|
||
| def simulate( |
There was a problem hiding this comment.
I like the simulate method in the base class because then it makes it clear that it's a storage method. And I don't mind that it's overwritten in the PySAM battery model
There was a problem hiding this comment.
Ditto, I'm glad to see this in the base class
| charge_rate, discharge_rate, storage_capacity, inputs, outputs, discrete_inputs | ||
| ) | ||
|
|
||
| def run_storage( |
There was a problem hiding this comment.
When this is ready I would love to see a more robust docstring explaining how to use run_storage() in classes that inherit this base class
There was a problem hiding this comment.
I just pushed up a doctoring to for the run_storage() method, but I will also add comments in the compute() method once we update the other storage performance models to inherit it.
There was a problem hiding this comment.
Thank you for including an example in the docstring! Very helpful.
jaredthomas68
left a comment
There was a problem hiding this comment.
I generally like where this is going. I have a lot of thoughts about the inputs and outputs, some of which I have shared in #521 and will likely need to be addressed in later PRs. Good job bringing more order and clarity to the dispatch/control code.
| # create inputs for pyomo control model | ||
| if "tech_to_dispatch_connections" in self.options["plant_config"]: | ||
| # get technology group name | ||
| # TODO: The split below seems brittle |
There was a problem hiding this comment.
Perhaps pulling a specific entry from the split() output would be better. However, I think this should generally work as long as no two technologies have the same name, even if they are the same type (e.g. two batteries in the system should be battery_1 and battery_2 or something similar.
| ) | ||
| return outputs | ||
|
|
||
| def simulate( |
There was a problem hiding this comment.
Ditto, I'm glad to see this in the base class
…rage_model_baseclass
kbrunik
left a comment
There was a problem hiding this comment.
Thanks @elenya-grant for putting this together! It really helps clarify what's unique to each storage method and the base class is very clearly laid out with nice docstrings 😎
I had a couple of small comments regarding the docstrings but should be pretty easy to resolve and won't require another re-review. Thanks again!
There was a problem hiding this comment.
Love how much simpler the pysam_battery is now!!
| demand, and unused commodities. | ||
| simulate(commodity_in, commodity_demand, time_step_duration, control_variable, | ||
| sim_start_index=0): | ||
| Simulates the storage behavior across timesteps using input commodity as control. |
There was a problem hiding this comment.
I'm not totally sure what it means when you say "using input commodity as control", could this be revised?
|
|
||
|
|
||
| Notes: | ||
| - Default timestep is 1 hour (``dt=1.0``). |
| # create inputs for pyomo control model | ||
| if "tech_to_dispatch_connections" in self.options["plant_config"]: | ||
| # get technology group name | ||
| # TODO: The split below seems brittle |
There was a problem hiding this comment.
Potentially we could give each tech it's own unique name, which I think could be helpful on multiple fronts not just dispatch.
self.tech_name = self.options.get("tech_name")
Then:
using_feedback_control = False
connections = self.options["plant_config"].get("tech_to_dispatch_connections", [])
for _source_tech, intended_dispatch_tech in connections:
if intended_dispatch_tech == self.tech_name:
self.add_discrete_input("pyomo_dispatch_solver", val=dummy_function)
using_feedback_control = True
break
I think this would be a bit more work than this PR is meant to do and relates very much so to #620. I think we could leave this for another PR.
| charge_rate, discharge_rate, storage_capacity, inputs, outputs, discrete_inputs | ||
| ) | ||
|
|
||
| def run_storage( |
There was a problem hiding this comment.
Thank you for including an example in the docstring! Very helpful.
| compute(inputs, outputs, discrete_inputs, discrete_outputs): | ||
| Runs the storage model for a simulation timestep, | ||
| updating outputs such as SOC, charge/discharge limits, unmet | ||
| demand, and unused commodities. |
There was a problem hiding this comment.
Add run_storage() to the docstring?
Baseclass for Storage Performance Models
This PR introduces a storage performance baseclass. The baseclass will help standardize the inputs and outputs of storage performance models.
The storage baseclass is in
h2integrate/storage/storage_baseclass.py. This would be inherited by the following storage performance models:StoragePerformanceModelinh2integrate/storage/storage_performance_model.pyPySAMBatteryPerformanceModelinh2integrate/storage/battery/pysam_battery.pyStorageAutoSizingModelinh2integrate/storage/simple_storage_auto_sizing.pySome notes on the current performance baseclass:
setup()method includes logic that would make it easy-to-use for all storage performance models, that is why it checks the configuration class for certain keys.run_storage()method is the main method that should be called at the end ofcompute(). This takes in the necessary information to simulate the storage performance and set the outputssimulate()method currently exists in the baseclass. This method would be over-written in thePySAMBatteryPerformanceModelThis PR is ready for a full-review.
Section 1: Type of Contribution
Section 2: Draft PR Checklist
TODO (once reviewers provided initial feedback):
Type of Reviewer Feedback Requested (on Draft PR)
Structural feedback:
Implementation feedback:
simulate()method (which is preferred?)simulate()lives in the baseclass and is over-written by the PySAM battery model (as it is currently) ORsimulate()raises a not-implemented error in the baseclass and each model has their own version of it (meaning that the method would be the same in theStoragePerformanceModelandStorageAutoSizingModelOther 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 3: Related Issues
Part of Issue #564
Section 4: Impacted Areas of the Software
Section 4.1: New Files
h2integrate/storage/storage_baseclass.pyStoragePerformanceBaseConfig: Base configuration class for storage performance models, currently includes the attributes that are shared across all storage performance models.StoragePerformanceBase: new baseclass for storage performance modelssetup(): initializes inputs and outputs of storage performance modelsrun_storage(): method that runs thesimulate()method and calculates the outputs of the storage performance modelsimulate(): simple storage simulate method that's used byStoragePerformanceModelandStorageAutoSizingModel(the PySAM battery model overwrites this method)Section 4.2: Modified Files
h2integrate/storage/battery/pysam_battery.py: updated to use base config and baseclassbattery_electricity_discharge->storage_electricity_dischargebattery_electricity_charge->storage_electricity_chargebattery_electricity_out->storage_electricity_outh2integrate/storage/simple_storage_auto_sizing.py: updated to use base config and baseclassh2integrate/storage/storage_performance_model.py: updated to use base config and baseclassSection 4.3: Removed Files
h2integrate/storage/battery/battery_baseclass.pySection 5: Additional Supporting Information
Section 6: Test Results, if applicable