Skip to content

Commit 03a3cae

Browse files
committed
Test exclusion bounds in power distribution algorithm
Signed-off-by: Sahas Subramanian <[email protected]>
1 parent ffb95bf commit 03a3cae

File tree

1 file changed

+288
-1
lines changed

1 file changed

+288
-1
lines changed

tests/power/test_distribution_algorithm.py

Lines changed: 288 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
from frequenz.sdk.actor.power_distributing.result import PowerBounds
1414
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
1616

1717
from ..utils.component_data_wrapper import BatteryDataWrapper, InverterDataWrapper
1818

@@ -919,3 +919,290 @@ def test_supply_two_batteries_distribution_exponent_less_then_1(self) -> None:
919919

920920
assert result.distribution == approx({1: 500, 3: 500})
921921
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

Comments
 (0)