From 8f4f4b6c48e45a7ff3d2c07e590d140580709c25 Mon Sep 17 00:00:00 2001 From: oblonski <4sschroeder@gmail.com> Date: Mon, 18 Nov 2013 22:33:10 +0100 Subject: [PATCH] make RuinRadial more memory efficient and make NeighborhoodCreation independent from RuinRadial --- .../src/main/java/algorithms/RuinRadial.java | 267 ++++++++++++++---- .../algorithms/JobNeighborhoodsImplTest.java | 110 ++++++++ ...ighborhoodsWithCapRestrictionImplTest.java | 111 ++++++++ 3 files changed, 435 insertions(+), 53 deletions(-) create mode 100644 jsprit-core/src/test/java/algorithms/JobNeighborhoodsImplTest.java create mode 100644 jsprit-core/src/test/java/algorithms/JobNeighborhoodsWithCapRestrictionImplTest.java diff --git a/jsprit-core/src/main/java/algorithms/RuinRadial.java b/jsprit-core/src/main/java/algorithms/RuinRadial.java index 7b9c5edf..6f198537 100644 --- a/jsprit-core/src/main/java/algorithms/RuinRadial.java +++ b/jsprit-core/src/main/java/algorithms/RuinRadial.java @@ -47,6 +47,194 @@ import basics.route.VehicleRoute; */ final class RuinRadial implements RuinStrategy { + static interface JobNeighborhoods { + + public Iterator getNearestNeighborsIterator(int nNeighbors, Job neighborTo); + + } + + static class NeighborhoodIterator implements Iterator{ + + private static Logger log = Logger.getLogger(NeighborhoodIterator.class); + + private Iterator jobIter; + + private int nJobs; + + private int jobCount = 0; + + public NeighborhoodIterator(Iterator jobIter, int nJobs) { + super(); + this.jobIter = jobIter; + this.nJobs = nJobs; + } + + @Override + public boolean hasNext() { + if(jobCount < nJobs){ + boolean hasNext = jobIter.hasNext(); + if(!hasNext) log.warn("more jobs are requested then iterator can iterate over. probably the number of neighbors memorized in JobNeighborhoods is too small"); + return hasNext; + } + return false; + } + + @Override + public Job next() { + ReferencedJob next = jobIter.next(); + jobCount++; + return next.getJob(); + } + + @Override + public void remove() { + jobIter.remove(); + } + + } + + static class JobNeighborhoodsImpl implements JobNeighborhoods { + + private static Logger logger = Logger.getLogger(JobNeighborhoodsImpl.class); + + private VehicleRoutingProblem vrp; + + private Map> distanceNodeTree = new HashMap>(); + + private JobDistance jobDistance; + + public JobNeighborhoodsImpl(VehicleRoutingProblem vrp, JobDistance jobDistance) { + super(); + this.vrp = vrp; + this.jobDistance = jobDistance; + logger.info("intialise " + this); + } + + public Iterator getNearestNeighborsIterator(int nNeighbors, Job neighborTo){ + TreeSet tree = distanceNodeTree.get(neighborTo.getId()); + Iterator descendingIterator = tree.iterator(); + return new NeighborhoodIterator(descendingIterator, nNeighbors); + } + + public void initialise(){ + logger.info("calculates and memorizes distances from EACH job to EACH job --> n^2 calculations"); + calculateDistancesFromJob2Job(); + } + + private void calculateDistancesFromJob2Job() { + logger.info("preprocess distances between locations ..."); + StopWatch stopWatch = new StopWatch(); + stopWatch.start(); + int nuOfDistancesStored = 0; + for (Job i : vrp.getJobs().values()) { + TreeSet treeSet = new TreeSet( + new Comparator() { + @Override + public int compare(ReferencedJob o1, ReferencedJob o2) { + if (o1.getDistance() <= o2.getDistance()) { + return -1; + } else { + return 1; + } + } + }); + distanceNodeTree.put(i.getId(), treeSet); + for (Job j : vrp.getJobs().values()) { + if(i==j) continue; + double distance = jobDistance.calculateDistance(i, j); + ReferencedJob refNode = new ReferencedJob(j, distance); + treeSet.add(refNode); + nuOfDistancesStored++; + } + + } + stopWatch.stop(); + logger.info("preprocessing comp-time: " + stopWatch + "; nuOfDistances stored: " + nuOfDistancesStored + "; estimated memory: " + + (distanceNodeTree.keySet().size()*64+nuOfDistancesStored*92) + " bytes"); + } + + } + + static class JobNeighborhoodsImplWithCapRestriction implements JobNeighborhoods { + + private static Logger logger = Logger.getLogger(JobNeighborhoodsImpl.class); + + private VehicleRoutingProblem vrp; + + private Map> distanceNodeTree = new HashMap>(); + + private JobDistance jobDistance; + + private int capacity; + + public JobNeighborhoodsImplWithCapRestriction(VehicleRoutingProblem vrp, JobDistance jobDistance, int capacity) { + super(); + this.vrp = vrp; + this.jobDistance = jobDistance; + this.capacity = capacity; + logger.info("intialise " + this); + } + + public Iterator getNearestNeighborsIterator(int nNeighbors, Job neighborTo){ + TreeSet tree = distanceNodeTree.get(neighborTo.getId()); + Iterator descendingIterator = tree.iterator(); + return new NeighborhoodIterator(descendingIterator, nNeighbors); + } + + public void initialise(){ + logger.info("calculates distances from EACH job to EACH job --> n^2="+Math.pow(vrp.getJobs().values().size(), 2) + " calculations, but 'only' "+(vrp.getJobs().values().size()*capacity)+ " are cached."); + calculateDistancesFromJob2Job(); + } + + private void calculateDistancesFromJob2Job() { + logger.info("preprocess distances between locations ..."); + StopWatch stopWatch = new StopWatch(); + stopWatch.start(); + int nuOfDistancesStored = 0; + for (Job i : vrp.getJobs().values()) { + TreeSet treeSet = new TreeSet( + new Comparator() { + @Override + public int compare(ReferencedJob o1, ReferencedJob o2) { + if (o1.getDistance() <= o2.getDistance()) { + return -1; + } else { + return 1; + } + } + }); + distanceNodeTree.put(i.getId(), treeSet); + for (Job j : vrp.getJobs().values()) { + if(i==j) continue; + double distance = jobDistance.calculateDistance(i, j); + ReferencedJob refNode = new ReferencedJob(j, distance); + if(treeSet.size() < capacity){ + treeSet.add(refNode); + nuOfDistancesStored++; + } + else{ + if(treeSet.last().getDistance() > distance){ + treeSet.pollLast(); + treeSet.add(refNode); + } + } + } + assert treeSet.size() <= capacity : "treeSet.size() is bigger than specified capacity"; + + } + stopWatch.stop(); + logger.info("preprocessing comp-time: " + stopWatch + "; nuOfDistances stored: " + nuOfDistancesStored + "; estimated memory: " + + (distanceNodeTree.keySet().size()*64+nuOfDistancesStored*92) + " bytes"); + } + + @Override + public String toString() { + return "[name=neighborhoodWithCapRestriction][capacity="+capacity+"]"; + } + + } + + static class ReferencedJob { private Job job; private double distance; @@ -72,14 +260,12 @@ final class RuinRadial implements RuinStrategy { private double fractionOfAllNodes2beRuined; - private Map> distanceNodeTree = new HashMap>(); - private Random random = RandomNumberGeneration.getRandom(); - private JobDistance jobDistance; - private RuinListeners ruinListeners; + private JobNeighborhoods jobNeighborhoods; + public void setRandom(Random random) { this.random = random; } @@ -94,42 +280,14 @@ final class RuinRadial implements RuinStrategy { public RuinRadial(VehicleRoutingProblem vrp, double fraction2beRemoved, JobDistance jobDistance) { super(); this.vrp = vrp; - this.jobDistance = jobDistance; this.fractionOfAllNodes2beRuined = fraction2beRemoved; ruinListeners = new RuinListeners(); - calculateDistancesFromJob2Job(); + int nJobsToMemorize = (int) Math.ceil(vrp.getJobs().values().size()*fraction2beRemoved); + JobNeighborhoodsImplWithCapRestriction jobNeighborhoodsImpl = new JobNeighborhoodsImplWithCapRestriction(vrp, jobDistance, nJobsToMemorize); + jobNeighborhoodsImpl.initialise(); + jobNeighborhoods = jobNeighborhoodsImpl; logger.info("intialise " + this); } - - private void calculateDistancesFromJob2Job() { - logger.info("preprocess distances between locations ..."); - StopWatch stopWatch = new StopWatch(); - stopWatch.start(); - int nuOfDistancesStored = 0; - for (Job i : vrp.getJobs().values()) { - TreeSet treeSet = new TreeSet( - new Comparator() { - @Override - public int compare(ReferencedJob o1, ReferencedJob o2) { - if (o1.getDistance() <= o2.getDistance()) { - return 1; - } else { - return -1; - } - } - }); - distanceNodeTree.put(i.getId(), treeSet); - for (Job j : vrp.getJobs().values()) { - double distance = jobDistance.calculateDistance(i, j); - ReferencedJob refNode = new ReferencedJob(j, distance); - treeSet.add(refNode); - nuOfDistancesStored++; - } - } - stopWatch.stop(); - logger.info("preprocessing comp-time: " + stopWatch + "; nuOfDistances stored: " + nuOfDistancesStored + "; estimated memory: " + - (distanceNodeTree.keySet().size()*64+nuOfDistancesStored*92) + " bytes"); - } @Override public String toString() { @@ -143,11 +301,11 @@ final class RuinRadial implements RuinStrategy { @Override public Collection ruin(Collection vehicleRoutes) { if(vehicleRoutes.isEmpty()){ - return Collections.EMPTY_LIST; + return Collections.emptyList(); } int nOfJobs2BeRemoved = getNuOfJobs2BeRemoved(); if (nOfJobs2BeRemoved == 0) { - return Collections.EMPTY_LIST; + return Collections.emptyList(); } Job randomJob = pickRandomJob(); Collection unassignedJobs = ruin(vehicleRoutes,randomJob,nOfJobs2BeRemoved); @@ -160,27 +318,30 @@ final class RuinRadial implements RuinStrategy { public Collection ruin(Collection vehicleRoutes, Job targetJob, int nOfJobs2BeRemoved){ ruinListeners.ruinStarts(vehicleRoutes); List unassignedJobs = new ArrayList(); - TreeSet tree = distanceNodeTree.get(targetJob.getId()); - Iterator descendingIterator = tree.descendingIterator(); - int counter = 0; - while (descendingIterator.hasNext() && counter < nOfJobs2BeRemoved) { - ReferencedJob refJob = descendingIterator.next(); - Job job = refJob.getJob(); + int nNeighbors = nOfJobs2BeRemoved - 1; + removeJob(targetJob,vehicleRoutes); + unassignedJobs.add(targetJob); + Iterator neighborhoodIterator = jobNeighborhoods.getNearestNeighborsIterator(nNeighbors, targetJob); + while(neighborhoodIterator.hasNext()){ + Job job = neighborhoodIterator.next(); + removeJob(job,vehicleRoutes); unassignedJobs.add(job); - counter++; - boolean removed = false; - for (VehicleRoute route : vehicleRoutes) { - removed = route.getTourActivities().removeJob(job);; - if (removed) { - ruinListeners.removed(job,route); - break; - } - } } ruinListeners.ruinEnds(vehicleRoutes, unassignedJobs); return unassignedJobs; } + private void removeJob(Job job, Collection vehicleRoutes) { + boolean removed = false; + for (VehicleRoute route : vehicleRoutes) { + removed = route.getTourActivities().removeJob(job);; + if (removed) { + ruinListeners.removed(job,route); + break; + } + } + } + private Job pickRandomJob() { int totNuOfJobs = vrp.getJobs().values().size(); int randomIndex = random.nextInt(totNuOfJobs); diff --git a/jsprit-core/src/test/java/algorithms/JobNeighborhoodsImplTest.java b/jsprit-core/src/test/java/algorithms/JobNeighborhoodsImplTest.java new file mode 100644 index 00000000..9ba678b6 --- /dev/null +++ b/jsprit-core/src/test/java/algorithms/JobNeighborhoodsImplTest.java @@ -0,0 +1,110 @@ +package algorithms; + +import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; + +import util.Coordinate; +import algorithms.RuinRadial.JobNeighborhoodsImpl; +import basics.Job; +import basics.Service; +import basics.VehicleRoutingProblem; + +public class JobNeighborhoodsImplTest { + + VehicleRoutingProblem vrp; + + JobDistance jobDistance; + + Service target; + Service s2; + Service s3; + Service s4; + Service s5; + Service s6; + Service s7; + + @Before + public void doBefore(){ + VehicleRoutingProblem.Builder builder = VehicleRoutingProblem.Builder.newInstance(); + target = Service.Builder.newInstance("s1", 1).setCoord(Coordinate.newInstance(0, 5)).build(); + s2 = Service.Builder.newInstance("s2", 1).setCoord(Coordinate.newInstance(0, 4)).build(); + s3 = Service.Builder.newInstance("s3", 1).setCoord(Coordinate.newInstance(0, 3)).build(); + s4 = Service.Builder.newInstance("s4", 1).setCoord(Coordinate.newInstance(0, 2)).build(); + + s5 = Service.Builder.newInstance("s5", 1).setCoord(Coordinate.newInstance(0, 6)).build(); + s6 = Service.Builder.newInstance("s6", 1).setCoord(Coordinate.newInstance(0, 7)).build(); + s7 = Service.Builder.newInstance("s7", 1).setCoord(Coordinate.newInstance(0, 8)).build(); + + vrp = builder.addJob(target).addJob(s2).addJob(s3).addJob(s4).addJob(s5).addJob(s6).addJob(s7).build(); + + jobDistance = new EuclideanServiceDistance(); + } + + @Test + public void whenRequestingNeighborhoodOfTargetJob_nNeighborsShouldBeTwo(){ + JobNeighborhoodsImpl jn = new JobNeighborhoodsImpl(vrp, jobDistance); + jn.initialise(); + Iterator iter = jn.getNearestNeighborsIterator(2, target); + List services = new ArrayList(); + while(iter.hasNext()){ + services.add((Service) iter.next()); + } + assertEquals(2,services.size()); + } + + @Test + public void whenRequestingNeighborhoodOfTargetJob_s2ShouldBeNeighbor(){ + JobNeighborhoodsImpl jn = new JobNeighborhoodsImpl(vrp, jobDistance); + jn.initialise(); + Iterator iter = jn.getNearestNeighborsIterator(2, target); + List services = new ArrayList(); + while(iter.hasNext()){ + services.add((Service) iter.next()); + } + assertTrue(services.contains(s2)); + } + + @Test + public void whenRequestingNeighborhoodOfTargetJob_s4ShouldBeNeighbor(){ + JobNeighborhoodsImpl jn = new JobNeighborhoodsImpl(vrp, jobDistance); + jn.initialise(); + Iterator iter = jn.getNearestNeighborsIterator(2, target); + List services = new ArrayList(); + while(iter.hasNext()){ + services.add((Service) iter.next()); + } + assertTrue(services.contains(s5)); + } + + @Test + public void whenRequestingNeighborhoodOfTargetJob_sizeShouldBe4(){ + JobNeighborhoodsImpl jn = new JobNeighborhoodsImpl(vrp, jobDistance); + jn.initialise(); + Iterator iter = jn.getNearestNeighborsIterator(4, target); + List services = new ArrayList(); + while(iter.hasNext()){ + services.add((Service) iter.next()); + } + assertEquals(4,services.size()); + } + + @Test + public void whenRequestingMoreNeighborsThanExisting_itShouldReturnMaxNeighbors(){ + JobNeighborhoodsImpl jn = new JobNeighborhoodsImpl(vrp, jobDistance); + jn.initialise(); + Iterator iter = jn.getNearestNeighborsIterator(100, target); + List services = new ArrayList(); + while(iter.hasNext()){ + services.add((Service) iter.next()); + } + assertEquals(6,services.size()); + } + +} diff --git a/jsprit-core/src/test/java/algorithms/JobNeighborhoodsWithCapRestrictionImplTest.java b/jsprit-core/src/test/java/algorithms/JobNeighborhoodsWithCapRestrictionImplTest.java new file mode 100644 index 00000000..8f4bbc28 --- /dev/null +++ b/jsprit-core/src/test/java/algorithms/JobNeighborhoodsWithCapRestrictionImplTest.java @@ -0,0 +1,111 @@ +package algorithms; + +import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; + +import util.Coordinate; +import algorithms.RuinRadial.JobNeighborhoodsImpl; +import algorithms.RuinRadial.JobNeighborhoodsImplWithCapRestriction; +import basics.Job; +import basics.Service; +import basics.VehicleRoutingProblem; + +public class JobNeighborhoodsWithCapRestrictionImplTest { + + VehicleRoutingProblem vrp; + + JobDistance jobDistance; + + Service target; + Service s2; + Service s3; + Service s4; + Service s5; + Service s6; + Service s7; + + @Before + public void doBefore(){ + VehicleRoutingProblem.Builder builder = VehicleRoutingProblem.Builder.newInstance(); + target = Service.Builder.newInstance("s1", 1).setCoord(Coordinate.newInstance(0, 5)).build(); + s2 = Service.Builder.newInstance("s2", 1).setCoord(Coordinate.newInstance(0, 4)).build(); + s3 = Service.Builder.newInstance("s3", 1).setCoord(Coordinate.newInstance(0, 3)).build(); + s4 = Service.Builder.newInstance("s4", 1).setCoord(Coordinate.newInstance(0, 2)).build(); + + s5 = Service.Builder.newInstance("s5", 1).setCoord(Coordinate.newInstance(0, 6)).build(); + s6 = Service.Builder.newInstance("s6", 1).setCoord(Coordinate.newInstance(0, 7)).build(); + s7 = Service.Builder.newInstance("s7", 1).setCoord(Coordinate.newInstance(0, 8)).build(); + + vrp = builder.addJob(target).addJob(s2).addJob(s3).addJob(s4).addJob(s5).addJob(s6).addJob(s7).build(); + + jobDistance = new EuclideanServiceDistance(); + } + + @Test + public void whenRequestingNeighborhoodOfTargetJob_nNeighborsShouldBeTwo(){ + JobNeighborhoodsImplWithCapRestriction jn = new JobNeighborhoodsImplWithCapRestriction(vrp, jobDistance, 2); + jn.initialise(); + Iterator iter = jn.getNearestNeighborsIterator(2, target); + List services = new ArrayList(); + while(iter.hasNext()){ + services.add((Service) iter.next()); + } + assertEquals(2,services.size()); + } + + @Test + public void whenRequestingNeighborhoodOfTargetJob_s2ShouldBeNeighbor(){ + JobNeighborhoodsImplWithCapRestriction jn = new JobNeighborhoodsImplWithCapRestriction(vrp, jobDistance, 2); + jn.initialise(); + Iterator iter = jn.getNearestNeighborsIterator(2, target); + List services = new ArrayList(); + while(iter.hasNext()){ + services.add((Service) iter.next()); + } + assertTrue(services.contains(s2)); + } + + @Test + public void whenRequestingNeighborhoodOfTargetJob_s4ShouldBeNeighbor(){ + JobNeighborhoodsImplWithCapRestriction jn = new JobNeighborhoodsImplWithCapRestriction(vrp, jobDistance, 2); + jn.initialise(); + Iterator iter = jn.getNearestNeighborsIterator(2, target); + List services = new ArrayList(); + while(iter.hasNext()){ + services.add((Service) iter.next()); + } + assertTrue(services.contains(s5)); + } + + @Test + public void whenRequestingNeighborhoodOfTargetJob_sizeShouldBe4(){ + JobNeighborhoodsImplWithCapRestriction jn = new JobNeighborhoodsImplWithCapRestriction(vrp, jobDistance, 4); + jn.initialise(); + Iterator iter = jn.getNearestNeighborsIterator(4, target); + List services = new ArrayList(); + while(iter.hasNext()){ + services.add((Service) iter.next()); + } + assertEquals(4,services.size()); + } + + @Test + public void whenRequestingMoreNeighborsThanExisting_itShouldReturnMaxNeighbors(){ + JobNeighborhoodsImplWithCapRestriction jn = new JobNeighborhoodsImplWithCapRestriction(vrp, jobDistance, 2); + jn.initialise(); + Iterator iter = jn.getNearestNeighborsIterator(100, target); + List services = new ArrayList(); + while(iter.hasNext()){ + services.add((Service) iter.next()); + } + assertEquals(2,services.size()); + } + +}