|
12 | 12 |
|
13 | 13 | from frequenz.sdk.actor.power_distributing.result import PowerBounds
|
14 | 14 | from frequenz.sdk.microgrid.component import BatteryData, InverterData
|
15 |
| -from frequenz.sdk.power import DistributionAlgorithm, InvBatPair |
| 15 | +from frequenz.sdk.power import DistributionAlgorithm, DistributionResult, InvBatPair |
16 | 16 |
|
17 | 17 | from ..utils.component_data_wrapper import BatteryDataWrapper, InverterDataWrapper
|
18 | 18 |
|
@@ -919,3 +919,290 @@ def test_supply_two_batteries_distribution_exponent_less_then_1(self) -> None:
|
919 | 919 |
|
920 | 920 | assert result.distribution == approx({1: 500, 3: 500})
|
921 | 921 | assert result.remaining_power == approx(0.0)
|
| 922 | + |
| 923 | + |
| 924 | +class TestDistWithExclBounds: |
| 925 | + """Test the distribution algorithm with exclusive bounds.""" |
| 926 | + |
| 927 | + @staticmethod |
| 928 | + def assert_result(result: DistributionResult, expected: DistributionResult) -> None: |
| 929 | + """Assert the result is as expected.""" |
| 930 | + assert result.distribution == approx(expected.distribution, abs=0.01) |
| 931 | + assert result.remaining_power == approx(expected.remaining_power, abs=0.01) |
| 932 | + |
| 933 | + def test_scenario_1(self) -> None: |
| 934 | + """Test scenario 1. |
| 935 | +
|
| 936 | + Set params for 3 batteries: |
| 937 | + capacities: 10000, 10000, 10000 |
| 938 | + socs: 30, 50, 70 |
| 939 | +
|
| 940 | + individual soc bounds: 10-90 |
| 941 | + individual bounds: -1000, -100, 100, 1000 |
| 942 | +
|
| 943 | + battery pool bounds: -3000, -300, 300, 1000 |
| 944 | +
|
| 945 | + Expected result: |
| 946 | +
|
| 947 | + | request | | excess | |
| 948 | + | power | distribution | remaining | |
| 949 | + |---------+------------------------+-----------| |
| 950 | + | -300 | -100, -100, -100 | 0 | |
| 951 | + | 300 | 100, 100, 100 | 0 | |
| 952 | + | -600 | -100, -200, -300 | 0 | |
| 953 | + | 900 | 466.66, 300, 133.33 | 0 | |
| 954 | + | -900 | -133.33, -300, -466.66 | 0 | |
| 955 | + | 2200 | 1000, 850, 350 | 0 | |
| 956 | + | -2200 | -350, -850, -1000 | 0 | |
| 957 | + | 2800 | 1000, 1000, 800 | 0 | |
| 958 | + | -2800 | -800, -1000, -1000 | 0 | |
| 959 | + | 3800 | 1000, 1000, 1000 | 800 | |
| 960 | + | -3200 | -1000, -1000, -1000 | -200 | |
| 961 | +
|
| 962 | + """ |
| 963 | + capacities: List[Metric] = [Metric(10000), Metric(10000), Metric(10000)] |
| 964 | + soc: List[Metric] = [ |
| 965 | + Metric(30.0, Bound(10, 90)), |
| 966 | + Metric(50.0, Bound(10, 90)), |
| 967 | + Metric(70.0, Bound(10, 90)), |
| 968 | + ] |
| 969 | + bounds = [ |
| 970 | + PowerBounds(-1000, -100, 20, 1000), |
| 971 | + PowerBounds(-1000, -90, 100, 1000), |
| 972 | + PowerBounds(-1000, -50, 100, 1000), |
| 973 | + PowerBounds(-1000, -100, 90, 1000), |
| 974 | + PowerBounds(-1000, -20, 100, 1000), |
| 975 | + PowerBounds(-1000, -100, 80, 1000), |
| 976 | + ] |
| 977 | + components = create_components(3, capacities, soc, bounds) |
| 978 | + |
| 979 | + algorithm = DistributionAlgorithm() |
| 980 | + |
| 981 | + self.assert_result( |
| 982 | + algorithm.distribute_power(-300, components), |
| 983 | + DistributionResult({1: -100, 3: -100, 5: -100}, remaining_power=0.0), |
| 984 | + ) |
| 985 | + self.assert_result( |
| 986 | + algorithm.distribute_power(300, components), |
| 987 | + DistributionResult({1: 100, 3: 100, 5: 100}, remaining_power=0.0), |
| 988 | + ) |
| 989 | + self.assert_result( |
| 990 | + algorithm.distribute_power(-600, components), |
| 991 | + DistributionResult({1: -100, 3: -200, 5: -300}, remaining_power=0.0), |
| 992 | + ) |
| 993 | + self.assert_result( |
| 994 | + algorithm.distribute_power(900, components), |
| 995 | + DistributionResult({1: 450, 3: 300, 5: 150}, remaining_power=0.0), |
| 996 | + ) |
| 997 | + self.assert_result( |
| 998 | + algorithm.distribute_power(-900, components), |
| 999 | + DistributionResult({1: -150, 3: -300, 5: -450}, remaining_power=0.0), |
| 1000 | + ) |
| 1001 | + self.assert_result( |
| 1002 | + algorithm.distribute_power(2200, components), |
| 1003 | + DistributionResult({1: 1000, 3: 833.33, 5: 366.66}, remaining_power=0.0), |
| 1004 | + ) |
| 1005 | + self.assert_result( |
| 1006 | + algorithm.distribute_power(-2200, components), |
| 1007 | + DistributionResult({1: -366.66, 3: -833.33, 5: -1000}, remaining_power=0.0), |
| 1008 | + ) |
| 1009 | + self.assert_result( |
| 1010 | + algorithm.distribute_power(2800, components), |
| 1011 | + DistributionResult({1: 1000, 3: 1000, 5: 800}, remaining_power=0.0), |
| 1012 | + ) |
| 1013 | + self.assert_result( |
| 1014 | + algorithm.distribute_power(-2800, components), |
| 1015 | + DistributionResult({1: -800, 3: -1000, 5: -1000}, remaining_power=0.0), |
| 1016 | + ) |
| 1017 | + self.assert_result( |
| 1018 | + algorithm.distribute_power(3800, components), |
| 1019 | + DistributionResult({1: 1000, 3: 1000, 5: 1000}, remaining_power=800.0), |
| 1020 | + ) |
| 1021 | + self.assert_result( |
| 1022 | + algorithm.distribute_power(-3200, components), |
| 1023 | + DistributionResult({1: -1000, 3: -1000, 5: -1000}, remaining_power=-200.0), |
| 1024 | + ) |
| 1025 | + |
| 1026 | + def test_scenario_2(self) -> None: |
| 1027 | + """Test scenario 2. |
| 1028 | +
|
| 1029 | + Set params for 3 batteries: |
| 1030 | + capacities: 10000, 10000, 10000 |
| 1031 | + socs: 50, 50, 70 |
| 1032 | +
|
| 1033 | + individual soc bounds: 10-90 |
| 1034 | + individual bounds: -1000, -100, 100, 1000 |
| 1035 | +
|
| 1036 | + battery pool bounds: -3000, -300, 300, 1000 |
| 1037 | +
|
| 1038 | + Expected result: |
| 1039 | +
|
| 1040 | + | request | | excess | |
| 1041 | + | power | distribution | remaining | |
| 1042 | + |---------+---------------------------+-----------| |
| 1043 | + | -300 | -100, -100, -100 | 0 | |
| 1044 | + | 300 | 100, 100, 100 | 0 | |
| 1045 | + | -530 | -151.42, -151.42, -227.14 | 0 | |
| 1046 | + | 530 | 212, 212, 106 | 0 | |
| 1047 | + | 2000 | 800, 800, 400 | 0 | |
| 1048 | + | -2000 | -571.42, -571.42, -857.14 | 0 | |
| 1049 | + | 2500 | 1000, 1000, 500 | 0 | |
| 1050 | + | -2500 | -785.71, -714.28, -1000.0 | 0 | |
| 1051 | + | 3000 | 1000, 1000, 1000 | 0 | |
| 1052 | + | -3000 | -1000, -1000, -1000 | 0 | |
| 1053 | + | 3500 | 1000, 1000, 1000 | 500 | |
| 1054 | + | -3500 | -1000, -1000, -1000 | -500 | |
| 1055 | + """ |
| 1056 | + capacities: List[Metric] = [Metric(10000), Metric(10000), Metric(10000)] |
| 1057 | + soc: List[Metric] = [ |
| 1058 | + Metric(50.0, Bound(10, 90)), |
| 1059 | + Metric(50.0, Bound(10, 90)), |
| 1060 | + Metric(70.0, Bound(10, 90)), |
| 1061 | + ] |
| 1062 | + bounds = [ |
| 1063 | + PowerBounds(-1000, -100, 20, 1000), |
| 1064 | + PowerBounds(-1000, -90, 100, 1000), |
| 1065 | + PowerBounds(-1000, -50, 100, 1000), |
| 1066 | + PowerBounds(-1000, -100, 90, 1000), |
| 1067 | + PowerBounds(-1000, -20, 100, 1000), |
| 1068 | + PowerBounds(-1000, -100, 80, 1000), |
| 1069 | + ] |
| 1070 | + components = create_components(3, capacities, soc, bounds) |
| 1071 | + |
| 1072 | + algorithm = DistributionAlgorithm() |
| 1073 | + |
| 1074 | + self.assert_result( |
| 1075 | + algorithm.distribute_power(-300, components), |
| 1076 | + DistributionResult({1: -100, 3: -100, 5: -100}, remaining_power=0.0), |
| 1077 | + ) |
| 1078 | + self.assert_result( |
| 1079 | + algorithm.distribute_power(300, components), |
| 1080 | + DistributionResult({1: 100, 3: 100, 5: 100}, remaining_power=0.0), |
| 1081 | + ) |
| 1082 | + self.assert_result( |
| 1083 | + algorithm.distribute_power(-530, components), |
| 1084 | + DistributionResult( |
| 1085 | + {1: -151.42, 3: -151.42, 5: -227.14}, remaining_power=0.0 |
| 1086 | + ), |
| 1087 | + ) |
| 1088 | + self.assert_result( |
| 1089 | + algorithm.distribute_power(530, components), |
| 1090 | + DistributionResult({1: 212, 3: 212, 5: 106}, remaining_power=0.0), |
| 1091 | + ) |
| 1092 | + self.assert_result( |
| 1093 | + algorithm.distribute_power(2000, components), |
| 1094 | + DistributionResult({1: 800, 3: 800, 5: 400}, remaining_power=0.0), |
| 1095 | + ) |
| 1096 | + self.assert_result( |
| 1097 | + algorithm.distribute_power(-2000, components), |
| 1098 | + DistributionResult( |
| 1099 | + {1: -571.42, 3: -571.42, 5: -857.14}, remaining_power=0.0 |
| 1100 | + ), |
| 1101 | + ) |
| 1102 | + self.assert_result( |
| 1103 | + algorithm.distribute_power(2500, components), |
| 1104 | + DistributionResult({1: 1000, 3: 1000, 5: 500}, remaining_power=0.0), |
| 1105 | + ) |
| 1106 | + self.assert_result( |
| 1107 | + algorithm.distribute_power(-2500, components), |
| 1108 | + DistributionResult( |
| 1109 | + {1: -785.71, 3: -714.28, 5: -1000.0}, remaining_power=0.0 |
| 1110 | + ), |
| 1111 | + ) |
| 1112 | + self.assert_result( |
| 1113 | + algorithm.distribute_power(3000, components), |
| 1114 | + DistributionResult({1: 1000, 3: 1000, 5: 1000}, remaining_power=0.0), |
| 1115 | + ) |
| 1116 | + self.assert_result( |
| 1117 | + algorithm.distribute_power(-3000, components), |
| 1118 | + DistributionResult({1: -1000, 3: -1000, 5: -1000}, remaining_power=0.0), |
| 1119 | + ) |
| 1120 | + self.assert_result( |
| 1121 | + algorithm.distribute_power(3500, components), |
| 1122 | + DistributionResult({1: 1000, 3: 1000, 5: 1000}, remaining_power=500.0), |
| 1123 | + ) |
| 1124 | + self.assert_result( |
| 1125 | + algorithm.distribute_power(-3500, components), |
| 1126 | + DistributionResult({1: -1000, 3: -1000, 5: -1000}, remaining_power=-500.0), |
| 1127 | + ) |
| 1128 | + |
| 1129 | + def test_scenario_3(self) -> None: |
| 1130 | + """Test scenario 3. |
| 1131 | +
|
| 1132 | + Set params for 3 batteries: |
| 1133 | + capacities: 10000, 10000, 10000 |
| 1134 | + socs: 50, 50, 70 |
| 1135 | +
|
| 1136 | + individual soc bounds: 10-90 |
| 1137 | + individual bounds 1: -1000, 0, 0, 1000 |
| 1138 | + individual bounds 2: -1000, -100, 100, 1000 |
| 1139 | + individual bounds 3: -1000, 0, 0, 1000 |
| 1140 | +
|
| 1141 | + battery pool bounds: -3000, -100, 100, 1000 |
| 1142 | +
|
| 1143 | + Expected result: |
| 1144 | +
|
| 1145 | + | request | | excess | |
| 1146 | + | power | distribution | remaining | |
| 1147 | + |---------+---------------------------+-----------| |
| 1148 | + | -300 | -88, -108.57, -123.43 | 0 | |
| 1149 | + | 300 | 128, 128, 64 | 0 | |
| 1150 | + | -1800 | -514.28, -514.28, -771.42 | 0 | |
| 1151 | + | 1800 | 720, 720, 360 | 0 | |
| 1152 | + | -2800 | -800, -1000, -1000 | 0 | |
| 1153 | + | 2800 | 1000, 1000, 800 | 0 | |
| 1154 | + | -3500 | -1000, -1000, -1000 | -500 | |
| 1155 | + | 3500 | 1000, 1000, 1000 | 500 | |
| 1156 | + """ |
| 1157 | + capacities: List[Metric] = [Metric(10000), Metric(10000), Metric(10000)] |
| 1158 | + soc: List[Metric] = [ |
| 1159 | + Metric(50.0, Bound(10, 90)), |
| 1160 | + Metric(50.0, Bound(10, 90)), |
| 1161 | + Metric(70.0, Bound(10, 90)), |
| 1162 | + ] |
| 1163 | + bounds = [ |
| 1164 | + PowerBounds(-1000, 0, 0, 1000), |
| 1165 | + PowerBounds(-1000, 0, 0, 1000), |
| 1166 | + PowerBounds(-1000, -100, 100, 1000), |
| 1167 | + PowerBounds(-1000, -100, 100, 1000), |
| 1168 | + PowerBounds(-1000, 0, 0, 1000), |
| 1169 | + PowerBounds(-1000, 0, 0, 1000), |
| 1170 | + ] |
| 1171 | + components = create_components(3, capacities, soc, bounds) |
| 1172 | + |
| 1173 | + algorithm = DistributionAlgorithm() |
| 1174 | + |
| 1175 | + self.assert_result( |
| 1176 | + algorithm.distribute_power(-320, components), |
| 1177 | + DistributionResult({1: -88, 3: -108.57, 5: -123.43}, remaining_power=0.0), |
| 1178 | + ) |
| 1179 | + self.assert_result( |
| 1180 | + algorithm.distribute_power(320, components), |
| 1181 | + DistributionResult({1: 128, 3: 128, 5: 64}, remaining_power=0.0), |
| 1182 | + ) |
| 1183 | + self.assert_result( |
| 1184 | + algorithm.distribute_power(-1800, components), |
| 1185 | + DistributionResult( |
| 1186 | + {1: -514.28, 3: -514.28, 5: -771.42}, remaining_power=0.0 |
| 1187 | + ), |
| 1188 | + ) |
| 1189 | + self.assert_result( |
| 1190 | + algorithm.distribute_power(1800, components), |
| 1191 | + DistributionResult({1: 720, 3: 720, 5: 360}, remaining_power=0.0), |
| 1192 | + ) |
| 1193 | + self.assert_result( |
| 1194 | + algorithm.distribute_power(-2800, components), |
| 1195 | + DistributionResult({1: -800, 3: -1000, 5: -1000}, remaining_power=0.0), |
| 1196 | + ) |
| 1197 | + self.assert_result( |
| 1198 | + algorithm.distribute_power(2800, components), |
| 1199 | + DistributionResult({1: 1000, 3: 1000, 5: 800}, remaining_power=0.0), |
| 1200 | + ) |
| 1201 | + self.assert_result( |
| 1202 | + algorithm.distribute_power(-3500, components), |
| 1203 | + DistributionResult({1: -1000, 3: -1000, 5: -1000}, remaining_power=-500.0), |
| 1204 | + ) |
| 1205 | + self.assert_result( |
| 1206 | + algorithm.distribute_power(3500, components), |
| 1207 | + DistributionResult({1: 1000, 3: 1000, 5: 1000}, remaining_power=500.0), |
| 1208 | + ) |
0 commit comments