/** * */ package vroom.trsp.datamodel; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.TimeUnit; import vroom.common.modeling.dataModel.Depot; import vroom.common.modeling.dataModel.Node; import vroom.common.modeling.dataModel.attributes.ITimeWindow; import vroom.common.utilities.ExtendedReentrantLock; import vroom.common.utilities.IToShortString; import vroom.common.utilities.Utilities; import vroom.common.utilities.optimization.IInstance; import vroom.optimization.online.jmsa.IActualRequest; import vroom.trsp.datamodel.ITRSPNode.NodeType; import vroom.trsp.datamodel.costDelegates.TRSPCostDelegate; import vroom.trsp.sim.TRSPSimulator; import vroom.trsp.util.TRSPGlobalParameters; import vroom.trsp.util.TRSPLogging; /** * <code>TRSPInstance</code> is a simplified representation of an instance, independent from the VroomModelling Library. * <p> * Creation date: Feb 23, 2011 - 11:19:53 AM * * @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 TRSPInstance implements IInstance, vroom.optimization.online.jmsa.IInstance { /** The id of the main depot, in general {@link TRSPTour#getMainDepotId()} should be used instead */ public static final int MAIN_DEPOT = 0; /** the name of this instance **/ private final String mName; /** * Getter for the name of this instance * * @return the name of this instance */ @Override public String getName() { return this.mName; } /** * Returns the group of instance (for TRSP instances) * * @return the group of instance (for TRSP instances) */ public String getGroup() { return getName().substring(0, (getName().contains("RC") ? 3 : 2)); } /** the technician fleet *. */ private final TechnicianFleet mFleet; /** * Getter for the technician fleet. * * @return the value of fleet */ public TechnicianFleet getFleet() { return this.mFleet; } /** * Returns the technician with the given id * * @param techId * the technician id * @return the technician with the given id */ public Technician getTechnician(int techId) { return getFleet().getVehicle(techId); } /** the depots array in this instance (including technician homes) *. */ private final List<Depot> mDepots; /** * Getter for the list of the depots in this instance (including technician homes). * * @return the list of depots in this instance */ public List<Depot> getDepots() { return mDepots; } /** * Gets the depot count. * * @return the depot count */ public int getDepotCount() { return getDepots().size(); } /** * Get a depot form its id. * <p> * Please note that a depot and its duplicate share a reference to the same {@link Depot} object * </p> * * @param depotId * the id of the depot * @return the depot with id <code>depotId</code>, or <code>null</code> if <code>depotId</code> is not a valid depot * id */ public Depot getDepot(int depotId) { return ((DepotNode) getTRSPNode(depotId)).getDepot(); } /** * Get the home duplicate id * * @param homeId * the original depot * @return the id used for the duplicate */ public int getHomeDuplicate(int homeId) { return ((DepotNode) getTRSPNode(homeId)).getDuplicate(); } /** * Returns the id of the main depot duplicate associated with the specified technician * * @param technicianId * the id of the considered technician * @return the id of the main depot duplicate associated with the specified technician */ public int getMainDepotDuplicate(int technicianId) { return mMainDepotDuplicates[technicianId].getID(); } /** * Get the original id of a node * * @param nodeId * the id of the considered node * @return the id of the original node: the id of the original depot is the ID of a depot duplicate, and * <code>nodeId</code> otherwise */ public int getOriginalId(int nodeId) { if (isDepot(nodeId)) return ((DepotNode) getTRSPNode(nodeId)).getOriginal(); return nodeId; } /** * Get the main depot * * @return the main depot in this instance */ public Depot getMainDepot() { return getDepot(0); } /** * Check if the given id is the id of a depot. * * @param id * the id to be checked * @return <code>true</code> if <code>id</code> corresponds to a depot */ public boolean isDepot(int id) { return getTRSPNode(id).getType().isDepot(); } /** * Check if the given id is the id of a/the main depot * * @param id * the id to be checked * @return <code>true</code> if <code>id</code> corresponds to a depot */ public boolean isMainDepot(int id) { return getTRSPNode(id).getType() == NodeType.MAIN_DEPOT; } /** An array of the actual requests contained in this instance, including dummy requests modeling depots */ private final ITRSPNode[] mNodes; /** An array of the main depot duplicates for each technician */ private final ITRSPNode[] mMainDepotDuplicates; /** A view of the list of requests in this instance */ private final List<TRSPRequest> mRequestsView; /** A view of the list of requests in this instance */ private final List<Integer> mRequestsIdView; /** * Returns all the requests in this instance (whether they are released or not). * <p> * Note that the returned list is an unmodifiable view of the internal request list. * </p> * * @return a list containing all the requests in this instance */ public List<TRSPRequest> getRequests() { return mRequestsView; } /** * Returns the ids all the requests in this instance (whether they are released or not). * * @return a list containing the ids of all the requests in this instance */ public List<Integer> getRequestsId() { return mRequestsIdView; } /** * Returns all the released requests in this instance (i.e. that have been made known to the system). * * @return a list containing all the released requests in this instance */ public Collection<Integer> getReleasedRequests() { return getSimulator() != null ? getSimulator().getReleasedRequests() : getRequestsId(); } /** * Returns all the unserved released requests in this instance (i.e. that have been made known to the system, and * are neither served or rejected). * * @return a list containing all the unserved released requests in this instance */ public Collection<Integer> getUnservedReleasedRequests() { return getSimulator() != null ? getSimulator().getUnservedReleasedRequests() : getRequestsId(); } /** * Returns the total number of requests in this instance, ignoring their released state. * * @return the request count */ public int getRequestCount() { return getRequests().size(); } /** * Gets a request from its id * * @param id * the id * @return the request with the given id */ public TRSPRequest getRequest(int id) { return (TRSPRequest) getTRSPNode(id); } /** * Returns the {@link ITRSPNode node} with the specified id. * <p> * {@code id} can either designate a request or a depot * </p> * * @param id * the node id * @return the {@link ITRSPNode node} with the specified id */ public ITRSPNode getTRSPNode(int id) { return mNodes[id]; } /** * Returns the {@link Node} object corresponding to the given {@code id} * * @param id * @return the {@link Node} object corresponding to the given {@code id} */ public Node getNode(int id) { return getTRSPNode(id).getNode(); } /** * Checks if is request. * * @param id * the id * @return true, if is request */ public boolean isRequest(int id) { return getTRSPNode(id).getType() == NodeType.REQUEST; } /** * Size of the instance: total number of nodes including both depots and requests * * @return the total number of nodes in this instance */ public int size() { return getDepotCount() + getRequestCount(); } /** the cost delegate *. */ private TRSPDistanceMatrix mCostDelegate; /** * Sets the cost delegate for this instance, use only when converting instances * * @param costDelegate */ public void setCostDelegate(TRSPDistanceMatrix costDelegate) { mCostDelegate = costDelegate; } /** * Getter for the cost delegate. * * @return the cost delegate */ public TRSPDistanceMatrix getCostDelegate() { return this.mCostDelegate; } /** the delegate class that will generate solutions hash codes **/ private ITRSPSolutionHasher mSolutionHasher; /** * Getter for the delegate class that will generate solutions hash codes * * @return the solution hasher associated with this instance */ public ITRSPSolutionHasher getSolutionHasher() { return this.mSolutionHasher; } /** * Set for the delegate class that will generate solutions hash codes * * @param solutionHasher * the solution hasher associated with this instance */ public void setSolutionHasher(ITRSPSolutionHasher solutionHasher) { mSolutionHasher = solutionHasher; } /** the number of skills *. */ private final int mSkillCount; /** * Getter for the number of skills. * * @return the number of skills */ public int getSkillCount() { return this.mSkillCount; } /** the number of tools *. */ private final int mToolCount; /** * Getter for the number of tools. * * @return the number of tools */ public int getToolCount() { return this.mToolCount; } /** the number of spare parts *. */ private final int mSpareCount; /** * Getter for the number of spare parts. * * @return the number of spare parts */ public int getSpareCount() { return this.mSpareCount; } /** the highest id used for requests and depots **/ private final int mMaxId; /** * Getter for the highest id used for requests and depots * * @return the value of the highest id */ public int getMaxId() { return this.mMaxId; } /** * A matrix representing the existing edges in the reduced graph: <code>mTWGraph[i][j]=true</code> iif node * <code>j</code> can be visited after node <code>i</code> without violating time window constraints */ private final boolean[][] mTWGraph; /** * Feasibility of an arc regarding time windows * * @param pred * the first node * @param succ * the second node * @return <code>true</code> iif node <code>succ</code> can be visited after node <code>pred</code> without * violating time window constraints */ public boolean isArcTWFeasible(int pred, int succ) { if (pred == ITRSPTour.UNDEFINED || succ == ITRSPTour.UNDEFINED) return true; return mTWGraph[pred][succ]; } /** * A matrix representing the skill compatibility between technicians and requests for faster compatibility checks * <code>mTechSkillCompatibility[k][i]=true</code> if technician k has the required skills to serve request i */ private final boolean[][] mTechSkillCompatibility; /** * A matrix representing the tools compatibility between technicians and requests for faster compatibility checks * <code>mTechSkillCompatibility[k][i]=true</code> if technician k initially has the required tools to serve request * i */ private final boolean[][] mTechToolCompatibility; /** * A matrix representing the tools compatibility between technicians and requests for faster compatibility checks * <code>mTechSkillCompatibility[k][i]=true</code> if technician k initially has the required spare parts to serve * request i */ private final boolean[][] mTechSpareCompatibility; /** * A matrix representing the compatibilities between technicians and requests. * <code>mTechReqCompatibility[k][i]=true</code> if k has the required skills to serve i within its time window. In * other words, <code>mTechReqCompatibility[k][i]=true</code> iif there exist a feasible tour visiting i. */ private final boolean[][] mTechReqCompatibility; /** * A mapping between requests and technicians that have the required skills and can service the request with its * time window */ private final ArrayList<Set<Integer>> mCompatibleTech; /** * Returns <code>true</code> iif there exist a feasible tour associated with technician <code>req</code> servicing * request <code>req</code> (considering skills, tools, spare parts, and time windows) * * @param tech * the id of the considered technician * @param req * the id of the considered request * @return <code>true</code> iif there exist a feasible tour associated with technician <code>req</code> servicing * request <code>req</code> */ public boolean isCompatible(int tech, int req) { return mTechReqCompatibility[tech][req]; } /** * @param tech * the id of the considered technician * @param req * the id of the considered request * @return <code>true</code> iif technician <code>tech</code> has the required skills to serve request * <code>req</code> */ public boolean hasRequiredSkills(int tech, int req) { return mTechSkillCompatibility[tech][req]; } /** * @param tech * the id of the considered technician * @param req * the id of the considered request * @return <code>true</code> iif technician <code>tech</code> initially has the required tools to serve request * <code>req</code> */ public boolean hasRequiredTools(int tech, int req) { return mTechToolCompatibility[tech][req]; } /** * @param tech * the id of the considered technician * @param req * the id of the considered request * @return <code>true</code> iif technician <code>tech</code> initially has the required spare parts to serve * request <code>req</code> */ public boolean hasRequiredSpareParts(int tech, int req) { return mTechSpareCompatibility[tech][req]; } /** * Set of compatible technicians. * <p> * A technician is said to be compatible if it has the required skills * </p> * * @param req * the considered request * @return a set containing the technicians that can potentially service request <code>req</code> */ public Set<Integer> getCompatibleTechnicians(int req) { return mCompatibleTech.get(req); } /** the allow main depot trip flag **/ private boolean mMainDepotTripAllowed = true; /** * Getter for the allow main depot trip flag (default value is <code>true</code>) * * @return <code>true</code> if trips to the main depot are allowed */ public boolean isMainDepotTripAllowed() { return this.mMainDepotTripAllowed; } /** * Setter for the allow main depot trip flag (default value is <code>true</code>) * * @param allowTrip * <code>true</code> if trips to the main depot are allowed */ public void setMainDepotTripAllowed(boolean allowTrip) { this.mMainDepotTripAllowed = allowTrip; } /** the allow unserved request flag **/ private boolean mUnservedReqAllowed = false; /** * Getter for the allow unserved request flag (default value is <code>false</code>) * * @return <code>true</code> if some requests can be left unserved, <code>false</code> otherwise */ public boolean isUnservedReqAllowed() { return this.mUnservedReqAllowed; } /** * Setter for the allow unserved request flag (default value is <code>false</code>) * * @param allowUnserved * <code>true</code> if some requests can be left unserved, <code>false</code> otherwise */ public void setUnservedReqAllowed(boolean allowUnserved) { this.mUnservedReqAllowed = allowUnserved; } /** the simulator being used with this instance **/ private TRSPSimulator mSimulator; /** * Setup the simulator being used with this instance * * @param params * the global parameters for the simulator * @param simulator * the simulator being used with this instance * @throws IllegalStateException * if the simulator was set previously */ public void setupSimulator(TRSPCostDelegate costDelegate, TRSPGlobalParameters params) { if (this.mSimulator != null) throw new IllegalStateException("The simulator was set previously"); this.mSimulator = new TRSPSimulator(this, costDelegate, params); } /** * Getter for the simulator being used with this instance * * @return the simulator being used with this instance */ public TRSPSimulator getSimulator() { return this.mSimulator; } /** * Returns {@code true} iif the request has already been served, or is already assigned, or has been rejected * * @param reqId * @return {@code true} iif the request has already been served, or is already assigned, or has been rejected * @see TRSPSimulator#isServedOrAssigned(int) * @see TRSPSimulator#isRejected(int) */ public boolean isServedOrAssignedOrRejected(int reqId) { return getSimulator() != null && (getSimulator().isServedOrAssigned(reqId) || getSimulator().isRejected(reqId)); } /** The degree of dynamism of this instance */ private double mDod; /** * Returns {@code true} if this is a dynamic instance * * @return {@code true} if this is a dynamic instance */ public boolean isDynamic() { return mDod != 0; } /** * Set the degree of dynamism of this instance * * @param dod * the degree of dynamism of this instance */ public void setDod(double dod) { mDod = dod; } /** * Returns the degree of dynamism of this instance * * @return the degree of dynamism of this instance */ public double getDod() { return mDod; } /** a flag to identify CVRPTW instances **/ private final boolean mCVRPTW; /** * Getter for a flag to identify CVRPTW instances * * @return {@code true} if this is an instance of the CVRPTW */ public boolean isCVRPTW() { return this.mCVRPTW; } /** The state (started/stoped) of each technician */ private final boolean[] mTechState; /** Cached time windows (used for performance as getTimeWindow is invoked very often) */ private final ITimeWindow[] mTimeWindows; /** the maximum duration of a tour **/ private double mMaxTourDuration = Double.POSITIVE_INFINITY; /** * Getter for the maximum duration of a tour * * @return the maximum duration of a tour */ public double getMaxTourDuration() { return this.mMaxTourDuration; } /** * Setter for the maximum duration of a tour * * @param maxDuration * the maximum duration of a tour */ public void setMaxTourDuration(double maxDuration) { this.mMaxTourDuration = maxDuration; } /** A flag to force the definition of fwd slack time **/ private boolean mForceFwdSlackTime; /** * Getter for A flag to force the definition of fwd slack time * * @return {@code true} if the fwd slack time has to be defined */ public boolean isForceFwdSlackTime() { return this.mForceFwdSlackTime; } /** * Setter for A flag to force the definition of fwd slack time * * @param forceFwdSlk * {@code true} if the fwd slack time has to be defined */ public void setForceFwdSlackTime(boolean forceFwdSlk) { this.mForceFwdSlackTime = forceFwdSlk; } /** * Creates a new <code>TRSPInstance</code>. * * @param name * the name of this instance * @param technicians * the fleet of technician * @param skillCount * the total number of skills * @param toolCount * the total number of tool types * @param spareCount * the total number of spare part types * @param depots * a list of the depot, including possible technician homes. The main depot is assumed to have id 0, all * other depots have ids 1 to <code>|depots|-1</code>. * @param requests * a list of the requests, with ids ranging from <code>|depots|</code> to * <code>|depots|+|requests|-1</code> * @param cvrptw * {@code true} if this is a cvrptw instance, {@code false} for a generic TRSP instance */ public TRSPInstance(String name, Collection<Technician> technicians, int skillCount, int toolCount, int spareCount, List<Depot> depots, List<TRSPRequest> requests, boolean cvrptw) { this(name, TechnicianFleet.newTechnicianFleet(technicians), skillCount, toolCount, spareCount, depots, requests, cvrptw); } /** * Creates a new <code>TRSPInstance</code>. * * @param name * the name of this instance * @param technicians * the fleet of technician * @param skillCount * the total number of skills * @param toolCount * the total number of tool types * @param spareCount * the total number of spare part types * @param depots * a list of the depot, including possible technician homes. The main depot is assumed to have id 0, all * other depots have ids 1 to <code>|depots|-1</code>. * @param requests * a list of the requests, with ids ranging from <code>|depots|</code> to * <code>|depots|+|requests|-1</code> * @param cvrptw * {@code true} if this is a cvrptw instance, {@code false} for a generic TRSP instance */ public TRSPInstance(String name, TechnicianFleet technicians, int skillCount, int toolCount, int spareCount, List<Depot> depots, List<TRSPRequest> requests, boolean cvrptw) { mDod = 0; mCVRPTW = cvrptw; mSolutionHasher = new ITRSPSolutionHasher() { @Override public int hash(TRSPSolution solution) { return solution != null ? solution.hashCode() : 0; } @Override public int hash(ITRSPTour tour) { return tour != null ? tour.hashCode() : 0; } }; final int depotCount = depots.size(); final int requestCount = requests.size(); // Find the maximum id int max = 0; for (Depot d : depots) { if (d.getID() >= max) max = d.getID() + 1; if (d.getID() >= depotCount) throw new IllegalArgumentException("Illegal id for depot " + d); } int depotMaxId = max; int reqMaxId = 0; for (TRSPRequest d : requests) { if (d.getID() >= max) max = d.getID() + 1; if (d.getID() >= reqMaxId) reqMaxId = d.getID(); } int homesOffset = max - 1; int mainDepotOffset = max + depotMaxId - 1; mMaxId = mainDepotOffset + (cvrptw ? 0 : technicians.size()); mNodes = new ITRSPNode[mMaxId]; mName = name; mFleet = technicians; mSkillCount = skillCount; mToolCount = toolCount; mSpareCount = spareCount; mTechState = new boolean[getFleet().size()]; mTechSkillCompatibility = new boolean[getFleet().size()][mMaxId]; mTechToolCompatibility = new boolean[getFleet().size()][mMaxId]; mTechSpareCompatibility = new boolean[getFleet().size()][mMaxId]; mTechReqCompatibility = new boolean[getFleet().size()][mMaxId]; // A list of all ids that were used Depot[] depotsArray = new Depot[depotCount]; mMainDepotDuplicates = new DepotNode[getFleet().size()]; for (Depot d : depots) { // Check id depotsArray[d.getID()] = d; DepotNode node; if (d.getID() == MAIN_DEPOT) { // This is the main depot node = new DepotNode(d, d.getID(), d.getID() == MAIN_DEPOT, d.getID(), ITRSPTour.UNDEFINED); for (Technician t : getFleet()) { DepotNode dup; if (!isCVRPTW()) { // Create a duplicate for this technician dup = node.duplicate(node.getID() + mainDepotOffset + t.getID()); setNode(dup); } else { dup = node; } // Store a reference to the duplicate for this technician mMainDepotDuplicates[t.getID()] = dup; } } else { // This is a home depot // Create a duplicate for the end of the tour node = new DepotNode(d, d.getID(), d.getID() == MAIN_DEPOT, d.getID(), d.getID() + homesOffset); setNode(node.duplicate(node.getDuplicate())); } setNode(node); } mDepots = Collections.unmodifiableList(Arrays.asList(depotsArray)); TRSPRequest[] reqArray = new TRSPRequest[requestCount]; ArrayList<Integer> requestsIdView = new ArrayList<>(requestCount); for (TRSPRequest r : requests) { setNode(r); reqArray[r.getID() - depotCount] = r; requestsIdView.add(r.getID()); } mRequestsIdView = Collections.unmodifiableList(requestsIdView); mRequestsView = Collections.unmodifiableList(Arrays.asList(reqArray)); if (getFleet().getVehicle().getSpeed() == 1) mCostDelegate = new TRSPDistanceMatrixUnitSpeed(this); else mCostDelegate = new TRSPDistanceMatrix(this); // Cache information mTimeWindows = new ITimeWindow[getMaxId()]; for (int i = 0; i < mTimeWindows.length; i++) { mTimeWindows[i] = getTRSPNode(i).getTimeWindow(); } // Preprocess the instance and create the TW compatible graph mTWGraph = new boolean[getMaxId()][getMaxId()]; mCompatibleTech = new ArrayList<Set<Integer>>(getMaxId()); for (int i = 0; i < getMaxId(); i++) mCompatibleTech.add(null); preprocess(); checkInstance(); } /** * Store the given node in the {@code mNodes} array * * @param n * the node to be stored * @throw {@link IllegalArgumentException} if a node with the same id was already stored */ private void setNode(ITRSPNode n) { if (mNodes[n.getID()] != null) throw new IllegalArgumentException(String.format( "Id %s is already used (is:%s new:%s)", n.getID(), mNodes[n.getID()], n)); mNodes[n.getID()] = n; } private void checkInstance() { for (int i = 0; i < mNodes.length; i++) { if (mNodes[i] == null) throw new IllegalStateException("No node for id " + i); } } /** * Pre-process the instance for faster operations * * @return the list of requests that were detected as infeasible */ public List<TRSPRequest> preprocess() { ArrayList<TRSPRequest> infeasible = new ArrayList<>(); for (int i = 0; i < mTWGraph.length; i++) { // Prune infeasible arcs for (int j = 0; j < mTWGraph.length; j++) { if (i != j) mTWGraph[i][j] = getTimeWindow(j).isFeasible( getTimeWindow(i).startAsDouble() + getServiceTime(i) + getCostDelegate() .getTravelTime(i, j, getFleet().getVehicle())); } // Store request - technician compatibilities boolean atLeastOneTech = false; if (isRequest(i)) mCompatibleTech.set(i, new HashSet<Integer>()); for (Technician t : getFleet()) { mTechReqCompatibility[t.getID()][MAIN_DEPOT] = true; mTechReqCompatibility[t.getID()][t.getHome().getID()] = true; if (!isRequest(i)) { // i is not a request, it is therefore compatible with the // technician mTechSkillCompatibility[t.getID()][i] = true; mTechToolCompatibility[t.getID()][i] = true; mTechSpareCompatibility[t.getID()][i] = true; atLeastOneTech = true; } else { // i is a request, store compatibilities mTechSkillCompatibility[t.getID()][i] = getRequest(i).getSkillSet() .isCompatibleWith(t.getSkillSet()); mTechToolCompatibility[t.getID()][i] = getRequest(i).getToolSet() .isCompatibleWith(t.getToolSet()); mTechSpareCompatibility[t.getID()][i] = Utilities.compare(getRequest(i) .getSparePartRequirements(), t.getSpareParts()) <= 0; // Check compatibility if (mTechSkillCompatibility[t.getID()][i]) { double arrivalTime; double returnTime; if (mTechToolCompatibility[t.getID()][i] && mTechSpareCompatibility[t.getID()][i]) { // The technician can service the request directly arrivalTime = calculateArrivalTime(i, t.getHome().getID(), t.getHome() .getTimeWindow().startAsDouble(), t.getID()); } else { // The technician needs to visit first the main // depot arrivalTime = calculateArrivalTime(getMainDepot().getID(), t.getHome() .getID(), t.getHome().getTimeWindow().startAsDouble(), t.getID()); arrivalTime = calculateArrivalTime(i, getMainDepot().getID(), arrivalTime, t.getID()); } returnTime = calculateArrivalTime(t.getHome().getID(), i, arrivalTime, t.getID()); mTechReqCompatibility[t.getID()][i] = // getTimeWindow(i).isFeasible(arrivalTime) && getTimeWindow(t.getHome().getID()).isFeasible(returnTime); } else { mTechReqCompatibility[t.getID()][i] = false; } // Check if the request can be serviced by the technician if (mTechReqCompatibility[t.getID()][i]) { atLeastOneTech = true; mCompatibleTech.get(i).add(t.getID()); } } } if (!atLeastOneTech) { TRSPLogging.getSetupLogger().warn( "Request %s cannot be served by any technician (%s)", i, getRequest(i)); infeasible.add(getRequest(i)); } } return infeasible; } /** * Utility method to get the spare part demand associated with a node * * @param node * the considered node * @param type * the type of spare part considered * @return the spare part requirement of <code>node</code>, or <code>0</code> if <code>node</code> is a depot * @see TRSPInstance#getDepot(int) * @see TRSPInstance#getRequest(int) * @see TRSPRequest#getSparePartRequirement(int) */ public int getSparePartReq(int node, int type) { if (isDepot(node)) return 0; else return getRequest(node).getSparePartRequirement(type); } /** * Utility method to get the time window associated with a node * * @param node * the considered node * @return the time window of <code>node</code> * @see TRSPInstance#getDepot(int) * @see TRSPInstance#getRequest(int) * @see TRSPRequest#getTimeWindow() * @see Depot#getTimeWindow() */ public ITimeWindow getTimeWindow(int node) { return mTimeWindows[node]; // if (isDepot(node)) // return getDepot(node).getTimeWindow(); // else // return getRequest(node).getTimeWindow(); } /** * Utility method to get the service time associated with a node. * <p> * In this implementation depots are assumed to have a service time of <code>0</code>. * </p> * * @param node * the considered node * @return the service time of <code>node</code> * @see TRSPInstance#getRequest(int) * @see TRSPRequest#getServiceTime() */ public double getServiceTime(int node) { return getTRSPNode(node).getServiceTime(); } /** * Utility method to evaluate the arrival time at a node depending on the arrival time at its predecessor * * @param node * the considered node * @param pred * the <code>node</code> predecessor * @param arrivalPred * the arrival time at <code>pred</code> * @param techId * the id of the considered technician * @return the arrival time at <code>node</code> */ public double calculateArrivalTime(int node, int pred, double arrivalPred, int techId) { return getTimeWindow(pred).getEarliestStartOfService(arrivalPred) + getServiceTime(pred) + getCostDelegate().getTravelTime(pred, node, getTechnician(techId)); } @Override public String toString() { return String.format("%s (crew:%s req:%s rel:%s)", getName(), getFleet().size(), getRequestCount(), getReleasedRequests().size()); } // -------------------------------------------------------------------------------------------- // MSA IInstance implementation // -------------------------------------------------------------------------------------------- @Override public boolean requestReleased(IActualRequest request) { if (!isRequest(request.getID())) throw new IllegalArgumentException("Unknown request: " + request); getSimulator().requestReleased((TRSPRequest) request); return true; } @Override public void setResourceStopped(int resourceId, Object param) { mTechState[resourceId] = false; } @Override public void setResourceStarted(int resourceId, Object param) { mTechState[resourceId] = true; } @Override public boolean isResourceStarted(int resourceId) { return mTechState[resourceId]; } @Override public boolean isResourceStopped(int resourceId) { return !mTechState[resourceId]; } @Override public IActualRequest getNodeVisit(int requestId) { return getRequest(requestId); } @Override public List<? extends IActualRequest> getServedRequests() { return toRequestList(getSimulator().getServedRequests()); } @Override public List<? extends IActualRequest> getServedRequests(int resourceId) { ArrayList<TRSPRequest> servedReq = new ArrayList<TRSPRequest>(getSimulator() .getCurrentSolution().getTour(resourceId).length()); for (Integer i : getSimulator().getCurrentSolution().getTour(resourceId)) { if (isRequest(i)) servedReq.add(getRequest(i)); } return servedReq; } @Override public List<? extends IActualRequest> getPendingRequests() { return toRequestList(getSimulator().getCurrentSolution().getUnservedRequests()); } @Override public boolean assignRequestToResource(IActualRequest request, int resourceId) { ITRSPNode prev = getSimulator().getAssignedNode(resourceId); if (prev != null) return false; getSimulator().assignNodeToTechnician(resourceId, (ITRSPNode) request, ((ITRSPNode) request).getArrivalTime()); return true; } @Override public boolean markRequestAsServed(IActualRequest request, int resourceId) { ITRSPNode prev = getSimulator().getAssignedNode(resourceId); if (prev == null || prev.getID() != request.getID()) throw new IllegalStateException(String.format( "Technician %s was assigned to %s but %s was marked as served", resourceId, prev, request)); getSimulator().markCurrentRequestAsServed(resourceId); return true; } @Override public TRSPSolution getCurrentSolution() { return getSimulator().getCurrentSolution(); } /** * Returns a list containing the requests which ids are contained in {@code ids} * * @param ids * @return a list containing the requests which ids are contained in {@code ids} */ public List<TRSPRequest> toRequestList(Collection<Integer> ids) { ArrayList<TRSPRequest> list = new ArrayList<TRSPRequest>(ids.size()); for (Integer i : ids) { if (isRequest(i)) list.add(getRequest(i)); } return list; } // -------------------------------------------------------------------------------------------- // -------------------------------------------------------------------------------------------- // ILockable interface implementation // -------------------------------------------------------------------------------------------- /** A lock to be used by this instance */ private final ExtendedReentrantLock mLock = new ExtendedReentrantLock(); @Override public boolean tryLock(long timeout) { try { return getLockInstance().tryLock(timeout, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { return false; } } @Override public void acquireLock() { try { if (!getLockInstance().tryLock(TRY_LOCK_TIMOUT, TRY_LOCK_TIMOUT_UNIT)) { throw new IllegalStateException( String.format( "Unable to acquire lock on this instance of %s (%s) after %s %s, owner: %s", this.getClass().getSimpleName(), hashCode(), TRY_LOCK_TIMOUT, TRY_LOCK_TIMOUT_UNIT, getLockInstance().getOwnerName())); } } catch (InterruptedException e) { throw new IllegalStateException(String.format( "Unable to acquire lock on this instance of %s (%s)", this.getClass() .getSimpleName(), hashCode()), e); } ; } @Override public void releaseLock() { mLock.unlock(); } @Override public boolean isLockOwnedByCurrentThread() { return mLock.isHeldByCurrentThread(); } @Override public ExtendedReentrantLock getLockInstance() { return mLock; } // -------------------------------------------------------------------------------------------- /** * The class <code>DepotNode</code> is a wrapper around an instance of {@link Depot} used in the MSA framework * <p> * Creation date: Mar 19, 2012 - 4:25:14 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 static class DepotNode implements ITRSPNode, IToShortString { private final Depot mDepot; private final NodeType mType; private final int mId; private final int mOriginal; private final int mDuplicate; private double mArrivalTime; /** * Returns the depot wrapped in this instance * * @return the depot wrapped in this instance */ public Depot getDepot() { return mDepot; } /** * Creates a new <code>DepotActualRequest</code> * * @param depot */ public DepotNode(Depot depot, int id, boolean mainDepot, int original, int duplicate) { mDepot = depot; mId = id; mType = mainDepot ? NodeType.MAIN_DEPOT : NodeType.HOME; mOriginal = original; mDuplicate = duplicate; mArrivalTime = Double.NaN; } public DepotNode duplicate(int newId) { DepotNode dup = new DepotNode(mDepot, newId, mType == NodeType.MAIN_DEPOT, mId, newId); return dup; } @Override public int getID() { return mId; } @Override public double getServiceTime() { return 0;// TODO add service time to depots? } @Override public double getReleaseDate() { return -1; } @Override public ITimeWindow getTimeWindow() { return getDepot().getTimeWindow(); } @Override public NodeType getType() { return mType; } @Override public Node getNode() { return getDepot(); } /** * Returns the id this node's duplicate, or {@link ITRSPTour#UNDEFINED} if none is defined * * @return the id this node's duplicate, or {@link ITRSPTour#UNDEFINED} if none is defined */ public int getDuplicate() { return mDuplicate; } /** * Returns the id of the original node, or this node's id if this is not a duplicate * * @return the id of the original node, or this node's id if this is not a duplicate */ public int getOriginal() { return mOriginal; } /** * Returns {@code true} if this is the duplicate of a node * * @return {@code true} if this is the duplicate of a node */ public boolean isDuplicate() { return mOriginal != mId; } @Override public String toString() { return String.format("%s loc:%s tw:%s s:%s %s(%s,%s)", getID(), getNode().getLocation(), getTimeWindow(), getServiceTime(), getType(), getOriginal(), getDuplicate()); } @Override public String toShortString() { return "" + getID(); } @Override public String getDescription() { return String.format("%s%-3s", getType().toShortString(), getID()); } /** * Getter for the arrival time at the request. The value will only be defined in a dynamic context * * @return the arrival */ @Override public double getArrivalTime() { if (Double.isNaN(mArrivalTime)) throw new IllegalStateException("ArrivalTime time is not defined for request " + mId); return mArrivalTime; } /** * Setter for the arrival time at the request * * @param arrival * the arrival time to set */ @Override public void setArrivalTime(double arrival) { mArrivalTime = arrival; } } }