66import logging
77import math
88from dataclasses import dataclass
9- from typing import NamedTuple
9+ from typing import NamedTuple , Sequence
1010
1111from ...._internal ._math import is_close_to_zero
1212from ....microgrid .component import BatteryData , InverterData
13+ from ..result import PowerBounds
1314
1415_logger = logging .getLogger (__name__ )
1516
@@ -36,17 +37,8 @@ class AggregatedBatteryData:
3637 soc_lower_bound : float
3738 """The aggregated lower SoC bound of the batteries."""
3839
39- power_inclusion_upper_bound : float
40- """The aggregated upper power inclusion bound of the batteries."""
41-
42- power_inclusion_lower_bound : float
43- """The aggregated lower power inclusion bound of the batteries."""
44-
45- power_exclusion_upper_bound : float
46- """The aggregated upper power exclusion bound of the batteries."""
47-
48- power_exclusion_lower_bound : float
49- """The aggregated lower power exclusion bound of the batteries."""
40+ power_bounds : PowerBounds
41+ """The aggregated power bounds of the batteries."""
5042
5143 def __init__ (self , batteries : list [BatteryData ]) -> None :
5244 """Create DistBatteryData from BatteryData.
@@ -63,7 +55,7 @@ def __init__(self, batteries: list[BatteryData]) -> None:
6355 Args:
6456 batteries: The batteries to aggregate.
6557 """
66- assert len (batteries ) > 0 , "No batteries given."
58+ assert len (batteries ) > 0 , "AggregatedBatteryData: No batteries given."
6759
6860 # We need only one component ID for DistBatteryData to be able to
6961 # identify the pair
@@ -84,21 +76,58 @@ def __init__(self, batteries: list[BatteryData]) -> None:
8476 self .soc_upper_bound = math .nan
8577 self .soc_lower_bound = math .nan
8678
87- self .power_inclusion_upper_bound = sum (
88- b .power_inclusion_upper_bound for b in batteries
89- )
90- self .power_inclusion_lower_bound = sum (
91- b .power_inclusion_lower_bound for b in batteries
79+ self .power_bounds = _aggregate_battery_power_bounds (
80+ list (
81+ map (
82+ lambda metrics : PowerBounds (
83+ inclusion_upper = metrics .power_inclusion_upper_bound ,
84+ inclusion_lower = metrics .power_inclusion_lower_bound ,
85+ exclusion_upper = metrics .power_exclusion_upper_bound ,
86+ exclusion_lower = metrics .power_exclusion_lower_bound ,
87+ ),
88+ batteries ,
89+ )
90+ )
9291 )
93- # To satisfy the largest exclusion bounds in the set we need to
94- # provide the power defined by the largest bounds multiplied by the
95- # number of batteries in the set.
96- self .power_exclusion_upper_bound = max (
97- b .power_exclusion_upper_bound for b in batteries
98- ) * len (batteries )
99- self .power_exclusion_lower_bound = min (
100- b .power_exclusion_lower_bound for b in batteries
101- ) * len (batteries )
92+
93+
94+ def _aggregate_battery_power_bounds (
95+ battery_metrics : Sequence [PowerBounds ],
96+ ) -> PowerBounds :
97+ """Calculate bounds for a set of batteries located behind one set of inverters.
98+
99+ Args:
100+ battery_metrics: List of PowerBounds for each battery.
101+
102+ Returns:
103+ A PowerBounds object containing the aggregated bounds for all given batteries
104+ """
105+ assert len (battery_metrics ) > 0 , "No batteries given."
106+
107+ # Calculate the aggregated bounds for the set of batteries
108+ power_inclusion_upper_bound = sum (
109+ bounds .inclusion_upper for bounds in battery_metrics
110+ )
111+ power_inclusion_lower_bound = sum (
112+ bounds .inclusion_lower for bounds in battery_metrics
113+ )
114+
115+ # To satisfy the largest exclusion bounds in the set we need to
116+ # provide the power defined by the largest bounds multiplied by the
117+ # number of batteries in the set.
118+ power_exclusion_upper_bound = max (
119+ bounds .exclusion_upper for bounds in battery_metrics
120+ ) * len (battery_metrics )
121+ power_exclusion_lower_bound = min (
122+ bounds .exclusion_lower for bounds in battery_metrics
123+ ) * len (battery_metrics )
124+
125+ return PowerBounds (
126+ inclusion_lower = power_inclusion_lower_bound ,
127+ exclusion_lower = power_exclusion_lower_bound ,
128+ exclusion_upper = power_exclusion_upper_bound ,
129+ inclusion_upper = power_inclusion_upper_bound ,
130+ )
102131
103132
104133class InvBatPair (NamedTuple ):
@@ -752,17 +781,21 @@ def _inclusion_exclusion_bounds(
752781 excl_bounds : dict [int , float ] = {}
753782 for battery , inverters in components :
754783 if supply :
755- excl_bounds [battery .component_id ] = - battery .power_exclusion_lower_bound
756- incl_bounds [battery .component_id ] = - battery .power_inclusion_lower_bound
784+ excl_bounds [
785+ battery .component_id
786+ ] = - battery .power_bounds .exclusion_lower
787+ incl_bounds [
788+ battery .component_id
789+ ] = - battery .power_bounds .inclusion_lower
757790 else :
758- excl_bounds [battery .component_id ] = battery .power_exclusion_upper_bound
759- incl_bounds [battery .component_id ] = battery .power_inclusion_upper_bound
791+ excl_bounds [battery .component_id ] = battery .power_bounds . exclusion_upper
792+ incl_bounds [battery .component_id ] = battery .power_bounds . inclusion_upper
760793
761794 for inverter in inverters :
762795 if supply :
763796 incl_bounds [inverter .component_id ] = - max (
764797 inverter .active_power_inclusion_lower_bound ,
765- battery .power_inclusion_lower_bound ,
798+ battery .power_bounds . inclusion_lower ,
766799 )
767800 excl_bounds [
768801 inverter .component_id
@@ -771,7 +804,7 @@ def _inclusion_exclusion_bounds(
771804 else :
772805 incl_bounds [inverter .component_id ] = min (
773806 inverter .active_power_inclusion_upper_bound ,
774- battery .power_inclusion_upper_bound ,
807+ battery .power_bounds . inclusion_upper ,
775808 )
776809 excl_bounds [
777810 inverter .component_id
0 commit comments