0

I'm getting an UnsupportedOperationException when implementing an immovable entity as per the Optaplanner documentation. I'm using version 6.4.0.

My entity:

@PlanningEntity(difficultyComparatorClass=AssignmentCreditHoursComparator.class, movableEntitySelectionFilter=ImmovableChangeMoveFilter.class) public class Assignment{...}

My Filter

public class ImmovableChangeMoveFilter implements SelectionFilter<Assignment> {
@Override
public boolean accept(ScoreDirector scoreDirector, Assignment a) {
    if(a.isLocked())
        return false;
    else
        return true;    
}

}

The exception:

Exception in thread "main" java.lang.UnsupportedOperationException
at org.optaplanner.core.impl.heuristic.selector.entity.decorator.FilteringEntitySelector.listIterator(FilteringEntitySelector.java:125)
at org.optaplanner.core.impl.heuristic.selector.common.iterator.AbstractOriginalSwapIterator.<init>(AbstractOriginalSwapIterator.java:49)
at org.optaplanner.core.impl.heuristic.selector.move.generic.SwapMoveSelector$1.<init>(SwapMoveSelector.java:129)
at org.optaplanner.core.impl.heuristic.selector.move.generic.SwapMoveSelector.iterator(SwapMoveSelector.java:129)
at org.optaplanner.core.impl.heuristic.selector.move.decorator.AbstractCachingMoveSelector.constructCache(AbstractCachingMoveSelector.java:76)
at org.optaplanner.core.impl.heuristic.selector.common.SelectionCacheLifecycleBridge.phaseStarted(SelectionCacheLifecycleBridge.java:49)
at org.optaplanner.core.impl.phase.event.PhaseLifecycleSupport.firePhaseStarted(PhaseLifecycleSupport.java:39)
at org.optaplanner.core.impl.heuristic.selector.AbstractSelector.phaseStarted(AbstractSelector.java:47)
at org.optaplanner.core.impl.phase.event.PhaseLifecycleSupport.firePhaseStarted(PhaseLifecycleSupport.java:39)
at org.optaplanner.core.impl.heuristic.selector.AbstractSelector.phaseStarted(AbstractSelector.java:47)
at org.optaplanner.core.impl.phase.event.PhaseLifecycleSupport.firePhaseStarted(PhaseLifecycleSupport.java:39)
at org.optaplanner.core.impl.heuristic.selector.AbstractSelector.phaseStarted(AbstractSelector.java:47)
at org.optaplanner.core.impl.localsearch.decider.LocalSearchDecider.phaseStarted(LocalSearchDecider.java:97)
at org.optaplanner.core.impl.localsearch.DefaultLocalSearchPhase.phaseStarted(DefaultLocalSearchPhase.java:122)
at org.optaplanner.core.impl.localsearch.DefaultLocalSearchPhase.solve(DefaultLocalSearchPhase.java:66)
at org.optaplanner.core.impl.solver.DefaultSolver.runPhases(DefaultSolver.java:215)
at org.optaplanner.core.impl.solver.DefaultSolver.solve(DefaultSolver.java:176)

This has been taken from the Optaplanner code...

public class FilteringEntitySelector extends AbstractEntitySelector {

protected final EntitySelector childEntitySelector;
protected final List<SelectionFilter> filterList;
protected final boolean bailOutEnabled;

protected ScoreDirector scoreDirector = null;

public FilteringEntitySelector(EntitySelector childEntitySelector, List<SelectionFilter> filterList) {
    this.childEntitySelector = childEntitySelector;
    this.filterList = filterList;
    bailOutEnabled = childEntitySelector.isNeverEnding();
    phaseLifecycleSupport.addEventListener(childEntitySelector);
}

// ************************************************************************
// Worker methods
// ************************************************************************

@Override
public void phaseStarted(AbstractPhaseScope phaseScope) {
    super.phaseStarted(phaseScope);
    scoreDirector = phaseScope.getScoreDirector();
}

@Override
public void phaseEnded(AbstractPhaseScope phaseScope) {
    super.phaseEnded(phaseScope);
    scoreDirector = null;
}

public EntityDescriptor getEntityDescriptor() {
    return childEntitySelector.getEntityDescriptor();
}

public boolean isCountable() {
    return childEntitySelector.isCountable();
}

public boolean isNeverEnding() {
    return childEntitySelector.isNeverEnding();
}

public long getSize() {
    return childEntitySelector.getSize();
}

public Iterator<Object> iterator() {
    return new JustInTimeFilteringEntityIterator(childEntitySelector.iterator());
}

protected class JustInTimeFilteringEntityIterator extends UpcomingSelectionIterator<Object> {

    private final Iterator<Object> childEntityIterator;
    private final long bailOutSize;

    public JustInTimeFilteringEntityIterator(Iterator<Object> childEntityIterator) {
        this.childEntityIterator = childEntityIterator;
        this.bailOutSize = determineBailOutSize();
    }

    @Override
    protected Object createUpcomingSelection() {
        Object next;
        long attemptsBeforeBailOut = bailOutSize;
        do {
            if (!childEntityIterator.hasNext()) {
                return noUpcomingSelection();
            }
            if (bailOutEnabled) {
                // if childEntityIterator is neverEnding and nothing is accepted, bail out of the infinite loop
                if (attemptsBeforeBailOut <= 0L) {
                    logger.warn("Bailing out of neverEnding selector ({}) to avoid infinite loop.",
                            FilteringEntitySelector.this);
                    return noUpcomingSelection();
                }
                attemptsBeforeBailOut--;
            }
            next = childEntityIterator.next();
        } while (!accept(scoreDirector, next));
        return next;
    }

}

protected long determineBailOutSize() {
    if (!bailOutEnabled) {
        return -1L;
    }
    return childEntitySelector.getSize() * 10L;
}

public ListIterator<Object> listIterator() {
    // TODO Not yet implemented
    throw new UnsupportedOperationException();
}

public ListIterator<Object> listIterator(int index) {
    // TODO Not yet implemented
    throw new UnsupportedOperationException();
}

public Iterator<Object> endingIterator() {
    return new JustInTimeFilteringEntityIterator(childEntitySelector.endingIterator());
}

private boolean accept(ScoreDirector scoreDirector, Object entity) {
    for (SelectionFilter filter : filterList) {
        if (!filter.accept(scoreDirector, entity)) {
            return false;
        }
    }
    return true;
}

@Override
public String toString() {
    return "Filtering(" + childEntitySelector + ")";
}

}

<solver>  
<scanAnnotatedClasses>
    <packageInclude>com.fast.planner.solution</packageInclude>
</scanAnnotatedClasses>

<scoreDirectorFactory>
    <scoreDefinitionType>HARD_SOFT</scoreDefinitionType>
    <scoreDrl>com/fast/planner/solution/rostering-rules.drl</scoreDrl>
    <initializingScoreTrend>ONLY_DOWN/ONLY_DOWN</initializingScoreTrend> 
</scoreDirectorFactory>



<localSearch>
    <unionMoveSelector>

        <cacheType>PHASE</cacheType>
        <selectionOrder>SHUFFLED</selectionOrder>

        <changeMoveSelector>
            <cacheType>PHASE</cacheType>

            <filterClass>com.fast.planner.solution.filter.ReserveRestrictionsChangeMoveFilter</filterClass>
            <fixedProbabilityWeight>1.0</fixedProbabilityWeight>
        </changeMoveSelector>
        <swapMoveSelector>
            <cacheType>PHASE</cacheType>
            <fixedProbabilityWeight>2.0</fixedProbabilityWeight>
        </swapMoveSelector>


    </unionMoveSelector>
    <acceptor>
        <simulatedAnnealingStartingTemperature>2hard/200soft</simulatedAnnealingStartingTemperature>
        <entityTabuSize>5</entityTabuSize>
    </acceptor>
    <forager>
        <acceptedCountLimit>4</acceptedCountLimit>
    </forager>
</localSearch>

  • What's your solver configuration? IIRC entity filtering in combination with selectionOrder ORIGINAL isn't supported yet, but I know that several examples and unit tests that should be doing that with a CH with an immovable filter. So it must be a very specific combination, so I wonder which one (hence the solver config xml need). – Geoffrey De Smet Jul 14 '16 at 06:09
  • Hi Geoffrey. Config added. – busdrivingtupperware Jul 14 '16 at 08:24
  • Can you reproduce with `7.0.0.Beta1`? I remember fixing some things around filtering after `6.4.0.Final`. It must be one of those 2 moveSelector. Can you comment out each one separately to determine which ones trigger the problem? – Geoffrey De Smet Jul 14 '16 at 09:58
  • 1
    Thanks Geoffrey. I commented out the section... ` PHASE 2.0 `, and it worked on 6.4.0 & 7.0.0Beta. However, using 7.0.0Beta without commenting it out still had the exception. – busdrivingtupperware Jul 14 '16 at 13:31
  • 1
    Thanks, that clarifies. [I 've created an issue](https://issues.jboss.org/browse/PLANNER-621). This is a bug. It's the union shuffling that triggers the swap move to be in original order - and that doesn't like to be combined with immovable entities. – Geoffrey De Smet Jul 14 '16 at 15:30
  • Thanks again Geoffrey. – busdrivingtupperware Jul 15 '16 at 11:07

1 Answers1

0

This is a bug and is resolved in later versions.