Skip to content

Commit

Permalink
feat: multi-threaded solving support for Ruin and Recreate moves
Browse files Browse the repository at this point in the history
  • Loading branch information
triceo committed Sep 19, 2024
1 parent 0bf63f6 commit 9c36871
Show file tree
Hide file tree
Showing 5 changed files with 252 additions and 7 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package ai.timefold.solver.core.impl.heuristic.move;

import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import ai.timefold.solver.core.api.domain.solution.PlanningSolution;
import ai.timefold.solver.core.api.score.director.ScoreDirector;
Expand Down Expand Up @@ -50,19 +52,19 @@ public final void doMoveOnly(ScoreDirector<Solution_> scoreDirector) {
// ************************************************************************

public static <E> List<E> rebaseList(List<E> externalObjectList, ScoreDirector<?> destinationScoreDirector) {
List<E> rebasedObjectList = new ArrayList<>(externalObjectList.size());
for (E entity : externalObjectList) {
var rebasedObjectList = new ArrayList<E>(externalObjectList.size());
for (var entity : externalObjectList) {
rebasedObjectList.add(destinationScoreDirector.lookUpWorkingObject(entity));
}
return rebasedObjectList;
}

public static Object[] rebaseArray(Object[] externalObjects, ScoreDirector<?> destinationScoreDirector) {
Object[] rebasedObjects = new Object[externalObjects.length];
for (int i = 0; i < externalObjects.length; i++) {
rebasedObjects[i] = destinationScoreDirector.lookUpWorkingObject(externalObjects[i]);
public static <E> Set<E> rebaseSet(Set<E> externalObjectSet, ScoreDirector<?> destinationScoreDirector) {
var rebasedObjectSet = new LinkedHashSet<E>(externalObjectSet.size());
for (var entity : externalObjectSet) {
rebasedObjectSet.add(destinationScoreDirector.lookUpWorkingObject(entity));
}
return rebasedObjects;
return rebasedObjectSet;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Set;

import ai.timefold.solver.core.api.score.director.ScoreDirector;
Expand Down Expand Up @@ -82,6 +83,30 @@ public boolean isMoveDoable(ScoreDirector<Solution_> scoreDirector) {
return true;
}

@Override
public Move<Solution_> rebase(ScoreDirector<Solution_> destinationScoreDirector) {
var rebasedRuinedEntityList = rebaseList(ruinedEntityList, destinationScoreDirector);
var rebasedAffectedValueSet = rebaseSet(affectedValueSet, destinationScoreDirector);
return new RuinRecreateMove<>(genuineVariableDescriptor, constructionHeuristicPhaseBuilder, solverScope,
rebasedRuinedEntityList, rebasedAffectedValueSet);
}

@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (!(o instanceof RuinRecreateMove<?> that))
return false;
return Objects.equals(genuineVariableDescriptor, that.genuineVariableDescriptor)
&& Objects.equals(ruinedEntityList, that.ruinedEntityList)
&& Objects.equals(affectedValueSet, that.affectedValueSet);
}

@Override
public int hashCode() {
return Objects.hash(genuineVariableDescriptor, ruinedEntityList, affectedValueSet);
}

@Override
public String toString() {
return "RuinMove{" +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import java.util.List;
import java.util.Map;
import java.util.NavigableSet;
import java.util.Objects;
import java.util.Set;
import java.util.TreeSet;

Expand Down Expand Up @@ -105,6 +106,30 @@ public boolean isMoveDoable(ScoreDirector<Solution_> scoreDirector) {
return true;
}

@Override
public Move<Solution_> rebase(ScoreDirector<Solution_> destinationScoreDirector) {
var rebasedRuinedValueList = rebaseList(ruinedValueList, destinationScoreDirector);
var rebasedAffectedEntitySet = rebaseSet(affectedEntitySet, destinationScoreDirector);
return new ListRuinRecreateMove<>(listVariableStateSupply, constructionHeuristicPhaseBuilder, solverScope,
rebasedRuinedValueList, rebasedAffectedEntitySet);
}

@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (!(o instanceof ListRuinRecreateMove<?> that))
return false;
return Objects.equals(listVariableStateSupply, that.listVariableStateSupply)
&& Objects.equals(ruinedValueList, that.ruinedValueList)
&& Objects.equals(affectedEntitySet, that.affectedEntitySet);
}

@Override
public int hashCode() {
return Objects.hash(listVariableStateSupply, ruinedValueList, affectedEntitySet);
}

@Override
public String toString() {
return "ListRuinMove{" +
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package ai.timefold.solver.core.impl.heuristic.selector.move.generic;

import static ai.timefold.solver.core.impl.testdata.util.PlannerTestUtils.mockRebasingScoreDirector;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.SoftAssertions.assertSoftly;
import static org.mockito.Mockito.mock;

import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Set;

import ai.timefold.solver.core.impl.domain.variable.descriptor.GenuineVariableDescriptor;
import ai.timefold.solver.core.impl.solver.scope.SolverScope;
import ai.timefold.solver.core.impl.testdata.domain.TestdataEntity;
import ai.timefold.solver.core.impl.testdata.domain.TestdataSolution;
import ai.timefold.solver.core.impl.testdata.domain.TestdataValue;

import org.junit.jupiter.api.Test;

class RuinRecreateMoveTest {

@SuppressWarnings({ "unchecked", "rawtypes" })
@Test
void rebase() {
var variableDescriptor = TestdataEntity.buildVariableDescriptorForValue();

var v1 = new TestdataValue("v1");
var v2 = new TestdataValue("v2");
var e1 = new TestdataEntity("e1", v1);
var e2 = new TestdataEntity("e2", null);
var e3 = new TestdataEntity("e3", v1);

var destinationV1 = new TestdataValue("v1");
var destinationV2 = new TestdataValue("v2");
var destinationE1 = new TestdataEntity("e1", destinationV1);
var destinationE2 = new TestdataEntity("e2", null);
var destinationE3 = new TestdataEntity("e3", destinationV1);

var destinationScoreDirector = mockRebasingScoreDirector(
variableDescriptor.getEntityDescriptor().getSolutionDescriptor(), new Object[][] {
{ v1, destinationV1 },
{ v2, destinationV2 },
{ e1, destinationE1 },
{ e2, destinationE2 },
{ e3, destinationE3 },
});

var move = new RuinRecreateMove<TestdataSolution>(mock(GenuineVariableDescriptor.class),
mock(RuinRecreateConstructionHeuristicPhaseBuilder.class), mock(SolverScope.class), Arrays.asList(e1, e2, e3),
Set.of(v1, v2));
var rebasedMove = move.rebase(destinationScoreDirector);

assertSoftly(softly -> {
softly.assertThat((Collection) rebasedMove.getPlanningEntities())
.containsExactly(destinationE1, destinationE2, destinationE3);
softly.assertThat((Collection) rebasedMove.getPlanningValues())
.containsExactlyInAnyOrder(destinationV1, destinationV2); // The input set is not ordered.
});

}

@SuppressWarnings("unchecked")
@Test
void equality() {
var v1 = new TestdataValue("v1");
var v2 = new TestdataValue("v2");
var e1 = new TestdataEntity("e1", v1);
var e2 = new TestdataEntity("e2", null);

var descriptor = mock(GenuineVariableDescriptor.class);
var move = new RuinRecreateMove<TestdataSolution>(descriptor,
mock(RuinRecreateConstructionHeuristicPhaseBuilder.class), mock(SolverScope.class), List.of(e1),
Set.of(v1));
var sameMove = new RuinRecreateMove<TestdataSolution>(descriptor,
mock(RuinRecreateConstructionHeuristicPhaseBuilder.class), mock(SolverScope.class), List.of(e1),
Set.of(v1));
assertThat(move).isEqualTo(sameMove);

var differentMove = new RuinRecreateMove<TestdataSolution>(descriptor,
mock(RuinRecreateConstructionHeuristicPhaseBuilder.class), mock(SolverScope.class), List.of(e1),
Set.of(v2));
assertThat(move).isNotEqualTo(differentMove);

var anotherDifferentMove = new RuinRecreateMove<TestdataSolution>(descriptor,
mock(RuinRecreateConstructionHeuristicPhaseBuilder.class), mock(SolverScope.class), List.of(e2),
Set.of(v1));
assertThat(move).isNotEqualTo(anotherDifferentMove);

var yetAnotherDifferentMove = new RuinRecreateMove<TestdataSolution>(mock(GenuineVariableDescriptor.class),
mock(RuinRecreateConstructionHeuristicPhaseBuilder.class), mock(SolverScope.class), List.of(e1),
Set.of(v1));
assertThat(move).isNotEqualTo(yetAnotherDifferentMove);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package ai.timefold.solver.core.impl.heuristic.selector.move.generic.list;

import static ai.timefold.solver.core.impl.testdata.util.PlannerTestUtils.mockRebasingScoreDirector;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.SoftAssertions.assertSoftly;
import static org.mockito.Mockito.mock;

import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Set;

import ai.timefold.solver.core.impl.domain.variable.ListVariableStateSupply;
import ai.timefold.solver.core.impl.heuristic.selector.move.generic.RuinRecreateConstructionHeuristicPhaseBuilder;
import ai.timefold.solver.core.impl.heuristic.selector.move.generic.list.ruin.ListRuinRecreateMove;
import ai.timefold.solver.core.impl.solver.scope.SolverScope;
import ai.timefold.solver.core.impl.testdata.domain.list.TestdataListEntity;
import ai.timefold.solver.core.impl.testdata.domain.list.TestdataListSolution;
import ai.timefold.solver.core.impl.testdata.domain.list.TestdataListValue;

import org.junit.jupiter.api.Test;

class ListRuinRecreateMoveTest {

@SuppressWarnings({ "unchecked", "rawtypes" })
@Test
void rebase() {
var variableDescriptor = TestdataListEntity.buildVariableDescriptorForValueList();

var v1 = new TestdataListValue("v1");
var v2 = new TestdataListValue("v2");
var e1 = new TestdataListEntity("e1", v1);
var e2 = new TestdataListEntity("e2");
var e3 = new TestdataListEntity("e3", v1);

var destinationV1 = new TestdataListValue("v1");
var destinationV2 = new TestdataListValue("v2");
var destinationE1 = new TestdataListEntity("e1", destinationV1);
var destinationE2 = new TestdataListEntity("e2");
var destinationE3 = new TestdataListEntity("e3", destinationV1);

var destinationScoreDirector = mockRebasingScoreDirector(
variableDescriptor.getEntityDescriptor().getSolutionDescriptor(), new Object[][] {
{ v1, destinationV1 },
{ v2, destinationV2 },
{ e1, destinationE1 },
{ e2, destinationE2 },
{ e3, destinationE3 },
});

var move = new ListRuinRecreateMove<TestdataListSolution>(mock(ListVariableStateSupply.class),
mock(RuinRecreateConstructionHeuristicPhaseBuilder.class), mock(SolverScope.class), Arrays.asList(v1, v2),
Set.of(e1, e2, e3));
var rebasedMove = move.rebase(destinationScoreDirector);

assertSoftly(softly -> {
softly.assertThat((Collection) rebasedMove.getPlanningEntities())
.containsExactlyInAnyOrder(destinationE1, destinationE2, destinationE3); // The input set is not ordered.
softly.assertThat((Collection) rebasedMove.getPlanningValues())
.containsExactly(destinationV1, destinationV2);
});

}

@SuppressWarnings("unchecked")
@Test
void equality() {
var v1 = new TestdataListValue("v1");
var v2 = new TestdataListValue("v2");
var e1 = new TestdataListEntity("e1", v1);
var e2 = new TestdataListEntity("e2");

var supply = mock(ListVariableStateSupply.class);
var move = new ListRuinRecreateMove<TestdataListSolution>(supply,
mock(RuinRecreateConstructionHeuristicPhaseBuilder.class), mock(SolverScope.class), List.of(e1),
Set.of(v1));
var sameMove = new ListRuinRecreateMove<TestdataListSolution>(supply,
mock(RuinRecreateConstructionHeuristicPhaseBuilder.class), mock(SolverScope.class), List.of(e1),
Set.of(v1));
assertThat(move).isEqualTo(sameMove);

var differentMove = new ListRuinRecreateMove<TestdataListSolution>(supply,
mock(RuinRecreateConstructionHeuristicPhaseBuilder.class), mock(SolverScope.class), List.of(e1),
Set.of(v2));
assertThat(move).isNotEqualTo(differentMove);

var anotherDifferentMove = new ListRuinRecreateMove<TestdataListSolution>(supply,
mock(RuinRecreateConstructionHeuristicPhaseBuilder.class), mock(SolverScope.class), List.of(e2),
Set.of(v1));
assertThat(move).isNotEqualTo(anotherDifferentMove);

var yetAnotherDifferentMove = new ListRuinRecreateMove<TestdataListSolution>(mock(ListVariableStateSupply.class),
mock(RuinRecreateConstructionHeuristicPhaseBuilder.class), mock(SolverScope.class), List.of(e1),
Set.of(v1));
assertThat(move).isNotEqualTo(yetAnotherDifferentMove);
}

}

0 comments on commit 9c36871

Please sign in to comment.