diff --git a/jsprit-analysis/src/main/java/jsprit/analysis/toolbox/AlgorithmEventRecorder.java b/jsprit-analysis/src/main/java/jsprit/analysis/toolbox/AlgorithmEventRecorder.java index 85e7b405..2020d949 100644 --- a/jsprit-analysis/src/main/java/jsprit/analysis/toolbox/AlgorithmEventRecorder.java +++ b/jsprit-analysis/src/main/java/jsprit/analysis/toolbox/AlgorithmEventRecorder.java @@ -24,14 +24,15 @@ import jsprit.core.algorithm.recreate.listener.BeforeJobInsertionListener; import jsprit.core.algorithm.recreate.listener.InsertionEndsListener; import jsprit.core.algorithm.recreate.listener.InsertionStartsListener; import jsprit.core.algorithm.ruin.listener.RuinListener; +import jsprit.core.problem.AbstractActivity; import jsprit.core.problem.VehicleRoutingProblem; -import jsprit.core.problem.job.Job; -import jsprit.core.problem.job.Service; +import jsprit.core.problem.job.*; import jsprit.core.problem.solution.VehicleRoutingProblemSolution; import jsprit.core.problem.solution.route.VehicleRoute; import jsprit.core.problem.solution.route.activity.TourActivity; import jsprit.core.problem.vehicle.Vehicle; import jsprit.core.problem.vehicle.VehicleImpl; +import jsprit.core.util.Coordinate; import jsprit.core.util.Solutions; import org.graphstream.graph.Edge; import org.graphstream.graph.Graph; @@ -43,11 +44,19 @@ import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.util.Collection; +import java.util.List; public class AlgorithmEventRecorder implements RuinListener, IterationStartsListener, InsertionStartsListener, BeforeJobInsertionListener, InsertionEndsListener, AlgorithmEndsListener { + private boolean renderShipments = false; + public static void writeSolution(VehicleRoutingProblem vrp, VehicleRoutingProblemSolution solution, File outfile){ + AlgorithmEventRecorder rec = new AlgorithmEventRecorder(vrp,outfile); + rec.initialiseGraph(vrp); + rec.addRoutes(solution.getRoutes()); + rec.finish(); + } public static enum RecordPolicy { RECORD_AND_WRITE @@ -75,7 +84,10 @@ public class AlgorithmEventRecorder implements RuinListener, IterationStartsList private int currentIteration = 0; + private VehicleRoutingProblem vrp; + public AlgorithmEventRecorder(VehicleRoutingProblem vrp, File outfile) { + this.vrp = vrp; graph = new MultiGraph("g"); try { writer = new FileWriter(outfile); @@ -88,6 +100,11 @@ public class AlgorithmEventRecorder implements RuinListener, IterationStartsList initialiseGraph(vrp); } + public AlgorithmEventRecorder(VehicleRoutingProblem vrp, File outfile, boolean renderShipments) { + this.renderShipments = renderShipments; + new AlgorithmEventRecorder(vrp,outfile); + } + public void setRecordingRange(int startIteration, int endIteration){ this.start_recording_at = startIteration; this.end_recording_at = endIteration; @@ -114,23 +131,41 @@ public class AlgorithmEventRecorder implements RuinListener, IterationStartsList public void ruinStarts(Collection routes) { if(!record()) return; fileSink.stepBegins(graph.getId(),0,BEFORE_RUIN_RENDER_SOLUTION); - recordRoutes(routes); + addRoutes(routes); fileSink.stepBegins(graph.getId(),0,RUIN); } - private void recordRoutes(Collection routes) { + private void addRoutes(Collection routes) { for(VehicleRoute route : routes){ String prevNode = makeStartId(route.getVehicle()); for(TourActivity act : route.getActivities()){ - String actNode = ((TourActivity.JobActivity)act).getJob().getId(); - addEdge(prevNode+"_"+actNode,prevNode,actNode); - prevNode = actNode; + String actNodeId = getNodeId(act); + addEdge(prevNode+"_"+actNodeId,prevNode,actNodeId); + prevNode = actNodeId; } - String lastNode = makeEndId(route.getVehicle()); - addEdge(prevNode+"_"+lastNode,prevNode,lastNode); + if(route.getVehicle().isReturnToDepot()) { + String lastNode = makeEndId(route.getVehicle()); + addEdge(prevNode+"_"+lastNode,prevNode,lastNode); + } + } } + private String getNodeId(TourActivity act) { + String nodeId = null; + if(act instanceof TourActivity.JobActivity){ + Job job = ((TourActivity.JobActivity) act).getJob(); + if(job instanceof Service){ + nodeId = job.getId(); + } + else if(job instanceof Shipment){ + if(act.getName().equals("pickupShipment")) nodeId = getFromNodeId((Shipment) job); + else nodeId = getToNodeId((Shipment) job); + } + } + return nodeId; + } + private boolean record() { return currentIteration >= start_recording_at && currentIteration <= end_recording_at; } @@ -143,19 +178,99 @@ public class AlgorithmEventRecorder implements RuinListener, IterationStartsList @Override public void removed(Job job, VehicleRoute fromRoute) { if(!record()) return; + if(job instanceof Service) removeService(job, fromRoute); + else if(job instanceof Shipment) removeShipment(job,fromRoute); + } + + private void removeShipment(Job job, VehicleRoute fromRoute) { + Shipment shipment = (Shipment)job; + String fromNodeId = getFromNodeId(shipment); + String toNodeId = getToNodeId(shipment); +// removeNodeAndBelongingEdges(fromNodeId,fromRoute); +// removeNodeAndBelongingEdges(toNodeId,fromRoute); + + Edge enteringToNode = getEnteringEdge(toNodeId); + if(enteringToNode.getNode0().getId().equals(fromNodeId)){ + markRemoved(graph.getNode(fromNodeId)); + markRemoved(graph.getNode(toNodeId)); + // i -> from -> to -> j: rem(i,from), rem(from,to), rem(to,j), add(i,j) + Edge enteringFromNode = getEnteringEdge(fromNodeId); + removeEdge(enteringFromNode.getId()); + removeEdge(enteringToNode.getId()); + if(graph.getNode(toNodeId).getLeavingEdgeSet().isEmpty()){ + if(fromRoute.getVehicle().isReturnToDepot()) throw new IllegalStateException("leaving edge is missing"); + return; + } + + Edge leavingToNode = getLeavingEdge(toNodeId); + removeEdge(leavingToNode.getId()); + Node from = enteringFromNode.getNode0(); + Node to = leavingToNode.getNode1(); + if(!fromRoute.getActivities().isEmpty()){ + addEdge(makeEdgeId(from,to),from.getId(),to.getId()); + } + } + else{ + removeNodeAndBelongingEdges(fromNodeId,fromRoute); + removeNodeAndBelongingEdges(toNodeId,fromRoute); + } + } + + private Edge getLeavingEdge(String toNodeId) { + Collection edges = graph.getNode(toNodeId).getLeavingEdgeSet(); + if(edges.size()==1) return edges.iterator().next(); + else{ + for(Edge e : edges){ + if(e.getId().startsWith("shipment")){ continue; } + return e; + } + } + return null; + } + + private Edge getEnteringEdge(String toNodeId) { + Collection enteringEdges = graph.getNode(toNodeId).getEnteringEdgeSet(); + if(enteringEdges.size()==1) return enteringEdges.iterator().next(); + else{ + for(Edge e : enteringEdges){ + if(e.getId().startsWith("shipment")){ continue; } + return e; + } + } + return null; + } + + private String getToNodeId(Shipment shipment) { + return shipment.getId() + "_delivery"; + } + + private String getFromNodeId(Shipment shipment) { + return shipment.getId() + "_pickup"; + } + + private void removeService(Job job, VehicleRoute fromRoute) { String nodeId = job.getId(); + removeNodeAndBelongingEdges(nodeId, fromRoute); + } + + private void removeNodeAndBelongingEdges(String nodeId, VehicleRoute fromRoute) { Node node = graph.getNode(nodeId); markRemoved(node); - Edge entering = node.getEnteringEdge(0); + Edge entering = getEnteringEdge(nodeId); removeEdge(entering.getId()); - Edge leaving = node.getLeavingEdge(0); + + if(node.getLeavingEdgeSet().isEmpty()){ + if(fromRoute.getVehicle().isReturnToDepot()) throw new IllegalStateException("leaving edge is missing"); + return; + } + + Edge leaving = getLeavingEdge(nodeId); removeEdge((leaving.getId())); Node from = entering.getNode0(); Node to = leaving.getNode1(); if(!fromRoute.getActivities().isEmpty()){ addEdge(makeEdgeId(from,to),from.getId(),to.getId()); } - } private void markRemoved(Node node) { @@ -170,7 +285,11 @@ public class AlgorithmEventRecorder implements RuinListener, IterationStartsList public void informAlgorithmEnds(VehicleRoutingProblem problem, Collection solutions) { VehicleRoutingProblemSolution solution = Solutions.bestOf(solutions); fileSink.stepBegins(graph.getId(),0,BEFORE_RUIN_RENDER_SOLUTION); - recordRoutes(solution.getRoutes()); + addRoutes(solution.getRoutes()); + finish(); + } + + private void finish() { try { fileSink.end(); writer.close(); @@ -189,10 +308,50 @@ public class AlgorithmEventRecorder implements RuinListener, IterationStartsList addVehicle(vehicle); } for(Job job : problem.getJobs().values()){ - Service service = (Service)job; - addService(service); + addJob(job); } + } + private void addJob(Job job) { + if(job instanceof Service){ + Service service = (Service)job; + addNode(service.getId(), service.getCoord()); + markService(service); + } + else if(job instanceof Shipment){ + Shipment shipment = (Shipment)job; + String fromNodeId = getFromNodeId(shipment); + addNode(fromNodeId, shipment.getPickupCoord()); + String toNodeId = getToNodeId(shipment); + addNode(toNodeId,shipment.getDeliveryCoord()); + markShipment(shipment); + if(renderShipments) { + Edge e = graph.addEdge("shipment_" + fromNodeId + "_" + toNodeId, fromNodeId, toNodeId, true); + e.addAttribute("ui.class", "shipment"); + } + } + } + + private void markShipment(Shipment shipment) { + markPickup(getFromNodeId(shipment)); + markDelivery(getToNodeId(shipment)); + } + + private void markService(Service service) { + if(service instanceof Pickup){ + markPickup(service.getId()); + } + else if(service instanceof Delivery){ + markDelivery(service.getId()); + } + } + + private void markPickup(String id) { + graph.getNode(id).addAttribute("ui.class","pickup"); + } + + private void markDelivery(String id) { + graph.getNode(id).addAttribute("ui.class","delivery"); } private void addVehicle(Vehicle vehicle) { @@ -220,25 +379,31 @@ public class AlgorithmEventRecorder implements RuinListener, IterationStartsList return vehicle.getId() + "_end"; } - private void addService(Service service) { - Node serviceNode = graph.addNode(service.getId()); - serviceNode.addAttribute("x", service.getCoord().getX()); - serviceNode.addAttribute("y", service.getCoord().getY()); + private void addNode(String nodeId, Coordinate nodeCoord) { + Node node = graph.addNode(nodeId); + node.addAttribute("x", nodeCoord.getX()); + node.addAttribute("y", nodeCoord.getY()); } @Override public void informInsertionEnds(Collection vehicleRoutes) { if(!record()) return; fileSink.stepBegins(graph.getId(),0,CLEAR_SOLUTION); + removeRoutes(vehicleRoutes); + } + + private void removeRoutes(Collection vehicleRoutes) { for(VehicleRoute route : vehicleRoutes){ String prevNode = makeStartId(route.getVehicle()); for(TourActivity act : route.getActivities()){ - String actNode = ((TourActivity.JobActivity)act).getJob().getId(); + String actNode = getNodeId(act); removeEdge(prevNode + "_" + actNode); prevNode = actNode; } - String lastNode = makeEndId(route.getVehicle()); - removeEdge(prevNode + "_" + lastNode); + if(route.getVehicle().isReturnToDepot()) { + String lastNode = makeEndId(route.getVehicle()); + removeEdge(prevNode + "_" + lastNode); + } } } @@ -246,6 +411,66 @@ public class AlgorithmEventRecorder implements RuinListener, IterationStartsList public void informBeforeJobInsertion(Job job, InsertionData data, VehicleRoute route) { if(!record()) return; markInserted(job); + handleVehicleSwitch(data, route); + insertJob(job, data, route); + } + + private void insertJob(Job job, InsertionData data, VehicleRoute route) { + if(job instanceof Service) insertService(job,data,route); + else if(job instanceof Shipment) insertShipment(job,data,route); + } + + private void insertShipment(Job job, InsertionData data, VehicleRoute route) { + String fromNodeId = getFromNodeId((Shipment) job); + String toNodeId = getToNodeId((Shipment) job); + insertNode(toNodeId,data.getDeliveryInsertionIndex(),data,route); + + List del = vrp.getActivities(job); + VehicleRoute copied = VehicleRoute.copyOf(route); + copied.getTourActivities().addActivity(data.getDeliveryInsertionIndex(),del.get(1)); + + insertNode(fromNodeId, data.getPickupInsertionIndex(), data, copied); + } + + private void insertService(Job job, InsertionData data, VehicleRoute route) { + insertNode(job.getId(), data.getDeliveryInsertionIndex(), data, route); + } + + private void insertNode(String nodeId, int insertionIndex, InsertionData data, VehicleRoute route) { +// VehicleRoute copied = VehicleRoute.copyOf(route); + + String node_i; + + if(isFirst(insertionIndex,route)) { + node_i = makeStartId(data.getSelectedVehicle()); + } + else { + TourActivity.JobActivity jobActivity = (TourActivity.JobActivity) route.getActivities().get(insertionIndex - 1); + node_i = getNodeId(jobActivity); + } + String node_k = nodeId; + String edgeId_1 = node_i + "_" + node_k; + String node_j; + if(isLast(insertionIndex,route)) { + node_j = makeEndId(data.getSelectedVehicle()); + } + else { + TourActivity.JobActivity jobActivity = (TourActivity.JobActivity) route.getActivities().get(insertionIndex); + node_j = getNodeId(jobActivity); + } + String edgeId_2 = node_k + "_" + node_j; + + addEdge(edgeId_1, node_i, node_k); + + if(!(isLast(insertionIndex,route) && !data.getSelectedVehicle().isReturnToDepot())) { + addEdge(edgeId_2, node_k, node_j); + if (!route.getActivities().isEmpty()) { + removeEdge(node_i + "_" + node_j); + } + } + } + + private void handleVehicleSwitch(InsertionData data, VehicleRoute route) { boolean vehicleSwitch = false; if(!(route.getVehicle() instanceof VehicleImpl.NoVehicle)) { if (!route.getVehicle().getId().equals(data.getSelectedVehicle().getId())) { @@ -258,56 +483,55 @@ public class AlgorithmEventRecorder implements RuinListener, IterationStartsList String oldEnd = makeEndId(route.getVehicle()); String lastAct = ((TourActivity.JobActivity)route.getActivities().get(route.getActivities().size()-1)).getJob().getId(); removeEdge(oldStart + "_" + firstAct); - removeEdge(lastAct + "_" + oldEnd); + + if(route.getVehicle().isReturnToDepot()) { + removeEdge(lastAct + "_" + oldEnd); + } + String newStart = makeStartId(data.getSelectedVehicle()); String newEnd = makeEndId(data.getSelectedVehicle()); addEdge(newStart + "_" + firstAct,newStart,firstAct); - addEdge(lastAct + "_" + newEnd, lastAct,newEnd); - } - String node_i; - if(isFirst(data,route)) { - node_i = makeStartId(data.getSelectedVehicle()); - } - else { - node_i = ((TourActivity.JobActivity)route.getActivities().get(data.getDeliveryInsertionIndex()-1)).getJob().getId(); - } - String node_k = job.getId(); - String edgeId_1 = node_i + "_" + node_k; - String node_j; - if(isLast(data,route)) { - node_j = makeEndId(data.getSelectedVehicle()); - } - else { - node_j = ((TourActivity.JobActivity)route.getActivities().get(data.getDeliveryInsertionIndex())).getJob().getId(); - } - String edgeId_2 = node_k + "_" + node_j; - - addEdge(edgeId_1, node_i, node_k); - addEdge(edgeId_2, node_k, node_j); - if(!route.getActivities().isEmpty()){ - removeEdge(node_i + "_" + node_j); + if(data.getSelectedVehicle().isReturnToDepot()) { + addEdge(lastAct + "_" + newEnd, lastAct, newEnd); + } } } private void markInserted(Job job) { - graph.getNode(job.getId()).removeAttribute("ui.class"); + if(job instanceof Service){ + markService((Service) job); + } + else{ + markShipment((Shipment)job); + } } private void removeEdge(String edgeId) { + markEdgeRemoved(edgeId); graph.removeEdge(edgeId); } - private boolean isFirst(InsertionData data, VehicleRoute route) { - return data.getDeliveryInsertionIndex() == 0; + private void markEdgeRemoved(String edgeId) { + graph.getEdge(edgeId).addAttribute("ui.class","removed"); } - private boolean isLast(InsertionData data, VehicleRoute route) { - return data.getDeliveryInsertionIndex() == route.getActivities().size(); + private boolean isFirst(int index, VehicleRoute route) { + return index == 0; + } + + private boolean isLast(int index, VehicleRoute route) { + return index == route.getActivities().size(); } private void addEdge(String edgeId, String fromNode, String toNode) { graph.addEdge(edgeId,fromNode,toNode,true); + markEdgeInserted(edgeId); + } + + private void markEdgeInserted(String edgeId) { + graph.getEdge(edgeId).addAttribute("ui.class","inserted"); + graph.getEdge(edgeId).removeAttribute("ui.class"); } @Override diff --git a/jsprit-analysis/src/main/java/jsprit/analysis/toolbox/GraphStreamViewer.java b/jsprit-analysis/src/main/java/jsprit/analysis/toolbox/GraphStreamViewer.java index 0cb2fd3a..76aa6566 100644 --- a/jsprit-analysis/src/main/java/jsprit/analysis/toolbox/GraphStreamViewer.java +++ b/jsprit-analysis/src/main/java/jsprit/analysis/toolbox/GraphStreamViewer.java @@ -33,16 +33,95 @@ import org.graphstream.graph.Edge; import org.graphstream.graph.Graph; import org.graphstream.graph.Node; import org.graphstream.graph.implementations.MultiGraph; +import org.graphstream.stream.Sink; +import org.graphstream.stream.file.FileSinkImages; import org.graphstream.ui.swingViewer.View; import org.graphstream.ui.swingViewer.Viewer; import javax.swing.*; import java.awt.*; - +import java.io.File; public class GraphStreamViewer { + private static class EmptySink implements Sink { + + @Override + public void graphAttributeAdded(String sourceId, long timeId, String attribute, Object value) { + + } + + @Override + public void graphAttributeChanged(String sourceId, long timeId, String attribute, Object oldValue, Object newValue) { + + } + + @Override + public void graphAttributeRemoved(String sourceId, long timeId, String attribute) { + + } + + @Override + public void nodeAttributeAdded(String sourceId, long timeId, String nodeId, String attribute, Object value) { + + } + + @Override + public void nodeAttributeChanged(String sourceId, long timeId, String nodeId, String attribute, Object oldValue, Object newValue) { + + } + + @Override + public void nodeAttributeRemoved(String sourceId, long timeId, String nodeId, String attribute) { + + } + + @Override + public void edgeAttributeAdded(String sourceId, long timeId, String edgeId, String attribute, Object value) { + + } + + @Override + public void edgeAttributeChanged(String sourceId, long timeId, String edgeId, String attribute, Object oldValue, Object newValue) { + + } + + @Override + public void edgeAttributeRemoved(String sourceId, long timeId, String edgeId, String attribute) { + + } + + @Override + public void nodeAdded(String sourceId, long timeId, String nodeId) { + + } + + @Override + public void nodeRemoved(String sourceId, long timeId, String nodeId) { + + } + + @Override + public void edgeAdded(String sourceId, long timeId, String edgeId, String fromNodeId, String toNodeId, boolean directed) { + + } + + @Override + public void edgeRemoved(String sourceId, long timeId, String edgeId) { + + } + + @Override + public void graphCleared(String sourceId, long timeId) { + + } + + @Override + public void stepBegins(String sourceId, long timeId, double step) { + + } + } public static Graph createMultiGraph(String name, String style){ Graph g = new MultiGraph(name); @@ -143,6 +222,12 @@ public class GraphStreamViewer { private double scaling = 1.0; + private boolean createImageByEvent = false; + + private File imageDirectory; + + Sink fsi = new EmptySink(); + public GraphStreamViewer(VehicleRoutingProblem vrp) { super(); this.vrp = vrp; @@ -159,6 +244,12 @@ public class GraphStreamViewer { return this; } + public GraphStreamViewer createImagesByEvent(boolean createImanges, File outDirectory){ + createImageByEvent = true; + imageDirectory = outDirectory; + return this; + } + public GraphStreamViewer setRenderDelay(long ms){ this.renderDelay_in_ms=ms; return this; @@ -204,7 +295,21 @@ public class GraphStreamViewer { JFrame jframe = createJFrame(view,scaling); - render(g,view); + Sink fsi; + if(createImageByEvent){ + FileSinkImages.OutputPolicy outputPolicy = FileSinkImages.OutputPolicy.BY_ELEMENT_EVENT; + String prefix = "screenshot_"; + FileSinkImages.OutputType type = FileSinkImages.OutputType.PNG; + FileSinkImages.Resolution resolution = FileSinkImages.Resolutions.HD720; + fsi = new FileSinkImages( type, resolution ); + ((FileSinkImages)fsi).setStyleSheet(STYLESHEET); + ((FileSinkImages)fsi).setOutputPolicy(outputPolicy); + ((FileSinkImages)fsi).setLayoutPolicy(FileSinkImages.LayoutPolicy.NO_LAYOUT); + ((FileSinkImages)fsi).setQuality(FileSinkImages.Quality.HIGH); + ((FileSinkImages)fsi).setRenderer(FileSinkImages.RendererType.SCALA); + } + + render(g, view); } private JFrame createJFrame(View view, double scaling) { @@ -254,8 +359,7 @@ public class GraphStreamViewer { private void render(Graph g, View view) { if(center != null){ view.resizeFrame(view.getWidth(), view.getHeight()); - view.getCamera().setViewCenter(center.x, center.y, 0); - view.getCamera().setViewPercent(zoomFactor); + alignCamera(view); } for(Vehicle vehicle : vrp.getVehicles()){ @@ -284,7 +388,16 @@ public class GraphStreamViewer { } - private JLabel createEmptyLabel() { + private void alignCamera(View view) { + view.getCamera().setViewCenter(center.x, center.y, 0); + view.getCamera().setViewPercent(zoomFactor); + if(fsi instanceof FileSinkImages){ + ((FileSinkImages) fsi).setViewCenter(center.x, center.y); + ((FileSinkImages) fsi).setViewPercent(zoomFactor); + } + } + + private JLabel createEmptyLabel() { JLabel emptyLabel1 = new JLabel(); emptyLabel1.setPreferredSize(new Dimension((int)(40*scaling),(int)(25*scaling))); return emptyLabel1;