44"""Power distribution algorithm to distribute power between batteries."""
55
66import logging
7+ import math
78from dataclasses import dataclass
89from typing import Dict , List , NamedTuple , Tuple
910
@@ -277,8 +278,11 @@ def _total_capacity(self, components: List[InvBatPair]) -> float:
277278 return total_capacity
278279
279280 def _compute_battery_availability_ratio (
280- self , components : List [InvBatPair ], available_soc : Dict [int , float ]
281- ) -> Tuple [List [Tuple [InvBatPair , float ]], float ]:
281+ self ,
282+ components : List [InvBatPair ],
283+ available_soc : Dict [int , float ],
284+ excl_bounds : Dict [int , float ],
285+ ) -> Tuple [List [Tuple [InvBatPair , float , float ]], float ]:
282286 r"""Compute battery ratio and the total sum of all of them.
283287
284288 battery_availability_ratio = capacity_ratio[i] * available_soc[i]
@@ -291,6 +295,7 @@ def _compute_battery_availability_ratio(
291295 available_soc: How much SoC remained to reach
292296 * SoC upper bound - if need to distribute consumption power
293297 * SoC lower bound - if need to distribute supply power
298+ excl_bounds: Exclusion bounds for each inverter
294299
295300 Returns:
296301 Tuple where first argument is battery availability ratio for each
@@ -299,32 +304,37 @@ def _compute_battery_availability_ratio(
299304 of all battery ratios in the list.
300305 """
301306 total_capacity = self ._total_capacity (components )
302- battery_availability_ratio : List [Tuple [InvBatPair , float ]] = []
307+ battery_availability_ratio : List [Tuple [InvBatPair , float , float ]] = []
303308 total_battery_availability_ratio : float = 0.0
304309
305310 for pair in components :
306- battery = pair [ 0 ]
311+ battery , inverter = pair
307312 capacity_ratio = battery .capacity / total_capacity
308313 soc_factor = pow (
309314 available_soc [battery .component_id ], self ._distributor_exponent
310315 )
311316
312317 ratio = capacity_ratio * soc_factor
313- battery_availability_ratio .append ((pair , ratio ))
318+ battery_availability_ratio .append (
319+ (pair , excl_bounds [inverter .component_id ], ratio )
320+ )
314321 total_battery_availability_ratio += ratio
315322
316- battery_availability_ratio .sort (key = lambda item : item [1 ], reverse = True )
323+ battery_availability_ratio .sort (
324+ key = lambda item : (item [1 ], item [2 ]), reverse = True
325+ )
317326
318327 return battery_availability_ratio , total_battery_availability_ratio
319328
320- def _distribute_power (
329+ def _distribute_power ( # pylint: disable=too-many-arguments
321330 self ,
322331 components : List [InvBatPair ],
323332 power_w : float ,
324333 available_soc : Dict [int , float ],
325- upper_bounds : Dict [int , float ],
334+ incl_bounds : Dict [int , float ],
335+ excl_bounds : Dict [int , float ],
326336 ) -> DistributionResult :
327- # pylint: disable=too-many-locals
337+ # pylint: disable=too-many-locals,too-many-branches,too-many-statements
328338 """Distribute power between given components.
329339
330340 After this method power should be distributed between batteries
@@ -336,57 +346,98 @@ def _distribute_power(
336346 available_soc: how much SoC remained to reach:
337347 * SoC upper bound - if need to distribute consumption power
338348 * SoC lower bound - if need to distribute supply power
339- upper_bounds: Min between upper bound of each pair in the components list:
340- * supply upper bound - if need to distribute consumption power
341- * consumption lower bound - if need to distribute supply power
349+ incl_bounds: Inclusion bounds for each inverter
350+ excl_bounds: Exclusion bounds for each inverter
342351
343352 Returns:
344353 Distribution result.
345354 """
346355 (
347356 battery_availability_ratio ,
348357 sum_ratio ,
349- ) = self ._compute_battery_availability_ratio (components , available_soc )
358+ ) = self ._compute_battery_availability_ratio (
359+ components , available_soc , excl_bounds
360+ )
350361
351362 distribution : Dict [int , float ] = {}
352-
363+ print ( f" { power_w = } " )
353364 # sum_ratio == 0 means that all batteries are fully charged / discharged
354365 if is_close_to_zero (sum_ratio ):
355366 distribution = {inverter .component_id : 0 for _ , inverter in components }
356367 return DistributionResult (distribution , power_w )
357368
358369 distributed_power : float = 0.0
370+ reserved_power : float = 0.0
359371 power_to_distribute : float = power_w
360372 used_ratio : float = 0.0
361373 ratio = sum_ratio
362- for pair , battery_ratio in battery_availability_ratio :
374+ excess_reserved : dict [int , float ] = {}
375+ deficits : dict [int , float ] = {}
376+ for pair , excl_bound , battery_ratio in battery_availability_ratio :
363377 inverter = pair [1 ]
364378 # ratio = 0, means all remaining batteries reach max SoC lvl or have no
365379 # capacity
366380 if is_close_to_zero (ratio ):
367381 distribution [inverter .component_id ] = 0.0
368382 continue
369383
370- distribution [inverter .component_id ] = (
371- power_to_distribute * battery_ratio / ratio
372- )
373-
384+ power_to_distribute = power_w - reserved_power
385+ calculated_power = power_to_distribute * battery_ratio / ratio
386+ reserved_power += max (calculated_power , excl_bound )
374387 used_ratio += battery_ratio
375-
388+ ratio = sum_ratio - used_ratio
376389 # If the power allocated for that inverter is out of bound,
377390 # then we need to distribute more power over all remaining batteries.
378- upper_bound = upper_bounds [inverter .component_id ]
379- if distribution [inverter .component_id ] > upper_bound :
380- distribution [inverter .component_id ] = upper_bound
381- distributed_power += upper_bound
382- # Distribute only the remaining power.
383- power_to_distribute = power_w - distributed_power
384- # Distribute between remaining batteries
385- ratio = sum_ratio - used_ratio
391+ incl_bound = incl_bounds [inverter .component_id ]
392+ if calculated_power > incl_bound :
393+ excess_reserved [inverter .component_id ] = incl_bound - excl_bound
394+ # # Distribute between remaining batteries
395+ elif calculated_power < excl_bound :
396+ deficits [inverter .component_id ] = calculated_power - excl_bound
386397 else :
387- distributed_power += distribution [inverter .component_id ]
398+ excess_reserved [inverter .component_id ] = calculated_power - excl_bound
399+
400+ distributed_power += excl_bound
401+ distribution [inverter .component_id ] = excl_bound
402+
403+ for inverter_id , deficit in deficits .items ():
404+ while not math .isclose (deficit , 0.0 , abs_tol = 1e-6 ) and deficit < 0.0 :
405+ take_from = max (excess_reserved .items (), key = lambda item : item [1 ])
406+ if math .isclose (take_from [1 ], 0.0 , abs_tol = 1e-6 ) or take_from [1 ] < 0.0 :
407+ break
408+ if take_from [1 ] >= - deficit or math .isclose (
409+ take_from [1 ], - deficit , abs_tol = 1e-6
410+ ):
411+ excess_reserved [take_from [0 ]] += deficit
412+ deficits [inverter_id ] = 0.0
413+ deficit = 0.0
414+ else :
415+ deficit += excess_reserved [take_from [0 ]]
416+ deficits [inverter_id ] = deficit
417+ excess_reserved [take_from [0 ]] = 0.0
418+
419+ for inverter_id , excess in excess_reserved .items ():
420+ distribution [inverter_id ] += excess
421+ distributed_power += excess
422+
423+ for inverter_id , deficit in deficits .items ():
424+ if deficit < - 0.1 :
425+ left_over = power_w - distributed_power
426+ if left_over > - deficit :
427+ distributed_power += deficit
428+ deficit = 0.0
429+ deficits [inverter_id ] = 0.0
430+ elif left_over > 0.0 :
431+ deficit += left_over
432+ distributed_power += left_over
433+ deficits [inverter_id ] = deficit
434+
435+ left_over = power_w - distributed_power
436+ dist = DistributionResult (distribution , left_over )
388437
389- return DistributionResult (distribution , power_w - distributed_power )
438+ return self ._greedy_distribute_remaining_power (
439+ dist .distribution , incl_bounds , dist .remaining_power
440+ )
390441
391442 def _greedy_distribute_remaining_power (
392443 self ,
@@ -487,19 +538,21 @@ def _distribute_consume_power(
487538 0.0 , battery .soc_upper_bound - battery .soc
488539 )
489540
490- bounds : Dict [int , float ] = {}
541+ incl_bounds : Dict [int , float ] = {}
542+ excl_bounds : Dict [int , float ] = {}
491543 for battery , inverter in components :
492544 # We can supply/consume with int only
493- inverter_bound = inverter .active_power_inclusion_upper_bound
494- battery_bound = battery .power_inclusion_upper_bound
495- bounds [inverter .component_id ] = min (inverter_bound , battery_bound )
545+ incl_bounds [inverter .component_id ] = min (
546+ inverter .active_power_inclusion_upper_bound ,
547+ battery .power_inclusion_upper_bound ,
548+ )
549+ excl_bounds [inverter .component_id ] = max (
550+ inverter .active_power_exclusion_upper_bound ,
551+ battery .power_exclusion_upper_bound ,
552+ )
496553
497- result : DistributionResult = self ._distribute_power (
498- components , power_w , available_soc , bounds
499- )
500-
501- return self ._greedy_distribute_remaining_power (
502- result .distribution , bounds , result .remaining_power
554+ return self ._distribute_power (
555+ components , power_w , available_soc , incl_bounds , excl_bounds
503556 )
504557
505558 def _distribute_supply_power (
@@ -525,19 +578,20 @@ def _distribute_supply_power(
525578 0.0 , battery .soc - battery .soc_lower_bound
526579 )
527580
528- bounds : Dict [int , float ] = {}
581+ incl_bounds : Dict [int , float ] = {}
582+ excl_bounds : Dict [int , float ] = {}
529583 for battery , inverter in components :
530- # We can consume with int only
531- inverter_bound = inverter .active_power_inclusion_lower_bound
532- battery_bound = battery .power_inclusion_lower_bound
533- bounds [inverter .component_id ] = - 1 * max (inverter_bound , battery_bound )
584+ incl_bounds [inverter .component_id ] = - 1 * max (
585+ inverter .active_power_inclusion_lower_bound ,
586+ battery .power_inclusion_lower_bound ,
587+ )
588+ excl_bounds [inverter .component_id ] = - 1 * min (
589+ inverter .active_power_exclusion_lower_bound ,
590+ battery .power_exclusion_lower_bound ,
591+ )
534592
535593 result : DistributionResult = self ._distribute_power (
536- components , - 1 * power_w , available_soc , bounds
537- )
538-
539- result = self ._greedy_distribute_remaining_power (
540- result .distribution , bounds , result .remaining_power
594+ components , - 1 * power_w , available_soc , incl_bounds , excl_bounds
541595 )
542596
543597 for inverter_id in result .distribution .keys ():
0 commit comments