All checks were successful
Build, test and publish / verify (push) Successful in 6m12s
134 lines
6.4 KiB
Java
134 lines
6.4 KiB
Java
package nl.agilecc.agiledancers;
|
|
|
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
|
|
import java.util.List;
|
|
import java.util.Set;
|
|
import nl.agilecc.agiledancers.model.scheduling.GroupRequirement;
|
|
import nl.agilecc.agiledancers.model.scheduling.ScheduleRules;
|
|
import nl.agilecc.agiledancers.model.scheduling.SchedulingInput;
|
|
import nl.agilecc.agiledancers.model.scheduling.SchedulingResult;
|
|
import nl.agilecc.agiledancers.model.scheduling.VarietyScore;
|
|
import nl.agilecc.agiledancers.model.specifications.DanceGroupStyle;
|
|
import nl.agilecc.agiledancers.service.ManagementException;
|
|
import nl.agilecc.agiledancers.solver.OrToolsSchedulingEngine;
|
|
import org.junit.jupiter.api.Test;
|
|
|
|
class S18SolverTest {
|
|
private GroupRequirement group(final long id, final int count, final boolean showcase, final long dancer) {
|
|
return new GroupRequirement(id, count, showcase, Set.of(dancer), Set.of(), 20);
|
|
}
|
|
|
|
private SchedulingResult solve(final SchedulingInput input) {
|
|
final SchedulingResult result = new OrToolsSchedulingEngine().solve(input);
|
|
assertTrue(ScheduleRules.violations(input, result.assignments()).isEmpty());
|
|
assertTrue(result.optimal());
|
|
return result;
|
|
}
|
|
|
|
public @Test void showcasesBookendEveryPartAndSharedDancersHaveChangingSpace() {
|
|
final List<GroupRequirement> groups = List.of(
|
|
group(1, 2, true, 1),
|
|
group(2, 2, true, 2),
|
|
group(3, 2, true, 3),
|
|
group(4, 1, false, 1),
|
|
group(5, 1, false, 5),
|
|
group(6, 1, false, 6),
|
|
group(7, 1, false, 7));
|
|
final SchedulingInput input = new SchedulingInput(1, 0, 2, groups, 1, Set.of());
|
|
final SchedulingResult result = solve(input);
|
|
for (final List<Long> part : result.assignments()) {
|
|
assertTrue(part.getFirst() <= 3);
|
|
assertTrue(part.getLast() <= 3);
|
|
}
|
|
assertEquals(
|
|
1,
|
|
result.assignments().stream()
|
|
.filter((final List<Long> p) -> p.contains(4L))
|
|
.count());
|
|
}
|
|
|
|
public @Test void ordinaryDancerMustShareDaypartsAcrossTheirGroups() {
|
|
final SchedulingInput input = new SchedulingInput(
|
|
1,
|
|
0,
|
|
2,
|
|
List.of(group(1, 1, false, 1), group(2, 1, false, 1), group(3, 1, false, 3), group(4, 1, false, 4)),
|
|
1,
|
|
Set.of());
|
|
assertThrows(ManagementException.class, () -> solve(input));
|
|
final SchedulingInput coach = new SchedulingInput(1, 0, 2, input.groups(), 1, Set.of(1L));
|
|
final SchedulingResult result = solve(coach);
|
|
assertTrue(result.assignments().stream().allMatch((final List<Long> p) -> p.size() == 2));
|
|
}
|
|
|
|
public @Test void showcaseExcessTakesPriorityOverPerfectBalance() {
|
|
final SchedulingInput input = new SchedulingInput(
|
|
1,
|
|
0,
|
|
2,
|
|
List.of(
|
|
group(1, 2, true, 1),
|
|
group(2, 1, false, 1),
|
|
group(3, 1, false, 1),
|
|
group(4, 1, false, 4),
|
|
group(5, 1, false, 5),
|
|
group(6, 1, false, 6),
|
|
group(7, 1, false, 7),
|
|
group(8, 1, false, 8),
|
|
group(9, 1, false, 9),
|
|
group(10, 1, false, 10),
|
|
group(11, 1, false, 11)),
|
|
1,
|
|
Set.of());
|
|
final SchedulingResult result = solve(input);
|
|
assertTrue(result.assignments().stream().anyMatch((final List<Long> p) -> p.containsAll(List.of(2L, 3L))));
|
|
assertEquals(
|
|
2,
|
|
Math.abs(result.assignments().get(0).size()
|
|
- result.assignments().get(1).size()));
|
|
}
|
|
|
|
public @Test void changingTimeAppliesToCoachesAndShowcasesWithoutExceptions() {
|
|
final SchedulingInput input =
|
|
new SchedulingInput(1, 0, 1, List.of(group(1, 1, true, 1), group(2, 1, false, 1)), 1, Set.of(1L));
|
|
assertThrows(ManagementException.class, () -> solve(input));
|
|
assertTrue(ScheduleRules.violations(input, List.of(List.of(1L, 2L))).stream()
|
|
.anyMatch((final String s) -> s.contains("two intervening")));
|
|
}
|
|
|
|
public @Test void singleShowcaseCanUseEitherBoundaryAndNoShowcaseHasNoBoundaryRule() {
|
|
solve(new SchedulingInput(1, 0, 1, List.of(group(1, 1, true, 1), group(2, 1, false, 2)), 1, Set.of()));
|
|
solve(new SchedulingInput(1, 0, 1, List.of(group(1, 1, false, 1)), 1, Set.of()));
|
|
}
|
|
|
|
public @Test void sharedValidatorRejectsMalformedAndIncompleteDrafts() {
|
|
final SchedulingInput input = new SchedulingInput(1, 0, 1, List.of(group(1, 1, false, 1)), 1, Set.of());
|
|
assertFalse(ScheduleRules.violations(input, null).isEmpty());
|
|
assertFalse(ScheduleRules.violations(input, List.of()).isEmpty());
|
|
assertFalse(ScheduleRules.violations(input, List.of(List.of())).isEmpty());
|
|
assertFalse(ScheduleRules.violations(input, List.of(List.of(1L, 1L))).isEmpty());
|
|
assertFalse(ScheduleRules.violations(input, List.of(List.of(99L))).isEmpty());
|
|
final SchedulingInput empty = new SchedulingInput(1, 0, 0, List.of(), 1, Set.of());
|
|
assertFalse(ScheduleRules.violations(empty, List.of()).isEmpty());
|
|
}
|
|
|
|
public @Test void varietyUsesStyleOverlapAndFiveYearLinearAgePenalty() {
|
|
final GroupRequirement hiphop =
|
|
new GroupRequirement(1, 1, false, Set.of(1L), Set.of(DanceGroupStyle.HIPHOP), 10);
|
|
final GroupRequirement mixed = new GroupRequirement(
|
|
2, 1, false, Set.of(2L), Set.of(DanceGroupStyle.HIPHOP, DanceGroupStyle.MODERN), 12.5);
|
|
final GroupRequirement modern =
|
|
new GroupRequirement(3, 1, false, Set.of(3L), Set.of(DanceGroupStyle.MODERN), 15);
|
|
assertEquals(1_000_000, VarietyScore.penalty(hiphop, mixed));
|
|
assertEquals(0, VarietyScore.penalty(hiphop, modern));
|
|
final List<Long> order = solve(new SchedulingInput(1, 0, 1, List.of(hiphop, mixed, modern), 1, Set.of()))
|
|
.assignments()
|
|
.getFirst();
|
|
assertEquals(1, Math.abs(order.indexOf(1L) - order.indexOf(3L)));
|
|
}
|
|
}
|