/******************************************************************************* * Copyright (c) 2014 Open Door Logistics (www.opendoorlogistics.com) * All rights reserved. This program and the accompanying materials * are made available under the terms of the GNU Lesser Public License v3 * which accompanies this distribution, and is available at http://www.gnu.org/licenses/lgpl.txt ******************************************************************************/ package com.opendoorlogistics.components.jsprit; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.TreeSet; import com.graphhopper.jsprit.core.problem.Location; import com.graphhopper.jsprit.core.problem.VehicleRoutingProblem; import com.graphhopper.jsprit.core.problem.VehicleRoutingProblem.FleetSize; import com.graphhopper.jsprit.core.problem.cost.VehicleRoutingTransportCosts; import com.graphhopper.jsprit.core.problem.driver.Driver; import com.graphhopper.jsprit.core.problem.job.Delivery; import com.graphhopper.jsprit.core.problem.job.Job; import com.graphhopper.jsprit.core.problem.job.Pickup; import com.graphhopper.jsprit.core.problem.job.Service; import com.graphhopper.jsprit.core.problem.job.Shipment; import com.graphhopper.jsprit.core.problem.solution.route.activity.DeliveryActivity; import com.graphhopper.jsprit.core.problem.solution.route.activity.PickupActivity; import com.graphhopper.jsprit.core.problem.solution.route.activity.TimeWindow; import com.graphhopper.jsprit.core.problem.solution.route.activity.TourActivity.JobActivity; import com.graphhopper.jsprit.core.problem.vehicle.Vehicle; import com.graphhopper.jsprit.core.problem.vehicle.VehicleImpl; import com.graphhopper.jsprit.core.problem.vehicle.VehicleType; import com.graphhopper.jsprit.core.problem.vehicle.VehicleTypeImpl; import com.graphhopper.jsprit.core.problem.vehicle.VehicleTypeImpl.VehicleCostParams; import com.opendoorlogistics.api.components.ComponentExecutionApi; import com.opendoorlogistics.api.components.PredefinedTags; import com.opendoorlogistics.api.distances.DistancesConfiguration; import com.opendoorlogistics.api.distances.DistancesOutputConfiguration.OutputDistanceUnit; import com.opendoorlogistics.api.distances.DistancesOutputConfiguration.OutputTimeUnit; import com.opendoorlogistics.api.distances.ODLCostMatrix; import com.opendoorlogistics.api.geometry.LatLong; import com.opendoorlogistics.api.tables.ODLColumnType; import com.opendoorlogistics.api.tables.ODLDatastore; import com.opendoorlogistics.api.tables.ODLTable; import com.opendoorlogistics.api.tables.ODLTableAlterable; import com.opendoorlogistics.api.tables.ODLTableReadOnly; import com.opendoorlogistics.api.tables.ODLTime; import com.opendoorlogistics.components.jsprit.tabledefinitions.InputTablesDfn; import com.opendoorlogistics.components.jsprit.tabledefinitions.StopsTableDefn; import com.opendoorlogistics.components.jsprit.tabledefinitions.StopsTableDefn.StopType; import com.opendoorlogistics.components.jsprit.tabledefinitions.VehiclesTableDfn; import com.opendoorlogistics.components.jsprit.tabledefinitions.VehiclesTableDfn.CostType; import com.opendoorlogistics.components.jsprit.tabledefinitions.VehiclesTableDfn.RowVehicleIndex; import gnu.trove.map.hash.TObjectIntHashMap; public class VRPBuilder { private VehicleRoutingProblem vrpProblem; private LocationsList locs; //private double maxFixedVehicleCost = 0; private VehicleRoutingTransportCostsImpl matrix; private final Map<String,List<BuiltStopRec>> jspritJobIdToStopRecords; private final Map<String,BuiltStopRec> stopIdToBuiltStopRecord; private InputTablesDfn dfn; private VRPConfig config; private ODLDatastore<? extends ODLTable> ioDb; private final ComponentExecutionApi api; private HashMap<String,ExtVehicleAttributes> extVehicleAttributeById = new HashMap<>(); private static class ExtVehicleAttributes{ double invSpeedMultiplier=1; double parkingCost=0; } private VRPBuilder(ComponentExecutionApi api){ this.api = api; jspritJobIdToStopRecords = api.getApi().stringConventions().createStandardisedMap(); stopIdToBuiltStopRecord= api.getApi().stringConventions().createStandardisedMap(); } public enum TravelCostType { COST(0), DISTANCE_KM(1), TIME(2); private final int matrixIndex; private TravelCostType(int matrixIndex) { this.matrixIndex = matrixIndex; } } public static class BuiltStopRec{ private final int rowNb; private final String stopId; private final StopType type; private Job job; BuiltStopRec(int rowNb, String stopId, StopType type) { this.rowNb = rowNb; this.stopId = stopId; this.type = type; } public Job getJSpritJob() { return job; } private void setJspritJob(Job job) { this.job = job; } public int getRowNbInStopsTable() { return rowNb; } public String getStopIdInStopsTable() { return stopId; } public StopType getType() { return type; } } /** * Class to ensure only one string id object is used per string identifier, making hash lookup quicker as it should test A = B in the equals * method first. Note we could use string.intern for this, but it's not clear when interned strings get garbage collected.. * * @author Phil * */ static class LocationsList { private final HashMap<String, LatLong> locs = new HashMap<>(); private final HashMap<LatLong, String> ids = new HashMap<>(); String addLatLong(LatLong ll) { String ret = ids.get(ll); if (ret == null) { ret = toId(ll); locs.put(ret, ll); ids.put(ll, ret); } return ret; } // LatLong getLatLong(String id) { // return locs.get(id); // } static String toId(LatLong ll) { StringBuilder builder = new StringBuilder(); builder.append(Double.toString(ll.getLatitude())); builder.append(','); builder.append(Double.toString(ll.getLongitude())); return builder.toString(); } } private class VehicleRoutingTransportCostsImpl implements VehicleRoutingTransportCosts { private final ODLCostMatrix distances; private final TObjectIntHashMap<String> idToIndex; private final double meanCostPerMillisecond; private final double meanCostPerMetre; private final double maxVehicleIndependentConnectedLocationsTravelCost; VehicleRoutingTransportCostsImpl(DistancesConfiguration distancesConfig, ComponentExecutionApi api,double meanCostPerMillisecond,double meanCostPerMetre) { this.meanCostPerMillisecond = meanCostPerMillisecond; this.meanCostPerMetre = meanCostPerMetre; // take copy of the distances and ensure in correct output units distancesConfig = distancesConfig.deepCopy(); distancesConfig.getOutputConfig().setOutputDistanceUnit(OutputDistanceUnit.METRES); distancesConfig.getOutputConfig().setOutputTimeUnit(OutputTimeUnit.MILLISECONDS); // build a table ODLTableAlterable table = api.getApi().tables().createAlterableTable("Locations"); table.addColumn(-1, PredefinedTags.LATITUDE, ODLColumnType.DOUBLE, 0); table.addColumn(-1, PredefinedTags.LONGITUDE, ODLColumnType.DOUBLE, 0); table.addColumn(-1, PredefinedTags.LOCATION_KEY, ODLColumnType.STRING, 0); for (Map.Entry<String, LatLong> entry : locs.locs.entrySet()) { api.getApi().tables().addRow(table, entry.getValue().getLatitude(), entry.getValue().getLongitude(), entry.getKey()); } // call the api distances = api.calculateDistances(distancesConfig, table); // read indices out idToIndex = new TObjectIntHashMap<>(); for (String id : locs.ids.values()) { int indx = distances.getIndex(id); idToIndex.put(id, indx); } // get the max connected transport cost... double max=0; for (String from : locs.ids.values()) { for (String to : locs.ids.values()) { float cost = getCost(from, to, null); // Unconnected locations would be infinite here... filter them if(!Float.isInfinite(cost)){ max = Math.max(max, cost); } } } maxVehicleIndependentConnectedLocationsTravelCost = max; } private float getTime(String fromId, String toId, Vehicle vehicle) { if(fromId == VRPConstants.NOWHERE || toId == VRPConstants.NOWHERE){ return 0; } if (vehicle == null){ return (float) ((float)distances.get(idToIndex.get(fromId), idToIndex.get(toId), TravelCostType.TIME.matrixIndex)); } double invMult=1; ExtVehicleAttributes eva = extVehicleAttributeById.get(vehicle.getId()); if(eva!=null){ invMult = eva.invSpeedMultiplier; } return (float)(distances.get(idToIndex.get(fromId), idToIndex.get(toId), TravelCostType.TIME.matrixIndex)*invMult); } private float getCost(String fromId, String toId, Vehicle vehicle) { double costPerMillisecond =meanCostPerMillisecond; double costPerMetre =meanCostPerMetre; if(vehicle!=null && vehicle.getType()!=null && vehicle.getType().getVehicleCostParams()!=null){ VehicleCostParams vcp = vehicle.getType().getVehicleCostParams(); costPerMillisecond =vcp.perTransportTimeUnit; costPerMetre = vcp.perDistanceUnit; } float cost= getCost(fromId, toId, costPerMillisecond, costPerMetre, vehicle); // apply parking costs to non-identical locations if(vehicle!=null && !fromId.equals(toId)){ ExtVehicleAttributes eva = extVehicleAttributeById.get(vehicle.getId()); if(eva!=null){ cost += eva.parkingCost; } } return cost; // if(vehicle != null){ // if(vehicle.getType() != null){ // costs = distance * vehicle.getType().getVehicleCostParams().perDistanceUnit; // } // } // if(vehicle == null) return getDistance(fromId, toId); // VehicleCostParams costParams = vehicle.getType().getVehicleCostParams(); // return costParams.perDistanceUnit*getDistance(fromId, toId) + costParams.perTimeUnit*getTime(fromId, toId); } /** * @param fromId * @param toId * @param costPerMillisecond * @param costPerMetre * @return */ float getCost(String fromId, String toId, double costPerMillisecond, double costPerMetre, Vehicle vehicle) { if(fromId == VRPConstants.NOWHERE || toId == VRPConstants.NOWHERE){ return 0; } double distance= getDistance(fromId, toId); double time = getTime(fromId, toId, vehicle); // Explicitly check for infinity (e.g. unconnected) and return infinity if found. // If costPerX=0 and its multiplied by infinity, we get NaN which we don't process properly later-on if(distance == Double.POSITIVE_INFINITY || time == Double.POSITIVE_INFINITY){ return Float.POSITIVE_INFINITY; } double cost = distance * costPerMetre + time * costPerMillisecond; return (float)cost; } /** * @param fromId * @param toId * @return */ float getDistance(String fromId, String toId) { if(fromId == VRPConstants.NOWHERE || toId == VRPConstants.NOWHERE){ return 0; } return (float)distances.get(idToIndex.get(fromId), idToIndex.get(toId), TravelCostType.DISTANCE_KM.matrixIndex); } private float get(String fromId, String toId, TravelCostType type, Vehicle vehicle) { switch(type){ case TIME: return getTime(fromId, toId, vehicle); case DISTANCE_KM: return getDistance(fromId, toId); case COST: return 0f; default: return 0f; } } @Override public double getBackwardTransportCost(Location fromId, Location toId, double arrivalTime, Driver driver, Vehicle vehicle) { return getCost(fromId.getId(), toId.getId(), vehicle); } @Override public double getTransportCost(Location fromId, Location toId, double departureTime, Driver driver, Vehicle vehicle) { return getCost(fromId.getId(), toId.getId(),vehicle); // if(Double.isInfinite(cost)){ // return cost; // } // return cost; } @Override public double getBackwardTransportTime(Location fromId, Location toId, double arrivalTime, Driver driver, Vehicle vehicle) { return getTime(fromId.getId(), toId.getId(), vehicle); } @Override public double getTransportTime(Location fromId, Location toId, double departureTime, Driver driver, Vehicle vehicle) { return getTime(fromId.getId(), toId.getId(), vehicle); } } private Service buildStop(ODLTableReadOnly table, int row, StopsTableDefn dfn, Service.Builder builder) { LatLong ll = dfn.latLong.getLatLong(table, row,false); Location location = Location.newInstance(locs.addLatLong(ll)); builder.setLocation(location); // validate and add quantities for (int q = 0; q < dfn.quantityIndices.length; q++) { builder.addSizeDimension(q, dfn.getQuantity(table, row, q)); } // validate and set service duration if (dfn.serviceDuration != -1) { builder.setServiceTime(dfn.getDuration(table, row).getTotalMilliseconds()); } // validate and set time window ODLTime[] tw = dfn.getTW(table, row); if (tw != null) { builder.setTimeWindow(new TimeWindow(tw[0].getTotalMilliseconds(), tw[1].getTotalMilliseconds())); } // add required skills for(String skill: getSkillsArray((String)table.getValueAt(row, dfn.requiredSkills))){ builder.addRequiredSkill(skill); } return builder.build(); } private List<Vehicle> buildVehicles(Map<Integer,List<RowVehicleIndex>> overrideVehiclesToBuild, BuildBlackboard bb) { List<Vehicle> vehicles = new ArrayList<>(); final VehiclesTableDfn vDfn = dfn.vehicles; ODLTableReadOnly table = ioDb.getTableByImmutableId(vDfn.tableId); if(overrideVehiclesToBuild==null){ int nr = table.getRowCount(); for (int row = 0; row < nr; row++) { int number = vDfn.getNumberOfVehiclesInType(table, row); if (config.isInfiniteFleetSize()) { number = 1; } buildVehiclesForType(bb,vDfn, table, row, number,new VehicleIdProvider() { @Override public String getId(ODLTableReadOnly vehicleTypesTable, int rowInVehicleTypesTable, int vehicleNb) { return vDfn.getId(vehicleTypesTable, rowInVehicleTypesTable, vehicleNb); } }, vehicles); } }else{ for(final Map.Entry<Integer, List<RowVehicleIndex>> entry:overrideVehiclesToBuild.entrySet()){ buildVehiclesForType(bb,vDfn, table, entry.getKey(), entry.getValue().size(),new VehicleIdProvider() { int callNb=0; @Override public String getId(ODLTableReadOnly vehicleTypesTable, int rowInVehicleTypesTable, int vehicleNb) { return entry.getValue().get(callNb++).id; } }, vehicles); } } return vehicles; } private interface VehicleIdProvider { String getId(ODLTableReadOnly vehicleTypesTable, int rowInVehicleTypesTable, int vehicleNb); } private static double costPerHourToCostPerMilli(double costPerHour){ return costPerHour /(60*60*1000); } private void buildVehiclesForType(BuildBlackboard bb, VehiclesTableDfn vDfn, ODLTableReadOnly vehicleTypesTable, int rowInVehicleTypesTable, int numberToBuild, VehicleIdProvider idProvider, List<Vehicle> vehicles) { // build type first VehicleTypeImpl.Builder vehicleTypeBuilder = VehicleTypeImpl.Builder.newInstance(vDfn.getId(vehicleTypesTable, rowInVehicleTypesTable, 0)); // add capacity for (int j = 0; j < config.getNbQuantities(); j++) { vehicleTypeBuilder.addCapacityDimension(j, vDfn.getCapacity(vehicleTypesTable, rowInVehicleTypesTable, j)); } // set costs - remembering we use metres and milliseconds internally double costPerMetre=vDfn.getCost(vehicleTypesTable, rowInVehicleTypesTable, CostType.COST_PER_KM) / 1000; vehicleTypeBuilder.setCostPerDistance(costPerMetre); double costPerMilli=costPerHourToCostPerMilli(vDfn.getCost(vehicleTypesTable, rowInVehicleTypesTable, CostType.COST_PER_HOUR)); vehicleTypeBuilder.setCostPerTransportTime( costPerMilli ); vehicleTypeBuilder.setCostPerWaitingTime( costPerHourToCostPerMilli(vDfn.getCost(vehicleTypesTable, rowInVehicleTypesTable, CostType.WAITING_COST_PER_HOUR)) ); double fixedCost = vDfn.getCost(vehicleTypesTable, rowInVehicleTypesTable, CostType.FIXED_COST); vehicleTypeBuilder.setFixedCost(fixedCost); // maxFixedVehicleCost = Math.max(maxFixedVehicleCost, fixedCost); // get available skills String [] skills = getSkillsArray((String)vehicleTypesTable.getValueAt(rowInVehicleTypesTable, dfn.vehicles.skills)); // read start and locations (can be null.. indicating zero distance) LatLong[] ends = vDfn.getStartAndEnd(vehicleTypesTable, rowInVehicleTypesTable); // Loop over all to create. Only create one if we have infinite fleet size as JSPRIT itself will duplicate them. VehicleType vehicleType = vehicleTypeBuilder.build(); for (int i = 0; i < numberToBuild; i++) { // Save the costs to the stats for each vehicle type, so the stats are weighted to the number of vehicles in a type bb.costsPerMetre.add(costPerMetre); bb.costsPerMillisecond.add(costPerMilli); // get id String id = idProvider.getId(vehicleTypesTable, rowInVehicleTypesTable, i); //vDfn.getId(vehicleTypesTable, rowInVehicleTypesTable, i); // add vehicle ID, speed multiplier to speedMultiplierMap ExtVehicleAttributes eva = new ExtVehicleAttributes(); eva.parkingCost = vDfn.getCost(vehicleTypesTable, rowInVehicleTypesTable, CostType.PARKING_COST); eva.invSpeedMultiplier=1.0/vDfn.getSpeedMultiplier(vehicleTypesTable, rowInVehicleTypesTable); extVehicleAttributeById.put(id, eva); // build the vehicle VehicleImpl.Builder vehicleBuilder = VehicleImpl.Builder.newInstance(id); vehicleBuilder.setType(vehicleType); // set start and end (hopefully not used internal to jsprit) // vehicleBuilder.setStartLocationCoordinate(Coordinate.newInstance(start.getLongitude(), start.getLatitude())); vehicleBuilder.setStartLocation(ends[0] != null? Location.newInstance(locs.addLatLong(ends[0])): Location.newInstance(VRPConstants.NOWHERE)); vehicleBuilder.setEndLocation(ends[1] != null? Location.newInstance(locs.addLatLong(ends[1])): Location.newInstance(VRPConstants.NOWHERE)); // always set this as we always have depot stops - they just might be dummy vehicleBuilder.setReturnToDepot(true); // set time window ODLTime[] tw = vDfn.getTimeWindow(vehicleTypesTable, rowInVehicleTypesTable); if (tw != null) { vehicleBuilder.setEarliestStart(tw[0].getTotalMilliseconds()); vehicleBuilder.setLatestArrival(tw[1].getTotalMilliseconds()); } // add skills for(String skill:skills){ vehicleBuilder.addSkill(skill); } vehicles.add(vehicleBuilder.build()); } } // int getStopRowById(String id) { // return stopIdToRow.get(id); // } BuiltStopRec getBuiltStop(String stopId){ return stopIdToBuiltStopRecord.get(stopId); } BuiltStopRec getBuiltStop(JobActivity jobActivity){ List<BuiltStopRec> rows = jspritJobIdToStopRecords.get(jobActivity.getJob().getId()); if(rows==null){ throw new RuntimeException("Unknown " + PredefinedTags.STOP_ID + " or " + PredefinedTags.JOB_ID + ": " + jobActivity.getJob().getId()); } if(rows.size()==1){ return rows.get(0); } if(rows.size()>1 && PickupActivity.class.isInstance(jobActivity)){ return rows.get(0); } if(rows.size()>1 && DeliveryActivity.class.isInstance(jobActivity)){ return rows.get(1); } return null; } // int getVehicleRowById(String id) { // Integer ret = vehicleIdToRow.get(id); // if (ret == null) { // return -1; // } // return ret; // } private String [] getSkillsArray(String s){ if(s==null){ return new String[]{}; } String [] split = s.split(","); ArrayList<String> ret = new ArrayList<>(); for(String splitStr : split){ splitStr = api.getApi().stringConventions().standardise(splitStr); if(splitStr.length()>0){ ret.add(splitStr); } } return ret.toArray(new String[ret.size()]); } private List<Job> buildJobs() { ArrayList<Job> ret = new ArrayList<>(); // do single stop jobs first StopsTableDefn stopsTableDfn = dfn.stops; ODLTableReadOnly stopsTable = ioDb.getTableByImmutableId(stopsTableDfn.tableId); int nr = stopsTable.getRowCount(); for (int row = 0; row < nr; row++) { // get type StopType type = stopsTableDfn.getStopType(stopsTable, row); if (type.getNbStopsInJob() == 1) { String id = stopsTableDfn.getId(stopsTable, row); // stop id is the same as job id for a single stop - check both are unique if(jspritJobIdToStopRecords.get(id)!=null || stopIdToBuiltStopRecord.get(id)!=null){ throw new RuntimeException("Duplicate stop id " + id); } // add the built stop record ArrayList<BuiltStopRec> rows = new ArrayList<>(); BuiltStopRec builtStopRec = new BuiltStopRec(row, id, type); rows.add(builtStopRec); jspritJobIdToStopRecords.put(id, rows); stopIdToBuiltStopRecord.put(id, builtStopRec); // create the correct type of single stop Service.Builder builder = null; switch (type) { case UNLINKED_DELIVERY: builder = Delivery.Builder.newInstance(id); break; case UNLINKED_PICKUP: builder = Pickup.Builder.newInstance(id); break; default: throw new RuntimeException(); } // built and save the jsprit stop builtStopRec.setJspritJob(buildStop(stopsTable, row, stopsTableDfn, builder)); ret.add(builtStopRec.getJSpritJob()); } } // do linked stops jobs Map<String,List<Integer>> multistops = stopsTableDfn.getGroupedByMultiStopJob(stopsTable,true); for (Map.Entry<String, List<Integer>> entry : multistops.entrySet()) { String shipmentId= entry.getKey(); Shipment.Builder builder = Shipment.Builder.newInstance(shipmentId); // loop over pickup then delivery ArrayList<BuiltStopRec> recs = new ArrayList<VRPBuilder.BuiltStopRec>(); List<Integer> rows = entry.getValue(); for (int i = 0; i <= 1; i++) { int row = rows.get(i); ODLTime serviceTime = stopsTableDfn.getDuration(stopsTable, row); LatLong ll = stopsTableDfn.latLong.getLatLong(stopsTable, row,false); String locId = locs.addLatLong(ll); // service time and location if (i == 0) { builder.setPickupServiceTime(serviceTime.getTotalMilliseconds()); builder.setPickupLocation(Location.newInstance(locId)); } else { builder.setDeliveryServiceTime(serviceTime.getTotalMilliseconds()); builder.setDeliveryLocation(Location.newInstance(locId)); } // time window ODLTime[] tw = stopsTableDfn.getTW(stopsTable, row); if (tw != null) { TimeWindow twObj = new TimeWindow(tw[0].getTotalMilliseconds(), tw[1].getTotalMilliseconds()); if (i == 0) { builder.setPickupTimeWindow(twObj); } else { builder.setDeliveryTimeWindow(twObj); } } // get the individual stop id and check unique String stopId = stopsTableDfn.getId(stopsTable, row); if(stopIdToBuiltStopRecord.get(stopId)!=null){ throw new RuntimeException("Duplicate stop id " + stopId); } // create built stop record BuiltStopRec rec= new BuiltStopRec(row, stopId, i==0?StopType.LINKED_PICKUP : StopType.LINKED_DELIVERY); stopIdToBuiltStopRecord.put(stopId, rec); recs.add(rec); } // validate and set quantities int [] quant1 = stopsTableDfn.getQuantities(stopsTable, rows.get(0)); int [] quant2 = stopsTableDfn.getQuantities(stopsTable, rows.get(1)); for(int q = 0 ; q<quant1.length ; q++){ // check quantities the same or the second one is zero (which probably means it was null) if(quant1[q]!=quant2[q] && quant2[q]!=0){ throw new RuntimeException("Job " + entry.getKey() + " has different quantities on its pickup and deliver stops."); } builder.addSizeDimension(q, quant1[q]); } // take the union of skills from both records and set them String [] pSkills = getSkillsArray((String)stopsTable.getValueAt(rows.get(0), stopsTableDfn.requiredSkills)); String [] dSkills = getSkillsArray((String)stopsTable.getValueAt(rows.get(1), stopsTableDfn.requiredSkills)); TreeSet<String> skillSet = new TreeSet<>(); skillSet.addAll(Arrays.asList(pSkills)); skillSet.addAll(Arrays.asList(dSkills)); for(String skill:skillSet){ builder.addRequiredSkill(skill); } // save jsprit job id to builtstoprecs if(jspritJobIdToStopRecords.get(shipmentId)!=null){ throw new RuntimeException("Duplicate job id " + shipmentId); } jspritJobIdToStopRecords.put(shipmentId,recs); // finally build the shipment Shipment shipment = builder.build(); for(BuiltStopRec rec:recs){ rec.setJspritJob(shipment); } ret.add(shipment); } return ret; } private static class MeanCalculator{ private double sum; private int count; void add(double value) { count++; sum += value; } double getMean(){ return sum/count; } } private static class BuildBlackboard{ final MeanCalculator costsPerMillisecond = new MeanCalculator(); final MeanCalculator costsPerMetre= new MeanCalculator(); } private void buildProblem(ODLDatastore<? extends ODLTable> ioDb, VRPConfig config,Map<Integer,List<RowVehicleIndex>> overrideVehiclesToBuild,ComponentExecutionApi api) { // // ensure we can't have stops with the depot names // stopIdToRow.put(VRPComponent.START_DEPOT_ID, null); // stopIdToRow.put(VRPComponent.END_DEPOT_ID, null); this.ioDb = ioDb; this.dfn = new InputTablesDfn(api.getApi(), config); this.config = config; VehicleRoutingProblem.Builder vrpBuilder = VehicleRoutingProblem.Builder.newInstance(); if (config.isInfiniteFleetSize() && overrideVehiclesToBuild==null) { vrpBuilder.setFleetSize(FleetSize.INFINITE); } else { vrpBuilder.setFleetSize(FleetSize.FINITE); } // build vehicles BuildBlackboard bb = new BuildBlackboard(); vrpBuilder.addAllVehicles(buildVehicles(overrideVehiclesToBuild,bb)); vrpBuilder.setFleetSize(config.isInfiniteFleetSize() ? FleetSize.INFINITE : FleetSize.FINITE); // build stops vrpBuilder.addAllJobs(buildJobs()); // build travel matrix double meanCostPerMilli = bb.costsPerMillisecond.count>0?bb.costsPerMillisecond.getMean():1; double meanCostPerMetre = bb.costsPerMetre.count>0? bb.costsPerMetre.getMean():1; matrix = new VehicleRoutingTransportCostsImpl(config.getDistances(), api,meanCostPerMilli,meanCostPerMetre); vrpBuilder.setRoutingCost(matrix); vrpProblem = vrpBuilder.build(); } public static VRPBuilder build(ODLDatastore<? extends ODLTable> ioDb, VRPConfig config,Map<Integer,List<RowVehicleIndex>> overrideVehiclesToBuild, ComponentExecutionApi api) { VRPBuilder ret = new VRPBuilder(api); ret.locs = new LocationsList(); ret.buildProblem(ioDb, config,overrideVehiclesToBuild,api); return ret; } public VehicleRoutingProblem getJspritProblem() { return vrpProblem; } public LocationsList getLocs() { return locs; } // /** // * Get travel cost from the stored matrix in a very inefficient way.... // */ // @Override // public double getTravelCost(LatLong from, LatLong to, double costPerMillisecond, double costPerMetre) { // if(from==null || to == null){ // return 0; // } // return matrix.getCost(LocationsList.toId(from), LocationsList.toId(to), costPerMillisecond, costPerMetre); // } // // @Override // public double getTravelDistance(LatLong from, LatLong to) { // if(from==null || to == null){ // return 0; // } // return matrix.getDistance(LocationsList.toId(from), LocationsList.toId(to)); // } // // @Override // public double getTravelTime(LatLong from, LatLong to) { // if(from==null || to == null){ // return 0; // } // return matrix.getTime(LocationsList.toId(from), LocationsList.toId(to)); // } public double getTravelDistanceKM(String from ,String to){ return matrix.get(from, to, TravelCostType.DISTANCE_KM, null); } public double getMaxVehicleIndependentConnectedLocationsTravelCost(){ return matrix.maxVehicleIndependentConnectedLocationsTravelCost; } // public Set<String> getJobIds(){ // return jspritJobIdToRows.keySet(); // } Collection<BuiltStopRec> getBuiltStops(){ return stopIdToBuiltStopRecord.values(); } }