1
0
Fork 0
mirror of https://github.com/graphhopper/jsprit.git synced 2020-01-24 07:45:05 +01:00

rename packages to com.graphhopper.* - #215

This commit is contained in:
oblonski 2016-02-03 14:31:36 +01:00
parent 2f4e9196d9
commit 8004676211
465 changed files with 3569 additions and 3567 deletions

View file

@ -0,0 +1,533 @@
/*******************************************************************************
* Copyright (C) 2014 Stefan Schroeder
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3.0 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
package com.graphhopper.jsprit.analysis.toolbox;
import com.graphhopper.jsprit.core.algorithm.listener.AlgorithmEndsListener;
import com.graphhopper.jsprit.core.algorithm.listener.IterationStartsListener;
import com.graphhopper.jsprit.core.algorithm.recreate.InsertionData;
import com.graphhopper.jsprit.core.algorithm.recreate.listener.BeforeJobInsertionListener;
import com.graphhopper.jsprit.core.algorithm.recreate.listener.InsertionEndsListener;
import com.graphhopper.jsprit.core.algorithm.recreate.listener.InsertionStartsListener;
import com.graphhopper.jsprit.core.algorithm.ruin.listener.RuinListener;
import com.graphhopper.jsprit.core.problem.AbstractActivity;
import com.graphhopper.jsprit.core.problem.VehicleRoutingProblem;
import com.graphhopper.jsprit.core.problem.job.Delivery;
import com.graphhopper.jsprit.core.problem.job.Job;
import com.graphhopper.jsprit.core.problem.job.Service;
import com.graphhopper.jsprit.core.problem.job.Shipment;
import com.graphhopper.jsprit.core.problem.solution.VehicleRoutingProblemSolution;
import com.graphhopper.jsprit.core.problem.solution.route.VehicleRoute;
import com.graphhopper.jsprit.core.problem.solution.route.activity.TourActivity;
import com.graphhopper.jsprit.core.problem.vehicle.Vehicle;
import com.graphhopper.jsprit.core.problem.vehicle.VehicleImpl;
import com.graphhopper.jsprit.core.util.Coordinate;
import com.graphhopper.jsprit.core.util.Solutions;
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.file.FileSinkDGS;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.zip.GZIPOutputStream;
/**
* Writes out what happens when algorithm searches (in graphstream dgs-file).
*/
public class AlgorithmEventsRecorder implements RuinListener, IterationStartsListener, InsertionStartsListener, BeforeJobInsertionListener, InsertionEndsListener, AlgorithmEndsListener {
private boolean renderShipments = false;
public static final int BEFORE_RUIN_RENDER_SOLUTION = 2;
public static final int RUIN = 0;
public static final int RECREATE = 1;
public static final int CLEAR_SOLUTION = 3;
private Graph graph;
private FileSinkDGS fileSink;
private FileOutputStream fos;
private GZIPOutputStream gzipOs;
private int start_recording_at = 0;
private int end_recording_at = Integer.MAX_VALUE;
private int currentIteration = 0;
private VehicleRoutingProblem vrp;
public AlgorithmEventsRecorder(VehicleRoutingProblem vrp, String dgsFileLocation) {
this.vrp = vrp;
graph = new MultiGraph("g");
try {
File dgsFile = new File(dgsFileLocation);
fos = new FileOutputStream(dgsFile);
fileSink = new FileSinkDGS();
if (dgsFile.getName().endsWith("gz")) {
gzipOs = new GZIPOutputStream(fos);
fileSink.begin(gzipOs);
} else {
fileSink.begin(fos);
}
graph.addSink(fileSink);
} catch (IOException e) {
e.printStackTrace();
}
initialiseGraph(vrp);
}
@Deprecated
public AlgorithmEventsRecorder(VehicleRoutingProblem vrp, String dgsFileLocation, boolean renderShipments) {
this.renderShipments = renderShipments;
new AlgorithmEventsRecorder(vrp, dgsFileLocation);
}
public void setRecordingRange(int startIteration, int endIteration) {
this.start_recording_at = startIteration;
this.end_recording_at = endIteration;
}
@Override
public void ruinStarts(Collection<VehicleRoute> routes) {
if (!record()) return;
fileSink.stepBegins(graph.getId(), 0, BEFORE_RUIN_RENDER_SOLUTION);
markAllNodesAsInserted();
addRoutes(routes);
fileSink.stepBegins(graph.getId(), 0, RUIN);
}
private void markAllNodesAsInserted() {
for (Job j : vrp.getJobs().values()) {
markInserted(j);
}
}
private void addRoutes(Collection<VehicleRoute> routes) {
for (VehicleRoute route : routes) {
String prevNode = makeStartId(route.getVehicle());
for (TourActivity act : route.getActivities()) {
String actNodeId = getNodeId(act);
addEdge(prevNode + "_" + actNodeId, prevNode, actNodeId);
prevNode = actNodeId;
}
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;
}
@Override
public void ruinEnds(Collection<VehicleRoute> routes, Collection<Job> unassignedJobs) {
}
@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<Edge> 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<Edge> 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 = getEnteringEdge(nodeId);
removeEdge(entering.getId());
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) {
node.setAttribute("ui.class", "removed");
}
private String makeEdgeId(Node from, Node to) {
return from.getId() + "_" + to.getId();
}
@Override
public void informAlgorithmEnds(VehicleRoutingProblem problem, Collection<VehicleRoutingProblemSolution> solutions) {
VehicleRoutingProblemSolution solution = Solutions.bestOf(solutions);
fileSink.stepBegins(graph.getId(), 0, BEFORE_RUIN_RENDER_SOLUTION);
addRoutes(solution.getRoutes());
finish();
}
private void finish() {
try {
fileSink.end();
fos.close();
if (gzipOs != null) gzipOs.close();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void informIterationStarts(int i, VehicleRoutingProblem problem, Collection<VehicleRoutingProblemSolution> solutions) {
currentIteration = i;
}
private void initialiseGraph(VehicleRoutingProblem problem) {
for (Vehicle vehicle : problem.getVehicles()) {
addVehicle(vehicle);
}
for (Job job : problem.getJobs().values()) {
addJob(job);
}
}
private void addJob(Job job) {
if (job instanceof Service) {
Service service = (Service) job;
addNode(service.getId(), service.getLocation().getCoordinate());
markService(service);
} else if (job instanceof Shipment) {
Shipment shipment = (Shipment) job;
String fromNodeId = getFromNodeId(shipment);
addNode(fromNodeId, shipment.getPickupLocation().getCoordinate());
String toNodeId = getToNodeId(shipment);
addNode(toNodeId, shipment.getDeliveryLocation().getCoordinate());
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 Delivery) {
markDelivery(service.getId());
} else {
markPickup(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) {
String startId = makeStartId(vehicle);
Node node = graph.addNode(startId);
node.addAttribute("x", vehicle.getStartLocation().getCoordinate().getX());
node.addAttribute("y", vehicle.getStartLocation().getCoordinate().getY());
node.addAttribute("ui.class", "depot");
String endId = makeEndId(vehicle);
if (!startId.equals(endId)) {
Node endNode = graph.addNode(endId);
endNode.addAttribute("x", vehicle.getEndLocation().getCoordinate().getX());
endNode.addAttribute("y", vehicle.getEndLocation().getCoordinate().getY());
endNode.addAttribute("ui.class", "depot");
}
}
private String makeStartId(Vehicle vehicle) {
return vehicle.getId() + "_start";
}
private String makeEndId(Vehicle vehicle) {
if (vehicle.getStartLocation().getId().equals(vehicle.getEndLocation().getId())) return makeStartId(vehicle);
return vehicle.getId() + "_end";
}
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<VehicleRoute> vehicleRoutes) {
if (!record()) return;
fileSink.stepBegins(graph.getId(), 0, CLEAR_SOLUTION);
removeRoutes(vehicleRoutes);
}
private void removeRoutes(Collection<VehicleRoute> vehicleRoutes) {
for (VehicleRoute route : vehicleRoutes) {
String prevNode = makeStartId(route.getVehicle());
for (TourActivity act : route.getActivities()) {
String actNode = getNodeId(act);
removeEdge(prevNode + "_" + actNode);
prevNode = actNode;
}
if (route.getVehicle().isReturnToDepot()) {
String lastNode = makeEndId(route.getVehicle());
removeEdge(prevNode + "_" + lastNode);
}
}
}
@Override
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<AbstractActivity> 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)) {
node_i = makeStartId(data.getSelectedVehicle());
} else {
TourActivity.JobActivity jobActivity = (TourActivity.JobActivity) route.getActivities().get(insertionIndex - 1);
node_i = getNodeId(jobActivity);
}
String edgeId_1 = node_i + "_" + nodeId;
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 = nodeId + "_" + node_j;
addEdge(edgeId_1, node_i, nodeId);
if (!(isLast(insertionIndex, route) && !data.getSelectedVehicle().isReturnToDepot())) {
addEdge(edgeId_2, nodeId, 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())) {
vehicleSwitch = true;
}
}
if (vehicleSwitch && !route.getActivities().isEmpty()) {
String oldStart = makeStartId(route.getVehicle());
String firstAct = ((TourActivity.JobActivity) route.getActivities().get(0)).getJob().getId();
String oldEnd = makeEndId(route.getVehicle());
String lastAct = ((TourActivity.JobActivity) route.getActivities().get(route.getActivities().size() - 1)).getJob().getId();
removeEdge(oldStart + "_" + firstAct);
if (route.getVehicle().isReturnToDepot()) {
removeEdge(lastAct + "_" + oldEnd);
}
String newStart = makeStartId(data.getSelectedVehicle());
String newEnd = makeEndId(data.getSelectedVehicle());
addEdge(newStart + "_" + firstAct, newStart, firstAct);
if (data.getSelectedVehicle().isReturnToDepot()) {
addEdge(lastAct + "_" + newEnd, lastAct, newEnd);
}
}
}
private void markInserted(Job job) {
if (job instanceof Service) {
markService((Service) job);
} else {
markShipment((Shipment) job);
}
}
private void removeEdge(String edgeId) {
markEdgeRemoved(edgeId);
graph.removeEdge(edgeId);
}
private void markEdgeRemoved(String edgeId) {
graph.getEdge(edgeId).addAttribute("ui.class", "removed");
}
private boolean isFirst(int index) {
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
public void informInsertionStarts(Collection<VehicleRoute> vehicleRoutes, Collection<Job> unassignedJobs) {
if (!record()) return;
fileSink.stepBegins(graph.getId(), 0, RECREATE);
}
}

View file

@ -0,0 +1,215 @@
/*******************************************************************************
* Copyright (C) 2014 Stefan Schroeder
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3.0 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
package com.graphhopper.jsprit.analysis.toolbox;
import org.graphstream.graph.Graph;
import org.graphstream.stream.Sink;
import org.graphstream.stream.file.FileSource;
import org.graphstream.stream.file.FileSourceDGS;
import org.graphstream.ui.view.Viewer;
import java.io.IOException;
public class AlgorithmEventsViewer {
private static class DelayContainer {
long delay = 0;
}
public static class DelaySink implements Sink {
private DelayContainer delayContainer;
private long delay = 2;
private long ruinDelay = 2;
private long recreateDelay = 2;
public DelaySink(DelayContainer delayContainer) {
this.delayContainer = delayContainer;
}
public void setRuinDelay(long ruinDelay) {
this.ruinDelay = ruinDelay;
}
public void setRecreateDelay(long recreateDelay) {
this.recreateDelay = recreateDelay;
}
public void setDelay(long delay) {
this.delay = delay;
}
@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) {
if (step == AlgorithmEventsRecorder.RECREATE) {
delayContainer.delay = recreateDelay;
}
if (step == AlgorithmEventsRecorder.RUIN) {
delayContainer.delay = ruinDelay;
} else if (step == AlgorithmEventsRecorder.CLEAR_SOLUTION) {
delayContainer.delay = delay;
} else if (step == AlgorithmEventsRecorder.BEFORE_RUIN_RENDER_SOLUTION) {
delayContainer.delay = delay;
}
}
}
private double zoomFactor;
private double scaling = 1.0;
private long delayRecreation = 5;
private long delayRuin = 5;
private long delay = 2;
public void setRecreationDelay(long delay_in_ms) {
this.delayRecreation = delay_in_ms;
}
public void setRuinDelay(long delay_in_ms) {
this.delayRuin = delay_in_ms;
}
public void display(String dgsFile) {
System.setProperty("org.graphstream.ui.renderer", "org.graphstream.ui.j2dviewer.J2DGraphRenderer");
Graph graph = GraphStreamViewer.createMultiGraph("g", GraphStreamViewer.StyleSheets.BLUE_FOREST);
Viewer viewer = graph.display();
viewer.disableAutoLayout();
FileSource fs = new FileSourceDGS();
fs.addSink(graph);
DelayContainer delayContainer = new DelayContainer();
DelaySink delaySink = new DelaySink(delayContainer);
delaySink.setDelay(delay);
delaySink.setRecreateDelay(delayRecreation);
delaySink.setRuinDelay(delayRuin);
fs.addSink(delaySink);
try {
fs.begin(dgsFile);
while (fs.nextEvents()) {
sleep(delayContainer.delay);
}
} catch (IOException e) {
e.printStackTrace();
}
try {
fs.end();
} catch (IOException e) {
e.printStackTrace();
} finally {
fs.removeSink(graph);
}
}
public static void main(String[] args) throws IOException {
AlgorithmEventsViewer viewer = new AlgorithmEventsViewer();
viewer.setRuinDelay(10);
viewer.setRecreationDelay(5);
viewer.display("output/events.dgs.gz");
}
private static void sleep(long renderDelay_in_ms2) {
try {
Thread.sleep(renderDelay_in_ms2);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}

View file

@ -0,0 +1,87 @@
/*******************************************************************************
* Copyright (C) 2013 Stefan Schroeder
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3.0 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
package com.graphhopper.jsprit.analysis.toolbox;
import com.graphhopper.jsprit.core.algorithm.VehicleRoutingAlgorithm;
import com.graphhopper.jsprit.core.algorithm.listener.AlgorithmEndsListener;
import com.graphhopper.jsprit.core.algorithm.listener.AlgorithmStartsListener;
import com.graphhopper.jsprit.core.algorithm.listener.IterationEndsListener;
import com.graphhopper.jsprit.core.problem.VehicleRoutingProblem;
import com.graphhopper.jsprit.core.problem.solution.VehicleRoutingProblemSolution;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.Collection;
/**
* VehicleRoutingAlgorithm-Listener to record the solution-search-progress.
* <p/>
* <p>Register this listener in VehicleRoutingAlgorithm.
*
* @author stefan schroeder
*/
public class AlgorithmSearchProgressChartListener implements IterationEndsListener, AlgorithmEndsListener, AlgorithmStartsListener {
private static Logger log = LogManager.getLogger(AlgorithmSearchProgressChartListener.class);
private String filename;
private XYLineChartBuilder chartBuilder;
/**
* Constructs chart listener with target png-file (filename plus path).
*
* @param pngFileName
*/
public AlgorithmSearchProgressChartListener(String pngFileName) {
super();
this.filename = pngFileName;
if (!this.filename.endsWith("png")) {
this.filename += ".png";
}
}
@Override
public void informAlgorithmEnds(VehicleRoutingProblem problem, Collection<VehicleRoutingProblemSolution> solutions) {
log.info("create chart {}", filename);
XYLineChartBuilder.saveChartAsPNG(chartBuilder.build(), filename);
}
@Override
public void informIterationEnds(int i, VehicleRoutingProblem problem, Collection<VehicleRoutingProblemSolution> solutions) {
double worst = 0.0;
double best = Double.MAX_VALUE;
double sum = 0.0;
for (VehicleRoutingProblemSolution sol : solutions) {
if (sol.getCost() > worst) worst = Math.min(sol.getCost(), Double.MAX_VALUE);
if (sol.getCost() < best) best = sol.getCost();
sum += Math.min(sol.getCost(), Double.MAX_VALUE);
}
chartBuilder.addData("best", i, best);
chartBuilder.addData("worst", i, worst);
chartBuilder.addData("avg", i, sum / (double) solutions.size());
}
@Override
public void informAlgorithmStarts(VehicleRoutingProblem problem, VehicleRoutingAlgorithm algorithm, Collection<VehicleRoutingProblemSolution> solutions) {
chartBuilder = XYLineChartBuilder.newInstance("search-progress", "iterations", "results");
}
}

View file

@ -0,0 +1,476 @@
/*******************************************************************************
* Copyright (c) 2014 Stefan Schroeder.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3.0 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Contributors:
* Stefan Schroeder - initial API and implementation
******************************************************************************/
package com.graphhopper.jsprit.analysis.toolbox;
import com.graphhopper.jsprit.core.algorithm.VehicleRoutingAlgorithm;
import com.graphhopper.jsprit.core.algorithm.VehicleRoutingAlgorithmFactory;
import com.graphhopper.jsprit.core.problem.VehicleRoutingProblem;
import com.graphhopper.jsprit.core.problem.solution.VehicleRoutingProblemSolution;
import com.graphhopper.jsprit.core.util.BenchmarkInstance;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class ComputationalLaboratory {
public static interface LabListener {
}
/**
* Listener-interface to listen to calculation.
* <p/>
* <p>Note that calculations are run concurrently, i.e. a unique task that is distributed to an available thread is
* {algorithm, instance, run}.
*
* @author schroeder
*/
public static interface CalculationListener extends LabListener {
public void calculationStarts(final BenchmarkInstance p, final String algorithmName, final VehicleRoutingAlgorithm algorithm, final int run);
public void calculationEnds(final BenchmarkInstance p, final String algorithmName, final VehicleRoutingAlgorithm algorithm, final int run, final Collection<VehicleRoutingProblemSolution> solutions);
}
public static interface LabStartsAndEndsListener extends LabListener {
public void labStarts(List<BenchmarkInstance> instances, int noAlgorithms, int runs);
public void labEnds();
}
/**
* Collects whatever indicators you require by algorithmName, instanceName, run and indicator.
*
* @author schroeder
*/
public static class DataCollector {
public static class Key {
private String instanceName;
private String algorithmName;
private int run;
private String indicatorName;
public Key(String instanceName, String algorithmName, int run, String indicatorName) {
super();
this.instanceName = instanceName;
this.algorithmName = algorithmName;
this.run = run;
this.indicatorName = indicatorName;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime
* result
+ ((algorithmName == null) ? 0 : algorithmName
.hashCode());
result = prime
* result
+ ((indicatorName == null) ? 0 : indicatorName
.hashCode());
result = prime
* result
+ ((instanceName == null) ? 0 : instanceName.hashCode());
result = prime * result + run;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Key other = (Key) obj;
if (algorithmName == null) {
if (other.algorithmName != null)
return false;
} else if (!algorithmName.equals(other.algorithmName))
return false;
if (indicatorName == null) {
if (other.indicatorName != null)
return false;
} else if (!indicatorName.equals(other.indicatorName))
return false;
if (instanceName == null) {
if (other.instanceName != null)
return false;
} else if (!instanceName.equals(other.instanceName))
return false;
if (run != other.run)
return false;
return true;
}
public String getInstanceName() {
return instanceName;
}
public String getAlgorithmName() {
return algorithmName;
}
public int getRun() {
return run;
}
public String getIndicatorName() {
return indicatorName;
}
@Override
public String toString() {
return "[algorithm=" + algorithmName + "][instance=" + instanceName + "][run=" + run + "][indicator=" + indicatorName + "]";
}
}
private final static String SOLUTION_INDICATOR_NAME = "vehicle-routing-problem-solution";
private ConcurrentHashMap<Key, Double> data = new ConcurrentHashMap<ComputationalLaboratory.DataCollector.Key, Double>();
private ConcurrentHashMap<Key, VehicleRoutingProblemSolution> solutions = new ConcurrentHashMap<ComputationalLaboratory.DataCollector.Key, VehicleRoutingProblemSolution>();
/**
* Adds a single date by instanceName, algorithmName, run and indicatorName.
* <p>If there is already an entry for this instance, algorithm, run and indicatorName, it is overwritten.
*
* @param instanceName
* @param algorithmName
* @param run
* @param indicatorName
* @param value
*/
public void addDate(String instanceName, String algorithmName, int run, String indicatorName, double value) {
if (indicatorName.equals(SOLUTION_INDICATOR_NAME))
throw new IllegalArgumentException(indicatorName + " is already used internally. please choose another indicator-name.");
Key key = new Key(instanceName, algorithmName, run, indicatorName);
data.put(key, value);
}
public void addSolution(String instanceName, String algorithmName, int run, VehicleRoutingProblemSolution solution) {
Key key = new Key(instanceName, algorithmName, run, SOLUTION_INDICATOR_NAME);
solutions.put(key, solution);
}
/**
* Returns a collections of indicator values representing the calculated values of individual runs.
*
* @param instanceName
* @param algorithmName
* @param indicator
* @return
*/
public Collection<Double> getData(String instanceName, String algorithmName, String indicator) {
List<Double> values = new ArrayList<Double>();
for (Key key : data.keySet()) {
if (key.getAlgorithmName().equals(algorithmName) && key.getInstanceName().equals(instanceName) && key.getIndicatorName().equals(indicator)) {
values.add(data.get(key));
}
}
return values;
}
/**
* Returns indicator value.
*
* @param instanceName
* @param algorithmName
* @param run
* @param indicator
* @return
*/
public Double getDate(String instanceName, String algorithmName, int run, String indicator) {
return data.get(new Key(instanceName, algorithmName, run, indicator));
}
public VehicleRoutingProblemSolution getSolution(String instanceName, String algorithmName, int run) {
return solutions.get(new Key(instanceName, algorithmName, run, "solution"));
}
/**
* Returns all keys that have been created. A key is a unique combination of algorithmName, instanceName, run and indicator.
*
* @return
*/
public Set<Key> getDataKeySet() {
return data.keySet();
}
public Set<Key> getSolutionKeySet() {
return solutions.keySet();
}
public VehicleRoutingProblemSolution getSolution(Key solutionKey) {
return solutions.get(solutionKey);
}
public Collection<VehicleRoutingProblemSolution> getSolutions() {
return solutions.values();
}
/**
* Returns date associated to specified key.
*
* @param key
* @return
*/
public Double getData(Key key) {
return data.get(key);
}
}
private static class Algorithm {
private String name;
private VehicleRoutingAlgorithmFactory factory;
public Algorithm(String name, VehicleRoutingAlgorithmFactory factory) {
super();
this.name = name;
this.factory = factory;
}
}
private List<BenchmarkInstance> benchmarkInstances = new ArrayList<BenchmarkInstance>();
private int runs = 1;
private Collection<CalculationListener> listeners = new ArrayList<ComputationalLaboratory.CalculationListener>();
private Collection<LabStartsAndEndsListener> startsAndEndslisteners = new ArrayList<LabStartsAndEndsListener>();
private List<Algorithm> algorithms = new ArrayList<ComputationalLaboratory.Algorithm>();
private Set<String> algorithmNames = new HashSet<String>();
private Set<String> instanceNames = new HashSet<String>();
private int threads = 1;
public ComputationalLaboratory() {
}
/**
* Adds algorithmFactory by name.
*
* @param name
* @param factory
* @throws IllegalStateException if there is already an algorithmFactory with the same name
*/
public void addAlgorithmFactory(String name, VehicleRoutingAlgorithmFactory factory) {
if (algorithmNames.contains(name))
throw new IllegalStateException("there is already a algorithmFactory with the same name (algorithmName=" + name + "). unique names are required.");
algorithms.add(new Algorithm(name, factory));
algorithmNames.add(name);
}
public Collection<String> getAlgorithmNames() {
return algorithmNames;
}
public Collection<String> getInstanceNames() {
return instanceNames;
}
/**
* Adds instance by name.
*
* @param name
* @param problem
* @throws IllegalStateException if there is already an instance with the same name.
*/
public void addInstance(String name, VehicleRoutingProblem problem) {
if (benchmarkInstances.contains(name))
throw new IllegalStateException("there is already an instance with the same name (instanceName=" + name + "). unique names are required.");
benchmarkInstances.add(new BenchmarkInstance(name, problem, null, null));
instanceNames.add(name);
}
/**
* Adds instance.
*
* @param instance the instance to be added
* @throws IllegalStateException if there is already an instance with the same name.
*/
public void addInstance(BenchmarkInstance instance) {
if (benchmarkInstances.contains(instance.name))
throw new IllegalStateException("there is already an instance with the same name (instanceName=" + instance.name + "). unique names are required.");
benchmarkInstances.add(instance);
instanceNames.add(instance.name);
}
/**
* Adds collection of instances.
*
* @param instances collection of instances to be added
* @throws IllegalStateException if there is already an instance with the same name.
*/
public void addAllInstances(Collection<BenchmarkInstance> instances) {
for (BenchmarkInstance i : instances) {
addInstance(i);
}
}
/**
* Adds instance by name, and with best known results.
*
* @param name
* @param problem
* @throws IllegalStateException if there is already an instance with the same name.
*/
public void addInstance(String name, VehicleRoutingProblem problem, Double bestKnownResult, Double bestKnownVehicles) {
addInstance(new BenchmarkInstance(name, problem, bestKnownResult, bestKnownVehicles));
}
/**
* Adds listener to listen computational experiments.
*
* @param listener
*/
public void addListener(LabListener listener) {
if (listener instanceof CalculationListener) {
listeners.add((CalculationListener) listener);
}
if (listener instanceof LabStartsAndEndsListener) {
startsAndEndslisteners.add((LabStartsAndEndsListener) listener);
}
}
/**
* Sets nuOfRuns with same algorithm on same instance.
* <p>Default is 1
*
* @param runs
*/
public void setNuOfRuns(int runs) {
this.runs = runs;
}
/**
* Runs experiments.
* <p/>
* <p>If nuThreads > 1 it runs them concurrently, i.e. individual runs are distributed to available threads. Therefore
* a unique task is defined by its algorithmName, instanceName and its runNumber.
* <p>If you have one algorithm called "myAlgorithm" and one instance called "myInstance", and you need to run "myAlgorithm" on "myInstance" three times
* with three threads then "myAlgorithm","myInstance",run1 runs on the first thread, "myAlgorithm", "myInstance", run2 on the second etc.
* <p>You can register whatever analysisTool you require by implementing and registering CalculationListener. Then your tool is informed just
* before a calculation starts as well as just after a calculation has been finished.
*
* @throws IllegalStateException if either no algorithm or no instance has been specified
* @see CalculationListener
*/
public void run() {
if (algorithms.isEmpty()) {
throw new IllegalStateException("no algorithm specified. at least one algorithm needs to be specified.");
}
if (benchmarkInstances.isEmpty()) {
throw new IllegalStateException("no instance specified. at least one instance needs to be specified.");
}
informStart();
System.out.println("start benchmarking [nuAlgorithms=" + algorithms.size() + "][nuInstances=" + benchmarkInstances.size() + "][runsPerInstance=" + runs + "]");
double startTime = System.currentTimeMillis();
ExecutorService executor = Executors.newFixedThreadPool(threads);
for (final Algorithm algorithm : algorithms) {
for (final BenchmarkInstance p : benchmarkInstances) {
for (int run = 0; run < runs; run++) {
final int r = run;
try {
executor.submit(new Runnable() {
@Override
public void run() {
runAlgorithm(p, algorithm, r + 1);
}
});
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
}
try {
executor.shutdown();
executor.awaitTermination(Long.MAX_VALUE, TimeUnit.MINUTES);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("benchmarking done [time=" + (System.currentTimeMillis() - startTime) / 1000 + "sec]");
informEnd();
}
private void informEnd() {
for (LabStartsAndEndsListener l : startsAndEndslisteners) {
l.labEnds();
}
}
private void informStart() {
for (LabStartsAndEndsListener l : startsAndEndslisteners) {
l.labStarts(benchmarkInstances, algorithms.size(), runs);
}
}
/**
* Sets number of threads.
* <p>By default: <code>nuThreads = Runtime.getRuntime().availableProcessors()+1</code>
*
* @param threads
*/
public void setThreads(int threads) {
this.threads = threads;
}
private void runAlgorithm(BenchmarkInstance p, Algorithm algorithm, int run) {
System.out.println("[algorithm=" + algorithm.name + "][instance=" + p.name + "][run=" + run + "][status=start]");
VehicleRoutingAlgorithm vra = algorithm.factory.createAlgorithm(p.vrp);
informCalculationStarts(p, algorithm.name, vra, run);
Collection<VehicleRoutingProblemSolution> solutions = vra.searchSolutions();
System.out.println("[algorithm=" + algorithm.name + "][instance=" + p.name + "][run=" + run + "][status=finished]");
informCalculationsEnds(p, algorithm.name, vra, run, solutions);
}
private void informCalculationStarts(BenchmarkInstance p, String name, VehicleRoutingAlgorithm vra, int run) {
for (CalculationListener l : listeners) l.calculationStarts(p, name, vra, run);
}
private void informCalculationsEnds(BenchmarkInstance p, String name, VehicleRoutingAlgorithm vra, int run,
Collection<VehicleRoutingProblemSolution> solutions) {
for (CalculationListener l : listeners) l.calculationEnds(p, name, vra, run, solutions);
}
}

View file

@ -0,0 +1,219 @@
/*******************************************************************************
* Copyright (C) 2013 Stefan Schroeder
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3.0 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
package com.graphhopper.jsprit.analysis.toolbox;
import com.graphhopper.jsprit.analysis.util.BenchmarkWriter;
import com.graphhopper.jsprit.core.algorithm.VehicleRoutingAlgorithm;
import com.graphhopper.jsprit.core.algorithm.VehicleRoutingAlgorithmFactory;
import com.graphhopper.jsprit.core.algorithm.io.VehicleRoutingAlgorithms;
import com.graphhopper.jsprit.core.algorithm.listener.VehicleRoutingAlgorithmListeners.Priority;
import com.graphhopper.jsprit.core.problem.VehicleRoutingProblem;
import com.graphhopper.jsprit.core.problem.solution.VehicleRoutingProblemSolution;
import com.graphhopper.jsprit.core.util.BenchmarkInstance;
import com.graphhopper.jsprit.core.util.BenchmarkResult;
import com.graphhopper.jsprit.core.util.Solutions;
import org.apache.commons.math.stat.descriptive.DescriptiveStatistics;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.*;
public class ConcurrentBenchmarker {
public static interface Cost {
public double getCost(VehicleRoutingProblemSolution sol);
}
private String algorithmConfig = null;
private List<BenchmarkInstance> benchmarkInstances = new ArrayList<BenchmarkInstance>();
private int runs = 1;
private Collection<BenchmarkWriter> writers = new ArrayList<BenchmarkWriter>();
private Collection<BenchmarkResult> results = new ArrayList<BenchmarkResult>();
private Cost cost = new Cost() {
@Override
public double getCost(VehicleRoutingProblemSolution sol) {
return sol.getCost();
}
};
private VehicleRoutingAlgorithmFactory algorithmFactory;
public void setCost(Cost cost) {
this.cost = cost;
}
public ConcurrentBenchmarker(String algorithmConfig) {
super();
this.algorithmConfig = algorithmConfig;
// LogManager.getRootLogger().setLevel(Level.ERROR);
}
public ConcurrentBenchmarker(VehicleRoutingAlgorithmFactory algorithmFactory) {
this.algorithmFactory = algorithmFactory;
}
public void addBenchmarkWriter(BenchmarkWriter writer) {
writers.add(writer);
}
public void addInstance(String name, VehicleRoutingProblem problem) {
benchmarkInstances.add(new BenchmarkInstance(name, problem, null, null));
}
public void addInstane(BenchmarkInstance instance) {
benchmarkInstances.add(instance);
}
public void addAllInstances(Collection<BenchmarkInstance> instances) {
benchmarkInstances.addAll(instances);
}
public void addInstance(String name, VehicleRoutingProblem problem, Double bestKnownResult, Double bestKnownVehicles) {
benchmarkInstances.add(new BenchmarkInstance(name, problem, bestKnownResult, bestKnownVehicles));
}
/**
* Sets nuOfRuns with same algorithm on same instance.
* <p>Default is 1
*
* @param runs
*/
public void setNuOfRuns(int runs) {
this.runs = runs;
}
public void run() {
System.out.println("start benchmarking [nuOfInstances=" + benchmarkInstances.size() + "][runsPerInstance=" + runs + "]");
double startTime = System.currentTimeMillis();
ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() + 1);
List<Future<BenchmarkResult>> futures = new ArrayList<Future<BenchmarkResult>>();
for (final BenchmarkInstance p : benchmarkInstances) {
Future<BenchmarkResult> futureResult = executor.submit(new Callable<BenchmarkResult>() {
@Override
public BenchmarkResult call() throws Exception {
return runAlgoAndGetResult(p);
}
});
futures.add(futureResult);
}
try {
int count = 1;
for (Future<BenchmarkResult> f : futures) {
BenchmarkResult r = f.get();
print(r, count);
results.add(f.get());
count++;
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ExecutionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
executor.shutdown();
print(results);
System.out.println("done [time=" + (System.currentTimeMillis() - startTime) / 1000 + "sec]");
}
private BenchmarkResult runAlgoAndGetResult(BenchmarkInstance p) {
double[] vehicles = new double[runs];
double[] results = new double[runs];
double[] times = new double[runs];
for (int run = 0; run < runs; run++) {
VehicleRoutingAlgorithm vra = createAlgorithm(p);
StopWatch stopwatch = new StopWatch();
vra.getAlgorithmListeners().addListener(stopwatch, Priority.HIGH);
Collection<VehicleRoutingProblemSolution> solutions = vra.searchSolutions();
VehicleRoutingProblemSolution best = Solutions.bestOf(solutions);
vehicles[run] = best.getRoutes().size();
results[run] = cost.getCost(best);
times[run] = stopwatch.getCompTimeInSeconds();
}
return new BenchmarkResult(p, runs, results, times, vehicles);
}
private VehicleRoutingAlgorithm createAlgorithm(BenchmarkInstance p) {
if (algorithmConfig != null) {
return VehicleRoutingAlgorithms.readAndCreateAlgorithm(p.vrp, algorithmConfig);
} else {
return algorithmFactory.createAlgorithm(p.vrp);
}
}
private void print(Collection<BenchmarkResult> results) {
double sumTime = 0.0;
double sumResult = 0.0;
for (BenchmarkResult r : results) {
sumTime += r.getTimesStats().getMean();
sumResult += r.getResultStats().getMean();
// print(r);
}
System.out.println("[avgTime=" + round(sumTime / (double) results.size(), 2) + "][avgResult=" + round(sumResult / (double) results.size(), 2) + "]");
for (BenchmarkWriter writer : writers) {
writer.write(results);
}
}
private void print(BenchmarkResult r, int count) {
Double avgDelta = null;
Double bestDelta = null;
Double worstDelta = null;
if (r.instance.bestKnownResult != null) {
avgDelta = (r.getResultStats().getMean() / r.instance.bestKnownResult - 1) * 100;
bestDelta = (r.getResultStats().getMin() / r.instance.bestKnownResult - 1) * 100;
worstDelta = (r.getResultStats().getMax() / r.instance.bestKnownResult - 1) * 100;
}
System.out.println("(" + count + "/" + benchmarkInstances.size() + ")" + "\t[instance=" + r.instance.name +
"][avgTime=" + round(r.getTimesStats().getMean(), 2) + "]" +
"[Result=" + getString(r.getResultStats()) + "]" +
"[Vehicles=" + getString(r.getVehicleStats()) + "]" +
"[Delta[%]=" + getString(bestDelta, avgDelta, worstDelta) + "]");
}
private String getString(Double bestDelta, Double avgDelta, Double worstDelta) {
return "[best=" + round(bestDelta, 2) + "][avg=" + round(avgDelta, 2) + "][worst=" + round(worstDelta, 2) + "]";
}
private String getString(DescriptiveStatistics stats) {
return "[best=" + round(stats.getMin(), 2) + "][avg=" + round(stats.getMean(), 2) + "][worst=" + round(stats.getMax(), 2) + "][stdDev=" + round(stats.getStandardDeviation(), 2) + "]";
}
private Double round(Double value, int i) {
if (value == null) return null;
long roundedVal = Math.round(value * Math.pow(10, i));
return (double) roundedVal / (double) (Math.pow(10, i));
}
}

View file

@ -0,0 +1,615 @@
/*******************************************************************************
* Copyright (C) 2014 Stefan Schroeder
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3.0 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
package com.graphhopper.jsprit.analysis.toolbox;
import com.graphhopper.jsprit.core.problem.VehicleRoutingProblem;
import com.graphhopper.jsprit.core.problem.job.Job;
import com.graphhopper.jsprit.core.problem.job.Service;
import com.graphhopper.jsprit.core.problem.job.Shipment;
import com.graphhopper.jsprit.core.problem.solution.VehicleRoutingProblemSolution;
import com.graphhopper.jsprit.core.problem.solution.route.VehicleRoute;
import com.graphhopper.jsprit.core.problem.solution.route.activity.DeliveryActivity;
import com.graphhopper.jsprit.core.problem.solution.route.activity.PickupActivity;
import com.graphhopper.jsprit.core.problem.solution.route.activity.TourActivity;
import com.graphhopper.jsprit.core.problem.solution.route.activity.TourActivity.JobActivity;
import com.graphhopper.jsprit.core.problem.vehicle.Vehicle;
import com.graphhopper.jsprit.core.util.Time;
import org.graphstream.graph.Edge;
import org.graphstream.graph.Graph;
import org.graphstream.graph.Node;
import org.graphstream.graph.implementations.MultiGraph;
import org.graphstream.ui.swingViewer.ViewPanel;
import org.graphstream.ui.view.View;
import org.graphstream.ui.view.Viewer;
import javax.swing.*;
import java.awt.*;
public class GraphStreamViewer {
public static class StyleSheets {
public static String BLUE_FOREST =
"graph { fill-color: #141F2E; }" +
"node {" +
" size: 7px, 7px;" +
" fill-color: #A0FFA0;" +
" text-alignment: at-right;" +
" stroke-mode: plain;" +
" stroke-color: #999;" +
" stroke-width: 1.0;" +
" text-font: couriernew;" +
" text-offset: 2,-5;" +
" text-size: 8;" +
"}" +
"node.pickup {" +
" fill-color: #6CC644;" +
"}" +
"node.delivery {" +
" fill-color: #f93;" +
"}" +
"node.pickupInRoute {" +
" fill-color: #6CC644;" +
" stroke-mode: plain;" +
" stroke-color: #333;" +
" stroke-width: 2.0;" +
"}" +
"node.deliveryInRoute {" +
" fill-color: #f93;" +
" stroke-mode: plain;" +
" stroke-color: #333;" +
" stroke-width: 2.0;" +
"}" +
"node.depot {" +
" fill-color: #BD2C00;" +
" size: 10px, 10px;" +
" shape: box;" +
"}" +
"node.removed {" +
" fill-color: #FF8080;" +
" size: 10px, 10px;" +
" stroke-mode: plain;" +
" stroke-color: #CCF;" +
" stroke-width: 2.0;" +
" shadow-mode: gradient-radial;" +
" shadow-width: 10px; shadow-color: #EEF, #000; shadow-offset: 0px;" +
"}" +
"edge {" +
" fill-color: #D3D3D3;" +
" arrow-size: 6px,3px;" +
"}" +
// "edge.inserted {" +
// " fill-color: #A0FFA0;" +
// " arrow-size: 6px,3px;" +
// " shadow-mode: gradient-radial;" +
// " shadow-width: 10px; shadow-color: #EEF, #000; shadow-offset: 0px;" +
// "}" +
// "edge.removed {" +
// " fill-color: #FF0000;" +
// " arrow-size: 6px,3px;" +
// " shadow-mode: gradient-radial;" +
// " shadow-width: 10px; shadow-color: #EEF, #000; shadow-offset: 0px;" +
// "}" +
"edge.shipment {" +
" fill-color: #999;" +
" arrow-size: 6px,3px;" +
"}";
@SuppressWarnings("UnusedDeclaration")
public static String SIMPLE_WHITE =
"node {" +
" size: 10px, 10px;" +
" fill-color: #6CC644;" +
" text-alignment: at-right;" +
" stroke-mode: plain;" +
" stroke-color: #999;" +
" stroke-width: 1.0;" +
" text-font: couriernew;" +
" text-offset: 2,-5;" +
" text-size: 8;" +
"}" +
"node.pickup {" +
" fill-color: #6CC644;" +
"}" +
"node.delivery {" +
" fill-color: #f93;" +
"}" +
"node.pickupInRoute {" +
" fill-color: #6CC644;" +
" stroke-mode: plain;" +
" stroke-color: #333;" +
" stroke-width: 2.0;" +
"}" +
"node.deliveryInRoute {" +
" fill-color: #f93;" +
" stroke-mode: plain;" +
" stroke-color: #333;" +
" stroke-width: 2.0;" +
"}" +
"node.depot {" +
" fill-color: #BD2C00;" +
" size: 10px, 10px;" +
" shape: box;" +
"}" +
"node.removed {" +
" fill-color: #BD2C00;" +
" size: 10px, 10px;" +
" stroke-mode: plain;" +
" stroke-color: #333;" +
" stroke-width: 2.0;" +
"}" +
"edge {" +
" fill-color: #333;" +
" arrow-size: 6px,3px;" +
"}" +
"edge.shipment {" +
" fill-color: #999;" +
" arrow-size: 6px,3px;" +
"}";
}
public static Graph createMultiGraph(String name, String style) {
Graph g = new MultiGraph(name);
g.addAttribute("ui.quality");
g.addAttribute("ui.antialias");
g.addAttribute("ui.stylesheet", style);
return g;
}
public static ViewPanel createEmbeddedView(Graph graph, double scaling) {
Viewer viewer = new Viewer(graph, Viewer.ThreadingModel.GRAPH_IN_ANOTHER_THREAD);
ViewPanel view = viewer.addDefaultView(false);
view.setPreferredSize(new Dimension((int) (698 * scaling), (int) (440 * scaling)));
return view;
}
public static String STYLESHEET =
"node {" +
" size: 10px, 10px;" +
" fill-color: #6CC644;" +
" text-alignment: at-right;" +
" stroke-mode: plain;" +
" stroke-color: #999;" +
" stroke-width: 1.0;" +
" text-font: couriernew;" +
" text-offset: 2,-5;" +
" text-size: 8;" +
"}" +
"node.pickup {" +
" fill-color: #6CC644;" +
"}" +
"node.delivery {" +
" fill-color: #f93;" +
"}" +
"node.pickupInRoute {" +
" fill-color: #6CC644;" +
" stroke-mode: plain;" +
" stroke-color: #333;" +
" stroke-width: 2.0;" +
"}" +
"node.deliveryInRoute {" +
" fill-color: #f93;" +
" stroke-mode: plain;" +
" stroke-color: #333;" +
" stroke-width: 2.0;" +
"}" +
"node.depot {" +
" fill-color: #BD2C00;" +
" size: 10px, 10px;" +
" shape: box;" +
"}" +
"node.removed {" +
" fill-color: #BD2C00;" +
" size: 10px, 10px;" +
" stroke-mode: plain;" +
" stroke-color: #333;" +
" stroke-width: 2.0;" +
"}" +
"edge {" +
" fill-color: #333;" +
" arrow-size: 6px,3px;" +
"}" +
"edge.shipment {" +
" fill-color: #999;" +
" arrow-size: 6px,3px;" +
"}";
public static enum Label {
NO_LABEL, ID, JOB_NAME, ARRIVAL_TIME, DEPARTURE_TIME, ACTIVITY
}
private static class Center {
final double x;
final double y;
public Center(double x, double y) {
super();
this.x = x;
this.y = y;
}
}
private Label label = Label.NO_LABEL;
private long renderDelay_in_ms = 0;
private boolean renderShipments = false;
private Center center;
private VehicleRoutingProblem vrp;
private VehicleRoutingProblemSolution solution;
private double zoomFactor;
private double scaling = 1.0;
public GraphStreamViewer(VehicleRoutingProblem vrp) {
super();
this.vrp = vrp;
}
public GraphStreamViewer(VehicleRoutingProblem vrp, VehicleRoutingProblemSolution solution) {
super();
this.vrp = vrp;
this.solution = solution;
}
public GraphStreamViewer labelWith(Label label) {
this.label = label;
return this;
}
public GraphStreamViewer setRenderDelay(long ms) {
this.renderDelay_in_ms = ms;
return this;
}
@Deprecated
public GraphStreamViewer setEnableAutoLayout(boolean enableAutoLayout) {
return this;
}
public GraphStreamViewer setRenderShipments(boolean renderShipments) {
this.renderShipments = renderShipments;
return this;
}
public GraphStreamViewer setGraphStreamFrameScalingFactor(double factor) {
this.scaling = factor;
return this;
}
/**
* Sets the camera-view. Center describes the center-focus of the camera and zoomFactor its
* zoomFactor.
* <p/>
* <p>a zoomFactor < 1 zooms in and > 1 out.
*
* @param centerX x coordinate of center
* @param centerY y coordinate of center
* @param zoomFactor zoom factor
* @return the viewer
*/
public GraphStreamViewer setCameraView(double centerX, double centerY, double zoomFactor) {
center = new Center(centerX, centerY);
this.zoomFactor = zoomFactor;
return this;
}
public void display() {
System.setProperty("org.graphstream.ui.renderer", "org.graphstream.ui.j2dviewer.J2DGraphRenderer");
Graph g = createMultiGraph("g");
ViewPanel view = createEmbeddedView(g, scaling);
createJFrame(view, scaling);
render(g, view);
}
private JFrame createJFrame(ViewPanel view, double scaling) {
JFrame jframe = new JFrame();
JPanel basicPanel = new JPanel();
basicPanel.setLayout(new BoxLayout(basicPanel, BoxLayout.Y_AXIS));
//result-panel
JPanel resultPanel = createResultPanel();
//graphstream-panel
JPanel graphStreamPanel = new JPanel();
graphStreamPanel.setPreferredSize(new Dimension((int) (800 * scaling), (int) (460 * scaling)));
graphStreamPanel.setBackground(Color.WHITE);
JPanel graphStreamBackPanel = new JPanel();
graphStreamBackPanel.setPreferredSize(new Dimension((int) (700 * scaling), (int) (450 * scaling)));
graphStreamBackPanel.setBorder(BorderFactory.createLineBorder(Color.LIGHT_GRAY, 1));
graphStreamBackPanel.setBackground(Color.WHITE);
graphStreamBackPanel.add(view);
graphStreamPanel.add(graphStreamBackPanel);
//setup basicPanel
basicPanel.add(resultPanel);
basicPanel.add(graphStreamPanel);
// basicPanel.add(legendPanel);
//put it together
jframe.add(basicPanel);
//conf jframe
jframe.setSize((int) (800 * scaling), (int) (580 * scaling));
jframe.setLocationRelativeTo(null);
jframe.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
jframe.setVisible(true);
jframe.pack();
jframe.setTitle("jsprit - GraphStream");
return jframe;
}
private Graph createMultiGraph(String name) {
return GraphStreamViewer.createMultiGraph(name, STYLESHEET);
}
private void render(Graph g, ViewPanel view) {
if (center != null) {
view.resizeFrame(view.getWidth(), view.getHeight());
alignCamera(view);
}
for (Vehicle vehicle : vrp.getVehicles()) {
renderVehicle(g, vehicle, label);
sleep(renderDelay_in_ms);
}
for (Job j : vrp.getJobs().values()) {
if (j instanceof Service) {
renderService(g, (Service) j, label);
} else if (j instanceof Shipment) {
renderShipment(g, (Shipment) j, label, renderShipments);
}
sleep(renderDelay_in_ms);
}
if (solution != null) {
int routeId = 1;
for (VehicleRoute route : solution.getRoutes()) {
renderRoute(g, route, routeId, renderDelay_in_ms, label);
sleep(renderDelay_in_ms);
routeId++;
}
}
}
private void alignCamera(View view) {
view.getCamera().setViewCenter(center.x, center.y, 0);
view.getCamera().setViewPercent(zoomFactor);
}
private JLabel createEmptyLabel() {
JLabel emptyLabel1 = new JLabel();
emptyLabel1.setPreferredSize(new Dimension((int) (40 * scaling), (int) (25 * scaling)));
return emptyLabel1;
}
private JPanel createResultPanel() {
int width = 800;
int height = 50;
JPanel panel = new JPanel();
panel.setPreferredSize(new Dimension((int) (width * scaling), (int) (height * scaling)));
panel.setBackground(Color.WHITE);
JPanel subpanel = new JPanel();
subpanel.setLayout(new FlowLayout());
subpanel.setPreferredSize(new Dimension((int) (700 * scaling), (int) (40 * scaling)));
subpanel.setBackground(Color.WHITE);
subpanel.setBorder(BorderFactory.createLineBorder(Color.LIGHT_GRAY, 1));
Font font = Font.decode("couriernew");
JLabel jobs = new JLabel("jobs");
jobs.setFont(font);
jobs.setPreferredSize(new Dimension((int) (40 * scaling), (int) (25 * scaling)));
int noJobs = 0;
if (this.vrp != null) noJobs = this.vrp.getJobs().values().size();
JFormattedTextField nJobs = new JFormattedTextField(noJobs);
nJobs.setFont(font);
nJobs.setEditable(false);
nJobs.setBorder(BorderFactory.createEmptyBorder());
nJobs.setBackground(new Color(230, 230, 230));
JLabel costs = new JLabel("costs");
costs.setFont(font);
costs.setPreferredSize(new Dimension((int) (40 * scaling), (int) (25 * scaling)));
JFormattedTextField costsVal = new JFormattedTextField(getSolutionCosts());
costsVal.setFont(font);
costsVal.setEditable(false);
costsVal.setBorder(BorderFactory.createEmptyBorder());
costsVal.setBackground(new Color(230, 230, 230));
JLabel vehicles = new JLabel("routes");
vehicles.setFont(font);
vehicles.setPreferredSize(new Dimension((int) (40 * scaling), (int) (25 * scaling)));
// vehicles.setForeground(Color.DARK_GRAY);
JFormattedTextField vehVal = new JFormattedTextField(getNoRoutes());
vehVal.setFont(font);
vehVal.setEditable(false);
vehVal.setBorder(BorderFactory.createEmptyBorder());
// vehVal.setForeground(Color.DARK_GRAY);
vehVal.setBackground(new Color(230, 230, 230));
//platzhalter
JLabel placeholder1 = new JLabel();
placeholder1.setPreferredSize(new Dimension((int) (60 * scaling), (int) (25 * scaling)));
JLabel emptyLabel1 = createEmptyLabel();
subpanel.add(jobs);
subpanel.add(nJobs);
subpanel.add(emptyLabel1);
subpanel.add(costs);
subpanel.add(costsVal);
JLabel emptyLabel2 = createEmptyLabel();
subpanel.add(emptyLabel2);
subpanel.add(vehicles);
subpanel.add(vehVal);
panel.add(subpanel);
return panel;
}
private Integer getNoRoutes() {
if (solution != null) return solution.getRoutes().size();
return 0;
}
private Double getSolutionCosts() {
if (solution != null) return solution.getCost();
return 0.0;
}
private void renderShipment(Graph g, Shipment shipment, Label label, boolean renderShipments) {
Node n1 = g.addNode(makeId(shipment.getId(), shipment.getPickupLocation().getId()));
if (label.equals(Label.ID)) n1.addAttribute("ui.label", shipment.getId());
n1.addAttribute("x", shipment.getPickupLocation().getCoordinate().getX());
n1.addAttribute("y", shipment.getPickupLocation().getCoordinate().getY());
n1.setAttribute("ui.class", "pickup");
Node n2 = g.addNode(makeId(shipment.getId(), shipment.getDeliveryLocation().getId()));
if (label.equals(Label.ID)) n2.addAttribute("ui.label", shipment.getId());
n2.addAttribute("x", shipment.getDeliveryLocation().getCoordinate().getX());
n2.addAttribute("y", shipment.getDeliveryLocation().getCoordinate().getY());
n2.setAttribute("ui.class", "delivery");
if (renderShipments) {
Edge s = g.addEdge(shipment.getId(), makeId(shipment.getId(), shipment.getPickupLocation().getId()),
makeId(shipment.getId(), shipment.getDeliveryLocation().getId()), true);
s.addAttribute("ui.class", "shipment");
}
}
private void sleep(long renderDelay_in_ms2) {
try {
Thread.sleep(renderDelay_in_ms2);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private void renderService(Graph g, Service service, Label label) {
Node n = g.addNode(makeId(service.getId(), service.getLocation().getId()));
if (label.equals(Label.ID)) n.addAttribute("ui.label", service.getId());
n.addAttribute("x", service.getLocation().getCoordinate().getX());
n.addAttribute("y", service.getLocation().getCoordinate().getY());
if (service.getType().equals("pickup")) n.setAttribute("ui.class", "pickup");
if (service.getType().equals("delivery")) n.setAttribute("ui.class", "delivery");
}
private String makeId(String id, String locationId) {
return id + "_" + locationId;
}
private void renderVehicle(Graph g, Vehicle vehicle, Label label) {
String nodeId = makeId(vehicle.getId(), vehicle.getStartLocation().getId());
Node vehicleStart = g.addNode(nodeId);
if (label.equals(Label.ID)) vehicleStart.addAttribute("ui.label", "depot");
// if(label.equals(Label.ACTIVITY)) n.addAttribute("ui.label", "start");
vehicleStart.addAttribute("x", vehicle.getStartLocation().getCoordinate().getX());
vehicleStart.addAttribute("y", vehicle.getStartLocation().getCoordinate().getY());
vehicleStart.setAttribute("ui.class", "depot");
if (!vehicle.getStartLocation().getId().equals(vehicle.getEndLocation().getId())) {
Node vehicleEnd = g.addNode(makeId(vehicle.getId(), vehicle.getEndLocation().getId()));
if (label.equals(Label.ID)) vehicleEnd.addAttribute("ui.label", "depot");
vehicleEnd.addAttribute("x", vehicle.getEndLocation().getCoordinate().getX());
vehicleEnd.addAttribute("y", vehicle.getEndLocation().getCoordinate().getY());
vehicleEnd.setAttribute("ui.class", "depot");
}
}
private void renderRoute(Graph g, VehicleRoute route, int routeId, long renderDelay_in_ms, Label label) {
int vehicle_edgeId = 1;
String prevIdentifier = makeId(route.getVehicle().getId(), route.getVehicle().getStartLocation().getId());
if (label.equals(Label.ACTIVITY) || label.equals(Label.JOB_NAME)) {
Node n = g.getNode(prevIdentifier);
n.addAttribute("ui.label", "start");
}
for (TourActivity act : route.getActivities()) {
Job job = ((JobActivity) act).getJob();
String currIdentifier = makeId(job.getId(), act.getLocation().getId());
if (label.equals(Label.ACTIVITY)) {
Node actNode = g.getNode(currIdentifier);
actNode.addAttribute("ui.label", act.getName());
} else if (label.equals(Label.JOB_NAME)) {
Node actNode = g.getNode(currIdentifier);
actNode.addAttribute("ui.label", job.getName());
} else if (label.equals(Label.ARRIVAL_TIME)) {
Node actNode = g.getNode(currIdentifier);
actNode.addAttribute("ui.label", Time.parseSecondsToTime(act.getArrTime()));
} else if (label.equals(Label.DEPARTURE_TIME)) {
Node actNode = g.getNode(currIdentifier);
actNode.addAttribute("ui.label", Time.parseSecondsToTime(act.getEndTime()));
}
g.addEdge(makeEdgeId(routeId, vehicle_edgeId), prevIdentifier, currIdentifier, true);
if (act instanceof PickupActivity) g.getNode(currIdentifier).addAttribute("ui.class", "pickupInRoute");
else if (act instanceof DeliveryActivity)
g.getNode(currIdentifier).addAttribute("ui.class", "deliveryInRoute");
prevIdentifier = currIdentifier;
vehicle_edgeId++;
sleep(renderDelay_in_ms);
}
if (route.getVehicle().isReturnToDepot()) {
String lastIdentifier = makeId(route.getVehicle().getId(), route.getVehicle().getEndLocation().getId());
g.addEdge(makeEdgeId(routeId, vehicle_edgeId), prevIdentifier, lastIdentifier, true);
}
}
private String makeEdgeId(int routeId, int vehicle_edgeId) {
return Integer.valueOf(routeId).toString() + "." + Integer.valueOf(vehicle_edgeId).toString();
}
// public void saveAsPNG(String filename){
//
// }
}

View file

@ -0,0 +1,28 @@
/*******************************************************************************
* Copyright (c) 2014 Stefan Schroeder.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3.0 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Contributors:
* Stefan Schroeder - initial API and implementation
******************************************************************************/
package com.graphhopper.jsprit.analysis.toolbox;
class NoLocationFoundException extends Exception {
/**
*
*/
private static final long serialVersionUID = 1L;
}

View file

@ -0,0 +1,626 @@
/*******************************************************************************
* Copyright (C) 2014 Stefan Schroeder
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3.0 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
package com.graphhopper.jsprit.analysis.toolbox;
import com.graphhopper.jsprit.core.problem.VehicleRoutingProblem;
import com.graphhopper.jsprit.core.problem.job.*;
import com.graphhopper.jsprit.core.problem.solution.VehicleRoutingProblemSolution;
import com.graphhopper.jsprit.core.problem.solution.route.VehicleRoute;
import com.graphhopper.jsprit.core.problem.solution.route.activity.TourActivity;
import com.graphhopper.jsprit.core.problem.vehicle.Vehicle;
import com.graphhopper.jsprit.core.util.Coordinate;
import com.graphhopper.jsprit.core.util.Locations;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jfree.chart.*;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.labels.XYItemLabelGenerator;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYItemRenderer;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.chart.title.LegendTitle;
import org.jfree.data.Range;
import org.jfree.data.xy.XYDataItem;
import org.jfree.data.xy.XYDataset;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;
import org.jfree.ui.RectangleEdge;
import org.jfree.util.ShapeUtilities;
import java.awt.*;
import java.awt.geom.Ellipse2D;
import java.io.File;
import java.io.IOException;
import java.util.*;
/**
* Visualizes problem and solution.
* <p>Note that every item to be rendered need to have coordinates.
*
* @author schroeder
*/
public class Plotter {
private final static Color START_COLOR = Color.RED;
private final static Color END_COLOR = Color.RED;
private final static Color PICKUP_COLOR = Color.GREEN;
private final static Color DELIVERY_COLOR = Color.BLUE;
private final static Color SERVICE_COLOR = Color.BLUE;
private final static Shape ELLIPSE = new Ellipse2D.Double(-3, -3, 6, 6);
private static class MyActivityRenderer extends XYLineAndShapeRenderer {
/**
*
*/
private static final long serialVersionUID = 1L;
private XYSeriesCollection seriesCollection;
private Map<XYDataItem, Activity> activities;
private Set<XYDataItem> firstActivities;
public MyActivityRenderer(XYSeriesCollection seriesCollection, Map<XYDataItem, Activity> activities, Set<XYDataItem> firstActivities) {
super(false, true);
this.seriesCollection = seriesCollection;
this.activities = activities;
this.firstActivities = firstActivities;
super.setSeriesOutlinePaint(0, Color.DARK_GRAY);
super.setUseOutlinePaint(true);
}
@Override
public Shape getItemShape(int seriesIndex, int itemIndex) {
XYDataItem dataItem = seriesCollection.getSeries(seriesIndex).getDataItem(itemIndex);
if (firstActivities.contains(dataItem)) {
return ShapeUtilities.createUpTriangle(4.0f);
}
return ELLIPSE;
}
@Override
public Paint getItemOutlinePaint(int seriesIndex, int itemIndex) {
XYDataItem dataItem = seriesCollection.getSeries(seriesIndex).getDataItem(itemIndex);
if (firstActivities.contains(dataItem)) {
return Color.BLACK;
}
return super.getItemOutlinePaint(seriesIndex, itemIndex);
}
@Override
public Paint getItemPaint(int seriesIndex, int itemIndex) {
XYDataItem dataItem = seriesCollection.getSeries(seriesIndex).getDataItem(itemIndex);
Activity activity = activities.get(dataItem);
if (activity.equals(Activity.PICKUP)) return PICKUP_COLOR;
if (activity.equals(Activity.DELIVERY)) return DELIVERY_COLOR;
if (activity.equals(Activity.SERVICE)) return SERVICE_COLOR;
if (activity.equals(Activity.START)) return START_COLOR;
if (activity.equals(Activity.END)) return END_COLOR;
throw new IllegalStateException("activity at " + dataItem.toString() + " cannot be assigned to a color");
}
}
private static class BoundingBox {
double minX;
double minY;
double maxX;
double maxY;
public BoundingBox(double minX, double minY, double maxX, double maxY) {
super();
this.minX = minX;
this.minY = minY;
this.maxX = maxX;
this.maxY = maxY;
}
}
private enum Activity {
START, END, PICKUP, DELIVERY, SERVICE
}
private static Logger log = LogManager.getLogger(Plotter.class);
/**
* Label to label ID (=jobId), SIZE (=jobSize=jobCapacityDimensions)
*
* @author schroeder
*/
public static enum Label {
ID, SIZE, @SuppressWarnings("UnusedDeclaration")NO_LABEL
}
private Label label = Label.SIZE;
private VehicleRoutingProblem vrp;
private boolean plotSolutionAsWell = false;
private boolean plotShipments = true;
private Collection<VehicleRoute> routes;
private BoundingBox boundingBox = null;
private Map<XYDataItem, Activity> activitiesByDataItem = new HashMap<XYDataItem, Plotter.Activity>();
private Map<XYDataItem, String> labelsByDataItem = new HashMap<XYDataItem, String>();
private XYSeries activities;
private Set<XYDataItem> firstActivities = new HashSet<XYDataItem>();
private boolean containsPickupAct = false;
private boolean containsDeliveryAct = false;
private boolean containsServiceAct = false;
private double scalingFactor = 1.;
private boolean invert = false;
/**
* Constructs Plotter with problem. Thus only the problem can be rendered.
*
* @param vrp the routing problem
*/
public Plotter(VehicleRoutingProblem vrp) {
super();
this.vrp = vrp;
}
/**
* Constructs Plotter with problem and solution to render them both.
*
* @param vrp the routing problem
* @param solution the solution
*/
public Plotter(VehicleRoutingProblem vrp, VehicleRoutingProblemSolution solution) {
super();
this.vrp = vrp;
this.routes = solution.getRoutes();
plotSolutionAsWell = true;
}
/**
* Constructs Plotter with problem and routes to render individual routes.
*
* @param vrp the routing problem
* @param routes routes
*/
public Plotter(VehicleRoutingProblem vrp, Collection<VehicleRoute> routes) {
super();
this.vrp = vrp;
this.routes = routes;
plotSolutionAsWell = true;
}
@SuppressWarnings("UnusedDeclaration")
public Plotter setScalingFactor(double scalingFactor) {
this.scalingFactor = scalingFactor;
return this;
}
/**
* Sets a label.
*
* @param label of jobs
* @return plotter
*/
public Plotter setLabel(Label label) {
this.label = label;
return this;
}
public Plotter invertCoordinates(boolean invert) {
this.invert = invert;
return this;
}
/**
* Sets a bounding box to zoom in to certain areas.
*
* @param minX lower left x
* @param minY lower left y
* @param maxX upper right x
* @param maxY upper right y
* @return
*/
@SuppressWarnings("UnusedDeclaration")
public Plotter setBoundingBox(double minX, double minY, double maxX, double maxY) {
boundingBox = new BoundingBox(minX, minY, maxX, maxY);
return this;
}
/**
* Flag that indicates whether shipments should be rendered as well.
*
* @param plotShipments flag to plot shipment
* @return the plotter
*/
public Plotter plotShipments(boolean plotShipments) {
this.plotShipments = plotShipments;
return this;
}
/**
* Plots problem and/or solution/routes.
*
* @param pngFileName - path and filename
* @param plotTitle - title that appears on top of image
*/
public void plot(String pngFileName, String plotTitle) {
String filename = pngFileName;
if (!pngFileName.endsWith(".png")) filename += ".png";
if (plotSolutionAsWell) {
plot(vrp, routes, filename, plotTitle);
} else if (!(vrp.getInitialVehicleRoutes().isEmpty())) {
plot(vrp, vrp.getInitialVehicleRoutes(), filename, plotTitle);
} else {
plot(vrp, null, filename, plotTitle);
}
}
private void plot(VehicleRoutingProblem vrp, final Collection<VehicleRoute> routes, String pngFile, String title) {
log.info("plot to {}", pngFile);
XYSeriesCollection problem;
XYSeriesCollection solution = null;
final XYSeriesCollection shipments;
try {
retrieveActivities(vrp);
problem = new XYSeriesCollection(activities);
shipments = makeShipmentSeries(vrp.getJobs().values());
if (routes != null) solution = makeSolutionSeries(vrp, routes);
} catch (NoLocationFoundException e) {
log.warn("cannot plot vrp, since coord is missing");
return;
}
final XYPlot plot = createPlot(problem, shipments, solution);
JFreeChart chart = new JFreeChart(title, plot);
LegendTitle legend = createLegend(routes, shipments, plot);
chart.removeLegend();
chart.addLegend(legend);
save(chart, pngFile);
}
private LegendTitle createLegend(final Collection<VehicleRoute> routes, final XYSeriesCollection shipments, final XYPlot plot) {
LegendItemSource lis = new LegendItemSource() {
@Override
public LegendItemCollection getLegendItems() {
LegendItemCollection lic = new LegendItemCollection();
LegendItem vehLoc = new LegendItem("vehLoc", Color.RED);
vehLoc.setShape(ELLIPSE);
vehLoc.setShapeVisible(true);
lic.add(vehLoc);
if (containsServiceAct) {
LegendItem item = new LegendItem("service", Color.BLUE);
item.setShape(ELLIPSE);
item.setShapeVisible(true);
lic.add(item);
}
if (containsPickupAct) {
LegendItem item = new LegendItem("pickup", Color.GREEN);
item.setShape(ELLIPSE);
item.setShapeVisible(true);
lic.add(item);
}
if (containsDeliveryAct) {
LegendItem item = new LegendItem("delivery", Color.BLUE);
item.setShape(ELLIPSE);
item.setShapeVisible(true);
lic.add(item);
}
if (routes != null) {
LegendItem item = new LegendItem("firstActivity", Color.BLACK);
Shape upTriangle = ShapeUtilities.createUpTriangle(3.0f);
item.setShape(upTriangle);
item.setOutlinePaint(Color.BLACK);
item.setLine(upTriangle);
item.setLinePaint(Color.BLACK);
item.setShapeVisible(true);
lic.add(item);
}
if (!shipments.getSeries().isEmpty()) {
lic.add(plot.getRenderer(1).getLegendItem(1, 0));
}
if (routes != null) {
lic.addAll(plot.getRenderer(2).getLegendItems());
}
return lic;
}
};
LegendTitle legend = new LegendTitle(lis);
legend.setPosition(RectangleEdge.BOTTOM);
return legend;
}
private XYItemRenderer getShipmentRenderer(XYSeriesCollection shipments) {
XYItemRenderer shipmentsRenderer = new XYLineAndShapeRenderer(true, false); // Shapes only
for (int i = 0; i < shipments.getSeriesCount(); i++) {
shipmentsRenderer.setSeriesPaint(i, Color.DARK_GRAY);
shipmentsRenderer.setSeriesStroke(i, new BasicStroke(
1.0f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND,
1.f, new float[]{4.0f, 4.0f}, 0.0f
));
}
return shipmentsRenderer;
}
private MyActivityRenderer getProblemRenderer(final XYSeriesCollection problem) {
MyActivityRenderer problemRenderer = new MyActivityRenderer(problem, activitiesByDataItem, firstActivities);
problemRenderer.setBaseItemLabelGenerator(new XYItemLabelGenerator() {
@Override
public String generateLabel(XYDataset arg0, int arg1, int arg2) {
XYDataItem item = problem.getSeries(arg1).getDataItem(arg2);
return labelsByDataItem.get(item);
}
});
problemRenderer.setBaseItemLabelsVisible(true);
problemRenderer.setBaseItemLabelPaint(Color.BLACK);
return problemRenderer;
}
private Range getRange(final XYSeriesCollection seriesCol) {
if (this.boundingBox == null) return seriesCol.getRangeBounds(false);
else return new Range(boundingBox.minY, boundingBox.maxY);
}
private Range getDomainRange(final XYSeriesCollection seriesCol) {
if (this.boundingBox == null) return seriesCol.getDomainBounds(true);
else return new Range(boundingBox.minX, boundingBox.maxX);
}
private XYPlot createPlot(final XYSeriesCollection problem, XYSeriesCollection shipments, XYSeriesCollection solution) {
XYPlot plot = new XYPlot();
plot.setBackgroundPaint(Color.LIGHT_GRAY);
plot.setRangeGridlinePaint(Color.LIGHT_GRAY);
plot.setDomainGridlinePaint(Color.LIGHT_GRAY);
XYLineAndShapeRenderer problemRenderer = getProblemRenderer(problem);
plot.setDataset(0, problem);
plot.setRenderer(0, problemRenderer);
XYItemRenderer shipmentsRenderer = getShipmentRenderer(shipments);
plot.setDataset(1, shipments);
plot.setRenderer(1, shipmentsRenderer);
if (solution != null) {
XYItemRenderer solutionRenderer = getRouteRenderer(solution);
plot.setDataset(2, solution);
plot.setRenderer(2, solutionRenderer);
}
NumberAxis xAxis = new NumberAxis();
NumberAxis yAxis = new NumberAxis();
if (boundingBox == null) {
xAxis.setRangeWithMargins(getDomainRange(problem));
yAxis.setRangeWithMargins(getRange(problem));
} else {
xAxis.setRangeWithMargins(new Range(boundingBox.minX, boundingBox.maxX));
yAxis.setRangeWithMargins(new Range(boundingBox.minY, boundingBox.maxY));
}
plot.setDomainAxis(xAxis);
plot.setRangeAxis(yAxis);
return plot;
}
private XYItemRenderer getRouteRenderer(XYSeriesCollection solutionColl) {
XYItemRenderer solutionRenderer = new XYLineAndShapeRenderer(true, false); // Lines only
for (int i = 0; i < solutionColl.getSeriesCount(); i++) {
XYSeries s = solutionColl.getSeries(i);
XYDataItem firstCustomer = s.getDataItem(1);
firstActivities.add(firstCustomer);
}
return solutionRenderer;
}
private void save(JFreeChart chart, String pngFile) {
try {
ChartUtilities.saveChartAsPNG(new File(pngFile), chart, 1000, 600);
} catch (IOException e) {
log.error("cannot plot");
log.error(e);
e.printStackTrace();
}
}
private XYSeriesCollection makeSolutionSeries(VehicleRoutingProblem vrp, Collection<VehicleRoute> routes) throws NoLocationFoundException {
Locations locations = retrieveLocations(vrp);
XYSeriesCollection coll = new XYSeriesCollection();
int counter = 1;
for (VehicleRoute route : routes) {
if (route.isEmpty()) continue;
XYSeries series = new XYSeries(counter, false, true);
Coordinate startCoord = getCoordinate(locations.getCoord(route.getStart().getLocation().getId()));
series.add(startCoord.getX() * scalingFactor, startCoord.getY() * scalingFactor);
for (TourActivity act : route.getTourActivities().getActivities()) {
Coordinate coord = getCoordinate(locations.getCoord(act.getLocation().getId()));
series.add(coord.getX() * scalingFactor, coord.getY() * scalingFactor);
}
Coordinate endCoord = getCoordinate(locations.getCoord(route.getEnd().getLocation().getId()));
series.add(endCoord.getX() * scalingFactor, endCoord.getY() * scalingFactor);
coll.addSeries(series);
counter++;
}
return coll;
}
private XYSeriesCollection makeShipmentSeries(Collection<Job> jobs) throws NoLocationFoundException {
XYSeriesCollection coll = new XYSeriesCollection();
if (!plotShipments) return coll;
int sCounter = 1;
String ship = "shipment";
boolean first = true;
for (Job job : jobs) {
if (!(job instanceof Shipment)) {
continue;
}
Shipment shipment = (Shipment) job;
XYSeries shipmentSeries;
if (first) {
first = false;
shipmentSeries = new XYSeries(ship, false, true);
} else {
shipmentSeries = new XYSeries(sCounter, false, true);
sCounter++;
}
Coordinate pickupCoordinate = getCoordinate(shipment.getPickupLocation().getCoordinate());
Coordinate delCoordinate = getCoordinate(shipment.getDeliveryLocation().getCoordinate());
shipmentSeries.add(pickupCoordinate.getX() * scalingFactor, pickupCoordinate.getY() * scalingFactor);
shipmentSeries.add(delCoordinate.getX() * scalingFactor, delCoordinate.getY() * scalingFactor);
coll.addSeries(shipmentSeries);
}
return coll;
}
private void addJob(XYSeries activities, Job job) {
if (job instanceof Shipment) {
Shipment s = (Shipment) job;
Coordinate pickupCoordinate = getCoordinate(s.getPickupLocation().getCoordinate());
XYDataItem dataItem = new XYDataItem(pickupCoordinate.getX() * scalingFactor, pickupCoordinate.getY() * scalingFactor);
activities.add(dataItem);
addLabel(s, dataItem);
markItem(dataItem, Activity.PICKUP);
containsPickupAct = true;
Coordinate deliveryCoordinate = getCoordinate(s.getDeliveryLocation().getCoordinate());
XYDataItem dataItem2 = new XYDataItem(deliveryCoordinate.getX() * scalingFactor, deliveryCoordinate.getY() * scalingFactor);
activities.add(dataItem2);
addLabel(s, dataItem2);
markItem(dataItem2, Activity.DELIVERY);
containsDeliveryAct = true;
} else if (job instanceof Pickup) {
Pickup service = (Pickup) job;
Coordinate coord = getCoordinate(service.getLocation().getCoordinate());
XYDataItem dataItem = new XYDataItem(coord.getX() * scalingFactor, coord.getY() * scalingFactor);
activities.add(dataItem);
addLabel(service, dataItem);
markItem(dataItem, Activity.PICKUP);
containsPickupAct = true;
} else if (job instanceof Delivery) {
Delivery service = (Delivery) job;
Coordinate coord = getCoordinate(service.getLocation().getCoordinate());
XYDataItem dataItem = new XYDataItem(coord.getX() * scalingFactor, coord.getY() * scalingFactor);
activities.add(dataItem);
addLabel(service, dataItem);
markItem(dataItem, Activity.DELIVERY);
containsDeliveryAct = true;
} else if (job instanceof Service) {
Service service = (Service) job;
Coordinate coord = getCoordinate(service.getLocation().getCoordinate());
XYDataItem dataItem = new XYDataItem(coord.getX() * scalingFactor, coord.getY() * scalingFactor);
activities.add(dataItem);
addLabel(service, dataItem);
markItem(dataItem, Activity.SERVICE);
containsServiceAct = true;
} else {
throw new IllegalStateException("job instanceof " + job.getClass().toString() + ". this is not supported.");
}
}
private void addLabel(Job job, XYDataItem dataItem) {
if (this.label.equals(Label.SIZE)) {
labelsByDataItem.put(dataItem, getSizeString(job));
} else if (this.label.equals(Label.ID)) {
labelsByDataItem.put(dataItem, String.valueOf(job.getId()));
}
}
private String getSizeString(Job job) {
StringBuilder builder = new StringBuilder();
builder.append("(");
boolean firstDim = true;
for (int i = 0; i < job.getSize().getNuOfDimensions(); i++) {
if (firstDim) {
builder.append(String.valueOf(job.getSize().get(i)));
firstDim = false;
} else {
builder.append(",");
builder.append(String.valueOf(job.getSize().get(i)));
}
}
builder.append(")");
return builder.toString();
}
private Coordinate getCoordinate(Coordinate coordinate) {
if (invert) {
return Coordinate.newInstance(coordinate.getY(), coordinate.getX());
}
return coordinate;
}
private void retrieveActivities(VehicleRoutingProblem vrp) throws NoLocationFoundException {
activities = new XYSeries("activities", false, true);
for (Vehicle v : vrp.getVehicles()) {
Coordinate start_coordinate = getCoordinate(v.getStartLocation().getCoordinate());
if (start_coordinate == null) throw new NoLocationFoundException();
XYDataItem item = new XYDataItem(start_coordinate.getX() * scalingFactor, start_coordinate.getY() * scalingFactor);
markItem(item, Activity.START);
activities.add(item);
if (!v.getStartLocation().getId().equals(v.getEndLocation().getId())) {
Coordinate end_coordinate = getCoordinate(v.getEndLocation().getCoordinate());
if (end_coordinate == null) throw new NoLocationFoundException();
XYDataItem end_item = new XYDataItem(end_coordinate.getX() * scalingFactor, end_coordinate.getY() * scalingFactor);
markItem(end_item, Activity.END);
activities.add(end_item);
}
}
for (Job job : vrp.getJobs().values()) {
addJob(activities, job);
}
for (VehicleRoute r : vrp.getInitialVehicleRoutes()) {
for (Job job : r.getTourActivities().getJobs()) {
addJob(activities, job);
}
}
}
private void markItem(XYDataItem item, Activity activity) {
activitiesByDataItem.put(item, activity);
}
private Locations retrieveLocations(VehicleRoutingProblem vrp) throws NoLocationFoundException {
return vrp.getLocations();
}
}

View file

@ -0,0 +1,77 @@
/*******************************************************************************
* Copyright (C) 2013 Stefan Schroeder
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3.0 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
package com.graphhopper.jsprit.analysis.toolbox;
import com.graphhopper.jsprit.core.algorithm.VehicleRoutingAlgorithm;
import com.graphhopper.jsprit.core.algorithm.listener.AlgorithmEndsListener;
import com.graphhopper.jsprit.core.algorithm.listener.AlgorithmStartsListener;
import com.graphhopper.jsprit.core.problem.VehicleRoutingProblem;
import com.graphhopper.jsprit.core.problem.solution.VehicleRoutingProblemSolution;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.Collection;
public class StopWatch implements AlgorithmStartsListener, AlgorithmEndsListener {
private static Logger log = LogManager.getLogger(StopWatch.class);
private double ran;
private double startTime;
@Override
public void informAlgorithmStarts(VehicleRoutingProblem problem, VehicleRoutingAlgorithm algorithm, Collection<VehicleRoutingProblemSolution> solutions) {
reset();
start();
}
public double getCompTimeInSeconds() {
return (ran) / 1000.0;
}
@Override
public void informAlgorithmEnds(VehicleRoutingProblem problem, Collection<VehicleRoutingProblemSolution> solutions) {
stop();
log.info("computation time [in sec]: {}", getCompTimeInSeconds());
}
public void stop() {
ran += System.currentTimeMillis() - startTime;
}
public void start() {
startTime = System.currentTimeMillis();
}
public void reset() {
startTime = 0;
ran = 0;
}
@Override
public String toString() {
return "stopWatch: " + getCompTimeInSeconds() + " sec";
}
public double getCurrTimeInSeconds() {
return (System.currentTimeMillis() - startTime) / 1000.0;
}
}

View file

@ -0,0 +1,111 @@
/*******************************************************************************
* Copyright (c) 2014 Stefan Schroeder.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3.0 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Contributors:
* Stefan Schroeder - initial API and implementation
******************************************************************************/
package com.graphhopper.jsprit.analysis.toolbox;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartUtilities;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.XYPlot;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;
import java.awt.*;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author schroeder
*/
public class XYLineChartBuilder {
/**
* Helper that just saves the chart as specified png-file. The width of the image is 1000 and height 600.
*
* @param chart
* @param pngFilename
*/
public static void saveChartAsPNG(JFreeChart chart, String pngFilename) {
try {
ChartUtilities.saveChartAsPNG(new File(pngFilename), chart, 1000, 600);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Returns a new instance of the builder.
*
* @param chartTitle appears on top of the XYLineChart
* @param xDomainName appears below the xAxis
* @param yDomainName appears beside the yAxis
* @return the builder
*/
public static XYLineChartBuilder newInstance(String chartTitle, String xDomainName, String yDomainName) {
return new XYLineChartBuilder(chartTitle, xDomainName, yDomainName);
}
private ConcurrentHashMap<String, XYSeries> seriesMap = new ConcurrentHashMap<String, XYSeries>();
private final String xDomain;
private final String yDomain;
private final String chartName;
private XYLineChartBuilder(String chartName, String xDomainName, String yDomainName) {
this.xDomain = xDomainName;
this.yDomain = yDomainName;
this.chartName = chartName;
}
/**
* Adds data to the according series (i.e. XYLine).
*
* @param seriesName
* @param xVal
* @param yVal
*/
public void addData(String seriesName, double xVal, double yVal) {
if (!seriesMap.containsKey(seriesName)) {
seriesMap.put(seriesName, new XYSeries(seriesName, true, true));
}
seriesMap.get(seriesName).add(xVal, yVal);
}
/**
* Builds and returns JFreeChart.
*
* @return
*/
public JFreeChart build() {
XYSeriesCollection collection = new XYSeriesCollection();
for (XYSeries s : seriesMap.values()) {
collection.addSeries(s);
}
JFreeChart chart = ChartFactory.createXYLineChart(chartName, xDomain, yDomain, collection, PlotOrientation.VERTICAL, true, true, false);
XYPlot plot = chart.getXYPlot();
plot.setBackgroundPaint(Color.WHITE);
plot.setDomainGridlinePaint(Color.LIGHT_GRAY);
plot.setRangeGridlinePaint(Color.LIGHT_GRAY);
return chart;
}
}