solutions) {
+ minPts = 1 + RandomNumberGeneration.getRandom().nextInt(2);
+ epsFactor = 0.5 + RandomNumberGeneration.getRandom().nextDouble();
+ }
+
+ public static class JobActivityWrapper implements Clusterable {
+
+ private TourActivity.JobActivity jobActivity;
+
+ public JobActivityWrapper(TourActivity.JobActivity jobActivity) {
+ this.jobActivity = jobActivity;
+ }
+
+ @Override
+ public double[] getPoint() {
+ return new double[]{ jobActivity.getLocation().getCoordinate().getX(), jobActivity.getLocation().getCoordinate().getY() };
+ }
+
+ public TourActivity.JobActivity getActivity(){
+ return jobActivity;
+ }
+ }
+
+ private Logger logger = LogManager.getLogger(RuinClusters.class);
+
+ private VehicleRoutingProblem vrp;
+
+
+ private JobNeighborhoods jobNeighborhoods;
+
+ private int noClusters = 2;
+
+ private int minPts = 1;
+
+ private double epsFactor = 0.8;
+
+ public RuinClusters(VehicleRoutingProblem vrp, final int initialNumberJobsToRemove, JobNeighborhoods jobNeighborhoods) {
+ super();
+ this.vrp = vrp;
+ setRuinShareFactory(new RuinShareFactory() {
+ @Override
+ public int createNumberToBeRemoved() {
+ return initialNumberJobsToRemove;
+ }
+ });
+ this.jobNeighborhoods = jobNeighborhoods;
+ logger.info("initialise " + this);
+ logger.info("done");
+ }
+
+ public void setNoClusters(int noClusters) {
+ this.noClusters = noClusters;
+ }
+
+ /**
+ * 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 IllegalStateException("not supported");
+ }
+
+ private void ruin(Collection vehicleRoutes, int nOfJobs2BeRemoved, List unassignedJobs) {
+ Map mappedRoutes = map(vehicleRoutes);
+ int toRemove = nOfJobs2BeRemoved;
+
+ Collection lastRemoved = new ArrayList();
+ Set ruined = new HashSet();
+ Set removed = new HashSet();
+ Set cycleCandidates = new HashSet();
+ while(toRemove > 0) {
+ Job target;
+ VehicleRoute targetRoute = null;
+ if(lastRemoved.isEmpty()){
+ target = RandomUtils.nextJob(vrp.getJobs().values(), random);
+ targetRoute = mappedRoutes.get(target);
+ }
+ else{
+ target = RandomUtils.nextJob(lastRemoved, random);
+ Iterator neighborIterator = jobNeighborhoods.getNearestNeighborsIterator(nOfJobs2BeRemoved,target);
+ while(neighborIterator.hasNext()){
+ Job j = neighborIterator.next();
+ if(!removed.contains(j) && !ruined.contains(mappedRoutes.get(j))){
+ targetRoute = mappedRoutes.get(j);
+ break;
+ }
+ }
+ lastRemoved.clear();
+ }
+ if(targetRoute == null) break;
+ if(cycleCandidates.contains(targetRoute)) break;
+ if(ruined.contains(targetRoute)) {
+ cycleCandidates.add(targetRoute);
+ break;
+ }
+ DBSCANClusterer dbscan = new DBSCANClusterer(vrp.getTransportCosts());
+ dbscan.setMinPts(minPts);
+ dbscan.setEpsFactor(epsFactor);
+ List cluster = dbscan.getRandomCluster(targetRoute);
+ for(Job j : cluster){
+ if(toRemove == 0) break;
+ removeJob(j, vehicleRoutes);
+ lastRemoved.add(j);
+ unassignedJobs.add(j);
+ toRemove--;
+ }
+ ruined.add(targetRoute);
+ }
+ }
+
+ private List wrap(List activities) {
+ List wl = new ArrayList();
+ for(TourActivity act : activities){
+ wl.add(new JobActivityWrapper((TourActivity.JobActivity) act));
+ }
+ return wl;
+ }
+
+ private Map map(Collection vehicleRoutes) {
+ Map map = new HashMap(vrp.getJobs().size());
+ for(VehicleRoute r : vehicleRoutes){
+ for(Job j : r.getTourActivities().getJobs()){
+ map.put(j,r);
+ }
+ }
+ return map;
+ }
+
+ @Override
+ public String toString() {
+ return "[name=clusterRuin]";
+ }
+
+}