From 4e838719848a61faaea5ddcc408513ca13a851de Mon Sep 17 00:00:00 2001 From: oblonski Date: Tue, 4 Aug 2015 20:36:48 +0200 Subject: [PATCH] add breaks --- .../recreate/BreakInsertionCalculator.java | 183 ++++++++++++++++++ .../algorithm/recreate/EventListeners.java | 1 + .../core/algorithm/recreate/InsertBreak.java | 42 ++++ .../recreate/InsertBreakListener.java | 31 +++ .../JobInsertionCostsCalculatorBuilder.java | 4 + .../algorithm/recreate/RegretInsertion.java | 12 +- .../core/algorithm/ruin/RuinBreaks.java | 31 +++ .../core/problem/VehicleRoutingProblem.java | 25 ++- .../java/jsprit/core/problem/job/Break.java | 80 ++++++++ .../route/activity/BreakActivity.java | 178 +++++++++++++++++ .../jsprit/core/problem/vehicle/Vehicle.java | 3 + .../core/problem/vehicle/VehicleImpl.java | 27 ++- .../core/algorithm/ruin/RuinBreakTest.java | 39 ++++ .../route/activity/BreakActivityTest.java | 106 ++++++++++ .../core/problem/vehicle/VehicleImplTest.java | 17 ++ .../java/jsprit/examples/BreakExample.java | 106 ++++++++++ 16 files changed, 871 insertions(+), 14 deletions(-) create mode 100644 jsprit-core/src/main/java/jsprit/core/algorithm/recreate/BreakInsertionCalculator.java create mode 100644 jsprit-core/src/main/java/jsprit/core/algorithm/recreate/InsertBreak.java create mode 100644 jsprit-core/src/main/java/jsprit/core/algorithm/recreate/InsertBreakListener.java create mode 100644 jsprit-core/src/main/java/jsprit/core/algorithm/ruin/RuinBreaks.java create mode 100644 jsprit-core/src/main/java/jsprit/core/problem/job/Break.java create mode 100644 jsprit-core/src/main/java/jsprit/core/problem/solution/route/activity/BreakActivity.java create mode 100644 jsprit-core/src/test/java/jsprit/core/algorithm/ruin/RuinBreakTest.java create mode 100644 jsprit-core/src/test/java/jsprit/core/problem/solution/route/activity/BreakActivityTest.java create mode 100644 jsprit-examples/src/main/java/jsprit/examples/BreakExample.java diff --git a/jsprit-core/src/main/java/jsprit/core/algorithm/recreate/BreakInsertionCalculator.java b/jsprit-core/src/main/java/jsprit/core/algorithm/recreate/BreakInsertionCalculator.java new file mode 100644 index 00000000..e511fa72 --- /dev/null +++ b/jsprit-core/src/main/java/jsprit/core/algorithm/recreate/BreakInsertionCalculator.java @@ -0,0 +1,183 @@ +/******************************************************************************* + * 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 . + ******************************************************************************/ +package jsprit.core.algorithm.recreate; + +import jsprit.core.problem.JobActivityFactory; +import jsprit.core.problem.Location; +import jsprit.core.problem.constraint.*; +import jsprit.core.problem.constraint.HardActivityConstraint.ConstraintsStatus; +import jsprit.core.problem.cost.VehicleRoutingTransportCosts; +import jsprit.core.problem.driver.Driver; +import jsprit.core.problem.job.Break; +import jsprit.core.problem.job.Job; +import jsprit.core.problem.misc.JobInsertionContext; +import jsprit.core.problem.solution.route.VehicleRoute; +import jsprit.core.problem.solution.route.activity.BreakActivity; +import jsprit.core.problem.solution.route.activity.End; +import jsprit.core.problem.solution.route.activity.Start; +import jsprit.core.problem.solution.route.activity.TourActivity; +import jsprit.core.problem.vehicle.Vehicle; +import jsprit.core.util.CalculationUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; + +/** + * Calculator that calculates the best insertion position for a {@link jsprit.core.problem.job.Service}. + * + * @author schroeder + * + */ +final class BreakInsertionCalculator implements JobInsertionCostsCalculator{ + + private static final Logger logger = LogManager.getLogger(BreakInsertionCalculator.class); + + private HardRouteConstraint hardRouteLevelConstraint; + + private HardActivityConstraint hardActivityLevelConstraint; + + private SoftRouteConstraint softRouteConstraint; + + private SoftActivityConstraint softActivityConstraint; + + private VehicleRoutingTransportCosts transportCosts; + + private ActivityInsertionCostsCalculator additionalTransportCostsCalculator; + + private JobActivityFactory activityFactory; + + private AdditionalAccessEgressCalculator additionalAccessEgressCalculator; + + public BreakInsertionCalculator(VehicleRoutingTransportCosts routingCosts, ActivityInsertionCostsCalculator additionalTransportCostsCalculator, ConstraintManager constraintManager) { + super(); + this.transportCosts = routingCosts; + hardRouteLevelConstraint = constraintManager; + hardActivityLevelConstraint = constraintManager; + softActivityConstraint = constraintManager; + softRouteConstraint = constraintManager; + this.additionalTransportCostsCalculator = additionalTransportCostsCalculator; + additionalAccessEgressCalculator = new AdditionalAccessEgressCalculator(routingCosts); + logger.debug("initialise " + this); + } + + public void setJobActivityFactory(JobActivityFactory jobActivityFactory){ + this.activityFactory = jobActivityFactory; + } + + @Override + public String toString() { + return "[name=calculatesServiceInsertion]"; + } + + /** + * Calculates the marginal cost of inserting job i locally. This is based on the + * assumption that cost changes can entirely covered by only looking at the predecessor i-1 and its successor i+1. + * + */ + @Override + public InsertionData getInsertionData(final VehicleRoute currentRoute, final Job jobToInsert, final Vehicle newVehicle, double newVehicleDepartureTime, final Driver newDriver, final double bestKnownCosts) { + Break breakToInsert = (Break) jobToInsert; + if(newVehicle.getBreak() == null || newVehicle.getBreak() != breakToInsert) return InsertionData.createEmptyInsertionData(); + + JobInsertionContext insertionContext = new JobInsertionContext(currentRoute, jobToInsert, newVehicle, newDriver, newVehicleDepartureTime); + int insertionIndex = InsertionData.NO_INDEX; + + BreakActivity breakAct2Insert = (BreakActivity) activityFactory.createActivities(breakToInsert).get(0); + insertionContext.getAssociatedActivities().add(breakAct2Insert); + + boolean differentVehicles = false; + if(!currentRoute.isEmpty()){ + differentVehicles = currentRoute.getVehicle() != newVehicle; + } + /* + check hard constraints at route level + */ + if(!hardRouteLevelConstraint.fulfilled(insertionContext)){ + return InsertionData.createEmptyInsertionData(); + } + + /* + check soft constraints at route level + */ + double additionalICostsAtRouteLevel = softRouteConstraint.getCosts(insertionContext); + + double bestCost = bestKnownCosts; + additionalICostsAtRouteLevel += additionalAccessEgressCalculator.getCosts(insertionContext); + + /* + generate new start and end for new vehicle + */ + Start start = new Start(newVehicle.getStartLocation(), newVehicle.getEarliestDeparture(), Double.MAX_VALUE); + start.setEndTime(newVehicleDepartureTime); + End end = new End(newVehicle.getEndLocation(), 0.0, newVehicle.getLatestArrival()); + + Location bestLocation = null; + + TourActivity prevAct = start; + double prevActStartTime = newVehicleDepartureTime; + int actIndex = 0; + Iterator activityIterator = currentRoute.getActivities().iterator(); + boolean tourEnd = false; + while(!tourEnd){ + TourActivity nextAct; + if(activityIterator.hasNext()) nextAct = activityIterator.next(); + else{ + nextAct = end; + tourEnd = true; + } + boolean breakThis = true; + List locations = Arrays.asList( prevAct.getLocation(), nextAct.getLocation()); + for(Location location : locations) { + breakAct2Insert.setLocation(location); + ConstraintsStatus status = hardActivityLevelConstraint.fulfilled(insertionContext, prevAct, breakAct2Insert, nextAct, prevActStartTime); + if (status.equals(ConstraintsStatus.FULFILLED)) { + //from job2insert induced costs at activity level + double additionalICostsAtActLevel = softActivityConstraint.getCosts(insertionContext, prevAct, breakAct2Insert, nextAct, prevActStartTime); + double additionalTransportationCosts = additionalTransportCostsCalculator.getCosts(insertionContext, prevAct, nextAct, breakAct2Insert, prevActStartTime); + if (additionalICostsAtRouteLevel + additionalICostsAtActLevel + additionalTransportationCosts < bestCost) { + bestCost = additionalICostsAtRouteLevel + additionalICostsAtActLevel + additionalTransportationCosts; + insertionIndex = actIndex; + bestLocation = location; + } + breakThis = false; + } else if (status.equals(ConstraintsStatus.NOT_FULFILLED)) { + breakThis = false; + } + double nextActArrTime = prevActStartTime + transportCosts.getTransportTime(prevAct.getLocation(), nextAct.getLocation(), prevActStartTime, newDriver, newVehicle); + prevActStartTime = CalculationUtils.getActivityEndTime(nextActArrTime, nextAct); + prevAct = nextAct; + actIndex++; + } + if(breakThis) break; + } + if(insertionIndex == InsertionData.NO_INDEX) { + return InsertionData.createEmptyInsertionData(); + } + InsertionData insertionData = new InsertionData(bestCost, InsertionData.NO_INDEX, insertionIndex, newVehicle, newDriver); + breakAct2Insert.setLocation(bestLocation); + insertionData.getEvents().add(new InsertBreak(currentRoute,newVehicle,breakAct2Insert,insertionIndex)); + insertionData.getEvents().add(new SwitchVehicle(currentRoute,newVehicle,newVehicleDepartureTime)); + insertionData.setVehicleDepartureTime(newVehicleDepartureTime); + return insertionData; + } + + + +} diff --git a/jsprit-core/src/main/java/jsprit/core/algorithm/recreate/EventListeners.java b/jsprit-core/src/main/java/jsprit/core/algorithm/recreate/EventListeners.java index 069bb1b3..9576b32c 100644 --- a/jsprit-core/src/main/java/jsprit/core/algorithm/recreate/EventListeners.java +++ b/jsprit-core/src/main/java/jsprit/core/algorithm/recreate/EventListeners.java @@ -13,6 +13,7 @@ class EventListeners { public EventListeners() { listeners.add(new InsertActivityListener()); listeners.add(new SwitchVehicleListener()); + listeners.add(new InsertBreakListener()); } public void inform(Event event){ diff --git a/jsprit-core/src/main/java/jsprit/core/algorithm/recreate/InsertBreak.java b/jsprit-core/src/main/java/jsprit/core/algorithm/recreate/InsertBreak.java new file mode 100644 index 00000000..8c5e33d9 --- /dev/null +++ b/jsprit-core/src/main/java/jsprit/core/algorithm/recreate/InsertBreak.java @@ -0,0 +1,42 @@ +package jsprit.core.algorithm.recreate; + +import jsprit.core.problem.solution.route.VehicleRoute; +import jsprit.core.problem.solution.route.activity.TourActivity; +import jsprit.core.problem.vehicle.Vehicle; + +/** + * Created by schroeder on 19/05/15. + */ +class InsertBreak implements Event { + + private VehicleRoute vehicleRoute; + + private Vehicle newVehicle; + + private TourActivity activity; + + private int index; + + public InsertBreak(VehicleRoute vehicleRoute, Vehicle newVehicle, TourActivity activity, int index) { + this.vehicleRoute = vehicleRoute; + this.newVehicle = newVehicle; + this.activity = activity; + this.index = index; + } + + public Vehicle getNewVehicle() { + return newVehicle; + } + + public VehicleRoute getVehicleRoute() { + return vehicleRoute; + } + + public TourActivity getActivity() { + return activity; + } + + public int getIndex() { + return index; + } +} diff --git a/jsprit-core/src/main/java/jsprit/core/algorithm/recreate/InsertBreakListener.java b/jsprit-core/src/main/java/jsprit/core/algorithm/recreate/InsertBreakListener.java new file mode 100644 index 00000000..bf8bbd4d --- /dev/null +++ b/jsprit-core/src/main/java/jsprit/core/algorithm/recreate/InsertBreakListener.java @@ -0,0 +1,31 @@ +package jsprit.core.algorithm.recreate; + +import jsprit.core.problem.solution.route.VehicleRoute; + +/** + * Created by schroeder on 19/05/15. + */ +class InsertBreakListener implements EventListener { + + @Override + public void inform(Event event) { + if(event instanceof InsertBreak){ + InsertBreak insertActivity = (InsertBreak) event; + if(!insertActivity.getNewVehicle().isReturnToDepot()){ + if(insertActivity.getIndex()>=insertActivity.getVehicleRoute().getActivities().size()){ + insertActivity.getVehicleRoute().getEnd().setLocation(insertActivity.getActivity().getLocation()); + } + } + VehicleRoute vehicleRoute = ((InsertBreak) event).getVehicleRoute(); + if(!vehicleRoute.isEmpty()){ + if(vehicleRoute.getVehicle() != ((InsertBreak) event).getNewVehicle()){ + if(vehicleRoute.getVehicle().getBreak() != null){ + vehicleRoute.getTourActivities().removeJob(vehicleRoute.getVehicle().getBreak()); + } + } + } + insertActivity.getVehicleRoute().getTourActivities().addActivity(insertActivity.getIndex(),((InsertBreak) event).getActivity()); + } + } + +} diff --git a/jsprit-core/src/main/java/jsprit/core/algorithm/recreate/JobInsertionCostsCalculatorBuilder.java b/jsprit-core/src/main/java/jsprit/core/algorithm/recreate/JobInsertionCostsCalculatorBuilder.java index 32ba952b..aa661a2a 100644 --- a/jsprit-core/src/main/java/jsprit/core/algorithm/recreate/JobInsertionCostsCalculatorBuilder.java +++ b/jsprit-core/src/main/java/jsprit/core/algorithm/recreate/JobInsertionCostsCalculatorBuilder.java @@ -290,11 +290,15 @@ public class JobInsertionCostsCalculatorBuilder { ServiceInsertionCalculator serviceInsertion = new ServiceInsertionCalculator(vrp.getTransportCosts(), actInsertionCalc, constraintManager); serviceInsertion.setJobActivityFactory(activityFactory); + BreakInsertionCalculator breakInsertionCalculator = new BreakInsertionCalculator(vrp.getTransportCosts(), actInsertionCalc, constraintManager); + breakInsertionCalculator.setJobActivityFactory(activityFactory); + JobCalculatorSwitcher switcher = new JobCalculatorSwitcher(); switcher.put(Shipment.class, shipmentInsertion); switcher.put(Service.class, serviceInsertion); switcher.put(Pickup.class, serviceInsertion); switcher.put(Delivery.class, serviceInsertion); + switcher.put(Break.class, breakInsertionCalculator); return new CalculatorPlusListeners(switcher); } diff --git a/jsprit-core/src/main/java/jsprit/core/algorithm/recreate/RegretInsertion.java b/jsprit-core/src/main/java/jsprit/core/algorithm/recreate/RegretInsertion.java index f6931f75..fe74acd9 100644 --- a/jsprit-core/src/main/java/jsprit/core/algorithm/recreate/RegretInsertion.java +++ b/jsprit-core/src/main/java/jsprit/core/algorithm/recreate/RegretInsertion.java @@ -160,10 +160,14 @@ public class RegretInsertion extends AbstractInsertionStrategy { } private double scoreService(InsertionData best, Job job) { - double maxDepotDistance = Math.max( - getDistance(best.getSelectedVehicle().getStartLocation(), ((Service) job).getLocation()), - getDistance(best.getSelectedVehicle().getEndLocation(), ((Service) job).getLocation()) - ); + Location location = ((Service) job).getLocation(); + double maxDepotDistance = 0; + if(location != null) { + maxDepotDistance = Math.max( + getDistance(best.getSelectedVehicle().getStartLocation(), location), + getDistance(best.getSelectedVehicle().getEndLocation(), location) + ); + } return Math.max(tw_param * (((Service)job).getTimeWindow().getEnd() - ((Service)job).getTimeWindow().getStart()),minTimeWindowScore) + depotDistance_param * maxDepotDistance; } diff --git a/jsprit-core/src/main/java/jsprit/core/algorithm/ruin/RuinBreaks.java b/jsprit-core/src/main/java/jsprit/core/algorithm/ruin/RuinBreaks.java new file mode 100644 index 00000000..f1f84fc2 --- /dev/null +++ b/jsprit-core/src/main/java/jsprit/core/algorithm/ruin/RuinBreaks.java @@ -0,0 +1,31 @@ +package jsprit.core.algorithm.ruin; + +import jsprit.core.algorithm.ruin.listener.RuinListener; +import jsprit.core.problem.job.Break; +import jsprit.core.problem.job.Job; +import jsprit.core.problem.solution.route.VehicleRoute; + +import java.util.Collection; + +/** + * Created by schroeder on 04/08/15. + */ +public class RuinBreaks implements RuinListener { + + @Override + public void ruinStarts(Collection routes) {} + + @Override + public void ruinEnds(Collection routes, Collection unassignedJobs) { + for(VehicleRoute r : routes){ + Break aBreak = r.getVehicle().getBreak(); + if(aBreak != null){ + r.getTourActivities().removeJob(aBreak); + unassignedJobs.add(aBreak); + } + } + } + + @Override + public void removed(Job job, VehicleRoute fromRoute) {} +} diff --git a/jsprit-core/src/main/java/jsprit/core/problem/VehicleRoutingProblem.java b/jsprit-core/src/main/java/jsprit/core/problem/VehicleRoutingProblem.java index 57a4c376..e4f63dae 100644 --- a/jsprit-core/src/main/java/jsprit/core/problem/VehicleRoutingProblem.java +++ b/jsprit-core/src/main/java/jsprit/core/problem/VehicleRoutingProblem.java @@ -23,6 +23,7 @@ import jsprit.core.problem.job.Job; import jsprit.core.problem.job.Service; import jsprit.core.problem.job.Shipment; import jsprit.core.problem.solution.route.VehicleRoute; +import jsprit.core.problem.solution.route.activity.BreakActivity; import jsprit.core.problem.solution.route.activity.DefaultShipmentActivityFactory; import jsprit.core.problem.solution.route.activity.DefaultTourActivityFactory; import jsprit.core.problem.solution.route.activity.TourActivity; @@ -117,7 +118,7 @@ public class VehicleRoutingProblem { acts.add(shipmentActivityFactory.createPickup((Shipment) job)); acts.add(shipmentActivityFactory.createDelivery((Shipment) job)); } - return acts; + return acts; } }; @@ -138,7 +139,7 @@ public class VehicleRoutingProblem { private final DefaultTourActivityFactory serviceActivityFactory = new DefaultTourActivityFactory(); - private void incJobIndexCounter(){ + private void incJobIndexCounter(){ jobIndexCounter++; } @@ -265,6 +266,16 @@ public class VehicleRoutingProblem { activityMap.put(job, jobActs); } + private void addBreaksToActivityMap(){ + for(Vehicle v : uniqueVehicles){ + if(v.getBreak() != null){ + AbstractActivity breakActivity = BreakActivity.newInstance(v.getBreak()); + breakActivity.setIndex(activityIndexCounter); + incActivityIndexCounter(); + activityMap.put(v.getBreak(),Arrays.asList(breakActivity)); + } + } + } /** * Adds an initial vehicle route. @@ -402,10 +413,12 @@ public class VehicleRoutingProblem { if(transportCosts == null){ transportCosts = new CrowFlyCosts(getLocations()); } - for(Job job : tentativeJobs.values()) - if (!jobsInInitialRoutes.contains(job.getId())) { - addJobToFinalJobMapAndCreateActivities(job); - } + for(Job job : tentativeJobs.values()) { + if (!jobsInInitialRoutes.contains(job.getId())) { + addJobToFinalJobMapAndCreateActivities(job); + } + } + addBreaksToActivityMap(); return new VehicleRoutingProblem(this); } diff --git a/jsprit-core/src/main/java/jsprit/core/problem/job/Break.java b/jsprit-core/src/main/java/jsprit/core/problem/job/Break.java new file mode 100644 index 00000000..4295eeaf --- /dev/null +++ b/jsprit-core/src/main/java/jsprit/core/problem/job/Break.java @@ -0,0 +1,80 @@ +/******************************************************************************* + * 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 . + ******************************************************************************/ +package jsprit.core.problem.job; + + +import jsprit.core.problem.Capacity; +import jsprit.core.problem.Skills; + +/** + * Pickup extends Service and is intended to model a Service where smth is LOADED (i.e. picked up) to a transport unit. + * + * @author schroeder + * + */ +public class Break extends Service { + + public static class Builder extends Service.Builder { + + /** + * Returns a new instance of builder that builds a pickup. + * + * @param id the id of the pickup + * @return the builder + */ + public static Builder newInstance(String id){ + return new Builder(id); + } + + private boolean variableLocation = true; + + Builder(String id) { + super(id); + } + + /** + * Builds Pickup. + * + *

Pickup type is "pickup" + * + * @return pickup + * @throws IllegalStateException if neither locationId nor coordinate has been set + */ + public Break build(){ + if(location != null){ + variableLocation = false; + } + this.setType("break"); + super.capacity = Capacity.Builder.newInstance().build(); + super.skills = Skills.Builder.newInstance().build(); + return new Break(this); + } + + } + + private boolean variableLocation = true; + + Break(Builder builder) { + super(builder); + this.variableLocation = builder.variableLocation; + } + + public boolean hasVariableLocation(){ + return variableLocation; + } + +} diff --git a/jsprit-core/src/main/java/jsprit/core/problem/solution/route/activity/BreakActivity.java b/jsprit-core/src/main/java/jsprit/core/problem/solution/route/activity/BreakActivity.java new file mode 100644 index 00000000..69cb8787 --- /dev/null +++ b/jsprit-core/src/main/java/jsprit/core/problem/solution/route/activity/BreakActivity.java @@ -0,0 +1,178 @@ +/******************************************************************************* + * 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 . + ******************************************************************************/ +package jsprit.core.problem.solution.route.activity; + +import jsprit.core.problem.AbstractActivity; +import jsprit.core.problem.Capacity; +import jsprit.core.problem.Location; +import jsprit.core.problem.job.Break; +import jsprit.core.problem.job.Service; +import jsprit.core.problem.solution.route.activity.TourActivity.JobActivity; + +public class BreakActivity extends AbstractActivity implements JobActivity{ + + public static int counter = 0; + + public double arrTime; + + public double endTime; + + private Location location; + + /** + * @return the arrTime + */ + public double getArrTime() { + return arrTime; + } + + /** + * @param arrTime the arrTime to set + */ + public void setArrTime(double arrTime) { + this.arrTime = arrTime; + } + + /** + * @return the endTime + */ + public double getEndTime() { + return endTime; + } + + /** + * @param endTime the endTime to set + */ + public void setEndTime(double endTime) { + this.endTime = endTime; + } + + public static BreakActivity copyOf(BreakActivity breakActivity){ + return new BreakActivity(breakActivity); + } + + public static BreakActivity newInstance(Break aBreak){ + return new BreakActivity(aBreak); + } + + private final Break aBreak; + + protected BreakActivity(Break aBreak) { + counter++; + this.aBreak = aBreak; + } + + protected BreakActivity(BreakActivity breakActivity) { + counter++; + this.aBreak = (Break) breakActivity.getJob(); + this.arrTime = breakActivity.getArrTime(); + this.endTime = breakActivity.getEndTime(); + this.location = breakActivity.getLocation(); + setIndex(breakActivity.getIndex()); + } + + + /* (non-Javadoc) + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((aBreak == null) ? 0 : aBreak.hashCode()); + return result; + } + + /* (non-Javadoc) + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + BreakActivity other = (BreakActivity) obj; + if (aBreak == null) { + if (other.aBreak != null) + return false; + } else if (!aBreak.equals(other.aBreak)) + return false; + return true; + } + + public double getTheoreticalEarliestOperationStartTime() { + return aBreak.getTimeWindow().getStart(); + } + + public double getTheoreticalLatestOperationStartTime() { + return aBreak.getTimeWindow().getEnd(); + } + + @Override + public double getOperationTime() { + return aBreak.getServiceDuration(); + } + + @Override + public String getLocationId() { + return aBreak.getLocation().getId(); + } + + @Override + public Location getLocation() { + return location; + } + + public void setLocation(Location breakLocation){ + this.location = breakLocation; + } + + @Override + public Service getJob() { + return aBreak; + } + + + @Override + public String toString() { + return "[type="+getName()+"][location=" + getLocation() + + "][size=" + getSize().toString() + + "][twStart=" + Activities.round(getTheoreticalEarliestOperationStartTime()) + + "][twEnd=" + Activities.round(getTheoreticalLatestOperationStartTime()) + "]"; + } + + @Override + public String getName() { + return aBreak.getType(); + } + + @Override + public TourActivity duplicate() { + return new BreakActivity(this); + } + + @Override + public Capacity getSize() { + return aBreak.getSize(); + } + + + +} diff --git a/jsprit-core/src/main/java/jsprit/core/problem/vehicle/Vehicle.java b/jsprit-core/src/main/java/jsprit/core/problem/vehicle/Vehicle.java index b29391d1..96d48c65 100644 --- a/jsprit-core/src/main/java/jsprit/core/problem/vehicle/Vehicle.java +++ b/jsprit-core/src/main/java/jsprit/core/problem/vehicle/Vehicle.java @@ -20,6 +20,7 @@ import jsprit.core.problem.HasId; import jsprit.core.problem.HasIndex; import jsprit.core.problem.Location; import jsprit.core.problem.Skills; +import jsprit.core.problem.job.Break; /** * Basic interface for vehicle-data. @@ -72,4 +73,6 @@ public interface Vehicle extends HasId, HasIndex { public abstract VehicleTypeKey getVehicleTypeIdentifier(); public abstract Skills getSkills(); + + public abstract Break getBreak(); } diff --git a/jsprit-core/src/main/java/jsprit/core/problem/vehicle/VehicleImpl.java b/jsprit-core/src/main/java/jsprit/core/problem/vehicle/VehicleImpl.java index bfcf00a1..bd78254d 100644 --- a/jsprit-core/src/main/java/jsprit/core/problem/vehicle/VehicleImpl.java +++ b/jsprit-core/src/main/java/jsprit/core/problem/vehicle/VehicleImpl.java @@ -19,6 +19,7 @@ package jsprit.core.problem.vehicle; import jsprit.core.problem.AbstractVehicle; import jsprit.core.problem.Location; import jsprit.core.problem.Skills; +import jsprit.core.problem.job.Break; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -91,7 +92,10 @@ public class VehicleImpl extends AbstractVehicle{ public Skills getSkills() { return null; } - } + + @Override + public Break getBreak() { return null; } + } /** * Builder that builds the vehicle. @@ -125,7 +129,9 @@ public class VehicleImpl extends AbstractVehicle{ private Location endLocation; - private Builder(String id) { + private Break aBreak; + + private Builder(String id) { super(); this.id = id; } @@ -245,7 +251,12 @@ public class VehicleImpl extends AbstractVehicle{ this.skillBuilder.addAllSkills(skills.values()); return this; } - } + + public Builder setBreak(Break aBreak) { + this.aBreak = aBreak; + return this; + } + } /** * Returns empty/noVehicle which is a vehicle having no capacity, no type and no reasonable id. @@ -274,6 +285,8 @@ public class VehicleImpl extends AbstractVehicle{ private final Location startLocation; + private final Break aBreak; + private VehicleImpl(Builder builder){ id = builder.id; type = builder.type; @@ -283,6 +296,7 @@ public class VehicleImpl extends AbstractVehicle{ skills = builder.skills; endLocation = builder.endLocation; startLocation = builder.startLocation; + aBreak = builder.aBreak; setVehicleIdentifier(new VehicleTypeKey(type.getTypeId(),startLocation.getId(),endLocation.getId(),earliestDeparture,latestArrival,skills)); } @@ -341,7 +355,12 @@ public class VehicleImpl extends AbstractVehicle{ return skills; } - /* (non-Javadoc) + @Override + public Break getBreak() { + return aBreak; + } + + /* (non-Javadoc) * @see java.lang.Object#hashCode() */ @Override diff --git a/jsprit-core/src/test/java/jsprit/core/algorithm/ruin/RuinBreakTest.java b/jsprit-core/src/test/java/jsprit/core/algorithm/ruin/RuinBreakTest.java new file mode 100644 index 00000000..3c554ecc --- /dev/null +++ b/jsprit-core/src/test/java/jsprit/core/algorithm/ruin/RuinBreakTest.java @@ -0,0 +1,39 @@ +package jsprit.core.algorithm.ruin; + +import jsprit.core.problem.Location; +import jsprit.core.problem.VehicleRoutingProblem; +import jsprit.core.problem.job.Break; +import jsprit.core.problem.job.Job; +import jsprit.core.problem.solution.route.VehicleRoute; +import jsprit.core.problem.solution.route.activity.BreakActivity; +import jsprit.core.problem.solution.route.activity.TourActivity; +import jsprit.core.problem.vehicle.VehicleImpl; +import junit.framework.Assert; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Created by schroeder on 04/08/15. + */ +public class RuinBreakTest { + + @Test + public void test(){ + Break aBreak = Break.Builder.newInstance("break").build(); + VehicleImpl v = VehicleImpl.Builder.newInstance("v").setStartLocation(Location.newInstance("loc")) + .setBreak(aBreak).build(); + VehicleRoutingProblem vrp = VehicleRoutingProblem.Builder.newInstance().addVehicle(v).build(); + VehicleRoute route = VehicleRoute.Builder.newInstance(v).setJobActivityFactory(vrp.getJobActivityFactory()).addService(aBreak).build(); + TourActivity tourActivity = route.getActivities().get(0); + System.out.println(tourActivity); + Assert.assertTrue(tourActivity instanceof BreakActivity); + RuinBreaks ruinBreaks = new RuinBreaks(); + List unassigned = new ArrayList(); + ruinBreaks.ruinEnds(Arrays.asList(route),unassigned); + Assert.assertEquals(1,unassigned.size()); + Assert.assertEquals(aBreak,unassigned.get(0)); + } +} diff --git a/jsprit-core/src/test/java/jsprit/core/problem/solution/route/activity/BreakActivityTest.java b/jsprit-core/src/test/java/jsprit/core/problem/solution/route/activity/BreakActivityTest.java new file mode 100644 index 00000000..c8bbed95 --- /dev/null +++ b/jsprit-core/src/test/java/jsprit/core/problem/solution/route/activity/BreakActivityTest.java @@ -0,0 +1,106 @@ +/******************************************************************************* + * 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 . + ******************************************************************************/ +package jsprit.core.problem.solution.route.activity; + +import jsprit.core.problem.Location; +import jsprit.core.problem.job.Break; +import jsprit.core.problem.job.Service; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.*; + + +public class BreakActivityTest { + + private Break service; + + private BreakActivity serviceActivity; + + @Before + public void doBefore(){ + service = (Break) Break.Builder.newInstance("service") + .setTimeWindow(TimeWindow.newInstance(1., 2.)).setServiceTime(3).build(); + serviceActivity = BreakActivity.newInstance(service); + } + + @Test + public void whenCallingCapacity_itShouldReturnCorrectCapacity(){ + assertEquals(0,serviceActivity.getSize().get(0)); + } + + @Test + public void hasVariableLocationShouldBeTrue(){ + Break aBreak = (Break) serviceActivity.getJob(); + assertTrue(aBreak.hasVariableLocation()); + } + + + @Test + public void whenStartIsIniWithEarliestStart_itShouldBeSetCorrectly(){ + assertEquals(1.,serviceActivity.getTheoreticalEarliestOperationStartTime(),0.01); + } + + @Test + public void whenStartIsIniWithLatestStart_itShouldBeSetCorrectly(){ + assertEquals(2.,serviceActivity.getTheoreticalLatestOperationStartTime(),0.01); + } + + @Test + public void whenSettingArrTime_itShouldBeSetCorrectly(){ + serviceActivity.setArrTime(4.0); + assertEquals(4.,serviceActivity.getArrTime(),0.01); + } + + @Test + public void whenSettingEndTime_itShouldBeSetCorrectly(){ + serviceActivity.setEndTime(5.0); + assertEquals(5.,serviceActivity.getEndTime(),0.01); + } + + + @Test + public void whenCopyingStart_itShouldBeDoneCorrectly(){ + BreakActivity copy = (BreakActivity) serviceActivity.duplicate(); + assertEquals(1.,copy.getTheoreticalEarliestOperationStartTime(),0.01); + assertEquals(2.,copy.getTheoreticalLatestOperationStartTime(),0.01); + assertTrue(copy!=serviceActivity); + } + + + @Test + public void whenTwoDeliveriesHaveTheSameUnderlyingJob_theyAreEqual(){ + Service s1 = Service.Builder.newInstance("s").setLocation(Location.newInstance("loc")).build(); + Service s2 = Service.Builder.newInstance("s").setLocation(Location.newInstance("loc")).build(); + + ServiceActivity d1 = ServiceActivity.newInstance(s1); + ServiceActivity d2 = ServiceActivity.newInstance(s2); + + assertTrue(d1.equals(d2)); + } + + @Test + public void whenTwoDeliveriesHaveTheDifferentUnderlyingJob_theyAreNotEqual(){ + Service s1 = Service.Builder.newInstance("s").setLocation(Location.newInstance("loc")).build(); + Service s2 = Service.Builder.newInstance("s1").setLocation(Location.newInstance("loc")).build(); + + ServiceActivity d1 = ServiceActivity.newInstance(s1); + ServiceActivity d2 = ServiceActivity.newInstance(s2); + + assertFalse(d1.equals(d2)); + } +} diff --git a/jsprit-core/src/test/java/jsprit/core/problem/vehicle/VehicleImplTest.java b/jsprit-core/src/test/java/jsprit/core/problem/vehicle/VehicleImplTest.java index 20a2c349..e3e1500b 100644 --- a/jsprit-core/src/test/java/jsprit/core/problem/vehicle/VehicleImplTest.java +++ b/jsprit-core/src/test/java/jsprit/core/problem/vehicle/VehicleImplTest.java @@ -20,6 +20,8 @@ package jsprit.core.problem.vehicle; import jsprit.core.problem.Location; +import jsprit.core.problem.job.Break; +import jsprit.core.problem.solution.route.activity.TimeWindow; import org.junit.Test; import static org.junit.Assert.*; @@ -220,6 +222,21 @@ public class VehicleImplTest { .addSkill("drill").build(); assertFalse(v.getSkills().containsSkill("ScrewDriver")); } + + @Test + public void whenAddingDriverBreak_itShouldBeAddedCorrectly(){ + VehicleTypeImpl type1 = VehicleTypeImpl.Builder.newInstance("type").build(); + Break aBreak = (Break) Break.Builder.newInstance("break").setTimeWindow(TimeWindow.newInstance(100, 200)).setServiceTime(30).build(); + Vehicle v = VehicleImpl.Builder.newInstance("v").setStartLocation(Location.newInstance("start")) + .setType(type1).setEndLocation(Location.newInstance("start")) + .setBreak(aBreak).build(); + assertNotNull(v.getBreak()); + assertEquals(100.,v.getBreak().getTimeWindow().getStart(),0.1); + assertEquals(200.,v.getBreak().getTimeWindow().getEnd(),0.1); + assertEquals(30.,v.getBreak().getServiceDuration(),0.1); + } + + } diff --git a/jsprit-examples/src/main/java/jsprit/examples/BreakExample.java b/jsprit-examples/src/main/java/jsprit/examples/BreakExample.java new file mode 100644 index 00000000..ab5bc9d7 --- /dev/null +++ b/jsprit-examples/src/main/java/jsprit/examples/BreakExample.java @@ -0,0 +1,106 @@ +/******************************************************************************* + * 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 . + ******************************************************************************/ +package jsprit.examples; + +import jsprit.analysis.toolbox.Plotter; +import jsprit.core.algorithm.VehicleRoutingAlgorithm; +import jsprit.core.algorithm.box.Jsprit; +import jsprit.core.algorithm.ruin.RuinBreaks; +import jsprit.core.problem.Location; +import jsprit.core.problem.VehicleRoutingProblem; +import jsprit.core.problem.job.Break; +import jsprit.core.problem.job.Service; +import jsprit.core.problem.solution.VehicleRoutingProblemSolution; +import jsprit.core.problem.solution.route.activity.TimeWindow; +import jsprit.core.problem.vehicle.VehicleImpl; +import jsprit.core.problem.vehicle.VehicleImpl.Builder; +import jsprit.core.problem.vehicle.VehicleType; +import jsprit.core.problem.vehicle.VehicleTypeImpl; +import jsprit.core.reporting.SolutionPrinter; +import jsprit.core.util.Solutions; + +import java.util.Collection; + + +public class BreakExample { + + + + public static void main(String[] args) { + + + /* + * get a vehicle type-builder and build a type with the typeId "vehicleType" and one capacity dimension, i.e. weight, and capacity dimension value of 2 + */ + final int WEIGHT_INDEX = 0; + VehicleTypeImpl.Builder vehicleTypeBuilder = VehicleTypeImpl.Builder.newInstance("vehicleType").addCapacityDimension(WEIGHT_INDEX, 4); + VehicleType vehicleType = vehicleTypeBuilder.build(); + + /* + * get a vehicle-builder and build a vehicle located at (10,10) with type "vehicleType" + */ + Builder vehicleBuilder = Builder.newInstance("vehicle"); + vehicleBuilder.setStartLocation(Location.newInstance(10, 10)); + Break myFirstBreak = (Break) Break.Builder.newInstance("myFirstBreak").setTimeWindow(TimeWindow.newInstance(30, 50)).setServiceTime(10).build(); + vehicleBuilder.setBreak(myFirstBreak); + vehicleBuilder.setType(vehicleType); + VehicleImpl vehicle = vehicleBuilder.build(); + + /* + * build services at the required locations, each with a capacity-demand of 1. + */ + Service service1 = Service.Builder.newInstance("1").addSizeDimension(WEIGHT_INDEX, 1).setLocation(Location.newInstance(5, 7)).build(); + Service service2 = Service.Builder.newInstance("2").addSizeDimension(WEIGHT_INDEX, 1).setLocation(Location.newInstance(5, 13)).build(); + + Service service3 = Service.Builder.newInstance("3").addSizeDimension(WEIGHT_INDEX, 1).setLocation(Location.newInstance(15, 7)).build(); + Service service4 = Service.Builder.newInstance("4").addSizeDimension(WEIGHT_INDEX, 1).setLocation(Location.newInstance(15, 13)).build(); + + + VehicleRoutingProblem.Builder vrpBuilder = VehicleRoutingProblem.Builder.newInstance(); + vrpBuilder.addVehicle(vehicle); + vrpBuilder.addJob(service1).addJob(service2).addJob(service3).addJob(service4); + vrpBuilder.setFleetSize(VehicleRoutingProblem.FleetSize.FINITE); + VehicleRoutingProblem problem = vrpBuilder.build(); + + /* + * get the algorithm out-of-the-box. + */ + VehicleRoutingAlgorithm algorithm = Jsprit.Builder.newInstance(problem) + .setProperty(Jsprit.Strategy.CLUSTER_REGRET,"0.") + .setProperty(Jsprit.Strategy.CLUSTER_BEST,"0.").buildAlgorithm(); + algorithm.addListener(new RuinBreaks()); + /* + * and search a solution + */ + Collection solutions = algorithm.searchSolutions(); + + /* + * get the best + */ + VehicleRoutingProblemSolution bestSolution = Solutions.bestOf(solutions); + + SolutionPrinter.print(problem, bestSolution, SolutionPrinter.Print.VERBOSE); + + /* + * plot + */ + new Plotter(problem,bestSolution).plot("output/plot","breaks"); + + + } + +}