diff --git a/jsprit-core/src/main/java/jsprit/core/algorithm/ruin/RuinWorst.java b/jsprit-core/src/main/java/jsprit/core/algorithm/ruin/RuinWorst.java
new file mode 100644
index 00000000..d32d55a5
--- /dev/null
+++ b/jsprit-core/src/main/java/jsprit/core/algorithm/ruin/RuinWorst.java
@@ -0,0 +1,165 @@
+/*******************************************************************************
+ * 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.ruin;
+
+import jsprit.core.problem.VehicleRoutingProblem;
+import jsprit.core.problem.driver.DriverImpl;
+import jsprit.core.problem.job.Job;
+import jsprit.core.problem.solution.route.VehicleRoute;
+import jsprit.core.problem.solution.route.activity.TourActivity;
+import jsprit.core.problem.vehicle.Vehicle;
+import jsprit.core.util.NoiseMaker;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.util.*;
+
+
+/**
+ * Ruin strategy that ruins current solution randomly. I.e.
+ * customer are removed randomly from current solution.
+ *
+ * @author stefan schroeder
+ *
+ */
+
+public final class RuinWorst extends AbstractRuinStrategy {
+
+ private Logger logger = LogManager.getLogger(RuinWorst.class);
+
+ private VehicleRoutingProblem vrp;
+
+ private NoiseMaker noiseMaker = new NoiseMaker(){
+
+ @Override
+ public double makeNoise() {
+ return 0;
+ }
+ };
+
+ public void setNoiseMaker(NoiseMaker noiseMaker) {
+ this.noiseMaker = noiseMaker;
+ }
+
+ public RuinWorst(VehicleRoutingProblem vrp, final int initialNumberJobsToRemove) {
+ super();
+ this.vrp = vrp;
+ setRuinShareFactory(new RuinShareFactory() {
+ @Override
+ public int createNumberToBeRemoved() {
+ return initialNumberJobsToRemove;
+ }
+ });
+ logger.info("initialise " + this);
+ logger.info("done");
+ }
+
+ /**
+ * Removes a fraction of jobs from vehicleRoutes.
+ *
+ *
The number of jobs is calculated as follows: Math.ceil(vrp.getJobs().values().size() * fractionOfAllNodes2beRuined).
+ */
+ @Override
+ public Collection ruinRoutes(Collection vehicleRoutes) {
+ List unassignedJobs = new ArrayList();
+ int nOfJobs2BeRemoved = getRuinShareFactory().createNumberToBeRemoved();
+ ruin(vehicleRoutes, nOfJobs2BeRemoved, unassignedJobs);
+ return unassignedJobs;
+ }
+
+ /**
+ * Removes nOfJobs2BeRemoved from vehicleRoutes, including targetJob.
+ */
+ @Override
+ public Collection ruinRoutes(Collection vehicleRoutes, Job targetJob, int nOfJobs2BeRemoved) {
+ throw new UnsupportedOperationException("ruinRoutes not supported");
+ }
+
+ private void ruin(Collection vehicleRoutes, int nOfJobs2BeRemoved, List unassignedJobs) {
+ LinkedList availableJobs = new LinkedList(vrp.getJobs().values());
+ int toRemove = nOfJobs2BeRemoved;
+ while(toRemove > 0){
+ Job worst = getWorst(vehicleRoutes);
+ if(worst == null) break;
+ removeJob(worst,vehicleRoutes);
+ availableJobs.remove(worst);
+ unassignedJobs.add(worst);
+ toRemove--;
+ }
+ }
+
+ private Job getWorst(Collection copied) {
+ Job worst = null;
+ double bestSavings = Double.MIN_VALUE;
+
+ for(VehicleRoute route : copied) {
+ if(route.isEmpty()) continue;
+ Map savingsMap = new HashMap();
+ TourActivity actBefore = route.getStart();
+ TourActivity actToEval = null;
+ for (TourActivity act : route.getActivities()) {
+ if (actToEval == null) {
+ actToEval = act;
+ continue;
+ }
+ double savings = savings(route, actBefore, actToEval, act);
+ Job job = ((TourActivity.JobActivity) actToEval).getJob();
+ if(!savingsMap.containsKey(job)){
+ savingsMap.put(job,savings);
+ }
+ else {
+ double s = savingsMap.get(job);
+ savingsMap.put(job,s+savings);
+ }
+ actBefore = actToEval;
+ actToEval = act;
+ }
+ double savings = savings(route, actBefore, actToEval, route.getEnd());
+ Job job = ((TourActivity.JobActivity) actToEval).getJob();
+ if(!savingsMap.containsKey(job)){
+ savingsMap.put(job,savings);
+ }
+ else {
+ double s = savingsMap.get(job);
+ savingsMap.put(job,s+savings);
+ }
+ //getCounts best
+ for(Job j : savingsMap.keySet()){
+ if(savingsMap.get(j) > bestSavings){
+ bestSavings = savingsMap.get(j);
+ worst = j;
+ }
+ }
+ }
+ return worst;
+ }
+
+ private double savings(VehicleRoute route, TourActivity actBefore, TourActivity actToEval, TourActivity act) {
+ double savings = c(actBefore, actToEval, route.getVehicle()) + c(actToEval, act, route.getVehicle()) - c(actBefore, act, route.getVehicle());
+ return Math.max(0,savings + noiseMaker.makeNoise());
+ }
+
+ private double c(TourActivity from, TourActivity to, Vehicle vehicle) {
+ return vrp.getTransportCosts().getTransportCost(from.getLocation(),to.getLocation(),from.getEndTime(), DriverImpl.noDriver(), vehicle);
+ }
+
+ @Override
+ public String toString() {
+ return "[name=worstRuin]";
+ }
+
+}
diff --git a/jsprit-core/src/main/java/jsprit/core/util/NoiseMaker.java b/jsprit-core/src/main/java/jsprit/core/util/NoiseMaker.java
new file mode 100644
index 00000000..ffcb1f7a
--- /dev/null
+++ b/jsprit-core/src/main/java/jsprit/core/util/NoiseMaker.java
@@ -0,0 +1,10 @@
+package jsprit.core.util;
+
+/**
+ * Created by schroeder on 16/01/15.
+ */
+public interface NoiseMaker {
+
+ public double makeNoise();
+
+}
diff --git a/jsprit-core/src/test/java/jsprit/core/algorithm/ruin/RuinWorstTest.java b/jsprit-core/src/test/java/jsprit/core/algorithm/ruin/RuinWorstTest.java
new file mode 100644
index 00000000..4e65494a
--- /dev/null
+++ b/jsprit-core/src/test/java/jsprit/core/algorithm/ruin/RuinWorstTest.java
@@ -0,0 +1,200 @@
+package jsprit.core.algorithm.ruin;
+
+import jsprit.core.problem.Location;
+import jsprit.core.problem.VehicleRoutingProblem;
+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.vehicle.VehicleImpl;
+import jsprit.core.util.Coordinate;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Created by schroeder on 30/01/15.
+ */
+public class RuinWorstTest {
+
+ @Test
+ public void itShouldRemoveCorrectNumber(){
+ Service s1 = Service.Builder.newInstance("s1")
+ .setLocation(Location.Builder.newInstance().setCoordinate(Coordinate.newInstance(1, 1)).build()).build();
+ Service s2 = Service.Builder.newInstance("s2")
+ .setLocation(Location.Builder.newInstance().setCoordinate(Coordinate.newInstance(3, 1)).build()).build();
+ Service s3 = Service.Builder.newInstance("s3")
+ .setLocation(Location.Builder.newInstance().setCoordinate(Coordinate.newInstance(10, 10)).build()).build();
+ VehicleImpl v = VehicleImpl.Builder.newInstance("v")
+ .setStartLocation(Location.Builder.newInstance().setCoordinate(Coordinate.newInstance(0, 0)).build()).build();
+ VehicleRoutingProblem vrp = VehicleRoutingProblem.Builder.newInstance().addJob(s1).addJob(s2).addJob(s3).addVehicle(v).build();
+ RuinWorst worst = new RuinWorst(vrp,1);
+
+ VehicleRoute route = VehicleRoute.Builder.newInstance(v).addService(s1).addService(s2).addService(s3).setJobActivityFactory(vrp.getJobActivityFactory()).build();
+ Collection unassigned = worst.ruinRoutes(Arrays.asList(route));
+ assertEquals(1,unassigned.size());
+
+ }
+
+ @Test
+ public void itShouldRemoveWorst(){
+ Service s1 = Service.Builder.newInstance("s1")
+ .setLocation(Location.Builder.newInstance().setCoordinate(Coordinate.newInstance(1, 1)).build()).build();
+ Service s2 = Service.Builder.newInstance("s2")
+ .setLocation(Location.Builder.newInstance().setCoordinate(Coordinate.newInstance(3, 1)).build()).build();
+ Service s3 = Service.Builder.newInstance("s3")
+ .setLocation(Location.Builder.newInstance().setCoordinate(Coordinate.newInstance(10, 10)).build()).build();
+ VehicleImpl v = VehicleImpl.Builder.newInstance("v")
+ .setStartLocation(Location.Builder.newInstance().setCoordinate(Coordinate.newInstance(0, 0)).build()).build();
+ VehicleRoutingProblem vrp = VehicleRoutingProblem.Builder.newInstance().addJob(s1).addJob(s2).addJob(s3).addVehicle(v).build();
+ RuinWorst worst = new RuinWorst(vrp,1);
+
+ VehicleRoute route = VehicleRoute.Builder.newInstance(v).addService(s1).addService(s2).addService(s3).setJobActivityFactory(vrp.getJobActivityFactory()).build();
+ Collection unassigned = worst.ruinRoutes(Arrays.asList(route));
+ assertEquals(s3,unassigned.iterator().next());
+
+ }
+
+ @Test
+ public void itShouldRemoveWorstTwo(){
+ Service s1 = Service.Builder.newInstance("s1")
+ .setLocation(Location.Builder.newInstance().setCoordinate(Coordinate.newInstance(1, 1)).build()).build();
+ Service s2 = Service.Builder.newInstance("s2")
+ .setLocation(Location.Builder.newInstance().setCoordinate(Coordinate.newInstance(3, 1)).build()).build();
+ Service s3 = Service.Builder.newInstance("s3")
+ .setLocation(Location.Builder.newInstance().setCoordinate(Coordinate.newInstance(10, 10)).build()).build();
+ VehicleImpl v = VehicleImpl.Builder.newInstance("v")
+ .setStartLocation(Location.Builder.newInstance().setCoordinate(Coordinate.newInstance(0, 0)).build()).build();
+ VehicleRoutingProblem vrp = VehicleRoutingProblem.Builder.newInstance().addJob(s1).addJob(s2).addJob(s3).addVehicle(v).build();
+ RuinWorst worst = new RuinWorst(vrp,1);
+ worst.setRuinShareFactory(new RuinShareFactory() {
+ @Override
+ public int createNumberToBeRemoved() {
+ return 2;
+ }
+ });
+
+ VehicleRoute route = VehicleRoute.Builder.newInstance(v).addService(s1).addService(s2).addService(s3).setJobActivityFactory(vrp.getJobActivityFactory()).build();
+ Collection unassigned = worst.ruinRoutes(Arrays.asList(route));
+
+ assertTrue(unassigned.size() == 2);
+ assertTrue(unassigned.contains(s2));
+ assertTrue(unassigned.contains(s3));
+
+ }
+
+ @Test
+ public void itShouldRemoveShipment(){
+ Service s1 = Service.Builder.newInstance("s1")
+ .setLocation(Location.Builder.newInstance().setCoordinate(Coordinate.newInstance(1, 1)).build()).build();
+ Service s2 = Service.Builder.newInstance("s2")
+ .setLocation(Location.Builder.newInstance().setCoordinate(Coordinate.newInstance(3, 1)).build()).build();
+ Service s3 = Service.Builder.newInstance("s3")
+ .setLocation(Location.Builder.newInstance().setCoordinate(Coordinate.newInstance(10, 10)).build()).build();
+ Shipment shipment = Shipment.Builder.newInstance("ship1")
+ .setPickupLocation(Location.Builder.newInstance().setCoordinate(Coordinate.newInstance(2, 2)).build())
+ .setDeliveryLocation(Location.Builder.newInstance().setCoordinate(Coordinate.newInstance(9, 9)).build()).build();
+ VehicleImpl v = VehicleImpl.Builder.newInstance("v")
+ .setStartLocation(Location.Builder.newInstance().setCoordinate(Coordinate.newInstance(0, 0)).build()).build();
+ VehicleRoutingProblem vrp = VehicleRoutingProblem.Builder.newInstance()
+ .addJob(shipment).addJob(s1).addJob(s2).addJob(s3).addVehicle(v).build();
+ RuinWorst worst = new RuinWorst(vrp,1);
+ worst.setRuinShareFactory(new RuinShareFactory() {
+ @Override
+ public int createNumberToBeRemoved() {
+ return 1;
+ }
+ });
+
+ VehicleRoute route = VehicleRoute.Builder.newInstance(v)
+ .addPickup(shipment).addService(s1).addService(s2).addService(s3).addDelivery(shipment)
+ .setJobActivityFactory(vrp.getJobActivityFactory()).build();
+ Collection unassigned = worst.ruinRoutes(Arrays.asList(route));
+
+ assertTrue(unassigned.size() == 1);
+ assertTrue(unassigned.contains(shipment));
+
+ }
+
+ @Test
+ public void itShouldRemoveShipmentFromSecondRoute(){
+ Service s1 = Service.Builder.newInstance("s1")
+ .setLocation(Location.Builder.newInstance().setCoordinate(Coordinate.newInstance(1, 1)).build()).build();
+ Service s2 = Service.Builder.newInstance("s2")
+ .setLocation(Location.Builder.newInstance().setCoordinate(Coordinate.newInstance(3, 1)).build()).build();
+ Service s3 = Service.Builder.newInstance("s3")
+ .setLocation(Location.Builder.newInstance().setCoordinate(Coordinate.newInstance(10, 10)).build()).build();
+ Shipment shipment = Shipment.Builder.newInstance("ship1")
+ .setPickupLocation(Location.Builder.newInstance().setCoordinate(Coordinate.newInstance(3, 1)).build())
+ .setDeliveryLocation(Location.Builder.newInstance().setCoordinate(Coordinate.newInstance(10, 10.1)).build()).build();
+ VehicleImpl v = VehicleImpl.Builder.newInstance("v")
+ .setStartLocation(Location.Builder.newInstance().setCoordinate(Coordinate.newInstance(0, 0)).build()).build();
+ VehicleImpl v2 = VehicleImpl.Builder.newInstance("v2")
+ .setStartLocation(Location.Builder.newInstance().setCoordinate(Coordinate.newInstance(0, 0)).build()).build();
+ VehicleRoutingProblem vrp = VehicleRoutingProblem.Builder.newInstance()
+ .addJob(shipment).addJob(s1).addJob(s2).addJob(s3).addVehicle(v).addVehicle(v2).build();
+ RuinWorst worst = new RuinWorst(vrp,1);
+ worst.setRuinShareFactory(new RuinShareFactory() {
+ @Override
+ public int createNumberToBeRemoved() {
+ return 1;
+ }
+ });
+
+ VehicleRoute route1 = VehicleRoute.Builder.newInstance(v)
+ .addService(s1).addService(s2).addService(s3)
+ .setJobActivityFactory(vrp.getJobActivityFactory()).build();
+ VehicleRoute route2 = VehicleRoute.Builder.newInstance(v2)
+ .addPickup(shipment).addDelivery(shipment).build();
+ Collection unassigned = worst.ruinRoutes(Arrays.asList(route1,route2));
+
+ assertTrue(unassigned.size() == 1);
+ assertTrue(unassigned.contains(shipment));
+
+ }
+
+ @Test
+ public void itShouldRemoveServiceAndShipmentFromSecondRoute(){
+ Service s1 = Service.Builder.newInstance("s1")
+ .setLocation(Location.Builder.newInstance().setCoordinate(Coordinate.newInstance(1, 1)).build()).build();
+ Service s2 = Service.Builder.newInstance("s2")
+ .setLocation(Location.Builder.newInstance().setCoordinate(Coordinate.newInstance(3, 1)).build()).build();
+ Service s3 = Service.Builder.newInstance("s3")
+ .setLocation(Location.Builder.newInstance().setCoordinate(Coordinate.newInstance(10, 10)).build()).build();
+ Shipment shipment = Shipment.Builder.newInstance("ship1")
+ .setPickupLocation(Location.Builder.newInstance().setCoordinate(Coordinate.newInstance(3, 1)).build())
+ .setDeliveryLocation(Location.Builder.newInstance().setCoordinate(Coordinate.newInstance(10, 10.1)).build()).build();
+ VehicleImpl v = VehicleImpl.Builder.newInstance("v")
+ .setStartLocation(Location.Builder.newInstance().setCoordinate(Coordinate.newInstance(0, 0)).build()).build();
+ VehicleImpl v2 = VehicleImpl.Builder.newInstance("v2")
+ .setStartLocation(Location.Builder.newInstance().setCoordinate(Coordinate.newInstance(0, 0)).build()).build();
+ VehicleRoutingProblem vrp = VehicleRoutingProblem.Builder.newInstance()
+ .addJob(shipment).addJob(s1).addJob(s2).addJob(s3).addVehicle(v).addVehicle(v2).build();
+ RuinWorst worst = new RuinWorst(vrp,1);
+ worst.setRuinShareFactory(new RuinShareFactory() {
+ @Override
+ public int createNumberToBeRemoved() {
+ return 2;
+ }
+ });
+
+ VehicleRoute route1 = VehicleRoute.Builder.newInstance(v)
+ .addService(s1).addService(s2).addService(s3)
+ .setJobActivityFactory(vrp.getJobActivityFactory()).build();
+ VehicleRoute route2 = VehicleRoute.Builder.newInstance(v2)
+ .addPickup(shipment).addDelivery(shipment).build();
+ Collection unassigned = worst.ruinRoutes(Arrays.asList(route1,route2));
+
+ assertTrue(unassigned.size() == 2);
+ assertTrue(unassigned.contains(shipment));
+ assertTrue(unassigned.contains(s3));
+
+ }
+
+
+
+}