-
Notifications
You must be signed in to change notification settings - Fork 21
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
GSYE-645: Add loss calculation to storage state #1799
Conversation
@@ -1,3 +1,4 @@ | |||
# pylint: disable=too-many-lines |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This module has only be changed by Black because I fixed one method interface. You can ignore reviewing this. Thanks
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #1799 +/- ##
==========================================
+ Coverage 69.52% 69.55% +0.03%
==========================================
Files 148 148
Lines 14029 14045 +16
Branches 2619 2618 -1
==========================================
+ Hits 9753 9769 +16
- Misses 3758 3759 +1
+ Partials 518 517 -1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, I am bit skeptic that charging and discharging losses are fixed values and not percentage over the charging and discharging power. But the logic seems good ! Left minor comments but looks good
"""Register energy from a bid trade event.""" | ||
assert energy >= -FLOATING_POINT_TOLERANCE | ||
self.pledged_buy_kWh[time_slot] += energy | ||
self.offered_buy_kWh[time_slot] -= energy | ||
self._track_energy_bought_type(energy, energy_origin) | ||
self._clamp_energy_to_buy_kWh([time_slot]) | ||
# if self.charging_loss_per_kWh > 0: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we can remove this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Of course, let me remove
@@ -411,6 +484,8 @@ def get_available_energy_to_buy_kWh(self, time_slot: DateTime) -> float: | |||
|
|||
self._clamp_energy_to_buy_kWh([time_slot]) | |||
energy_kWh = self.energy_to_buy_dict[time_slot] | |||
# if self.charging_loss_per_kWh >0 and energy_kWh: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And removing this as well
They are indeed implemented as percentages (have a look here: https://github.com/gridsingularity/gsy-e/pull/1799/files#diff-4581ce8152beebe7aa76014b4a741adac802ef0fcd8ac7a578d7ec4aabf39dfbR362-R363) |
|
||
charging_loss_percent: float = 0.0 | ||
discharging_loss_percent: float = 0.0 | ||
self_discharge_per_day_kWh: float = 0.0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All good, however the unit is striking as a bit odd here. Did you find it in any spec sheet of a battery? Reason I am asking is because I have mostly seen this unit being the percentage of the total capacity of the battery, e.g. https://www.batterypowertips.com/why-self-discharge-is-important-in-batteries/ or more specifically here https://www.batterypowertips.com/wp-content/uploads/2023/02/Why-is-self-discharge-important-in-batteries-Figure-1.jpeg .
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh yes, it is also a percentage value. I also found the links that you shared. Let me rename. Also, I need to fix the calculation then. I don't know why I came up with the current implementation.
losses: Optional[StorageLosses] = StorageLosses(), | ||
): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor, but since it is Optional
I would prefer None
as default value:
losses: Optional[StorageLosses] = StorageLosses(), | |
): | |
losses: Optional[StorageLosses] = None, | |
): | |
if not losses: | |
losses = StorageLosses() |
self_discharging_kWh = ( | ||
self.losses.self_discharge_per_day_kWh * GlobalConfig.slot_length.total_days() | ||
) | ||
total_loss_kWh = charging_loss_kWh + discharging_loss_kWh + self_discharging_kWh |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Admittedly an overkill, but do you think that it makes sense to also keep the total_loss_kWh in the results for debugging purposes? On second thought, better to omit it and add it if necessary in the future.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it is not an overkill and indeed interesting to know if you run storages with losses. Let me add it to the storage CSV file.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did add it here: 3c11349
However, there is a corner case for that last market slot. We only calculate the losses for the past market slot in the market_cycle event method. But after the last market slot this is not triggered, hence the losses are not calculated. I gave it some time to fix, but could not find a solution for this. As you named this an overkill, I left it like this now. Let us deal with the last market slot once we need it. OK?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed and thanks!
initial_selling_rate: Union[float, dict] = StorageSettings.SELLING_RATE_RANGE.initial, | ||
final_selling_rate: Union[float, dict] = StorageSettings.SELLING_RATE_RANGE.final, | ||
initial_buying_rate: Union[float, dict] = StorageSettings.BUYING_RATE_RANGE.initial, | ||
final_buying_rate: Union[float, dict] = StorageSettings.BUYING_RATE_RANGE.final, | ||
fit_to_limit=True, | ||
energy_rate_increase_per_update=None, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Definitely not for now, but we need to migrate these constructor arguments to OrderUpdaterParameters
at some point. I will open a ticket to do this refactoring in one go for all other strategies if it does not exist.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Definitely! IMO we should do it in the frame of https://gridsingularity.atlassian.net/browse/GSYE-769.
src/gsy_e/models/strategy/storage.py
Outdated
self.cap_price_strategy = cap_price_strategy | ||
self.balancing_energy_ratio = BalancingRatio(*balancing_energy_ratio) | ||
|
||
@staticmethod | ||
def deserialize_args(constructor_args: Dict) -> Dict: | ||
"""Deserialize the constructor arguments for the HeatPump strategy.""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor
"""Deserialize the constructor arguments for the HeatPump strategy.""" | |
"""Deserialize the constructor arguments for the Storage strategy.""" |
src/gsy_e/models/strategy/storage.py
Outdated
energy_rate_decrease_per_update=None, | ||
update_interval=None, | ||
initial_energy_origin: Enum = ESSEnergyOrigin.EXTERNAL, | ||
losses: Optional[StorageLosses] = StorageLosses(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can be None
here too AFAIS.
src/gsy_e/models/strategy/storage.py
Outdated
self.cap_price_strategy = cap_price_strategy | ||
self.balancing_energy_ratio = BalancingRatio(*balancing_energy_ratio) | ||
|
||
@staticmethod | ||
def deserialize_args(constructor_args: Dict) -> Dict: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that this method only needs to be implemented if we want to involve gsy-web. Since we do not this could be completely omitted. That said, no problem keeping it in case we need it in the future.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, let me remove it here, only to explicitly add it once/if we add the losses to the FE.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some comments, LGTM in general, thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
Reason for the proposed changes
Please describe what we want to achieve and why.
Proposed changes
INTEGRATION_TESTS_BRANCH=feature/GSYE-645
GSY_FRAMEWORK_BRANCH=master