/* * Copyright 2012 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.heuristic.selector.move.composite; import java.util.Collections; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.NavigableMap; import java.util.TreeMap; import com.google.common.collect.Iterators; import org.optaplanner.core.impl.heuristic.move.Move; import org.optaplanner.core.impl.heuristic.selector.common.decorator.SelectionProbabilityWeightFactory; import org.optaplanner.core.impl.heuristic.selector.common.iterator.SelectionIterator; import org.optaplanner.core.impl.heuristic.selector.move.MoveSelector; import org.optaplanner.core.impl.phase.scope.AbstractStepScope; import org.optaplanner.core.impl.score.director.ScoreDirector; import org.optaplanner.core.impl.solver.random.RandomUtils; /** * A {@link CompositeMoveSelector} that unions 2 or more {@link MoveSelector}s. * <p> * For example: a union of {A, B, C} and {X, Y} will result in {A, B, C, X, Y}. * <p> * Warning: there is no duplicated {@link Move} check, so union of {A, B, C} and {B, D} will result in {A, B, C, B, D}. * @see CompositeMoveSelector */ public class UnionMoveSelector extends CompositeMoveSelector { protected final SelectionProbabilityWeightFactory selectorProbabilityWeightFactory; protected ScoreDirector scoreDirector; public UnionMoveSelector(List<MoveSelector> childMoveSelectorList, boolean randomSelection) { this(childMoveSelectorList, randomSelection, null); } public UnionMoveSelector(List<MoveSelector> childMoveSelectorList, boolean randomSelection, SelectionProbabilityWeightFactory selectorProbabilityWeightFactory) { super(childMoveSelectorList, randomSelection); this.selectorProbabilityWeightFactory = selectorProbabilityWeightFactory; if (!randomSelection) { if (selectorProbabilityWeightFactory != null) { throw new IllegalArgumentException("The selector (" + this + ") with randomSelection (" + randomSelection + ") cannot have a selectorProbabilityWeightFactory (" + selectorProbabilityWeightFactory + ")."); } } else { if (selectorProbabilityWeightFactory == null) { throw new IllegalArgumentException("The selector (" + this + ") with randomSelection (" + randomSelection + ") requires a selectorProbabilityWeightFactory (" + selectorProbabilityWeightFactory + ")."); } } } @Override public void stepStarted(AbstractStepScope stepScope) { scoreDirector = stepScope.getScoreDirector(); super.stepStarted(stepScope); } @Override public void stepEnded(AbstractStepScope stepScope) { super.stepEnded(stepScope); scoreDirector = null; } // ************************************************************************ // Worker methods // ************************************************************************ @Override public boolean isNeverEnding() { if (randomSelection) { for (MoveSelector moveSelector : childMoveSelectorList) { if (moveSelector.isNeverEnding()) { return true; } } // The UnionMoveSelector is special: it can be randomSelection true and still neverEnding false return false; } else { // Only the last childMoveSelector can be neverEnding if (!childMoveSelectorList.isEmpty() && childMoveSelectorList.get(childMoveSelectorList.size() - 1).isNeverEnding()) { return true; } return false; } } @Override public long getSize() { long size = 0L; for (MoveSelector moveSelector : childMoveSelectorList) { size += moveSelector.getSize(); } return size; } @Override public Iterator<Move> iterator() { if (!randomSelection) { Iterator<Move> iterator = Collections.emptyIterator(); for (MoveSelector moveSelector : childMoveSelectorList) { iterator = Iterators.concat(iterator, moveSelector.iterator()); } return iterator; } else { return new RandomUnionMoveIterator(); } } public class RandomUnionMoveIterator extends SelectionIterator<Move> { protected final Map<Iterator<Move>, ProbabilityItem> probabilityItemMap; protected final NavigableMap<Double, Iterator<Move>> moveIteratorMap; protected double probabilityWeightTotal; protected boolean stale; public RandomUnionMoveIterator() { probabilityItemMap = new LinkedHashMap<>(childMoveSelectorList.size()); for (MoveSelector moveSelector : childMoveSelectorList) { Iterator<Move> moveIterator = moveSelector.iterator(); ProbabilityItem probabilityItem = new ProbabilityItem(); probabilityItem.moveSelector = moveSelector; probabilityItem.moveIterator = moveIterator; probabilityItem.probabilityWeight = selectorProbabilityWeightFactory .createProbabilityWeight(scoreDirector, moveSelector); if (probabilityItem.probabilityWeight < 0.0) { throw new IllegalStateException( "The selectorProbabilityWeightFactory (" + selectorProbabilityWeightFactory + ") returned a negative probabilityWeight (" + probabilityItem.probabilityWeight + ")."); } probabilityItemMap.put(moveIterator, probabilityItem); } moveIteratorMap = new TreeMap<>(); stale = true; } @Override public boolean hasNext() { if (stale) { refreshMoveIteratorMap(); } return !moveIteratorMap.isEmpty(); } @Override public Move next() { if (stale) { refreshMoveIteratorMap(); } double randomOffset = RandomUtils.nextDouble(workingRandom, probabilityWeightTotal); Map.Entry<Double, Iterator<Move>> entry = moveIteratorMap.floorEntry(randomOffset); // entry is never null because randomOffset < probabilityWeightTotal Iterator<Move> moveIterator = entry.getValue(); Move next = moveIterator.next(); if (!moveIterator.hasNext()) { stale = true; } return next; } private void refreshMoveIteratorMap() { moveIteratorMap.clear(); double probabilityWeightOffset = 0.0; for (ProbabilityItem probabilityItem : probabilityItemMap.values()) { if (probabilityItem.probabilityWeight != 0.0 && probabilityItem.moveIterator.hasNext()) { moveIteratorMap.put(probabilityWeightOffset, probabilityItem.moveIterator); probabilityWeightOffset += probabilityItem.probabilityWeight; } } probabilityWeightTotal = probabilityWeightOffset; } } private static class ProbabilityItem { protected MoveSelector moveSelector; protected Iterator<Move> moveIterator; protected double probabilityWeight; } @Override public String toString() { return "Union(" + childMoveSelectorList + ")"; } }