/**
*
*/
package vroom.trsp.sim;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Observable;
import java.util.Observer;
import java.util.Set;
import vroom.common.utilities.IntegerSet;
import vroom.common.utilities.Utilities;
import vroom.trsp.datamodel.ITRSPNode;
import vroom.trsp.datamodel.ITRSPNode.NodeType;
import vroom.trsp.datamodel.ITRSPTour;
import vroom.trsp.datamodel.TRSPInstance;
import vroom.trsp.datamodel.TRSPRequest;
import vroom.trsp.datamodel.TRSPSolution;
import vroom.trsp.datamodel.TRSPSolutionChecker;
import vroom.trsp.datamodel.TRSPTour;
import vroom.trsp.datamodel.TRSPTour.TRSPTourIterator;
import vroom.trsp.datamodel.costDelegates.TRSPCostDelegate;
import vroom.trsp.optimization.mpa.DTRSPSolution;
import vroom.trsp.util.TRSPGlobalParameters;
import vroom.trsp.util.TRSPLogging;
/**
* <code>TRSPSimulator</code>
* <p>
* Creation date: Nov 8, 2011 - 4:53:20 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 TRSPSimulator extends Observable {
/**
* The cutoff times depending on the instance group
*/
public static final Map<String, Double> CUTOFF_TIMES = new HashMap<>();
static {
CUTOFF_TIMES.put("C1", 0.380);
CUTOFF_TIMES.put("C2", 0.509);
CUTOFF_TIMES.put("R1", 0.357);
CUTOFF_TIMES.put("R2", 0.419);
CUTOFF_TIMES.put("RC1", 0.321);
CUTOFF_TIMES.put("RC2", 0.400);
}
public static enum NotificationType {
NODE_ASSIGNED, NODE_SERVED, REQ_REJECTED, REQ_RELEASED, NODE_SET_AS_CURRENT, TIME_CHANGED
};
private final TRSPInstance mInstance;
/** A list of the requests that have already been released */
private final IntegerSet mReleasedRequests;
private final Set<Integer> mReleasedRequestsView;
/** A list of the unserved requests that have already been released */
private final IntegerSet mUnservedReleasedRequests;
private final Set<Integer> mUnservedReleasedRequestsView;
private final IntegerSet mServedRequests;
private final Set<Integer> mServedRequestsView;
private final IntegerSet mRejectedRequests;
private final Set<Integer> mRejectedRequestsView;
private final LinkedList<TRSPRequest> mReleaseQueue;
private final List<TRSPRequest> mReleaseQueueView;
private double mClock;
private final DTRSPSolution mCurrentSolution;
private final ITRSPNode[] mAssignedNodes;
private final ITRSPNode[] mCurrentNode;
/** a flag set to {@code true} when the simulator is in a static setting **/
private boolean mStaticSetting;
private final TRSPGlobalParameters mParameters;
/**
* Getter for a flag set to {@code true} when the simulator is in a static setting
*
* @return {@code true} if the simulator is in a static setting
*/
public boolean isStaticSetting() {
return this.mStaticSetting;
}
/**
* Creates a new <code>TRSPSimulator</code>
*
* @param instance
* @param costDelegate
* @param params
*/
public TRSPSimulator(TRSPInstance instance, TRSPCostDelegate costDelegate,
TRSPGlobalParameters params) {
super();
mParameters = params;
mStaticSetting = false;
mInstance = instance;
mServedRequests = new IntegerSet(mInstance.getMaxId());
mServedRequestsView = Collections.unmodifiableSet(mServedRequests);
mRejectedRequests = new IntegerSet(mInstance.getMaxId());
mRejectedRequestsView = Collections.unmodifiableSet(mRejectedRequests);
mReleaseQueue = new LinkedList<TRSPRequest>();
mReleaseQueueView = Collections.unmodifiableList(mReleaseQueue);
mReleasedRequests = new IntegerSet(mInstance.getMaxId());
mReleasedRequestsView = Collections.unmodifiableSet(mReleasedRequests);
mUnservedReleasedRequests = new IntegerSet(mInstance.getMaxId());
mUnservedReleasedRequestsView = Collections.unmodifiableSet(mUnservedReleasedRequests);
for (TRSPRequest r : mInstance.getRequests()) {
if (r.getReleaseDate() >= 0) {
mReleaseQueue.add(r);
} else {
mReleasedRequests.add(r.getID());
mUnservedReleasedRequests.add(r.getID());
}
}
Collections.sort(mReleaseQueue, new Comparator<TRSPRequest>() {
@Override
public int compare(TRSPRequest o1, TRSPRequest o2) {
return Double.compare(o1.getReleaseDate(), o2.getReleaseDate());
}
});
setSimulationTime(-1);
mAssignedNodes = new ITRSPNode[instance.getFleet().size()];
mCurrentNode = new ITRSPNode[instance.getFleet().size()];
mCurrentSolution = new DTRSPSolution(getInstance(), costDelegate);
for (TRSPRequest r : mReleaseQueue)
mCurrentSolution.markAsServed(r.getID());
for (TRSPTour t : mCurrentSolution)
t.setAutoUpdated(true);
mCurrentSolution.freeze();
// TODO Find more robust way of setting the cutoff
if (!instance.isCVRPTW() && CUTOFF_TIMES.containsKey(instance.getGroup())) {
params.set(TRSPGlobalParameters.RUN_CUTOFF_TIME, CUTOFF_TIMES.get(instance.getGroup()));
TRSPLogging.getSimulationLogger().info(
"Setting the cutoff time to %4.3f (%.1f)",
params.get(TRSPGlobalParameters.RUN_CUTOFF_TIME),
instance.getMainDepot().getTimeWindow().endAsDouble()
* params.get(TRSPGlobalParameters.RUN_CUTOFF_TIME));
} else {
params.set(TRSPGlobalParameters.RUN_CUTOFF_TIME, Double.POSITIVE_INFINITY);
}
}
/**
* Returns the parent instance
*
* @return the parent instance
*/
public TRSPInstance getInstance() {
return mInstance;
}
/**
* Returns the currently executed solution
*
* @return the currently executed solution
*/
public DTRSPSolution getCurrentSolution() {
return mCurrentSolution;
}
/**
* Setter for <code>clock</code>
*
* @param clock
* the clock to set
*/
private void setSimulationTime(double clock) {
mClock = clock;
}
/**
* Returns the current simulation time
*
* @return the current simulation time
*/
public double simulationTime() {
return mClock;
}
/**
* Advance the simulation time
*
* @param time
* @throws IllegalArgumentException
* if {@code time} is before than current simulation time
*/
public void advanceTime(double time) {
if (time < simulationTime())
throw new IllegalArgumentException("Time must be greater than current simulation time");
setSimulationTime(time);
// Remove requests from the release queue
while (!mReleaseQueue.isEmpty()
&& mReleaseQueue.peek().getReleaseDate() <= simulationTime())
mReleaseQueue.pop();
notifyUpdate(NotificationType.TIME_CHANGED, -1, -1);
}
/**
* Returns a view of the list of unreleased requests
*
* @return a view of the list of unreleased requests
*/
public List<TRSPRequest> getUnreleasedRequests() {
return mReleaseQueueView;
}
/**
* Returns the number of unreleased requests
*
* @return the number of unreleased requests
*/
public int getUnreleasedCount() {
return mReleaseQueue.size();
}
public boolean hasUnreleasedRequests() {
return !mReleaseQueue.isEmpty();
}
/**
* Returns the next released requests and advance the internal clock to the correct time
*
* @return the next released requests
*/
public Collection<TRSPRequest> nextRelease() {
TRSPRequest r = mReleaseQueue.pop();
setSimulationTime(r.getReleaseDate());
LinkedList<TRSPRequest> release = new LinkedList<TRSPRequest>();
release.add(r);
requestReleased(r);
while (!mReleaseQueue.isEmpty()
&& mReleaseQueue.peek().getReleaseDate() == simulationTime()) {
r = mReleaseQueue.pop();
release.add(r);
requestReleased(r);
}
return release;
}
public void requestReleased(TRSPRequest request) {
mReleaseQueue.remove(request);
mReleasedRequests.add(request.getID());
mUnservedReleasedRequests.add(request.getID());
mCurrentSolution.markAsUnserved(request.getID());
notifyUpdate(NotificationType.REQ_RELEASED, -1, request.getID());
}
/**
* 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 Set<Integer> getReleasedRequests() {
return mReleasedRequestsView;
}
/**
* 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 Set<Integer> getUnservedReleasedRequests() {
return mUnservedReleasedRequestsView;
}
/**
* Returns {@code true} iif the request has already been served or has been rejected
*
* @param reqId
* @return {@code true} iif the request has already been served or has been rejected
*/
public boolean isServedOrRejected(int reqId) {
return isServed(reqId) || isRejected(reqId);
}
/**
* Returns {@code true} iif the node with id {@code nodeId} has been served or is currently assigned to a
* technician.
* <p>
* This method is based on the definition of the {@link #getCurrentSolution() current solution}</p<
*
* @param nodeId
* @return {@code true} iif the node with id {@code nodeId} has been served or is currently assigned to a
* technician.
*/
public boolean isServedOrAssigned(int nodeId) {
return getCurrentSolution() != null && getCurrentSolution().getVisitingTour(nodeId) != null;
}
/**
* Returns {@code true} iif the request has been served
*
* @param id
* @return {@code true} iif the request has been served
*/
public boolean isServed(int id) {
return mServedRequests.contains(id);
}
/**
* Returns {@code true} iif the request has been rejected
*
* @param id
* @return {@code true} iif the request has been rejected
*/
public boolean isRejected(int id) {
return mRejectedRequests.contains(id);
}
/**
* Returns the set of served requests
*
* @return the set of served requests
*/
public Set<Integer> getServedRequests() {
return mServedRequestsView;
}
/**
* Mark a request as rejected
*
* @param reqId
*/
public void markAsRejected(int reqId) {
if (getInstance().isRequest(reqId)) {
mRejectedRequests.add(reqId);
mReleasedRequests.remove(reqId);
mUnservedReleasedRequests.remove(reqId);
getCurrentSolution().markAsServed(reqId);
notifyUpdate(NotificationType.REQ_REJECTED, -1, reqId);
}
}
/**
* Returns the set of rejected requests
*
* @return the set of rejected requests
*/
public Set<Integer> getRejectedRequests() {
return mRejectedRequestsView;
}
/**
* Returns {@code true} iif the request has already been released, i.e. made known to the system.
*
* @param reqId
* @return {@code true} iif the request has already been released
*/
public boolean isReleased(int reqId) {
return getReleasedRequests().contains(reqId);
}
/**
* Assign a request to a technician, and append it to the current solution
*
* @param technician
* the technician to which {@code node} will be assigned
* @param node
* the request to be assigned
* @param arrivalTime
* the arrival time at {@code node}
* @return the request previously assigned to {@code technician}
*/
public ITRSPNode assignNodeToTechnician(int technician, ITRSPNode node, double arrivalTime) {
ITRSPNode prev = mAssignedNodes[technician];
mAssignedNodes[technician] = node;
if (node != null) {
appendToCurrentSolution(node.getID(), technician, arrivalTime);
notifyUpdate(NotificationType.NODE_ASSIGNED, technician, node.getID());
}
return prev;
}
/**
* Append {@code node} to the tour of {@code technician} in the current solution
*
* @param node
* the node to be appended
* @param technician
* the id of the considered technician
* @param arrivalTime
* the time at which the appended node will be visited
*/
private void appendToCurrentSolution(int node, int technician, double arrivalTime) {
mCurrentSolution.unfreeze();
// Ensure that earliestArrival is properly set
TRSPTour tour = mCurrentSolution.getTour(technician);
tour.appendNode(node, arrivalTime);
mCurrentSolution.freeze(node, arrivalTime, tour.getEarliestDepartureTime(node));
mCurrentSolution.markAsServed(node);
mUnservedReleasedRequests.remove(node);
mCurrentSolution.freeze();
}
/**
* Returns the node assigned to a technician
*
* @param technician
* @return the node assigned to {@code technician}
*/
public ITRSPNode getAssignedNode(int technician) {
return mAssignedNodes[technician];
}
/**
* Returns a list of the currently assigned nodes
*
* @return a list of the currently assigned nodes
*/
public List<ITRSPNode> getAssignedNodes() {
ArrayList<ITRSPNode> nodes = new ArrayList<>(mAssignedNodes.length);
for (ITRSPNode n : mAssignedNodes)
if (n != null)
nodes.add(n);
return nodes;
}
/**
* Returns the current (or last known) location of a technician
*
* @param technician
* @return the current (or last known) location of a technician
*/
public ITRSPNode getCurrentNode(int technician) {
return mCurrentNode[technician];
}
/**
* Sets the current location of the technician as the previously assigned location
*
* @param technician
* the id of the technician
* @throws IllegalStateException
* if no assigned node is defined for {@code technician}
* @return the assigned/current node
*/
public ITRSPNode setAssignedNodeAsCurrent(int technician) {
ITRSPNode node = getAssignedNode(technician);
if (node == null)
throw new IllegalStateException(String.format("Technician %s has no assigned node",
technician));
mCurrentNode[technician] = node;
notifyUpdate(NotificationType.NODE_SET_AS_CURRENT, technician, node.getID());
return node;
}
/**
* Mark the current request as served, and set the assigned node to <code>null</code>
*
* @param technician
* the id of the technician
* @throws IllegalStateException
* if no current node is defined for {@code technician}
* @return the current/served node
*/
public ITRSPNode markCurrentRequestAsServed(int technician) {
ITRSPNode node = getCurrentNode(technician);
if (node == null)
throw new IllegalStateException(String.format(
"Technician %s has no current node defined", technician));
if (node.getType() == NodeType.REQUEST) {
mServedRequests.add(node.getID());
mUnservedReleasedRequests.remove(node.getID());
}
notifyUpdate(NotificationType.NODE_SERVED, technician, node.getID());
assignNodeToTechnician(technician, null, 0);
return node;
}
/**
* Update the current solution
*
* @param solution
* the solution that is currently executed
*/
public void updateState(TRSPSolution solution) {
TRSPLogging.getSimulationLogger().info("Updating the current state - sim time: %.1f",
simulationTime());
DTRSPSolution dsol = DTRSPSolution.class.isAssignableFrom(solution.getClass()) ? (DTRSPSolution) solution
: null;
for (TRSPTour currentTour : mCurrentSolution) {
TRSPTour updateTour = solution.getTour(currentTour.getTechnicianId());
TRSPTourIterator curIt = currentTour.iterator();
TRSPTourIterator upIt = updateTour.iterator();
// Check that the current executed portions are the same
int pred = ITRSPTour.UNDEFINED;
while (curIt.hasNext()) {
if (!upIt.hasNext())
throw new IllegalStateException(
"The updated tour is shorter than the current tour :" + updateTour);
int cur = curIt.next();
pred = upIt.next();
if (cur != pred)
throw new IllegalStateException(
"The updated tour differs from the current tour :" + updateTour);
if (currentTour.getEarliestArrivalTime(cur) != updateTour
.getEarliestArrivalTime(cur))
throw new IllegalStateException(
"The updated tour has a different earliest departure for node " + cur);
}
// Append the portion that was executed
// boolean assignNext = false;
int node = ITRSPTour.UNDEFINED;
while (upIt.hasNext()) {
node = upIt.next();
// if (updateTour.getLatestFeasibleDepartureTime(n) <= simulationTime()) {
// The latest departure time so that there is no waiting at the next node
double latestDeparture = pred != ITRSPTour.UNDEFINED ? updateTour
.getWaitDepartureTime(pred) : currentTour.getEarliestStartTime();
boolean departed = latestDeparture <= simulationTime();
if ((// assignNext ||
departed || currentTour.length() == 0) // node should be appended
// Do not send a technician home before cutoff
&& !(pred != ITRSPTour.UNDEFINED
&& getInstance().getTRSPNode(node).getType().isHome() && !cutoff(simulationTime()))) {
// The arrival time at node depending on the latest departure time at the predecessor
double arrivalTime = latestDeparture
+ (pred != ITRSPTour.UNDEFINED ? currentTour.getTravelTime(pred, node)
: 0);
assignNodeToTechnician(updateTour.getTechnicianId(),
getInstance().getTRSPNode(node), arrivalTime);
if (dsol != null) {
if (!dsol.isFrozen(node))
updateTour.setEarliestArrivalTime(node, arrivalTime);
if (pred != ITRSPTour.UNDEFINED)
// Update the previous node
dsol.freeze(pred, updateTour.getEarliestArrivalTime(pred),
latestDeparture);
// Freeze the current node
dsol.freeze(node, arrivalTime, updateTour.getEarliestDepartureTime(node));
}
// if (!departed)
// // The vehicle is assigned to node but has not finished servicing it yet
// assignNext = false;
// else
// // We will assign the next node, regardless of its departure date as vehicle may be en route
// assignNext = true;
} else {
freezeTechnician(dsol, currentTour.getTechnicianId(), pred);
break;
}
pred = node;
}
int current = currentTour.getLastNode();
TRSPLogging.getSimulationLogger().info(
" Technician %2s is at %3s - next %3s - ed:%7.1f - executed tour: %s (%s)",
currentTour.getTechnicianId(), current, updateTour.getSucc(current),
currentTour.getEarliestDepartureTime(current), currentTour,
TRSPSolutionChecker.INSTANCE.checkTour(currentTour));
}
}
/**
* Freezes a technician at its current node by setting its earliest departure time
*
* @param updateSolution
* @param techId
* @param node
*/
private void freezeTechnician(DTRSPSolution updateSolution, int techId, int node) {
if (updateSolution == null)
return;
TRSPTour updateTour = updateSolution.getTour(techId);
int succ = updateTour.getSucc(node);
double earliestDeparture = Math.max(simulationTime(),
updateTour.getEarliestDepartureTime(node));
mCurrentSolution.unfreeze();
if (!updateSolution.isFrozen(node))
updateSolution.freeze(node, updateTour.getEarliestArrivalTime(node), earliestDeparture);
mCurrentSolution.freeze(node, updateTour.getEarliestArrivalTime(node), earliestDeparture);
String err = TRSPSolutionChecker.INSTANCE.checkTour(updateTour);
if (!err.isEmpty())
TRSPLogging.getBaseLogger().warn(err);
if (succ != ITRSPTour.UNDEFINED) {
if (!updateSolution.isFrozen(node))
updateTour.setEarliestArrivalTime(succ,
earliestDeparture + updateTour.getTravelTime(node, succ));
mCurrentSolution.getTour(techId).setEarliestArrivalTime(succ,
earliestDeparture + updateTour.getTravelTime(node, succ));
}
mCurrentSolution.freeze();
}
private void notifyUpdate(NotificationType type, int resource, int request) {
setChanged();
notifyObservers(new UpdateNotification(type, resource, request));
}
/**
* Setup this simulator for static a-posteri setting
*/
public void staticSetup() {
setSimulationTime(Double.POSITIVE_INFINITY);
mReleaseQueue.clear();
mServedRequests.clear();
mUnservedReleasedRequests.addAll(mReleasedRequests);
mStaticSetting = true;
mCurrentSolution.unfreeze();
mCurrentSolution.clear();
mCurrentSolution.freeze();
}
/**
* Terminate the simulation and update to the final state
*
* @param solution
*/
public void terminate(TRSPSolution solution) {
if (hasUnreleasedRequests())
throw new IllegalStateException();
setSimulationTime(Double.POSITIVE_INFINITY);
updateState(solution);
}
/**
* Returns the simulation time after which technicians will be sent home when idle
*
* @return the simulation time after which technicians will be sent home when idle
*/
public double getCutoffTime() {
return getInstance().getMainDepot().getTimeWindow().endAsDouble()
* mParameters.get(TRSPGlobalParameters.RUN_CUTOFF_TIME);
}
/**
* Returns {@code true} if {@code simulationTime} is after the cutoff
*
* @param simulationTime
* @return {@code true} if {@code simulationTime} is after the cutoff
*/
public boolean cutoff(double simulationTime) {
return simulationTime > getCutoffTime();
}
@Override
public String toString() {
return String.format("Time:%s Served:%s Rejected:%s Unserved:%s Unreleased:%s",
simulationTime(), Utilities.toShortString(mServedRequestsView),
Utilities.toShortString(mRejectedRequestsView),
Utilities.toShortString(mUnservedReleasedRequestsView),
Utilities.toShortString(mReleaseQueueView));
}
/**
* The class <code>UpdateNotification</code> is used to send information to attached {@link Observer observers} when
* this instance has been updated
* <p>
* Creation date: Mar 27, 2012 - 10:12:41 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 static class UpdateNotification {
public final NotificationType type;
public final int resource;
public final int request;
/**
* Creates a new <code>UpdateNotification</code>
*
* @param type
* @param resource
* @param request
*/
public UpdateNotification(NotificationType type, int resource, int request) {
this.type = type;
this.resource = resource;
this.request = request;
}
}
}