diff --git a/jsprit-core/src/main/java/jsprit/core/analysis/SolutionAnalyser.java b/jsprit-core/src/main/java/jsprit/core/analysis/SolutionAnalyser.java index 539067cd..8db73edd 100644 --- a/jsprit-core/src/main/java/jsprit/core/analysis/SolutionAnalyser.java +++ b/jsprit-core/src/main/java/jsprit/core/analysis/SolutionAnalyser.java @@ -27,10 +27,7 @@ import jsprit.core.util.ActivityTimeTracker; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; /** * @@ -247,6 +244,48 @@ public class SolutionAnalyser { } } + private static class SkillUpdater implements StateUpdater, ActivityVisitor { + + private StateManager stateManager; + + private StateId skill_id; + + private VehicleRoute route; + + private boolean skillConstraintViolatedOnRoute; + + private SkillUpdater(StateManager stateManager, StateId skill_id) { + this.stateManager = stateManager; + this.skill_id = skill_id; + } + + @Override + public void begin(VehicleRoute route) { + this.route = route; + skillConstraintViolatedOnRoute = false; + } + + @Override + public void visit(TourActivity activity) { + boolean violatedAtActivity = false; + if(activity instanceof TourActivity.JobActivity){ + Set requiredForActivity = ((TourActivity.JobActivity) activity).getJob().getRequiredSkills().values(); + for(String skill : requiredForActivity){ + if(!route.getVehicle().getSkills().containsSkill(skill)){ + violatedAtActivity = true; + skillConstraintViolatedOnRoute = true; + } + } + } + stateManager.putActivityState(activity,skill_id,violatedAtActivity); + } + + @Override + public void finish() { + stateManager.putRouteState(route,skill_id, skillConstraintViolatedOnRoute); + } + } + private static final Logger log = LogManager.getLogger(SolutionAnalyser.class); private VehicleRoutingProblem vrp; @@ -271,16 +310,14 @@ public class SolutionAnalyser { private final StateId backhaul_id; + private final StateId skill_id; + private ActivityTimeTracker.ActivityPolicy activityPolicy; - private final VehicleRoutingProblemSolution working_solution; - public SolutionAnalyser(VehicleRoutingProblem vrp, VehicleRoutingProblemSolution solution, DistanceCalculator distanceCalculator) { this.vrp = vrp; -// this.original_solution = VehicleRoutingProblemSolution.copyOf(solution); - this.working_solution = solution; - this.routes = new ArrayList(working_solution.getRoutes()); + this.routes = new ArrayList(solution.getRoutes()); this.stateManager = new StateManager(vrp); this.stateManager.updateTimeWindowStates(); this.stateManager.updateLoadStates(); @@ -296,9 +333,11 @@ public class SolutionAnalyser { too_late_id = stateManager.createStateId("too-late"); shipment_id = stateManager.createStateId("shipment"); backhaul_id = stateManager.createStateId("backhaul"); + skill_id = stateManager.createStateId("skills-violated"); stateManager.addStateUpdater(new SumUpActivityTimes(waiting_time_id, transport_time_id, service_time_id,too_late_id , stateManager, activityPolicy)); stateManager.addStateUpdater(new DistanceUpdater(distance_id,stateManager,distanceCalculator)); stateManager.addStateUpdater(new BackhaulAndShipmentUpdater(backhaul_id,shipment_id,stateManager)); + stateManager.addStateUpdater(new SkillUpdater(stateManager,skill_id)); refreshStates(); } @@ -437,7 +476,7 @@ public class SolutionAnalyser { */ public Double getTimeWindowViolation(VehicleRoute route){ if(route == null) throw new IllegalStateException("route is missing."); - return stateManager.getRouteState(route,too_late_id,Double.class); + return stateManager.getRouteState(route, too_late_id, Double.class); } /** @@ -448,25 +487,56 @@ public class SolutionAnalyser { public Double getTimeWindowViolationAtActivity(TourActivity activity, VehicleRoute route){ if(route == null) throw new IllegalStateException("route is missing."); if(activity == null) throw new IllegalStateException("activity is missing."); - return Math.max(0,activity.getArrTime()-activity.getTheoreticalLatestOperationStartTime()); + return Math.max(0, activity.getArrTime() - activity.getTheoreticalLatestOperationStartTime()); } + /** + * @param route to check skill constraint + * @return true if skill constraint is violated, i.e. if vehicle does not have the required skills to conduct all + * activities on the specified route. Returns null if route is null or skill state cannot be found. + */ public Boolean skillConstraintIsViolated(VehicleRoute route){ if(route == null) throw new IllegalStateException("route is missing."); - return null; + return stateManager.getRouteState(route, skill_id, Boolean.class); } + /** + * @param activity to check skill constraint + * @param route that must contain specified activity + * @return true if vehicle does not have the skills to conduct specified activity, false otherwise. Returns null + * if specified route or activity is null or if route does not contain specified activity or if skill state connot be + * found. If specified activity is Start or End, it returns false. + */ public Boolean skillConstraintIsViolatedAtActivity(TourActivity activity, VehicleRoute route){ if(route == null) throw new IllegalStateException("route is missing."); if(activity == null) throw new IllegalStateException("activity is missing."); + if(activity instanceof Start) return false; + if(activity instanceof End) return false; verifyThatRouteContainsAct(activity, route); - return null; + return stateManager.getActivityState(activity, skill_id, Boolean.class); } + /** + * Returns true if backhaul constraint is violated (false otherwise). Backhaul constraint is violated if either a + * pickupService, serviceActivity (which is basically modeled as pickupService) or a pickupShipment occur before + * a deliverService activity or - to put it in other words - if a depot bounded delivery occurs after a pickup, thus + * the backhaul ensures depot bounded delivery activities first. + * + * @param route to check backhaul constraint + * @return true if backhaul constraint for specified route is violated. returns null if route is null or no backhaul + * state can be found. In latter case try routeChanged(route). + */ public Boolean backhaulConstraintIsViolated(VehicleRoute route){ + if(route == null) throw new IllegalStateException("route is missing."); return stateManager.getRouteState(route,backhaul_id,Boolean.class); } + /** + * @param activity to check backhaul violation + * @param route that must contain the specified activity + * @return true if backhaul constraint is violated, false otherwise. Null if either specified route or activity is null. + * Null if specified route does not contain specified activity. + */ public Boolean backhaulConstraintIsViolatedAtActivity(TourActivity activity, VehicleRoute route){ if(route == null) throw new IllegalStateException("route is missing."); if(activity == null) throw new IllegalStateException("activity is missing."); @@ -476,11 +546,28 @@ public class SolutionAnalyser { return stateManager.getActivityState(activity,backhaul_id,Boolean.class); } + /** + * Returns true, if shipment constraint is violated. Two activities are associated to a shipment: pickupShipment + * and deliverShipment. If both shipments are not in the same route OR deliverShipment occurs before pickupShipment + * then the shipment constraint is violated. + * + * @param route to check the shipment constraint. + * @return true if violated, false otherwise. Null if no state can be found or specified route is null. + */ public Boolean shipmentConstraintIsViolated(VehicleRoute route){ if(route == null) throw new IllegalStateException("route is missing."); return stateManager.getRouteState(route,shipment_id,Boolean.class); } + /** + * Returns true if shipment constraint is violated, i.e. if activity is deliverShipment but no pickupShipment can be + * found before OR activity is pickupShipment and no deliverShipment can be found afterwards. + * + * @param activity to check the shipment constraint + * @param route that must contain specified activity + * @return true if shipment constraint is violated, false otherwise. If activity is either Start or End, it returns + * false. Returns null if either specified activity or route is null or route does not containt activity. + */ public Boolean shipmentConstraintIsViolatedAtActivity(TourActivity activity, VehicleRoute route){ if(route == null) throw new IllegalStateException("route is missing."); if(activity == null) throw new IllegalStateException("activity is missing."); diff --git a/jsprit-core/src/test/java/jsprit/core/analysis/SolutionAnalyserTest.java b/jsprit-core/src/test/java/jsprit/core/analysis/SolutionAnalyserTest.java index 3bac4cca..b282483b 100644 --- a/jsprit-core/src/test/java/jsprit/core/analysis/SolutionAnalyserTest.java +++ b/jsprit-core/src/test/java/jsprit/core/analysis/SolutionAnalyserTest.java @@ -58,18 +58,31 @@ public class SolutionAnalyserTest { VehicleType type = VehicleTypeImpl.Builder.newInstance("type").setFixedCost(100.).setCostPerDistance(2.).addCapacityDimension(0, 15).build(); VehicleImpl vehicle = VehicleImpl.Builder.newInstance("v1").setType(type) - .setStartLocationCoordinate(Coordinate.newInstance(-5, 0)).build(); + .setStartLocationCoordinate(Coordinate.newInstance(-5, 0)) + .addSkill("skill1").addSkill("skill2") + .build(); VehicleImpl vehicle2 = VehicleImpl.Builder.newInstance("v2").setType(type) .setStartLocationCoordinate(Coordinate.newInstance(5, 0)).build(); Service s1 = Service.Builder.newInstance("s1") .setTimeWindow(TimeWindow.newInstance(10, 20)) - .setCoord(Coordinate.newInstance(-10, 1)).addSizeDimension(0, 2).build(); - Service s2 = Service.Builder.newInstance("s2").setCoord(Coordinate.newInstance(-10, 10)).addSizeDimension(0,3).build(); - Shipment shipment1 = Shipment.Builder.newInstance("ship1").setPickupCoord(Coordinate.newInstance(-15, 2)) - .setDeliveryCoord(Coordinate.newInstance(-16, 5)).addSizeDimension(0,10) - .setPickupServiceTime(20.).setDeliveryServiceTime(20.).build(); + .setCoord(Coordinate.newInstance(-10, 1)).addSizeDimension(0, 2) + .addRequiredSkill("skill1") + .build(); + Service s2 = Service.Builder.newInstance("s2") + .setCoord(Coordinate.newInstance(-10, 10)) + .addSizeDimension(0,3) + .addRequiredSkill("skill2").addRequiredSkill("skill1") + .build(); + Shipment shipment1 = Shipment.Builder.newInstance("ship1") + .setPickupCoord(Coordinate.newInstance(-15, 2)) + .setDeliveryCoord(Coordinate.newInstance(-16, 5)) + .addSizeDimension(0,10) + .setPickupServiceTime(20.) + .setDeliveryServiceTime(20.) + .addRequiredSkill("skill3") + .build(); Service s3 = Service.Builder.newInstance("s3") .setTimeWindow(TimeWindow.newInstance(10, 20)) @@ -95,6 +108,63 @@ public class SolutionAnalyserTest { solution = new VehicleRoutingProblemSolution(Arrays.asList(route1,route2),42); } + public void buildAnotherScenarioWithOnlyOneVehicleAndWithoutAnyConstraintsBefore(){ + VehicleType type = VehicleTypeImpl.Builder.newInstance("type").setFixedCost(100.).setCostPerDistance(2.).addCapacityDimension(0, 15).build(); + + VehicleImpl vehicle = VehicleImpl.Builder.newInstance("v1").setType(type) + .setStartLocationCoordinate(Coordinate.newInstance(-5, 0)) + .setLatestArrival(150.) + .build(); + + Pickup s1 = (Pickup) Pickup.Builder.newInstance("s1") + .setTimeWindow(TimeWindow.newInstance(10, 20)) + .setCoord(Coordinate.newInstance(-10, 1)) + .addSizeDimension(0, 10) + .build(); + Delivery s2 = (Delivery) Delivery.Builder.newInstance("s2") + .setCoord(Coordinate.newInstance(-10, 10)) + .setTimeWindow(TimeWindow.newInstance(10, 20)) + .addSizeDimension(0, 20) + .build(); + Shipment shipment1 = Shipment.Builder.newInstance("ship1").setPickupCoord(Coordinate.newInstance(-15, 2)).setDeliveryCoord(Coordinate.newInstance(-16, 5)) + .addSizeDimension(0, 15) + .setPickupServiceTime(20.).setDeliveryServiceTime(20.) + .setPickupTimeWindow(TimeWindow.newInstance(10,20)).setDeliveryTimeWindow(TimeWindow.newInstance(10,20)) + .build(); + + Pickup s3 = (Pickup) Pickup.Builder.newInstance("s3") + .setTimeWindow(TimeWindow.newInstance(10, 20)) + .setCoord(Coordinate.newInstance(10, 1)) + .addSizeDimension(0, 10) + .build(); + Delivery s4 = (Delivery) Delivery.Builder.newInstance("s4").setCoord(Coordinate.newInstance(10, 10)) + .addSizeDimension(0, 20) + .setTimeWindow(TimeWindow.newInstance(10, 20)) + .build(); + Shipment shipment2 = Shipment.Builder.newInstance("ship2").setPickupCoord(Coordinate.newInstance(15, 2)) + .setPickupServiceTime(20.).setDeliveryServiceTime(20.) + .setDeliveryCoord(Coordinate.newInstance(16, 5)) + .setPickupTimeWindow(TimeWindow.newInstance(10, 20)).setDeliveryTimeWindow(TimeWindow.newInstance(10, 20)) + .addSizeDimension(0, 15).build(); + + VehicleRoutingProblem.Builder vrpBuilder = VehicleRoutingProblem.Builder.newInstance().addVehicle(vehicle) + .addJob(s1) + .addJob(s2).addJob(shipment1).addJob(s3).addJob(s4).addJob(shipment2).setFleetSize(VehicleRoutingProblem.FleetSize.FINITE); + vrpBuilder.setRoutingCost(new ManhattanCosts(vrpBuilder.getLocations())); + vrp = vrpBuilder.build(); + + VehicleRoute route = VehicleRoute.Builder.newInstance(vehicle).setJobActivityFactory(vrp.getJobActivityFactory()) + .addPickup(s3) + .addPickup(shipment2).addDelivery(shipment2) + .addDelivery(s4) + .addDelivery(s2) + .addPickup(shipment1).addDelivery(shipment1) + .addPickup(s1) + .build(); + + solution = new VehicleRoutingProblemSolution(Arrays.asList(route),300); + } + @Test public void constructionShouldWork(){ SolutionAnalyser analyser = new SolutionAnalyser(vrp,solution, new SolutionAnalyser.DistanceCalculator() { @@ -1689,79 +1759,117 @@ public class SolutionAnalyserTest { assertTrue(violation); } - - - public void buildAnotherScenarioWithOnlyOneVehicleAndWithoutAnyConstraintsBefore(){ - VehicleType type = VehicleTypeImpl.Builder.newInstance("type").setFixedCost(100.).setCostPerDistance(2.).addCapacityDimension(0, 15).build(); - - VehicleImpl vehicle = VehicleImpl.Builder.newInstance("v1").setType(type) - .setStartLocationCoordinate(Coordinate.newInstance(-5, 0)) - .setLatestArrival(150.) - .build(); - - Pickup s1 = (Pickup) Pickup.Builder.newInstance("s1") - .setTimeWindow(TimeWindow.newInstance(10, 20)) - .setCoord(Coordinate.newInstance(-10, 1)) - .addSizeDimension(0, 10) - .build(); - Delivery s2 = (Delivery) Delivery.Builder.newInstance("s2") - .setCoord(Coordinate.newInstance(-10, 10)) - .setTimeWindow(TimeWindow.newInstance(10, 20)) - .addSizeDimension(0, 20) - .build(); - Shipment shipment1 = Shipment.Builder.newInstance("ship1").setPickupCoord(Coordinate.newInstance(-15, 2)).setDeliveryCoord(Coordinate.newInstance(-16, 5)) - .addSizeDimension(0, 15) - .setPickupServiceTime(20.).setDeliveryServiceTime(20.) - .setPickupTimeWindow(TimeWindow.newInstance(10,20)).setDeliveryTimeWindow(TimeWindow.newInstance(10,20)) - .build(); - - Pickup s3 = (Pickup) Pickup.Builder.newInstance("s3") - .setTimeWindow(TimeWindow.newInstance(10, 20)) - .setCoord(Coordinate.newInstance(10, 1)) - .addSizeDimension(0, 10) - .build(); - Delivery s4 = (Delivery) Delivery.Builder.newInstance("s4").setCoord(Coordinate.newInstance(10, 10)) - .addSizeDimension(0, 20) - .setTimeWindow(TimeWindow.newInstance(10, 20)) - .build(); - Shipment shipment2 = Shipment.Builder.newInstance("ship2").setPickupCoord(Coordinate.newInstance(15, 2)) - .setPickupServiceTime(20.).setDeliveryServiceTime(20.) - .setDeliveryCoord(Coordinate.newInstance(16, 5)) - .setPickupTimeWindow(TimeWindow.newInstance(10, 20)).setDeliveryTimeWindow(TimeWindow.newInstance(10, 20)) - .addSizeDimension(0, 15).build(); - - VehicleRoutingProblem.Builder vrpBuilder = VehicleRoutingProblem.Builder.newInstance().addVehicle(vehicle) - .addJob(s1) - .addJob(s2).addJob(shipment1).addJob(s3).addJob(s4).addJob(shipment2).setFleetSize(VehicleRoutingProblem.FleetSize.FINITE); - vrpBuilder.setRoutingCost(new ManhattanCosts(vrpBuilder.getLocations())); - vrp = vrpBuilder.build(); - - VehicleRoute route = VehicleRoute.Builder.newInstance(vehicle).setJobActivityFactory(vrp.getJobActivityFactory()) - .addPickup(s3) - .addPickup(shipment2).addDelivery(shipment2) - .addDelivery(s4) - .addDelivery(s2) - .addPickup(shipment1).addDelivery(shipment1) - .addPickup(s1) - .build(); - - solution = new VehicleRoutingProblemSolution(Arrays.asList(route),300); -// VehicleRoutingAlgorithmBuilder vraBuilder = new VehicleRoutingAlgorithmBuilder(vrp,"src/test/resources/algorithmConfig.xml"); -// vraBuilder.addDefaultCostCalculators(); -// -// //adds updater -// StateManager stateManager = new StateManager(vrp); -// stateManager.updateLoadStates(); -// stateManager.updateTimeWindowStates(); -// -// //but no constraints to simulate violation -// ConstraintManager constraintManager = new ConstraintManager(vrp,stateManager); -// vraBuilder.setStateAndConstraintManager(stateManager,constraintManager); -// -// VehicleRoutingAlgorithm vra = vraBuilder.build(); -// vra.setMaxIterations(100); -// solution = Solutions.bestOf(vra.searchSolutions()); - + @Test + public void skillViolationOnRoute_shouldWorkWhenViolated(){ + SolutionAnalyser analyser = new SolutionAnalyser(vrp,solution, new SolutionAnalyser.DistanceCalculator() { + @Override + public double getDistance(String fromLocationId, String toLocationId) { + return vrp.getTransportCosts().getTransportCost(fromLocationId,toLocationId,0.,null,null); + } + }); + VehicleRoute route = solution.getRoutes().iterator().next(); + Boolean violated = analyser.skillConstraintIsViolated(route); + assertTrue(violated); } + @Test + public void skillViolationAtStart_shouldWork(){ + SolutionAnalyser analyser = new SolutionAnalyser(vrp,solution, new SolutionAnalyser.DistanceCalculator() { + @Override + public double getDistance(String fromLocationId, String toLocationId) { + return vrp.getTransportCosts().getTransportCost(fromLocationId,toLocationId,0.,null,null); + } + }); + VehicleRoute route = solution.getRoutes().iterator().next(); + Boolean violated = analyser.skillConstraintIsViolatedAtActivity(route.getStart(), route); + assertFalse(violated); + } + + @Test + public void skillViolationAtAct1_shouldWork(){ + SolutionAnalyser analyser = new SolutionAnalyser(vrp,solution, new SolutionAnalyser.DistanceCalculator() { + @Override + public double getDistance(String fromLocationId, String toLocationId) { + return vrp.getTransportCosts().getTransportCost(fromLocationId,toLocationId,0.,null,null); + } + }); + VehicleRoute route = solution.getRoutes().iterator().next(); + Boolean violated = analyser.skillConstraintIsViolatedAtActivity(route.getActivities().get(0),route); + assertFalse(violated); + } + + @Test + public void skillViolationAtAct2_shouldWork(){ + SolutionAnalyser analyser = new SolutionAnalyser(vrp,solution, new SolutionAnalyser.DistanceCalculator() { + @Override + public double getDistance(String fromLocationId, String toLocationId) { + return vrp.getTransportCosts().getTransportCost(fromLocationId,toLocationId,0.,null,null); + } + }); + VehicleRoute route = solution.getRoutes().iterator().next(); + Boolean violated = analyser.skillConstraintIsViolatedAtActivity(route.getActivities().get(1),route); + assertTrue(violated); + } + + @Test + public void skillViolationAtAct3_shouldWork(){ + SolutionAnalyser analyser = new SolutionAnalyser(vrp,solution, new SolutionAnalyser.DistanceCalculator() { + @Override + public double getDistance(String fromLocationId, String toLocationId) { + return vrp.getTransportCosts().getTransportCost(fromLocationId,toLocationId,0.,null,null); + } + }); + VehicleRoute route = solution.getRoutes().iterator().next(); + Boolean violated = analyser.skillConstraintIsViolatedAtActivity(route.getActivities().get(2),route); + assertTrue(violated); + } + + @Test + public void skillViolationAtAct4_shouldWork(){ + SolutionAnalyser analyser = new SolutionAnalyser(vrp,solution, new SolutionAnalyser.DistanceCalculator() { + @Override + public double getDistance(String fromLocationId, String toLocationId) { + return vrp.getTransportCosts().getTransportCost(fromLocationId,toLocationId,0.,null,null); + } + }); + VehicleRoute route = solution.getRoutes().iterator().next(); + Boolean violated = analyser.skillConstraintIsViolatedAtActivity(route.getActivities().get(3),route); + assertFalse(violated); + } + + @Test + public void skillViolationAtEnd_shouldWork(){ + SolutionAnalyser analyser = new SolutionAnalyser(vrp,solution, new SolutionAnalyser.DistanceCalculator() { + @Override + public double getDistance(String fromLocationId, String toLocationId) { + return vrp.getTransportCosts().getTransportCost(fromLocationId,toLocationId,0.,null,null); + } + }); + VehicleRoute route = solution.getRoutes().iterator().next(); + Boolean violated = analyser.skillConstraintIsViolatedAtActivity(route.getEnd(),route); + assertFalse(violated); + } + + + + @Test + public void skillViolationOnRoute_shouldWorkWhenNotViolated(){ + SolutionAnalyser analyser = new SolutionAnalyser(vrp,solution, new SolutionAnalyser.DistanceCalculator() { + @Override + public double getDistance(String fromLocationId, String toLocationId) { + return vrp.getTransportCosts().getTransportCost(fromLocationId,toLocationId,0.,null,null); + } + }); + + Iterator iterator = solution.getRoutes().iterator(); + iterator.next(); + VehicleRoute route = iterator.next(); + Boolean violated = analyser.skillConstraintIsViolated(route); + assertFalse(violated); + } + + + + + }