Skip to content

Commit

Permalink
PLANNER-491 locator prototype (unfinished)
Browse files Browse the repository at this point in the history
  • Loading branch information
ge0ffrey committed Nov 17, 2016
1 parent 7103acc commit 006c70f
Show file tree
Hide file tree
Showing 27 changed files with 248 additions and 91 deletions.
@@ -0,0 +1,50 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.optaplanner.core.api.domain.id;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.util.Comparator;

import org.optaplanner.core.api.domain.entity.PlanningEntity;
import org.optaplanner.core.api.domain.solution.PlanningSolution;
import org.optaplanner.core.api.domain.solution.drools.ProblemFactCollectionProperty;
import org.optaplanner.core.api.domain.valuerange.ValueRangeProvider;
import org.optaplanner.core.api.domain.variable.PlanningVariableGraphType;
import org.optaplanner.core.impl.heuristic.move.Move;
import org.optaplanner.core.impl.heuristic.selector.common.decorator.SelectionFilter;
import org.optaplanner.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory;
import org.optaplanner.core.impl.score.director.ScoreDirector;
import org.optaplanner.core.impl.solver.ProblemFactChange;

import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.*;

/**
* Specifies that a bean property (or a field) is the id to match
* when {@link ScoreDirector#locateWorkingObject(Object) locating}
* an externalObject (often from another {@link Thread} or JVM).
* Used during {@link Move} relocation and in a {@link ProblemFactChange}.
* <p>
* It is specified on a getter of a java bean property (or directly on a field) of a {@link PlanningEntity} class,
* {@link ValueRangeProvider planning value} class or any {@link ProblemFactCollectionProperty problem fact} class.
*/
@Target({METHOD, FIELD})
@Retention(RUNTIME)
public @interface PlanningId {

}
Expand Up @@ -64,7 +64,7 @@ public boolean isConstraintMatchEnabled() {
public Collection<ConstraintMatchTotal> getConstraintMatchTotals() {
if (!isConstraintMatchEnabled()) {
throw new IllegalStateException("When constraintMatchEnabled (" + isConstraintMatchEnabled()
+ ") is disabled, this method should not be called.");
+ ") is disabled in the constructor, this method should not be called.");
}
return constraintMatchTotalMap.values();
}
Expand Down
Expand Up @@ -221,7 +221,7 @@ public <Solution_> Solver<Solution_> buildSolver(SolverConfigContext configConte
configContext, environmentMode_, solutionDescriptor);
boolean constraintMatchEnabledPreference = environmentMode_.isAsserted();
DefaultSolverScope<Solution_> solverScope = new DefaultSolverScope<>();
solverScope.setScoreDirector(scoreDirectorFactory.buildScoreDirector(constraintMatchEnabledPreference));
solverScope.setScoreDirector(scoreDirectorFactory.buildScoreDirector(true, constraintMatchEnabledPreference));

HeuristicConfigPolicy configPolicy = new HeuristicConfigPolicy(environmentMode_, scoreDirectorFactory);
TerminationConfig terminationConfig_ = terminationConfig == null ? new TerminationConfig()
Expand Down
@@ -0,0 +1,93 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.optaplanner.core.impl.domain.id;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.HashMap;
import java.util.Map;

import org.optaplanner.core.api.domain.id.PlanningId;
import org.optaplanner.core.impl.domain.solution.descriptor.SolutionDescriptor;
import org.optaplanner.core.impl.score.director.ScoreDirector;

/**
* @see PlanningId
* @see ScoreDirector#locateWorkingObject(Object)
*/
public class Locator {

protected final Map<Class, LocationStrategy> decisionClassCache = new HashMap<>();

public Locator() {
decisionClassCache.put(Boolean.class, new ImmutableLocationStrategy());
decisionClassCache.put(Byte.class, new ImmutableLocationStrategy());
decisionClassCache.put(Short.class, new ImmutableLocationStrategy());
decisionClassCache.put(Integer.class, new ImmutableLocationStrategy());
decisionClassCache.put(Long.class, new ImmutableLocationStrategy());
decisionClassCache.put(Float.class, new ImmutableLocationStrategy());
decisionClassCache.put(Double.class, new ImmutableLocationStrategy());
decisionClassCache.put(BigInteger.class, new ImmutableLocationStrategy());
decisionClassCache.put(BigDecimal.class, new ImmutableLocationStrategy());
decisionClassCache.put(Character.class, new ImmutableLocationStrategy());
decisionClassCache.put(String.class, new ImmutableLocationStrategy());
decisionClassCache.put(LocalDate.class, new ImmutableLocationStrategy());
decisionClassCache.put(LocalTime.class, new ImmutableLocationStrategy());
decisionClassCache.put(LocalDateTime.class, new ImmutableLocationStrategy());

}

public <E> E locateWorkingObject(E externalObject) {
if (externalObject == null) {
return null;
}
// TODO HACK UNEFFICIENT BUGGY IMPLEMENTATION!!!!

// SolutionDescriptor<Solution_> solutionDescriptor = getSolutionDescriptor();
// for (Object object : solutionDescriptor.getAllFacts(workingSolution)) {
// if (externalObject.toString().equals(object.toString())) {
// return (E) object;
// }
// }

throw new IllegalArgumentException("The externalObject (" + externalObject + ") cannot be located.");
}

public void clearWorkingSolution() {

}

private interface LocationStrategy {

}

private static class ImmutableLocationStrategy implements LocationStrategy {

}

private static class PlanningIdLocationStrategy implements LocationStrategy {

}

private static class EqualsLocationStrategy implements LocationStrategy {

}

}
Expand Up @@ -109,7 +109,7 @@ public void solve(DefaultSolverScope<Solution_> solverScope) {
InnerScoreDirector<Solution_> childScoreDirector = partitionSolver.solverScope.getScoreDirector();
PartitionChangeMove<Solution_> move = PartitionChangeMove.createMove(childScoreDirector);
InnerScoreDirector<Solution_> parentScoreDirector = solverScope.getScoreDirector();
move = move.rebase(childScoreDirector, parentScoreDirector);
move = move.relocate(parentScoreDirector);
partitionQueue.addMove(partIndex, move);
});
threadPoolExecutor.submit(() -> {
Expand Down
Expand Up @@ -104,8 +104,7 @@ public Collection<? extends Object> getPlanningValues() {
throw new UnsupportedOperationException();
}

public PartitionChangeMove<Solution_> rebase(InnerScoreDirector<Solution_> originScoreDirector,
InnerScoreDirector<Solution_> destinationScoreDirector) {
public PartitionChangeMove<Solution_> relocate(InnerScoreDirector<Solution_> destinationScoreDirector) {
Map<GenuineVariableDescriptor<Solution_>, List<Pair<Object, Object>>> destinationChangeMap
= new LinkedHashMap<>(changeMap.size());
for (Map.Entry<GenuineVariableDescriptor<Solution_>, List<Pair<Object, Object>>> entry : changeMap.entrySet()) {
Expand All @@ -115,8 +114,8 @@ public PartitionChangeMove<Solution_> rebase(InnerScoreDirector<Solution_> origi
for (Pair<Object, Object> pair : originPairList) {
Object originEntity = pair.getKey();
Object originValue = pair.getValue();
Object destinationEntity = destinationScoreDirector.translateForRebase(originScoreDirector, originEntity);
Object destinationValue = destinationScoreDirector.translateForRebase(originScoreDirector, originValue);
Object destinationEntity = destinationScoreDirector.locateWorkingObject(originEntity);
Object destinationValue = destinationScoreDirector.locateWorkingObject(originValue);
destinationPairList.add(Pair.of(destinationEntity, destinationValue));
}
destinationChangeMap.put(variableDescriptor, destinationPairList);
Expand Down
Expand Up @@ -32,8 +32,8 @@
import org.optaplanner.core.api.score.constraint.ConstraintMatch;
import org.optaplanner.core.api.score.constraint.ConstraintMatchTotal;
import org.optaplanner.core.impl.domain.entity.descriptor.EntityDescriptor;
import org.optaplanner.core.impl.domain.id.Locator;
import org.optaplanner.core.impl.domain.solution.descriptor.SolutionDescriptor;
import org.optaplanner.core.impl.domain.variable.descriptor.GenuineVariableDescriptor;
import org.optaplanner.core.impl.domain.variable.descriptor.ShadowVariableDescriptor;
import org.optaplanner.core.impl.domain.variable.descriptor.VariableDescriptor;
import org.optaplanner.core.impl.domain.variable.listener.VariableListener;
Expand Down Expand Up @@ -62,6 +62,7 @@ public abstract class AbstractScoreDirector<Solution_, Factory_ extends Abstract
protected final Factory_ scoreDirectorFactory;
protected boolean constraintMatchEnabledPreference;
protected final VariableListenerSupport<Solution_> variableListenerSupport;
protected final Locator locator;

protected Solution_ workingSolution;
protected long workingEntityListRevision = 0L;
Expand All @@ -71,11 +72,13 @@ public abstract class AbstractScoreDirector<Solution_, Factory_ extends Abstract

protected long calculationCount = 0L;

protected AbstractScoreDirector(Factory_ scoreDirectorFactory, boolean constraintMatchEnabledPreference) {
protected AbstractScoreDirector(Factory_ scoreDirectorFactory,
boolean locatorEnabled, boolean constraintMatchEnabledPreference) {
this.scoreDirectorFactory = scoreDirectorFactory;
this.constraintMatchEnabledPreference = constraintMatchEnabledPreference;
variableListenerSupport = new VariableListenerSupport<>(this);
variableListenerSupport.linkVariableListeners();
locator = locatorEnabled ? new Locator() : null;
}

@Override
Expand All @@ -93,6 +96,10 @@ public ScoreDefinition getScoreDefinition() {
return scoreDirectorFactory.getScoreDefinition();
}

public boolean isLocatorEnabled() {
return locator != null;
}

public boolean isConstraintMatchEnabledPreference() {
return constraintMatchEnabledPreference;
}
Expand Down Expand Up @@ -242,15 +249,15 @@ public AbstractScoreDirector<Solution_, Factory_> clone() {
// Breaks incremental score calculation.
// Subclasses should overwrite this method to avoid breaking it if possible.
AbstractScoreDirector<Solution_, Factory_> clone = (AbstractScoreDirector<Solution_, Factory_>)
scoreDirectorFactory.buildScoreDirector(constraintMatchEnabledPreference);
scoreDirectorFactory.buildScoreDirector(isLocatorEnabled(), constraintMatchEnabledPreference);
clone.setWorkingSolution(cloneWorkingSolution());
return clone;
}

@Override
public InnerScoreDirector<Solution_> createChildThreadScoreDirector(ChildThreadType childThreadType) {
AbstractScoreDirector<Solution_, Factory_> childThreadScoreDirector = (AbstractScoreDirector<Solution_, Factory_>)
scoreDirectorFactory.buildScoreDirector(constraintMatchEnabledPreference);
scoreDirectorFactory.buildScoreDirector(false, constraintMatchEnabledPreference);
if (childThreadType == ChildThreadType.PART_THREAD) {
// ScoreCalculationCountTermination takes into account previous phases
// but the calculationCount of partitions is maxed, not summed.
Expand All @@ -266,6 +273,7 @@ public void dispose() {
workingSolution = null;
workingInitScore = null;
variableListenerSupport.clearWorkingSolution();
locator.clearWorkingSolution();
}

// ************************************************************************
Expand Down Expand Up @@ -387,25 +395,13 @@ public void afterProblemFactRemoved(Object problemFact) {
variableListenerSupport.resetWorkingSolution(); // TODO do not nuke it
}

// ************************************************************************
// Rebase methods
// ************************************************************************

@Override
public Object translateForRebase(InnerScoreDirector<Solution_> originScoreDirector, Object originObject) {
if (originObject == null) {
return null;
}

// TODO HACK UNEFFICIENT BUGGY IMPLEMENTATION!!!!

SolutionDescriptor<Solution_> solutionDescriptor = getSolutionDescriptor();
for (Object object : solutionDescriptor.getAllFacts(workingSolution)) {
if (originObject.toString().equals(object.toString())) {
return object;
}
public <E> E locateWorkingObject(E externalObject) {
if (!isLocatorEnabled()) {
throw new IllegalStateException("When locatorEnabled (" + isLocatorEnabled()
+ ") is disabled in the constructor, this method should not be called.");
}
throw new IllegalArgumentException("The originObject (" + originObject + ") cannot be translated for rebase.");
return locator.locateWorkingObject(externalObject);
}

// ************************************************************************
Expand Down Expand Up @@ -481,7 +477,8 @@ public void assertWorkingScoreFromScratch(Score workingScore, Object completedAc
if (assertionScoreDirectorFactory == null) {
assertionScoreDirectorFactory = scoreDirectorFactory;
}
InnerScoreDirector<Solution_> uncorruptedScoreDirector = assertionScoreDirectorFactory.buildScoreDirector(true);
InnerScoreDirector<Solution_> uncorruptedScoreDirector =
assertionScoreDirectorFactory.buildScoreDirector(false, true);
uncorruptedScoreDirector.setWorkingSolution(workingSolution);
Score uncorruptedScore = uncorruptedScoreDirector.calculateScore();
if (!workingScore.equals(uncorruptedScore)) {
Expand Down
Expand Up @@ -86,14 +86,14 @@ public void setAssertClonedSolution(boolean assertClonedSolution) {

@Override
public InnerScoreDirector<Solution_> buildScoreDirector() {
return buildScoreDirector(true);
return buildScoreDirector(true, true);
}

@Override
public void assertScoreFromScratch(Solution_ solution) {
// Get the score before uncorruptedScoreDirector.calculateScore() modifies it
Score score = getSolutionDescriptor().getScore(solution);
InnerScoreDirector<Solution_> uncorruptedScoreDirector = buildScoreDirector(true);
InnerScoreDirector<Solution_> uncorruptedScoreDirector = buildScoreDirector(false, true);
uncorruptedScoreDirector.setWorkingSolution(solution);
Score uncorruptedScore = uncorruptedScoreDirector.calculateScore();
uncorruptedScoreDirector.dispose();
Expand Down
Expand Up @@ -24,8 +24,10 @@
import org.optaplanner.core.impl.domain.solution.descriptor.SolutionDescriptor;
import org.optaplanner.core.impl.domain.variable.listener.VariableListener;
import org.optaplanner.core.impl.domain.variable.supply.SupplyManager;
import org.optaplanner.core.impl.heuristic.move.Move;
import org.optaplanner.core.impl.score.definition.ScoreDefinition;
import org.optaplanner.core.impl.solver.ChildThreadType;
import org.optaplanner.core.impl.solver.ProblemFactChange;

/**
* @param <Solution_> the solution type, the class with the {@link PlanningSolution} annotation
Expand Down Expand Up @@ -124,14 +126,6 @@ public interface InnerScoreDirector<Solution_> extends ScoreDirector<Solution_>
*/
void setAllChangesWillBeUndoneBeforeStepEnds(boolean allChangesWillBeUndoneBeforeStepEnds);

/**
* Rebasing translates an entity or fact instance from another {@link Thread}'s {@link ScoreDirector} to this one's.
* @param originScoreDirector never null
* @param originEntity sometimes null
* @return only null if originEntity is null
*/
Object translateForRebase(InnerScoreDirector<Solution_> originScoreDirector, Object originEntity);

/**
* Asserts that if the {@link Score} is calculated for the current {@link PlanningSolution working solution}
* in the current {@link ScoreDirector} (with possibly incremental calculation residue),
Expand Down
Expand Up @@ -42,15 +42,17 @@ public interface InnerScoreDirectorFactory<Solution_> extends ScoreDirectorFacto
InnerScoreDirector<Solution_> buildScoreDirector();

/**
* Like {@link #buildScoreDirector()}, but optionally disables {@link ConstraintMatch} tracking
* for more performance (presuming the {@link ScoreDirector} implementation actually supports it to begin with).
* Like {@link #buildScoreDirector()}, but optionally disables {@link ConstraintMatch} tracking and location
* for more performance (presuming the {@link ScoreDirector} implementation actually supports it to begin with)
* @param locatorEnabled true if a {@link ScoreDirector} implementation should track all working objects
* for {@link ScoreDirector#locateWorkingObject(Object)}
* @param constraintMatchEnabledPreference false if a {@link ScoreDirector} implementation
* should not do {@link ConstraintMatch} tracking even if it supports it.
* @return never null
* @see ScoreDirector#isConstraintMatchEnabled()
* @see ScoreDirector#getConstraintMatchTotals()
*/
InnerScoreDirector<Solution_> buildScoreDirector(boolean constraintMatchEnabledPreference);
InnerScoreDirector<Solution_> buildScoreDirector(boolean locatorEnabled, boolean constraintMatchEnabledPreference);

/**
* @return never null
Expand Down

0 comments on commit 006c70f

Please sign in to comment.