From 2c5a16b40390178e99f95225847ac9fbd81affd5 Mon Sep 17 00:00:00 2001 From: oblonski Date: Fri, 5 May 2023 08:19:39 +0200 Subject: [PATCH] add new ruin strategy --- .../jsprit/core/algorithm/box/Jsprit.java | 37 ++++- .../core/algorithm/ruin/RuinTimeRelated.java | 156 ++++++++++++++++++ .../algorithm/ruin/RuinTimeRelatedTest.java | 80 +++++++++ 3 files changed, 265 insertions(+), 8 deletions(-) create mode 100644 jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/ruin/RuinTimeRelated.java create mode 100644 jsprit-core/src/test/java/com/graphhopper/jsprit/core/algorithm/ruin/RuinTimeRelatedTest.java diff --git a/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/box/Jsprit.java b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/box/Jsprit.java index a1cdf9fb5..d76452834 100644 --- a/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/box/Jsprit.java +++ b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/box/Jsprit.java @@ -82,7 +82,10 @@ public enum Strategy { CLUSTER_BEST("cluster_best"), CLUSTER_REGRET("cluster_regret"), STRING_BEST("string_best"), - STRING_REGRET("string_regret"); + STRING_REGRET("string_regret"), + TIME_RELATED_REGRET("time_related_regret"), + TIME_RELATED_BEST("time_related_best"); + String strategyName; @@ -189,6 +192,10 @@ private Properties createDefaultProperties() { Properties defaults = new Properties(); defaults.put(Strategy.RADIAL_BEST.toString(), "0."); defaults.put(Strategy.RADIAL_REGRET.toString(), ".5"); + + defaults.put(Strategy.TIME_RELATED_BEST.toString(), "0."); + defaults.put(Strategy.TIME_RELATED_REGRET.toString(), "0."); + defaults.put(Strategy.RANDOM_BEST.toString(), ".5"); defaults.put(Strategy.RANDOM_REGRET.toString(), ".5"); @@ -466,18 +473,18 @@ private VehicleRoutingAlgorithm create(final VehicleRoutingProblem vrp) { RuinRadial radial = new RuinRadial(vrp, vrp.getJobs().size(), jobNeighborhoods); radial.setRandom(random); - radial.setRuinShareFactory(new RuinShareFactoryImpl( + RuinShareFactoryImpl radialRuinFactory = new RuinShareFactoryImpl( toInteger(properties.getProperty(Parameter.RADIAL_MIN_SHARE.toString())), toInteger(properties.getProperty(Parameter.RADIAL_MAX_SHARE.toString())), - random) - ); + random); + radial.setRuinShareFactory(radialRuinFactory); final RuinRandom random_for_regret = new RuinRandom(vrp, 0.5); random_for_regret.setRandom(random); random_for_regret.setRuinShareFactory(new RuinShareFactoryImpl( - toInteger(properties.getProperty(Parameter.RANDOM_REGRET_MIN_SHARE.toString())), - toInteger(properties.getProperty(Parameter.RANDOM_REGRET_MAX_SHARE.toString())), - random) + toInteger(properties.getProperty(Parameter.RANDOM_REGRET_MIN_SHARE.toString())), + toInteger(properties.getProperty(Parameter.RANDOM_REGRET_MAX_SHARE.toString())), + random) ); final RuinRandom random_for_best = new RuinRandom(vrp, 0.5); @@ -521,12 +528,16 @@ private VehicleRoutingAlgorithm create(final VehicleRoutingProblem vrp) { stringRuin.setStringLength(lMin, lMax); stringRuin.setRandom(random); + final RuinTimeRelated ruinTimeRelated = new RuinTimeRelated(vrp); + ruinTimeRelated.setRuinShareFactory(radialRuinFactory); + ruinTimeRelated.setRandom(random); + AbstractInsertionStrategy regret; final ScoringFunction scorer; boolean fastRegret = Boolean.parseBoolean(getProperty(Parameter.FAST_REGRET.toString())); if (es != null) { - if(fastRegret){ + if (fastRegret) { RegretInsertionConcurrentFast regretInsertion = (RegretInsertionConcurrentFast) new InsertionStrategyBuilder(vrp, vehicleFleetManager, stateManager, constraintManager) .setInsertionStrategy(InsertionStrategyBuilder.Strategy.REGRET) .setConcurrentMode(es, noThreads) @@ -624,6 +635,12 @@ private VehicleRoutingAlgorithm create(final VehicleRoutingProblem vrp) { SearchStrategy radialBest = new SearchStrategy(Strategy.RADIAL_BEST.toString(), new SelectBest(), acceptor, objectiveFunction); radialBest.addModule(configureModule(new RuinAndRecreateModule(Strategy.RADIAL_BEST.toString(), best, radial))); + SearchStrategy timeRelatedRegret = new SearchStrategy(Strategy.TIME_RELATED_REGRET.toString(), new SelectBest(), acceptor, objectiveFunction); + timeRelatedRegret.addModule(configureModule(new RuinAndRecreateModule(Strategy.TIME_RELATED_REGRET.toString(), regret, ruinTimeRelated))); + + SearchStrategy timeRelatedBest = new SearchStrategy(Strategy.TIME_RELATED_BEST.toString(), new SelectBest(), acceptor, objectiveFunction); + timeRelatedBest.addModule(configureModule(new RuinAndRecreateModule(Strategy.TIME_RELATED_BEST.toString(), best, ruinTimeRelated))); + SearchStrategy randomBest = new SearchStrategy(Strategy.RANDOM_BEST.toString(), new SelectBest(), acceptor, objectiveFunction); randomBest.addModule(configureModule(new RuinAndRecreateModule(Strategy.RANDOM_BEST.toString(), best, random_for_best))); @@ -655,6 +672,10 @@ private VehicleRoutingAlgorithm create(final VehicleRoutingProblem vrp) { } prettyBuilder.withStrategy(radialRegret, toDouble(getProperty(Strategy.RADIAL_REGRET.toString()))) .withStrategy(radialBest, toDouble(getProperty(Strategy.RADIAL_BEST.toString()))) + + .withStrategy(timeRelatedBest, toDouble(getProperty(Strategy.TIME_RELATED_BEST.toString()))) + .withStrategy(timeRelatedRegret, toDouble(getProperty(Strategy.TIME_RELATED_REGRET.toString()))) + .withStrategy(randomBest, toDouble(getProperty(Strategy.RANDOM_BEST.toString()))) .withStrategy(randomRegret, toDouble(getProperty(Strategy.RANDOM_REGRET.toString()))) .withStrategy(worstBest, toDouble(getProperty(Strategy.WORST_BEST.toString()))) diff --git a/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/ruin/RuinTimeRelated.java b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/ruin/RuinTimeRelated.java new file mode 100644 index 000000000..d1982bcad --- /dev/null +++ b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/ruin/RuinTimeRelated.java @@ -0,0 +1,156 @@ +/* + * Licensed to GraphHopper GmbH under one or more contributor + * license agreements. See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + * + * GraphHopper GmbH licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.graphhopper.jsprit.core.algorithm.ruin; + +import com.graphhopper.jsprit.core.problem.VehicleRoutingProblem; +import com.graphhopper.jsprit.core.problem.job.Job; +import com.graphhopper.jsprit.core.problem.solution.route.VehicleRoute; +import com.graphhopper.jsprit.core.problem.solution.route.activity.TourActivity; +import com.graphhopper.jsprit.core.util.NoiseMaker; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + + +/** + * @author stefan schroeder + */ + +public final class RuinTimeRelated extends AbstractRuinStrategy { + + static class RelatednessToTourActivity { + + final double time; + + final TourActivity tourActivity; + + final VehicleRoute route; + + final double distance; + + public RelatednessToTourActivity(double relatednessToTarget, double distance, TourActivity tourActivity, VehicleRoute route) { + this.time = relatednessToTarget; + this.distance = distance; + this.tourActivity = tourActivity; + this.route = route; + } + } + + private static final Logger logger = LoggerFactory.getLogger(RuinTimeRelated.class); + + private final VehicleRoutingProblem vrp; + + private NoiseMaker noiseMaker = () -> 0; + + public void setNoiseMaker(NoiseMaker noiseMaker) { + this.noiseMaker = noiseMaker; + } + + public RuinTimeRelated(VehicleRoutingProblem vrp) { + super(vrp); + this.vrp = vrp; + this.ruinShareFactory = () -> (int) Math.max(50, Math.round(vrp.getJobs().size() * 0.3)); + logger.debug("initialise {}", this); + } + + /** + * 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 totalActivities = 0; + for (VehicleRoute route : vehicleRoutes) { + totalActivities += route.getActivities().size(); + } + if (totalActivities == 0) return unassignedJobs; + int randomIndex = random.nextInt(totalActivities); + int numActs = 0; + TourActivity targetActivity = null; + for (VehicleRoute route : vehicleRoutes) { + if (numActs + route.getActivities().size() < randomIndex) { + numActs += route.getActivities().size(); + } else { + for (TourActivity activity : route.getActivities()) { + if (numActs == randomIndex) { + targetActivity = activity; + break; + } + numActs++; + } + if (targetActivity != null) { + break; + } + } + } + if (targetActivity == null) { + return unassignedJobs; + } + List neighborActivities = new ArrayList<>(); + long maxTime = 0; + double maxDistance = 0; + for (VehicleRoute route : vehicleRoutes) { + for (TourActivity activity : route.getActivities()) { + if (activity == targetActivity) continue; + long absTime = Math.abs((long) targetActivity.getArrTime() - (long) activity.getArrTime()); + maxTime = Math.max(maxTime, absTime); + double distance = Math.abs(vrp.getTransportCosts().getDistance(targetActivity.getLocation(), activity.getLocation(), 0, route.getVehicle())); + maxDistance = Math.max(maxDistance, distance); + neighborActivities.add(new RelatednessToTourActivity(absTime, distance, activity, route)); + } + } + final double maxT = maxTime; + final double maxD = maxTime; + final double timeI = 10; + final double distanceI; + double distanceInfluence = 1; + if (random.nextDouble() < 0.5) { + distanceI = 0; + } else distanceI = distanceInfluence; + neighborActivities.sort((o1, o2) -> { + double rO1 = relatedness(o1, maxD, maxT, timeI, distanceI); + double rO2 = relatedness(o2, maxD, maxT, timeI, distanceI); + return Double.compare(rO1, rO2); + }); + int toRemove = getRuinShareFactory().createNumberToBeRemoved(); + for (RelatednessToTourActivity neighborActivity : neighborActivities) { + if (toRemove == 0) break; + Job j = ((TourActivity.JobActivity) neighborActivity.tourActivity).getJob(); + if (removeJob(j, neighborActivity.route)) { + unassignedJobs.add(j); + toRemove--; + } + } + return unassignedJobs; + } + + private double relatedness(RelatednessToTourActivity o1, double maxDistance, double maxTime, double timeI, double distanceI) { + return timeI * o1.time / maxTime + distanceI * o1.distance / maxDistance; + } + + @Override + public String toString() { + return "[name=timeRelatedRuin]"; + } + +} diff --git a/jsprit-core/src/test/java/com/graphhopper/jsprit/core/algorithm/ruin/RuinTimeRelatedTest.java b/jsprit-core/src/test/java/com/graphhopper/jsprit/core/algorithm/ruin/RuinTimeRelatedTest.java new file mode 100644 index 000000000..a23e72609 --- /dev/null +++ b/jsprit-core/src/test/java/com/graphhopper/jsprit/core/algorithm/ruin/RuinTimeRelatedTest.java @@ -0,0 +1,80 @@ +/* + * Licensed to GraphHopper GmbH under one or more contributor + * license agreements. See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + * + * GraphHopper GmbH licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.graphhopper.jsprit.core.algorithm.ruin; + +import com.graphhopper.jsprit.core.problem.Location; +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.solution.route.VehicleRoute; +import com.graphhopper.jsprit.core.problem.solution.route.activity.TourActivity; +import com.graphhopper.jsprit.core.problem.vehicle.VehicleImpl; +import com.graphhopper.jsprit.core.util.RandomNumberGeneration; +import org.junit.Test; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Random; + +/** + * Created by schroeder on 06/03/15. + */ +public class RuinTimeRelatedTest { + + @Test + public void itShouldRuinTwoObviousClusters() { + Service s0 = Service.Builder.newInstance("s0").setLocation(Location.newInstance(9, 0)).build(); + Service s1 = Service.Builder.newInstance("s1").setLocation(Location.newInstance(9, 1)).build(); + Service s2 = Service.Builder.newInstance("s2").setLocation(Location.newInstance(9, 10)).build(); + Service s3 = Service.Builder.newInstance("s3").setLocation(Location.newInstance(9, 9)).build(); + Service s4 = Service.Builder.newInstance("s4").setLocation(Location.newInstance(9, 16)).build(); + Service s5 = Service.Builder.newInstance("s5").setLocation(Location.newInstance(9, 17)).build(); + Service s6 = Service.Builder.newInstance("s6").setLocation(Location.newInstance(9, 15.5)).build(); + Service s7 = Service.Builder.newInstance("s7").setLocation(Location.newInstance(9, 30)).build(); + + VehicleImpl v = VehicleImpl.Builder.newInstance("v").setStartLocation(Location.newInstance(0, 0)).build(); + + VehicleRoutingProblem vrp = VehicleRoutingProblem.Builder.newInstance().addJob(s1).addJob(s2) + .addJob(s6).addJob(s7).addJob(s0).addJob(s3).addJob(s4).addJob(s5).addVehicle(v).build(); + + VehicleRoute vr1 = VehicleRoute.Builder.newInstance(v).addService(s0).addService(s1).addService(s2).addService(s3).setJobActivityFactory(vrp.getJobActivityFactory()).build(); + long time = 0; + for (TourActivity act : vr1.getActivities()) { + time += 2; + act.setArrTime(time); + } + VehicleRoute vr2 = VehicleRoute.Builder.newInstance(v) + .addService(s6).addService(s7).addService(s4).addService(s5).setJobActivityFactory(vrp.getJobActivityFactory()).build(); + time = 0; + for (TourActivity act : vr2.getActivities()) { + time += 2; + act.setArrTime(time); + } + + RuinTimeRelated ruin = new RuinTimeRelated(vrp); + + Random r = RandomNumberGeneration.newInstance(); + ruin.setRandom(r); + Collection ruined = ruin.ruinRoutes(Arrays.asList(vr1, vr2)); + + + } + + +}