/** * */ package vroom.trsp.optimization.constructive; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import vroom.common.heuristics.ProcedureStatus; import vroom.common.heuristics.vns.VariableNeighborhoodSearch; import vroom.common.heuristics.vns.VariableNeighborhoodSearch.VNSVariant; import vroom.common.utilities.BatchThreadPoolExecutor; import vroom.common.utilities.Stopwatch; import vroom.common.utilities.Stopwatch.ReadOnlyStopwatch; import vroom.common.utilities.algorithms.HungarianAlgorithm; import vroom.common.utilities.dataModel.ObjectWithIdComparator; import vroom.common.utilities.optimization.ILocalSearch; import vroom.common.utilities.optimization.IParameters.LSStrategy; import vroom.common.utilities.optimization.OptimizationSense; import vroom.common.utilities.optimization.SimpleParameters; import vroom.trsp.datamodel.ITRSPTour; import vroom.trsp.datamodel.TRSPDetailedSolutionChecker; import vroom.trsp.datamodel.TRSPInstance; import vroom.trsp.datamodel.TRSPRequest; import vroom.trsp.datamodel.TRSPSolution; import vroom.trsp.datamodel.TRSPTour; import vroom.trsp.datamodel.Technician; import vroom.trsp.datamodel.costDelegates.TRSPCostDelegate; import vroom.trsp.optimization.InsertionMove; import vroom.trsp.optimization.constraints.TourConstraintHandler; import vroom.trsp.optimization.localSearch.TRSPShift; import vroom.trsp.optimization.localSearch.TRSPTwoOpt; import vroom.trsp.util.TRSPGlobalParameters; import vroom.trsp.util.TRSPLogging; /** * <code>TRSPpInsertion</code> * <p> * Creation date: Mar 23, 2011 - 2:12:18 PM * * @author Victor Pillac, <a href="http://uniandes.edu.co">Universidad de Los Andes</a>-<a * href="http://copa.uniandes.edu.co">Copa</a> <a href="http://www.emn.fr">Ecole des Mines de Nantes</a>-<a * href="http://www.irccyn.ec-nantes.fr/irccyn/d/en/equipes/Slp">SLP</a> * @version 1.0 */ public class TRSPpInsertion extends TRSPConstructiveHeuristic { public static enum AssignmentStrategy { AP, SEQUENTIAL, MIXED }; private static int sMaxBacktracking = 100; /** * a flag to toggle the reproducibility of experiments: if set to <code>false</code> algorithm may be faster but * result may change from one run to another. * <p> * Default value is <code>true</code> * </p> **/ private static boolean sEnsureReproducibility = true; /** * A flag to toggle the reproducibility of experiments: if set to <code>false</code> algorithm may be faster but * result may change from one run to another * * @return the value of name */ public static boolean isEnsureReproducibility() { return sEnsureReproducibility; } /** * Setter for the flag to toggle the reproducibility of experiments: if set to <code>false</code> algorithm may be * faster but result may change from one run to another * * @param ensureRep * the value to be set for a flag to toggle the reproducibility of experiments: if set to * <code>false</code> algorithm may be faster but result may change from one run to another */ public static void setEnsureReproducibility(boolean ensureRep) { sEnsureReproducibility = ensureRep; } /** A flag to toggle mSolution checking after each move */ private static boolean sCheckSolutionAfterMove = false; /** * Setter for <code>checkSolutionAfterMove</code> * * @param checkSolutionAfterMove * the checkSolutionAfterMove to set */ public static void setCheckSolutionAfterMove(boolean checkSolutionAfterMove) { sCheckSolutionAfterMove = checkSolutionAfterMove; if (isCheckSolutionAfterMove()) { TRSPLogging .getOptimizationLogger() .warn("TRSPpInsertion.CheckSolutionAfterMove is set to true, set to false to increase performance (set in %s)", Thread.currentThread().getStackTrace()[2]); } } /** * Getter for <code>checkSolutionAfterMove</code> * * @return the checkSolutionAfterMove */ public static boolean isCheckSolutionAfterMove() { return sCheckSolutionAfterMove; } /** the strategy used for assignment rounds **/ private AssignmentStrategy mAssignmentStrategy; /** * Getter for the strategy used for assignment rounds * * @return the strategy used for assignment rounds */ public AssignmentStrategy getAssignmentStrategy() { return this.mAssignmentStrategy; } /** * Setter for the strategy used for assignment rounds * * @param strategy * the strategy used for assignment rounds */ public void setAssignmentStrategy(AssignmentStrategy strategy) { this.mAssignmentStrategy = strategy; } /** The set of available requests */ private Set<TRSPRequest> mUnassignedRequests; /** * Returns the set of available requests * * @return the set of available requests */ public Set<TRSPRequest> getAvailableRequests() { return mUnassignedRequests; } /** the set of infeasible requests **/ // private Set<TRSPRequest> mInfeasibleRequests; // /** // * Getter for the set of infeasible requests // * // * @return the set of infeasible requests // */ // public Set<TRSPRequest> getUnfeasibleRequests() { // return this.mInfeasibleRequests; // } /** A timer used to measure time spent on backtracking */ private final Stopwatch mBTTimer; /** A timer used to measure time spent on request evaluation */ private final Stopwatch mEvalTimer; /** A timer used to measure time spent on AP assignment */ private final Stopwatch mAPAssignmentTimer; /** A timer used to measure time spent on sequential assignment */ private final Stopwatch mSeqAssignmentTimer; /** A timer used to measure time spent on post optimization */ private final Stopwatch mPostOptTimer; /** * Getter for the backtracking timer * * @return the backtracking timer */ public ReadOnlyStopwatch getBTTimer() { return mBTTimer.getReadOnlyStopwatch(); } /** * Getter for the evaluation timer * * @return the evaluation timer */ public ReadOnlyStopwatch getEvalTimer() { return mEvalTimer.getReadOnlyStopwatch(); } /** * Getter for the AP assignment timer * * @return the assignment timer */ public ReadOnlyStopwatch getAPAssignmentTimer() { return mAPAssignmentTimer.getReadOnlyStopwatch(); } /** * Getter for the sequential assignment timer * * @return the sequential assignment timer */ public ReadOnlyStopwatch getSeqAssignmentTimer() { return mSeqAssignmentTimer.getReadOnlyStopwatch(); } /** * Getter for the post optimization timer * * @return the post optimization timer */ public ReadOnlyStopwatch getPostOpTimer() { return mPostOptTimer.getReadOnlyStopwatch(); } /** An array containing the agents: one per tour */ private InsertionAgent[] mAgents; /** A sorted copy of mAgents */ private InsertionAgent[] mSortedAgents; /** An executor that will execute the agent tasks */ private BatchThreadPoolExecutor mExecutor; /** * A list containing the assigned requests in the same order they were assigned */ private final LinkedList<Decision> mOrderedDecisions; /** An array containing the number of times a move was unassigned */ private final int[] mUnassignedCount; /** * A set containing the requests that were unassigned in the backtracking process */ private Set<TRSPRequest> mBacktrackUnassignedRequests; /** The number of times backtrack was used */ private int mBacktrackCount = 0; /** The best solution found so far in terms of number of served requests */ private TRSPSolution mBestSolution; @Override protected void setSolution(TRSPSolution solution) { super.setSolution(solution); mAgents = new InsertionAgent[getInstance().getFleet().size()]; mSortedAgents = new InsertionAgent[getInstance().getFleet().size()]; for (Technician t : getInstance().getFleet()) { mAgents[t.getID()] = new InsertionAgent(this, t, getSolution().getTour(t.getID())); mSortedAgents[t.getID()] = mAgents[t.getID()]; } } /** The cost of the solution before post optimization */ private double mInitSolCost = 0; /** * Returns the cost of the solution before post optimization * * @return The cost of the solution before post optimization */ public double getInitSolCost() { return mInitSolCost; } /** * Creates a new <code>TRSPpInsertion</code> * * @param costDelegate * the cost delegate that will be used to evaluate moves * @param constraintHandler * the constraint handler that will be used to evaluate the feasibility of moves */ public TRSPpInsertion(TRSPInstance instance, TRSPGlobalParameters parameters, TourConstraintHandler constraintHandler, TRSPCostDelegate costDelegate) { super(instance, parameters, constraintHandler, costDelegate); setNumThreads(Runtime.getRuntime().availableProcessors()); mAPAssignmentTimer = new Stopwatch(); mSeqAssignmentTimer = new Stopwatch(); mBTTimer = new Stopwatch(); mEvalTimer = new Stopwatch(); mPostOptTimer = new Stopwatch(); setAssignmentStrategy(AssignmentStrategy.MIXED); if (isEnsureReproducibility()) { // Tree sets are used in order to ensure reproducibility // (reproducible foreach) mUnassignedRequests = new TreeSet<TRSPRequest>(new ObjectWithIdComparator()); // mInfeasibleRequests = new TreeSet<TRSPRequest>(new // ObjectWithIdComparator()); mBacktrackUnassignedRequests = new TreeSet<TRSPRequest>(new ObjectWithIdComparator()); } else { // Hash sets are used for performance (O(1) add/remove/contains) mUnassignedRequests = new HashSet<TRSPRequest>(); // mInfeasibleRequests = new HashSet<TRSPRequest>(); mBacktrackUnassignedRequests = new HashSet<TRSPRequest>(); } for (Integer r : getInstance().getReleasedRequests()) { mUnassignedRequests.add(instance.getRequest(r)); } mOrderedDecisions = new LinkedList<Decision>(); mUnassignedCount = new int[getInstance().getMaxId()]; } @SuppressWarnings("unchecked") protected ILocalSearch<ITRSPTour> getTourLS() { return VariableNeighborhoodSearch.<ITRSPTour> newVNS(VNSVariant.VND, OptimizationSense.MINIMIZATION, null, getParameters().getALNSRndStream(), new TRSPShift(getTourConstraintHandler()), new TRSPTwoOpt( getTourConstraintHandler())); } /** * Returns a string describing the context (current state) of the ALNS * * @return a string describing the context (current state) of the ALNS */ public String getContextString() { return String.format("pIns[t:%-5sms it:%-5s]: ", getTimer().readTimeMS(), mIteration); } @Override protected void initializeSolutionInternal(TRSPSolution sol) { reset(); mBestSolution = getSolution().clone(); mTimer.start(); TRSPLogging.getOptimizationLogger().info( getContextString() + "Optimization started with %s threads (%-4s pending requests)", mExecutor.getMaximumPoolSize(), mUnassignedRequests.size()); boolean reqAssigned = true; boolean backtrack = false; // Check for feasibility: all requests should be compatible with at // least one empty tour // ---------------------------------------------------------- Set<TRSPRequest> unfeasibleRequests = new HashSet<TRSPRequest>(mUnassignedRequests); executeAllAgents(); for (TRSPRequest r : mUnassignedRequests) { for (InsertionAgent a : mAgents) { if (a.getMove(r).isFeasible()) { unfeasibleRequests.remove(r); break; } } } if (!unfeasibleRequests.isEmpty()) { TRSPLogging.getOptimizationLogger().warn( getContextString() + "Some requests cannot be inserted in any tour: %s", unfeasibleRequests); setStatus(ProcedureStatus.INFEASIBLE); } // ---------------------------------------------------------- // Assign all requests that are feasible with only one technician // ---------------------------------------------------------- // ---------------------------------------------------------- // Build tours // ---------------------------------------------------------- while (!mUnassignedRequests.isEmpty() && getStatus() == ProcedureStatus.RUNNING) { mIteration++; TRSPLogging.getOptimizationLogger().lowDebug( getContextString() + "Start of iteration %-4s, pending requests: %-4s", mIteration, mUnassignedRequests.size()); // Try to assign at most 1 move per technician reqAssigned = assignmentRound(backtrack); // No assignment was made but there still are unassigned requests: // backtrack if (reqAssigned && backtrack) { TRSPLogging.getOptimizationLogger().debug( getContextString() + "Backtraking was successfull - %s unassigned requests", mUnassignedRequests.size()); backtrackSucceeded(); backtrack = false; } else if (!reqAssigned && !mUnassignedRequests.isEmpty()) { TRSPLogging.getOptimizationLogger().debug( getContextString() + "No feasible assignment was found, backtraking - %s unassigned requests", mUnassignedRequests.size()); backtrack(); backtrack = true; } if (getSolution().getUnservedCount() < mBestSolution.getUnservedRequests().size() || (getSolution().getUnservedCount() == mBestSolution.getUnservedCount() && getSolution() .getObjectiveValue() < mBestSolution.getObjectiveValue())) mBestSolution = getSolution().clone(); TRSPLogging.getOptimizationLogger().lowDebug( getContextString() + "End of iteration %-4s, unassigned requests: %-4s, total cost: %-4.2f", mIteration, mUnassignedRequests.size(), getSolution().getObjectiveValue()); } // ---------------------------------------------------------- setSolution(mBestSolution); // Local search // ---------------------------------------------------------- postOptimization(); // ---------------------------------------------------------- mTimer.stop(); if (mUnassignedRequests.isEmpty()) { if (getStatus() == ProcedureStatus.RUNNING) // Prevent status // overwriting setStatus(ProcedureStatus.TERMINATED); } else { TRSPLogging.getOptimizationLogger().warn(getContextString() + "Some requests have been left over: %s", mUnassignedRequests); if (getStatus() == ProcedureStatus.RUNNING) // Prevent status // overwriting setStatus(ProcedureStatus.INFEASIBLE_SOLUTION); } TRSPLogging.getOptimizationLogger().info( getContextString() + "Optimization finished after %sit and %sms, unserved requests: %-4s, total cost: %-4.2f", mIteration, mTimer.readTimeMS(), getSolution().getUnservedCount(), getSolution().getObjectiveValue()); mExecutor.shutdown(); } private void reset() { mTimer.reset(); mAPAssignmentTimer.reset(); mSeqAssignmentTimer.reset(); mEvalTimer.reset(); mBTTimer.reset(); int proc = getParameters().getThreadCount(); mExecutor = new BatchThreadPoolExecutor(proc, "pIns"); mIteration = 0; } /** * Try to assign requests to technicians * * @param backtrack * <code>true</code> if a backtrack was performed * @return <code>true</code> if at least one assignment was made, <code>false</code> otherwise */ private boolean assignmentRound(boolean backtrack) { // FIXME abort if at least one request is infeasible switch (getAssignmentStrategy()) { case AP: return assignmentRoundAP(); case SEQUENTIAL: return assignmentRoundSeq(); case MIXED: if (backtrack) return assignmentRoundSeq(); else return assignmentRoundAP(); default: throw new IllegalStateException("Unknown assignment strategy: " + getAssignmentStrategy()); } } /** * Try to assign one request to the technician currently with the lowest load. * * @return <code>true</code> if one assignment was made, <code>false</code> otherwise */ private boolean assignmentRoundSeq() { mSeqAssignmentTimer.resume(); boolean reqAssigned = false; TRSPLogging.getOptimizationLogger().lowDebug( getContextString() + "Trying to assign the following requests: %s", mUnassignedRequests); // Sort agents Arrays.sort(mSortedAgents); // The current agent int a = 0; while (!reqAssigned && a < mSortedAgents.length) { // Select the agent with the smallest cost InsertionAgent agent = mSortedAgents[a]; // Evaluate insertion costs mEvalTimer.resume(); agent.call(); mEvalTimer.pause(); // Select the best insertion InsertionMove mve = agent.getBestMove(); if (mve != null) reqAssigned = executeMove(mve, agent); if (!reqAssigned) a++; } mSeqAssignmentTimer.pause(); return reqAssigned; } /** * Check if all requests can be inserted in at least one tour * * @return <code>true</code> if the current partial solution is feasible */ private boolean isPartialSolFeasible() { for (TRSPRequest r : mUnassignedRequests) { boolean feasible = false; for (int i = 0; !feasible && i < mAgents.length; i++) { InsertionMove move = mAgents[i].getMove(r); if (move != null) feasible = true; } if (!feasible) return false; } return true; } /** * Try to assign requests to technicians in a parallel manner by solving an assignment problem * * @return <code>true</code> if at least one assignment was made, <code>false</code> otherwise */ private boolean assignmentRoundAP() { mAPAssignmentTimer.resume(); boolean reqAssigned = false; TRSPLogging.getOptimizationLogger().lowDebug( getContextString() + "Trying to assign the following requests: %s", mUnassignedRequests); executeAllAgents(); if (!isPartialSolFeasible()) return false; // Translate the agent move evaluations into a penalty matrix int size = Math.max(mAgents.length, mUnassignedRequests.size()); double[][] penalties = new double[size][size]; TRSPRequest[] requests = new TRSPRequest[size]; double maxEval = Double.NEGATIVE_INFINITY; int k = 0; for (TRSPRequest r : mUnassignedRequests) { requests[k] = r; for (int i = 0; i < mAgents.length; i++) { InsertionMove move = mAgents[i].getMove(r); penalties[i][k] = move.getCost(); if (penalties[i][k] != Double.POSITIVE_INFINITY && penalties[i][k] > maxEval) maxEval = penalties[i][k]; } k++; } if (maxEval == Double.NEGATIVE_INFINITY) { // mInfeasibleRequests.addAll(mUnassignedRequests); // No coherent evaluation was found, abort mAPAssignmentTimer.pause(); return false; } for (int i = 0; i < mAgents.length; i++) { for (int j = 0; j < mUnassignedRequests.size(); j++) { if (penalties[i][j] == Double.POSITIVE_INFINITY) penalties[i][j] = 100 * maxEval; } } // Solve the assignment problem int[] assignment = HungarianAlgorithm.solveAssignment(penalties); for (int i = 0; i < mAgents.length; i++) { InsertionAgent agent = mAgents[i]; InsertionMove move = agent.getMove(requests[assignment[i]]); if (move != null) { // Check if an assignment was found reqAssigned |= executeMove(move, agent); } } mAPAssignmentTimer.pause(); return reqAssigned; } /** * Performs a local search on each technician tour */ private void postOptimization() { mInitSolCost = getSolution().getObjectiveValue(); TRSPLogging.getOptimizationLogger().debug(getContextString() + "Start of post optimization, cost: %.3f", getSolution().getObjectiveValue()); mPostOptTimer.resume(); final SimpleParameters params = new SimpleParameters(LSStrategy.DET_BEST_IMPROVEMENT, Long.MAX_VALUE, Integer.MAX_VALUE, getParameters().getALNSRndStream()); List<Callable<Object>> optJobs = new ArrayList<Callable<Object>>(mAgents.length); for (final InsertionAgent a : mAgents) { optJobs.add(new Callable<Object>() { @Override public Object call() { TRSPLogging.getOptimizationLogger().lowDebug(getContextString() + "optimizing tour %s", a); a.mTour = (TRSPTour) getTourLS().localSearch(getInstance(), a.mTour, params); getSolution().importTour(a.mTour); return new Object(); } }); } try { // Evaluate all requests in parallel Map<Callable<Object>, Future<Object>> future = mExecutor.submitBatch(optJobs, true); // Log exceptions if any for (Future<?> f : future.values()) { try { f.get(); } catch (ExecutionException e) { TRSPLogging.getBaseLogger().exception("TRSPpInsertion.postOptimization", e); } } } catch (InterruptedException e) { // Wait for evaluation to finish TRSPLogging.getBaseLogger().exception("TRSPpInsertion.postOptimization", e); } finally { mEvalTimer.pause(); } mPostOptTimer.pause(); double imp = getSolution().getObjectiveValue() - mInitSolCost; TRSPLogging.getOptimizationLogger().debug( getContextString() + "Optimization finished after %sms, cost: %.3f (improvement: %.3f [%.1f])", mPostOptTimer.readTimeMS(), getSolution().getObjectiveValue(), imp, 100 * imp / mInitSolCost); } /** * Execute all the agents */ private void executeAllAgents() { mEvalTimer.resume(); try { // Evaluate all requests in parallel Map<InsertionAgent, Future<Boolean>> future = mExecutor.submitBatch(Arrays.asList(mAgents), true); // Log exceptions if any for (Future<?> f : future.values()) { try { f.get(); } catch (ExecutionException e) { TRSPLogging.getBaseLogger().exception("TRSPpInsertion.executeAllAgents", e); } } } catch (InterruptedException e) { // Wait for evaluation to finish TRSPLogging.getBaseLogger().exception("TRSPpInsertion.executeAllAgents", e); } finally { mEvalTimer.pause(); } } /** * Execute an {@link InsertionMove} with a specific {@link InsertionAgent}, log messages and store the decision * information for eventual backtracking * * @param move * the move to be executed * @param agent * the agent that will execute the move * @return <code>true</code> if the insertion was successful, <code>false</code> otherwise */ private boolean executeMove(InsertionMove move, InsertionAgent agent) { if (!move.isFeasible()) return false; // if (!mUnassignedRequests.contains(move.getRequest())) // throw new IllegalArgumentException("Request " + move.getRequest() // + " is not in the list of unassigned requests"); if (agent.executeMove(move)) { TRSPLogging.getOptimizationLogger().lowDebug(getContextString() + "Assigned %s to agent %s", move.getNodeId(), agent); mUnassignedRequests.remove(getInstance().getRequest(move.getNodeId())); // mInfeasibleRequests.remove(move.getRequest()); // TODO store more information on the assignment decision for better // backtracking mOrderedDecisions.addLast(new Decision(move, agent)); return true; } else { // mInfeasibleRequests.add(move.getRequest()); TRSPLogging.getOptimizationLogger().lowDebug(getContextString() + "Cannot assign %s to agent %s", move.getNodeId(), agent); return false; } } /** * Performs a backtrack by removing a subset of requests from tours */ private void backtrack() { if (mOrderedDecisions.isEmpty()) { setStatus(ProcedureStatus.EXCEPTION); TRSPLogging .getOptimizationLogger() .warn(getContextString() + "Attempting to backtrack when no decision can be undone - aborting (%s unassigned requests: %s)", mUnassignedRequests.size(), mUnassignedRequests); return; } mBTTimer.resume(); int count = 0; mBacktrackCount++; LinkedList<Decision> bt = new LinkedList<TRSPpInsertion.Decision>(); while (count < Math.max(mAgents.length / 2 * mBacktrackCount, mUnassignedRequests.size() * mBacktrackCount) && !mOrderedDecisions.isEmpty()) { // Gets the earliest assignment Decision decision = mOrderedDecisions.pop(); if (mUnassignedCount[decision.move.getNodeId()] > sMaxBacktracking) { setStatus(ProcedureStatus.LIMIT_ITERATION); TRSPLogging.getOptimizationLogger().warn( getContextString() + "Procedure aborted after request %s was removed %s times", decision.move.getNodeId(), mUnassignedCount[decision.move.getNodeId()]); mBTTimer.pause(); return; } decision.agent.undoMove(decision.move); mBacktrackUnassignedRequests.add(getInstance().getRequest(decision.move.getNodeId())); mUnassignedCount[decision.move.getNodeId()]++; bt.add(decision); // mUnassignedRequests.add(assignment.request); We don't want to // reassign the move right away count++; // Clear the stored information to allow re-evaluation for (InsertionAgent a : mAgents) { a.clear(getInstance().getRequest(decision.move.getNodeId())); } } TRSPLogging.getOptimizationLogger().lowDebug(getContextString() + "%s revoked assignments: %s", count, bt); mBTTimer.pause(); } /** * Performs post-backtrack operations in the case backtrack was successful */ private void backtrackSucceeded() { mBacktrackCount = 0; mUnassignedRequests.addAll(mBacktrackUnassignedRequests); mBacktrackUnassignedRequests.clear(); } /** * <code>InsertionAgent</code> is the class responsible for the evaluation of requests in the parallel insertion * heuristic. * <p> * Creation date: Mar 24, 2011 - 1:49:51 PM * * @author Victor Pillac, <a href="http://uniandes.edu.co">Universidad de Los Andes</a>-<a * href="http://copa.uniandes.edu.co">Copa</a> <a href="http://www.emn.fr">Ecole des Mines de Nantes</a>-<a * href="http://www.irccyn.ec-nantes.fr/irccyn/d/en/equipes/Slp" >SLP</a> * @version 1.0 */ private class InsertionAgent implements Callable<Boolean>, Comparable<InsertionAgent> { private final TRSPpInsertion mController; private final InsertionMove[] mMoves; private InsertionMove mBestMove; private final Technician mTechnician; private TRSPTour mTour; /** * A flag equal to <code>true</code> if the associated tour was modified since last iteration */ private boolean mChanged; /** * Creates a new <code>InsertionAgent</code> * * @param controller */ private InsertionAgent(TRSPpInsertion controller, Technician technician, TRSPTour tour) { super(); mController = controller; mTechnician = technician; mTour = tour; mMoves = new InsertionMove[tour.getInstance().getMaxId()]; mChanged = true; } /** * Clear all stored information */ protected void clear() { for (int i = 0; i < mMoves.length; i++) { mMoves[i] = null; } mBestMove = null; mChanged = true; } /** * Clear the stored data associated with the given request * * @param req */ protected void clear(TRSPRequest req) { mMoves[req.getID()] = null; } @Override public Boolean call() { // Evaluate requests mBestMove = null; if (mChanged) clear(); for (TRSPRequest req : mController.getAvailableRequests()) { InsertionMove mve = getMove(req); if (mChanged || mve == null) { mve = evaluateRequest(req); mMoves[req.getID()] = mve; } if (mBestMove == null || mBestMove.getImprovement() < mve.getImprovement()) mBestMove = mve; } mChanged = false; return mChanged; } /** * Evaluates the cost of visiting the given move in this tour. * <p> * The mEvaluation takes into account possible trips to a central depot * </p> * * @param req * the move to be evaluated * @return the cost of visiting the given move in this tour */ private InsertionMove evaluateRequest(TRSPRequest req) { return InsertionMove.findInsertion(req.getID(), mTour, getCostDelegate(), getTourConstraintHandler(), TRSPGlobalParameters.CTR_CHK_FWD_FEAS, true); } /** * Returns the {@link InsertionMove} corresponding to a move * * @param req * the considered move * @return the {@link InsertionMove} associated with the specified move */ protected InsertionMove getMove(TRSPRequest req) { return req != null ? mMoves[req.getID()] : null; } /** * Getter for the best move found by this agent * * @return the best move found by this agent */ protected InsertionMove getBestMove() { return mBestMove; } /** * Execute an insertion move * * @param move * the insertion to be executed * @return <code>true</code> if the insertion was executed successfully, <code>false</code> otherwise */ protected boolean executeMove(InsertionMove move) { if (move.getTour() != mTour) throw new IllegalArgumentException("The move tour should be the agent tour"); double prev = mTour.getTotalCost(); boolean executed = InsertionMove.executeMove(move); if (executed) { checkSolution(prev, move, false); mChanged = true; } return executed; } /** * Undo an insertion move * * @param move * the move that will be undone */ protected void undoMove(InsertionMove move) { double prev = mTour.getTotalCost(); // if (!mTour.isVisited(move.getRequestId())) // throw new IllegalArgumentException("The tour " + this + // " does not visit request " + move.getRequest()); mTour.removeNode(move.getNodeId()); mTour.getSolution().markAsUnserved(move.getNodeId()); if (move.isDepotTrip() && mTour.isMainDepotVisited()) { // Check if a trip to the depot is still required if (!mTour.isVisitToMainDepotRequired()) mTour.removeNode(mTour.getMainDepotId()); } mChanged = true; checkSolution(prev, move, true); } @Override public String toString() { return mTour.toString(); } private void checkSolution(double prev, InsertionMove move, boolean undo) { if (isCheckSolutionAfterMove()) { String mod = undo ? "undoing " : ""; String stack = undo ? "undoMove" : "executeMove"; String err = TRSPDetailedSolutionChecker.INSTANCE.checkTour(mTour); if (!err.isEmpty()) { TRSPLogging.getOptimizationLogger().warn( "TRSPpInsertion.InsertionAgent.%s: Incoherencies in tour after %smove %s (%s)", stack, mod, move, err); } err = TRSPpInsertion.this.getTourConstraintHandler().getInfeasibilityExplanation(mTour); if (err != null) { TRSPLogging.getOptimizationLogger().warn( "TRSPpInsertion.InsertionAgent.%s: Infeasible solution after %smove %s (%s)", stack, mod, move, err); } double dif = prev + move.getCost() - mTour.getTotalCost(); if (!undo && Math.abs(dif) > 1e-3) { TRSPLogging .getOptimizationLogger() .warn("TRSPpInsertion.InsertionAgent.%s: Unexpected change in cost after %smove %s in tour %s (expected %.3f, was %.3f, delegate:%s)", stack, mod, move, mTechnician.getID(), move.getCost(), mTour.getTotalCost() - prev, mController.getCostDelegate().getClass().getSimpleName()); } } } @Override public int compareTo(InsertionAgent o) { return Double.compare(this.mTour.getTotalCost(), o.mTour.getTotalCost()); } } /** * <code>Decision</code> is a container class to store information on an assignment * <p> * Creation date: Mar 29, 2011 - 3:54:05 PM * * @author Victor Pillac, <a href="http://uniandes.edu.co">Universidad de Los Andes</a>-<a * href="http://copa.uniandes.edu.co">Copa</a> <a href="http://www.emn.fr">Ecole des Mines de Nantes</a>-<a * href="http://www.irccyn.ec-nantes.fr/irccyn/d/en/equipes/Slp" >SLP</a> * @version 1.0 */ private static class Decision { private final InsertionMove move; private final InsertionAgent agent; /** * Creates a new <code>Decision</code> * * @param move * @param agent */ public Decision(InsertionMove move, InsertionAgent agent) { super(); this.move = move; this.agent = agent; } @Override public String toString() { return String.format("r:%s->t:%s", move.getNodeId(), agent.mTechnician.getID()); } } @Override public void dispose() { super.dispose(); if (mExecutor != null) mExecutor.shutdownNow(); mExecutor = null; } @Override protected void finalize() throws Throwable { mExecutor.shutdown(); super.finalize(); } }