ido flax
ido flax

Reputation: 528

Optaplanner custom MoveFactory not being used

i'm in the process of reviving my optaplanner code that i wrote about 6 months ago and while trying to figure out why some of my hard constraints are being broken i found that a filter that i wrote that is supposed to filter out illegal moves is not being referred to. I put breakpoints at all of the methods of the move factory, the moves methods, and the filter and none are being called. I'm pretty sure that that wasn'nt the case before i updated to the latest version but i might be wrong.

Update: the factory is being used when i run optaplanner in my test case but not in production, so i guess this is not to due with my configuration but rather the scenario, but i don't know what may affect it being used or not

my solver config:

<?xml version="1.0" encoding="UTF-8"?>
<solver>
<environmentMode>FULL_ASSERT</environmentMode>

<!-- Domain model configuration -->
<solutionClass>com.rdthree.plenty.services.activities.planner.ActivitySolution</solutionClass>
<entityClass>com.rdthree.plenty.services.activities.helpers.dtos.TaskPlannerDto</entityClass>
<entityClass>com.rdthree.plenty.services.activities.helpers.dtos.TaskResourceAllocationPlannerDto</entityClass>

<!-- Score configuration -->
<scoreDirectorFactory>
    <scoreDefinitionType>HARD_SOFT</scoreDefinitionType>
    <scoreDrl>com/rdthree/plenty/services/activities/planner/activity-scoring.drl</scoreDrl>
    <initializingScoreTrend>ONLY_DOWN</initializingScoreTrend>
</scoreDirectorFactory>

<!-- Optimization algorithms configuration -->
<termination>
    <terminationCompositionStyle>OR</terminationCompositionStyle>
    <bestScoreLimit>0hard/0soft</bestScoreLimit>
    <secondsSpentLimit>60</secondsSpentLimit>
</termination>

<constructionHeuristic>
    <queuedEntityPlacer>
        <entitySelector id="resourceAllocationSelector">
            <entityClass>com.rdthree.plenty.services.activities.helpers.dtos.TaskResourceAllocationPlannerDto</entityClass>
            <cacheType>PHASE</cacheType>
            <selectionOrder>SORTED</selectionOrder>
            <sorterManner>DECREASING_DIFFICULTY_IF_AVAILABLE</sorterManner>
        </entitySelector>
        <changeMoveSelector>
            <entitySelector mimicSelectorRef="resourceAllocationSelector" />
            <valueSelector>
                <variableName>resource</variableName>
                <cacheType>PHASE</cacheType>
            </valueSelector>
        </changeMoveSelector>
    </queuedEntityPlacer>
</constructionHeuristic>

<constructionHeuristic>
    <queuedEntityPlacer>
        <entitySelector id="taskSelector">
            <entityClass>com.rdthree.plenty.services.activities.helpers.dtos.TaskPlannerDto</entityClass>
            <cacheType>PHASE</cacheType>
            <selectionOrder>SORTED</selectionOrder>
            <sorterManner>DECREASING_DIFFICULTY_IF_AVAILABLE</sorterManner>
        </entitySelector>
        <changeMoveSelector>
            <entitySelector mimicSelectorRef="taskSelector" />
            <filterClass>com.rdthree.plenty.services.activities.planner.filters.TaskLengthChnageFilter</filterClass>
            <valueSelector>
                <variableName>interval</variableName>
                <cacheType>PHASE</cacheType>
            </valueSelector>
        </changeMoveSelector>
    </queuedEntityPlacer>
</constructionHeuristic>

<localSearch>
    <unionMoveSelector>
        <moveListFactory>
            <moveListFactoryClass>com.rdthree.plenty.services.activities.planner.MoveResourceAllocationMoveFactory</moveListFactoryClass>
        </moveListFactory>
        <changeMoveSelector>
            <fixedProbabilityWeight>1.0</fixedProbabilityWeight>
            <filterClass>com.rdthree.plenty.services.activities.planner.filters.TaskLengthChnageFilter</filterClass>
            <entitySelector id="taskMoveSelector">
                <entityClass>com.rdthree.plenty.services.activities.helpers.dtos.TaskPlannerDto</entityClass>
            </entitySelector>
            <valueSelector>
                <variableName>interval</variableName>
            </valueSelector>
        </changeMoveSelector>
    </unionMoveSelector>

    <acceptor>
        <valueTabuSize>7</valueTabuSize>
    </acceptor>
    <forager>
        <acceptedCountLimit>2000</acceptedCountLimit>
    </forager>
</localSearch>

my custom move factory:

public class MoveResourceAllocationMoveFactory implements MoveListFactory<ActivitySolution> {

@Override
public List<? extends Move> createMoveList(ActivitySolution solution) {
    List<Move> moveList = new ArrayList<Move>();
    for (TaskResourceAllocationPlannerDto allocation : solution.getResourceAllocations()) {
        for (TaskResourcePlannerDto resource : solution.getResources()) {
            moveList.add(new MoveResourceAllocations(allocation, resource));
        }
    }
    return moveList;
}

}

my custom move:

public class MoveResourceAllocations extends AbstractMove {

private TaskResourceAllocationPlannerDto allocation;

private TaskResourcePlannerDto newResource;

@Getter
@Setter
boolean doMove;

public MoveResourceAllocations(TaskResourceAllocationPlannerDto allocation, TaskResourcePlannerDto newResource) {
    super();
    this.allocation = allocation;
    this.newResource = newResource;
}

@Override
public boolean isMoveDoable(ScoreDirector scoreDirector) {
    if (allocation.getResource().equals(newResource)) {
        return false;
    }
    return new ResourceTypeMismatchFilter().acceptCustomMove(scoreDirector, this);
}

@Override
public Move createUndoMove(ScoreDirector scoreDirector) {
    return new MoveResourceAllocations(allocation, allocation.getResource());
}

@Override
public void doMoveOnGenuineVariables(ScoreDirector scoreDirector) {
    scoreDirector.beforeVariableChanged(allocation, "resource");

    updateOnHandAmounts(scoreDirector);

    allocation.setResource(newResource);

    scoreDirector.afterVariableChanged(allocation, "resource");
}


private void updateOnHandAmounts(ScoreDirector scoreDirector) {
    ActivitySolution solution = (ActivitySolution) scoreDirector.getWorkingSolution();
    List<OnHandForProduct> onHandForProducts = solution.getOnHandForProducts();
    List<ProductInventoryTransactionPlannerDto> transactions = solution.getTransactions();
    boolean transactionFoundForTask = false;
    if ((newResource.getClass().getSimpleName().contains(Product.class.getSimpleName()))
            && allocation.getResourceClass().equals(Product.class)) {
        // find the transaction caused by the task and product in question and replace the product in the
        // transaction with the newly assigned product and revert this for an undo move
        for (ProductInventoryTransactionPlannerDto transaction : transactions) {
            if (transaction.getCauseId().equals(allocation.getTaskId())
                    && transaction.getProductId() == (allocation.getResource().getId())
                    && transaction.getTransactionTypeName().equals(InventoryTransactionType.SUBTRACT)) {
                transaction.setProductId(newResource.getId());
                transactionFoundForTask = true;
                break;
            }
        }
        if (!transactionFoundForTask) {
            throw new EmptyResultDataAccessException(
                    "Internal scheduler fail: no product transaction found for the product-requiring task with id: "
                            + allocation.getTaskId() + " for product : " + allocation.getResource(), 1);
        }
        TaskPlannerDto thisTask = null;
        for (TaskPlannerDto task : solution.getTasks()) {
            if (task.getId().equals(allocation.getTaskId())) {
                thisTask = task;
            }
        }
        Long oldProductId = allocation.getResource().getId();
        Long newProductId = newResource.getId();
        for (OnHandForProduct onHandForProduct : onHandForProducts) {
            if (onHandForProduct.getProductId().equals(oldProductId)
                    && onHandForProduct.getDate().isAfter(
                            thisTask.getInterval().getStart().withTimeAtStartOfDay()
                                    .plusDays(0/* - GeneralPrefs.PRODUCT_PRESENCE_SAFETY_BUFFER*/))) {
                onHandForProduct.setAmount(onHandForProduct.getAmount() + allocation.getAmount());
            }
            if (onHandForProduct.getProductId().equals(newProductId)
                    && onHandForProduct.getDate().isAfter(
                            thisTask.getInterval().getStart().withTimeAtStartOfDay()
                            .plusDays(0/* - GeneralPrefs.PRODUCT_PRESENCE_SAFETY_BUFFER*/))) {
                onHandForProduct.setAmount(onHandForProduct.getAmount() - allocation.getAmount());
            }
        }
    }
}

@Override
public Collection<? extends Object> getPlanningEntities() {
    return Collections.singletonList(allocation);
}

@Override
public Collection<? extends Object> getPlanningValues() {
    return Collections.singletonList(newResource);
}

@Override
public String toString() {
    return "replacing resource " + allocation.getResource() + " for task with id " + allocation.getId() + " with "
            + newResource;
}

@Override
public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + ((allocation == null) ? 0 : allocation.hashCode());
    result = prime * result + ((newResource == null) ? 0 : newResource.hashCode());
    return result;
}

@Override
public boolean equals(Object obj) {
    if (this == obj)
        return true;
    if (obj == null)
        return false;
    if (getClass() != obj.getClass())
        return false;
    MoveResourceAllocations other = (MoveResourceAllocations) obj;
    if (allocation == null) {
        if (other.allocation != null)
            return false;
    } else if (!allocation.equals(other.allocation))
        return false;
    if (newResource == null) {
        if (other.newResource != null)
            return false;
    } else if (!newResource.equals(other.newResource))
        return false;
    return true;
}

}

Upvotes: 1

Views: 198

Answers (1)

Geoffrey De Smet
Geoffrey De Smet

Reputation: 27312

The config looks good.

1) Maybe the 2 Construction Heuristics phase never completely finish. Turn on INFO logging (or better yet DEBUG). It will log when each of the 2 Construction Heuristics ends.

2) Maybe Local Search starts with a ChangeMoveSelector (it's a union, so any one of the 2 selectors can go first), and it hangs somehow in the filter. Turn on TRACE logging to see the selected moves.

Upvotes: 3

Related Questions