/******************************************************************************* * 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.demo; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Random; import com.opendoorlogistics.api.ODLApi; import com.opendoorlogistics.api.geometry.LatLong; import com.opendoorlogistics.api.tables.ODLDatastore; import com.opendoorlogistics.api.tables.ODLTable; import com.opendoorlogistics.api.tables.ODLTime; import com.opendoorlogistics.components.jsprit.VRPConfig; 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; public class DemoBuilder { private final static int MIN_STOP_START_TIME = 8; private final static int MAX_STOP_END_TIME= 18; private final Random random = new Random(123); private final ArrayList<Integer> freelocs = new ArrayList<>(); private final ODLApi api; private final DemoConfig demoConfig; private final VRPConfig config; private final ODLDatastore<? extends ODLTable> ioDb; private final InputTablesDfn dfn; private final DemoAddresses addresses; public DemoBuilder(ODLApi api,DemoConfig demoConfig, VRPConfig config, ODLDatastore<? extends ODLTable> ioDb) { this.api = api; this.demoConfig = demoConfig; this.config = config; this.ioDb = ioDb; dfn = new InputTablesDfn(api, config); addresses =DemoAddresses.DEMO_ADDRESSES.get(demoConfig.country); for(int i =0; i< addresses.size() ; i++){ freelocs.add(i); } } private LatLong [] getLatLongs(List<Integer> list){ LatLong [] ret = new LatLong[list.size()]; for(int i =0 ; i < ret.length;i++){ ret[i] = addresses.position(list.get(i)); } return ret; } private String getRandomSkills(){ StringBuilder builder = new StringBuilder(); if(random.nextDouble() < 0.25){ builder.append("tail-lift"); } if(random.nextDouble() < 0.25){ if(builder.length()>0){ builder.append(", "); } builder.append("refridgerated"); } return builder.toString(); } private class StopSeed{ int addressIndex1=-1; int addressIndex2=-1; } public void build(){ if(demoConfig.depotConcentration<1){ demoConfig.depotConcentration=1; } if(demoConfig.nbDepots > 100){ demoConfig.nbDepots = 100; } // choose depots ArrayList<Integer> depots = new ArrayList<>(); for(int i =0 ; i < demoConfig.nbDepots && freelocs.size()>0 ; i++){ depots.add(allocateAddress(0)); // if(i==0){ // depots.add(allocateAddress()); // }else { // depots.add(allocateFurthestFreeAddress(getLatLongs(depots))); // } } // assign vehicles evenly int nbFreeVehicles = demoConfig.nbVehicles; int [] vehicleCountByDepot = new int[depots.size()]; while(nbFreeVehicles>0){ for(int i =0 ; i< vehicleCountByDepot.length && nbFreeVehicles>0;i++){ vehicleCountByDepot[i]++; nbFreeVehicles--; } } // choose stop addresses LatLong[] depotlls = getLatLongs(depots); ArrayList<StopSeed> stopSeeds = new ArrayList<>(); int nbChosenStops=0; while(nbChosenStops< demoConfig.nbStops && freelocs.size()>0){ StopSeed ss = new StopSeed(); LatLong [] centre = new LatLong[]{depotlls[random.nextInt(depotlls.length)]}; ss.addressIndex1= allocateNearestFreeAddress(demoConfig.depotConcentration,centre); nbChosenStops++; if(demoConfig.includePDs && nbChosenStops< demoConfig.nbStops && freelocs.size()>0 && random.nextBoolean()){ ss.addressIndex2= allocateNearestFreeAddress(demoConfig.depotConcentration,centre); nbChosenStops++; } stopSeeds.add(ss); } // assume vehicles have 5,000 kilos each and get average stop quantity based on number of stops long capacity = 5000; long totalCapacity = demoConfig.nbVehicles * capacity; double averageStopQuantity = (double)0.5 * totalCapacity / stopSeeds.size(); // write stops ODLTable stopsTable = ioDb.getTableAt(dfn.stops.tableIndex); api.tables().clearTable(stopsTable); for(int i =0 ; i< stopSeeds.size() ; i++){ StopSeed seed = stopSeeds.get(i); if(seed.addressIndex2==-1){ createNonPDStop(averageStopQuantity, i, seed,stopsTable); }else{ createPDStops(averageStopQuantity, i, seed, stopsTable); } } // write vehicles ODLTable vehiclesTable = ioDb.getTableAt(dfn.vehicles.tableIndex); api.tables().clearTable(vehiclesTable); for(int depotIndex =0 ; depotIndex< depots.size();depotIndex++){ if(demoConfig.includeSkills){ // create individual vehicles so they each have different skills for(int vehicle = 0 ; vehicle < vehicleCountByDepot[depotIndex]; vehicle++){ createVehicles(depotlls, capacity, vehiclesTable,depotIndex, 1, new Integer(vehicle+1).toString()); } }else{ // create vehicle types createVehicles(depotlls, capacity, vehiclesTable,depotIndex, vehicleCountByDepot[depotIndex], ""); } } } private void createVehicles(LatLong[] depotlls, long capacity, ODLTable vehiclesTable, int depotIndex, int nbVehicles, String namePostfix) { VehiclesTableDfn vehiclesDfn = dfn.vehicles; // create row int row=vehiclesTable.createEmptyRow(-1); // set name and id String name = "Dep" + (depotIndex+1) + "-Veh" + namePostfix; vehiclesTable.setValueAt(name, row, vehiclesDfn.id); vehiclesTable.setValueAt(name, row, vehiclesDfn.vehicleName); // set position LatLong ll = depotlls[depotIndex]; vehiclesTable.setValueAt(ll.getLatitude(), row, vehiclesDfn.start.latitude); vehiclesTable.setValueAt(ll.getLongitude(), row, vehiclesDfn.start.longitude); vehiclesTable.setValueAt(ll.getLatitude(), row, vehiclesDfn.end.latitude); vehiclesTable.setValueAt(ll.getLongitude(), row, vehiclesDfn.end.longitude); // set start and end times ODLTime startTime = new ODLTime(8, 30); ODLTime endTime = new ODLTime(18, 0); vehiclesTable.setValueAt(startTime, row, vehiclesDfn.tw.earliest); vehiclesTable.setValueAt(endTime, row, vehiclesDfn.tw.latest); // capacity for(int q=0; q < config.getNbQuantities(); q++){ vehiclesTable.setValueAt(capacity, row, vehiclesDfn.capacities[q]); } // skills if(demoConfig.includeSkills){ vehiclesTable.setValueAt(getRandomSkills(), row, vehiclesDfn.skills); } for(CostType ct:CostType.values()){ vehiclesTable.setValueAt(ct.defaultVal, row, vehiclesDfn.costs[ct.ordinal()]); } // vehiclesTable.setValueAt(100.0, row, vehiclesDfn.costs[CostType.FIXED_COST.ordinal()]); // vehiclesTable.setValueAt(10.0, row, vehiclesDfn.costs[CostType.COST_PER_HOUR.ordinal()]); // vehiclesTable.setValueAt(5, row, vehiclesDfn.costs[CostType.COST_PER_HOUR.ordinal()]); // vehiclesTable.setValueAt(0.05, row, vehiclesDfn.costs[CostType.COST_PER_KM.ordinal()]); vehiclesTable.setValueAt((long)nbVehicles, row, vehiclesDfn.number); vehiclesTable.setValueAt(1.0, row, vehiclesDfn.speedMultiplier); } private void createPDStops(double averageStopQuantity, int i, StopSeed seed, ODLTable stopsTable) { StopsTableDefn stopsDefn = dfn.stops; // create the pickup int row=stopsTable.createEmptyRow(-1); stopsTable.setValueAt("Pickup" + (i+1), row, stopsDefn.id); stopsTable.setValueAt(StopType.LINKED_PICKUP.getPrimaryCode(), row, stopsDefn.type); stopsTable.setValueAt("Job" + (i+1), row, stopsDefn.jobId); setStopAddress( seed.addressIndex1, row, stopsTable); setQuantitySkillsDuration(averageStopQuantity, row, stopsTable); // create the delivery row=stopsTable.createEmptyRow(-1); stopsTable.setValueAt("Delivery" + (i+1), row, stopsDefn.id); stopsTable.setValueAt(StopType.LINKED_DELIVERY.getPrimaryCode(), row, stopsDefn.type); stopsTable.setValueAt("Job" + (i+1), row, stopsDefn.jobId); setStopAddress( seed.addressIndex2, row, stopsTable); setDuration(row, stopsTable); // create time windows suitable for both int midHour = (int)(MIN_STOP_START_TIME + MAX_STOP_END_TIME)/2; ODLTime s1 = new ODLTime(MIN_STOP_START_TIME + random.nextInt(1), random.nextBoolean()?30:0); ODLTime e1 = new ODLTime(midHour - 1, random.nextInt(60)); ODLTime s2 = new ODLTime(midHour , random.nextInt(60)); ODLTime e2 = new ODLTime(MAX_STOP_END_TIME- 1, random.nextInt(60)); stopsTable.setValueAt(s1, row-1, stopsDefn.tw.earliest); stopsTable.setValueAt(e1, row-1, stopsDefn.tw.latest); stopsTable.setValueAt(s2, row, stopsDefn.tw.earliest); stopsTable.setValueAt(e2, row, stopsDefn.tw.latest); } private void createNonPDStop(double averageStopQuantity, int i, StopSeed seed, ODLTable stopsTable) { int addressIndx = seed.addressIndex1; int row=stopsTable.createEmptyRow(-1); StopsTableDefn stopsDefn = dfn.stops; stopsTable.setValueAt("Stop" + (i+1), row, stopsDefn.id); setStopAddress( addressIndx, row, stopsTable); setQuantitySkillsDuration(averageStopQuantity, row, stopsTable); // set type String type = StopType.UNLINKED_DELIVERY.getPrimaryCode(); if(demoConfig.includeUnlinkedPickups && random.nextBoolean()){ type = StopType.UNLINKED_PICKUP.getPrimaryCode(); } stopsTable.setValueAt(type, row, stopsDefn.type); ODLTime startTime = new ODLTime(MIN_STOP_START_TIME + random.nextInt(2), random.nextBoolean()?30:0); ODLTime endTime = new ODLTime(MAX_STOP_END_TIME-random.nextInt(2), 0); stopsTable.setValueAt(startTime, row, stopsDefn.tw.earliest); stopsTable.setValueAt(endTime, row, stopsDefn.tw.latest); } private void setQuantitySkillsDuration(double averageStopQuantity, int row,ODLTable stopsTable) { StopsTableDefn stopsDefn= dfn.stops; // set quantities for(int q=0 ; q<config.getNbQuantities() ; q++){ double quantity = averageStopQuantity + (random.nextDouble()-0.5) * averageStopQuantity * 0.5; quantity = Math.ceil(quantity); stopsTable.setValueAt(quantity, row, stopsDefn.quantityIndices[q]); } // skills if(demoConfig.includeSkills){ stopsTable.setValueAt(getRandomSkills(), row, stopsDefn.requiredSkills); } // set service setDuration(row, stopsTable); } private void setDuration(int row, ODLTable stopsTable) { int duration = 2 + random.nextInt(8); ODLTime time = new ODLTime(0, duration); stopsTable.setValueAt(time, row, dfn.stops.serviceDuration); } private void setStopAddress( int addressIndx,int row, ODLTable stopsTable) { StopsTableDefn stopsDefn = dfn.stops; stopsTable.setValueAt(addresses.companyName(addressIndx), row, stopsDefn.name); stopsTable.setValueAt(addresses.address(addressIndx), row, stopsDefn.address); stopsTable.setValueAt(addresses.position(addressIndx).getLatitude(), row, stopsDefn.latLong.latitude); stopsTable.setValueAt(addresses.position(addressIndx).getLongitude(), row, stopsDefn.latLong.longitude); } /** * @param freelocs * @return */ private int allocateAddress() { int index = random.nextInt(freelocs.size()); return allocateAddress(index); } /** * @param index * @return */ private int allocateAddress(int index) { int addressIndex = freelocs.get(index); freelocs.remove(index); return addressIndex; } private int allocateFurthestFreeAddress(LatLong ...froms){ double maxDist=0; int ret=-1; int n = freelocs.size(); for(int i =0 ; i<n;i++ ){ int addressIndex=freelocs.get(i); double mindist = getMinDistance(addressIndex, froms); if(mindist>maxDist){ maxDist = mindist; ret = i; } } return freelocs.remove(ret); } private int allocateNearestFreeAddress(int depotConcentration,LatLong ...froms){ double minDist=Double.MAX_VALUE; int ret=-1; // depot concentration.... 0 to 1 int selectNb = Math.max(1,depotConcentration); selectNb = Math.min(selectNb, freelocs.size()); List<Integer> tmpLocs = new ArrayList<>(freelocs); Collections.shuffle(tmpLocs, random); tmpLocs = tmpLocs.subList(0, selectNb); int n = tmpLocs.size(); for(int i =0 ; i<n;i++ ){ int addressIndex=tmpLocs.get(i); double d = getMinDistance(addressIndex, froms); if(d<minDist){ minDist = d; ret = addressIndex; } } int index = freelocs.indexOf(ret); freelocs.remove(index); return ret; } /** * @param addressIndex * @param froms * @return */ private double getMinDistance(int addressIndex, LatLong... froms) { LatLong other = addresses.position(addressIndex); // get the min distance from this point to any other depot double mindist=Double.MAX_VALUE; for(LatLong from:froms){ mindist = Math.min(api.geometry().calculateGreatCircleDistance(from, other),mindist); } return mindist; } }