/** * */ package vroom.trsp.optimization.split; import vroom.common.heuristics.ConstraintHandler; import vroom.common.utilities.optimization.IConstraint; import vroom.trsp.datamodel.ITRSPTour; import vroom.trsp.datamodel.TRSPInstance; import vroom.trsp.datamodel.TRSPSimpleTour; import vroom.trsp.datamodel.Technician; import vroom.trsp.datamodel.costDelegates.TRSPCostDelegate; import vroom.trsp.datamodel.costDelegates.TRSPWorkingTime; import vroom.trsp.optimization.constraints.SparePartsConstraint; import vroom.trsp.optimization.constraints.TWConstraint; import vroom.trsp.optimization.constraints.ToolsConstraint; import vroom.trsp.util.TRSPGlobalParameters; /** * The class <code>SplitTourArcBuilder</code> is responsible for the creation of the arcs of the Split auxiliary graph. * In particular it allows incremental construction and evaluation of arcs. * <p> * Creation date: Sep 27, 2011 - 4:16:36 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 SplitTourArcBuilder { private final TRSPGlobalParameters mParameters; private final boolean mCVRPTW; private final ConstraintHandler<ITRSPTour> mConstraints; private final TRSPCostDelegate mCostDelegate; private SplitTourArc mArc; private boolean mTWChecked; /** * Returns the arc that were generated during the last call to {@link #buildArc(TRSPSimpleTour, int, int)} * * @return the arc that were generated during the last call to {@link #buildArc(TRSPSimpleTour, int, int)} */ public SplitTourArc getArc() { return mArc; } /** * Sets the arc * * @param arc */ void setArc(SplitTourArc arc) { mArc = arc; } /** * Returns the constraint handler defined in this instance * * @return the constraint handler defined in this instance */ protected ConstraintHandler<ITRSPTour> getConstraints() { return mConstraints; } /** * Returns the cost delegate used in this instance * * @return the cost delegate used in this instance */ protected TRSPCostDelegate getCostDelegate() { return mCostDelegate; } /** * Creates a new <code>SplitTourArcBuilder</code> * * @param constraints * @param costDelegate * @param parameters */ public SplitTourArcBuilder(ConstraintHandler<ITRSPTour> constraints, TRSPCostDelegate costDelegate, TRSPGlobalParameters parameters) { super(); mConstraints = constraints; mCostDelegate = costDelegate; mParameters = parameters; mCVRPTW = mParameters.isCVRPTW(); } /** * Build a arc of the split procedure auxiliary graph * * @param giantTour * the giant tour being split * @param start * the index of the arc tail in the giant tour * @param end * the index of the arc head in the giant tour * @return an arc corresponding to the section of the <code>giantTour</code> delimited by <code>start</code> and * <code>end</code>, or <code>null</code> if such arc does not exist */ public SplitTourArc buildArc(ITRSPTour giantTour, int start, int end) { mTWChecked = false; setArc(new SplitTourArc(giantTour, start, end)); // Add a visit to the main depot if required boolean feasible = fixArc(); if (!feasible) setArc(null); return getArc(); } /** * Check that the arc represent a tour that is feasible with regard to the tools and spare part constraints, and * insert a visit to the main depot if needed</p> * <p> * This method is called by {@link #buildArc(TRSPSimpleTour, int, int)} to add a visit to the main depot when * required * </p> * <p> * Implementations should ensure that the constraints are only enforced if they are present in the * {@linkplain #getConstraints() constraint delegate} * </p> * * @return <code>true</code> if the resulting arc is feasible, <code>false</code> otherwise */ protected boolean fixArc() { boolean checkTools = false; boolean checkParts = false; int failureIdx = getArc().length(); int toolsFailure = failureIdx, partsFailure = failureIdx; for (IConstraint<?> ctr : getConstraints()) if (ToolsConstraint.class.isAssignableFrom(ctr.getClass())) checkTools = true; else if (SparePartsConstraint.class.isAssignableFrom(ctr.getClass())) checkParts = true; TRSPInstance instance = getArc().getInstance(); Technician tech = instance.getTechnician(getArc().getTechnicianId()); if (checkTools) { for (int i = 0; i < getArc().length(); i++) { int node = getArc().getNodeAt(i); if (!instance.hasRequiredTools(getArc().getTechnicianId(), node)) { // Found a request that cannot be serviced toolsFailure = i; break; } } } if (checkParts) { int[] parts = tech.getSpareParts(); for (int i = 0; i < toolsFailure && i < partsFailure; i++) { for (int p = 0; p < parts.length; p++) { parts[p] -= instance.getSparePartReq(getArc().getNodeAt(i), p); if (parts[p] < 0) { // Found a request that cannot be serviced partsFailure = i; if (mCVRPTW) return false; break; } } } } failureIdx = Math.min(partsFailure, toolsFailure); if (failureIdx < getArc().length()) { // A visit to the depot must be inserted before failureIdx TRSPInstance ins = getArc().getInstance(); int mainDepot = ins.getMainDepot().getID(); // Initialize the data structures for TW checking for main depot insertion // We will also check the tour TW, set the flag to true to prevent second checking mTWChecked = true; // Array of latest feasible arrival times double[] latest = new double[getArc().length()]; // Array of arrival times double[] arrival = new double[getArc().length()]; // Array of slack times double[] waiting = new double[getArc().length()]; // --------------------------------------------- // Evaluate arrival times arrival[0] = getArc().getInstance().getTimeWindow(getArc().getFirstNode()).startAsDouble(); for (int i = 1; i < getArc().length(); i++) { int pred = getArc().getNodeAt(i - 1); int node = getArc().getNodeAt(i); arrival[i] = ins.calculateArrivalTime(node, pred, arrival[i - 1], getArc().getTechnicianId()); if (!ins.getTimeWindow(node).isFeasible(arrival[i])) // A TW is violated // The tour is not feasible return false; } // --------------------------------------------- // Evaluate latest feasible arrival times and cumulated waiting latest[getArc().length() - 1] = ins.getTimeWindow(getArc().getLastNode()).endAsDouble(); waiting[getArc().length() - 1] = 0; for (int i = latest.length - 2; i > 0; i--) { int succ = getArc().getNodeAt(i + 1); int node = getArc().getNodeAt(i); latest[i] = Math.min( // // TW End at i ins.getTimeWindow(node).endAsDouble(), // Latest at i+1 - (service + travel time) latest[i + 1] - ins.getServiceTime(node) - ins.getCostDelegate().getTravelTime(node, succ, tech)); waiting[i] = waiting[i + 1] + ins.getTimeWindow(succ).getWaiting(arrival[i + 1]); if (latest[i] < ins.getTimeWindow(node).startAsDouble()) // The latest feasible arrival time is before the start of the node TW // The tour is infeasible return false; } // Find the cheapest feasible insertion point double min = Double.POSITIVE_INFINITY; int insIdx = SplitTourArc.DEPOT_NOT_VISITED; for (int i = 1; i <= failureIdx; i++) { int pred = getArc().getNodeAt(i - 1); int succ = getArc().getNodeAt(i); // Insertion cost double cost = evaluateInsertionCost(mainDepot, i, arrival, waiting); if (cost < min) { // Cheapest insertion // Check feasibility // Arrival time at the main depot double newArrival = ins.calculateArrivalTime(mainDepot, pred, arrival[i - 1], getArc() .getTechnicianId()); // Check depot TW if (!ins.getTimeWindow(mainDepot).isFeasible(newArrival)) continue; // Arrival time at the depot successor newArrival = ins.calculateArrivalTime(succ, mainDepot, newArrival, getArc().getTechnicianId()); // Check arrival time if (newArrival < latest[i]) { // The insertion is feasible min = cost; insIdx = i; } } } if (insIdx != SplitTourArc.DEPOT_NOT_VISITED) { // A feasible insertion has been found getArc().setDepotVisitIndex(insIdx); } else { // No feasible insertion, arc is infeasible return false; } } // if (!getConstraints().isFeasible(getArc())) { // TRSPLogging // .getOptimizationLogger() // .warn("SplitTourArcBuilder.fixArc: arc is infeasible (toolFailure:%s partFailure:%s failure:%s): %s (%s)", // toolsFailure, partsFailure, failureIdx, // getConstraints().getInfeasibilityExplanation(getArc()), getArc()); // } return true; } /** * Evaluate the cost of inserting <code>node</code> at the index <code>idx</code> of the current arc. * * @param node * @param idx * @param arrival * @param waiting * @return the insertion cost */ protected double evaluateInsertionCost(int node, int idx, double[] arrival, double[] waiting) { final int pred = getArc().getNodeAt(idx - 1); final int succ = getArc().getNodeAt(idx); TRSPInstance instance = getArc().getInstance(); if (getCostDelegate() instanceof TRSPWorkingTime) { // Faster implementation for the WT // Arrival time at the main depot double newArrival = instance.calculateArrivalTime(node, pred, arrival[idx - 1], getArc().getTechnicianId()); // Arrival time at the successor newArrival = instance.calculateArrivalTime(succ, node, newArrival, getArc().getTechnicianId()); // New arrival time at the last node return Math.max(0, newArrival - arrival[idx] - waiting[idx - 1]); } else { return getCostDelegate().evaluateDetour(getArc(), pred, node, succ, false); } } /** * Evaluate the cost of an arc * * @return the cost of the arc */ public double evaluateArc() { return getCostDelegate().evaluateTour(getArc(), true); } /** * Check the feasibility of an arc * * @return <code>true</code> if the arc is feasible, <code>false</code> otherwise */ public boolean isFeasible() { for (IConstraint<ITRSPTour> ctr : getConstraints()) { if ((mTWChecked && ctr instanceof TWConstraint) || ctr instanceof ToolsConstraint || ctr instanceof SparePartsConstraint) // Ignore constraints that were already checked in the construction continue; else if (!ctr.isFeasible(getArc())) return false; } return true; } }