Skip to content

Commit

Permalink
PLANNER-1020 Add method Move.rebase(Scoredirector) on ChangeMove
Browse files Browse the repository at this point in the history
  • Loading branch information
ge0ffrey committed Feb 21, 2018
1 parent bf3888d commit 53729c3
Show file tree
Hide file tree
Showing 6 changed files with 102 additions and 11 deletions.
Expand Up @@ -82,6 +82,28 @@ public interface Move<Solution_> {
*/ */
Move<Solution_> doMove(ScoreDirector<Solution_> scoreDirector); Move<Solution_> doMove(ScoreDirector<Solution_> scoreDirector);


/**
* Rebases a move from an origin {@link ScoreDirector} to another destination {@link ScoreDirector}
* which is usually on another {@link Thread} or JVM.
* The new move returned by this method mutates the entities and problem facts
* on the {@link PlanningSolution} of the destination {@link ScoreDirector},
* which should be (at least) a deep planning clone of the {@link PlanningSolution}
* that this move was generated from.
* That new move does the exact same change as this move,
* resulting in the same {@link PlanningSolution} state,
* presuming that destination {@link PlanningSolution} was in the same state as the original {@link PlanningSolution}.
* <p>
* Generally speaking, an implementation of this method iterates through every entity and fact instance in this move,
* translates each one to the destination {@link ScoreDirector} with {@link ScoreDirector#lookUpWorkingObject(Object)}
* and creates a new move instance of the same move type, using those translated instances.
* @param destinationScoreDirector never null, the
* @return never null,
*/
default Move<Solution_> rebase(ScoreDirector<Solution_> destinationScoreDirector) {
throw new UnsupportedOperationException("The custom move class (" + getClass()
+ ") doesn't implement the rebase() method, so multithreaded solving is impossible.");
}

// ************************************************************************ // ************************************************************************
// Introspection methods // Introspection methods
// ************************************************************************ // ************************************************************************
Expand Down
Expand Up @@ -78,6 +78,13 @@ protected void doMoveOnGenuineVariables(ScoreDirector<Solution_> scoreDirector)
scoreDirector.afterVariableChanged(variableDescriptor, entity); scoreDirector.afterVariableChanged(variableDescriptor, entity);
} }


@Override
public ChangeMove<Solution_> rebase(ScoreDirector<Solution_> destinationScoreDirector) {
return new ChangeMove<>(destinationScoreDirector.lookUpWorkingObject(entity),
variableDescriptor,
destinationScoreDirector.lookUpWorkingObject(toPlanningValue));
}

// ************************************************************************ // ************************************************************************
// Introspection methods // Introspection methods
// ************************************************************************ // ************************************************************************
Expand Down
Expand Up @@ -100,16 +100,7 @@ protected AbstractMove<Solution_> createUndoMove(ScoreDirector<Solution_> scoreD
} }


@Override @Override
public Collection<? extends Object> getPlanningEntities() { public PartitionChangeMove<Solution_> rebase(ScoreDirector<Solution_> destinationScoreDirector) {
throw new UnsupportedOperationException();
}

@Override
public Collection<? extends Object> getPlanningValues() {
throw new UnsupportedOperationException();
}

public PartitionChangeMove<Solution_> rebase(InnerScoreDirector<Solution_> destinationScoreDirector) {
Map<GenuineVariableDescriptor<Solution_>, List<Pair<Object, Object>>> destinationChangeMap Map<GenuineVariableDescriptor<Solution_>, List<Pair<Object, Object>>> destinationChangeMap
= new LinkedHashMap<>(changeMap.size()); = new LinkedHashMap<>(changeMap.size());
for (Map.Entry<GenuineVariableDescriptor<Solution_>, List<Pair<Object, Object>>> entry : changeMap.entrySet()) { for (Map.Entry<GenuineVariableDescriptor<Solution_>, List<Pair<Object, Object>>> entry : changeMap.entrySet()) {
Expand Down Expand Up @@ -141,6 +132,16 @@ public PartitionChangeMove<Solution_> rebase(InnerScoreDirector<Solution_> desti
return new PartitionChangeMove<>(destinationChangeMap, partIndex); return new PartitionChangeMove<>(destinationChangeMap, partIndex);
} }


@Override
public Collection<? extends Object> getPlanningEntities() {
throw new UnsupportedOperationException();
}

@Override
public Collection<? extends Object> getPlanningValues() {
throw new UnsupportedOperationException();
}

@Override @Override
public String toString() { public String toString() {
int changeCount = changeMap.values().stream().mapToInt(List::size).sum(); int changeCount = changeMap.values().stream().mapToInt(List::size).sum();
Expand Down
Expand Up @@ -138,7 +138,7 @@ public interface ScoreDirector<Solution_> extends AutoCloseable {
/** /**
* Translates an entity or fact instance (often from another {@link Thread} or JVM) * Translates an entity or fact instance (often from another {@link Thread} or JVM)
* to this {@link ScoreDirector}'s internal working instance. * to this {@link ScoreDirector}'s internal working instance.
* Useful during {@link Move} rebasing and in a {@link ProblemFactChange}. * Useful for {@link Move#rebase(ScoreDirector)} and in a {@link ProblemFactChange}.
* <p> * <p>
* Matching is determined by the {@link LookUpStrategyType} on {@link PlanningSolution}. * Matching is determined by the {@link LookUpStrategyType} on {@link PlanningSolution}.
* Matching uses a {@link PlanningId} by default. * Matching uses a {@link PlanningId} by default.
Expand Down
Expand Up @@ -34,6 +34,8 @@
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.*; import static org.mockito.Mockito.*;
import static org.optaplanner.core.impl.testdata.util.PlannerAssert.*; import static org.optaplanner.core.impl.testdata.util.PlannerAssert.*;
import static org.optaplanner.core.impl.testdata.util.PlannerAssert.assertSame;
import static org.optaplanner.core.impl.testdata.util.PlannerTestUtils.*;


public class ChangeMoveTest { public class ChangeMoveTest {


Expand Down Expand Up @@ -138,4 +140,44 @@ public void toStringTestMultiVar() {
assertEquals("c {v4 -> v3}", new ChangeMove<>(c, variableDescriptor, v3).toString()); assertEquals("c {v4 -> v3}", new ChangeMove<>(c, variableDescriptor, v3).toString());
} }


@Test
public void rebase() {
GenuineVariableDescriptor<TestdataSolution> variableDescriptor = TestdataEntity.buildVariableDescriptorForValue();

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

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

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

assertSameProperties(destinationE1, null,
new ChangeMove<>(e1, variableDescriptor, null).rebase(destinationScoreDirector));
assertSameProperties(destinationE1, destinationV1,
new ChangeMove<>(e1, variableDescriptor, v1).rebase(destinationScoreDirector));
assertSameProperties(destinationE2, null,
new ChangeMove<>(e2, variableDescriptor, null).rebase(destinationScoreDirector));
assertSameProperties(destinationE3, destinationV2,
new ChangeMove<>(e3, variableDescriptor, v2).rebase(destinationScoreDirector));
}

public void assertSameProperties(Object entity, Object toPlanningVariable, ChangeMove changeMove) {
assertSame(entity, changeMove.getEntity());
assertSame(toPlanningVariable, changeMove.getToPlanningValue());
}

} }
Expand Up @@ -98,6 +98,25 @@ public static <Solution_> InnerScoreDirector<Solution_> mockScoreDirector(Soluti
return mock(InnerScoreDirector.class, AdditionalAnswers.delegatesTo(scoreDirectorFactory.buildScoreDirector(false, false))); return mock(InnerScoreDirector.class, AdditionalAnswers.delegatesTo(scoreDirectorFactory.buildScoreDirector(false, false)));
} }


public static <Solution_> InnerScoreDirector<Solution_> mockRebasingScoreDirector(
SolutionDescriptor<Solution_> solutionDescriptor, Object[][] lookUpMappings) {
InnerScoreDirector scoreDirector = mock(InnerScoreDirector.class);
when(scoreDirector.getSolutionDescriptor()).thenReturn(solutionDescriptor);
when(scoreDirector.lookUpWorkingObject(any())).thenAnswer((invocation) -> {
Object externalObject = invocation.getArguments()[0];
if (externalObject == null) {
return null;
}
for (Object[] lookUpMapping : lookUpMappings) {
if (externalObject == lookUpMapping[0]) {
return lookUpMapping[1];
}
}
throw new IllegalStateException("No method mocked for parameter (" + externalObject + ").");
});
return scoreDirector;
}

// ************************************************************************ // ************************************************************************
// Serialization methods // Serialization methods
// ************************************************************************ // ************************************************************************
Expand Down

0 comments on commit 53729c3

Please sign in to comment.