/******************************************************************************* * 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.cluster.capacitated; import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.UUID; import javax.swing.Icon; import javax.swing.JPanel; import com.opendoorlogistics.api.ODLApi; import com.opendoorlogistics.api.components.ComponentConfigurationEditorAPI; import com.opendoorlogistics.api.components.ComponentExecutionApi; import com.opendoorlogistics.api.components.ODLComponent; import com.opendoorlogistics.api.distances.DistancesOutputConfiguration.OutputType; import com.opendoorlogistics.api.scripts.ScriptTemplatesBuilder; import com.opendoorlogistics.api.tables.ODLDatastore; import com.opendoorlogistics.api.tables.ODLDatastoreAlterable; import com.opendoorlogistics.api.tables.ODLTable; import com.opendoorlogistics.api.tables.ODLTableAlterable; import com.opendoorlogistics.api.tables.ODLTableDefinition; import com.opendoorlogistics.api.tables.ODLTableDefinitionAlterable; import com.opendoorlogistics.api.tables.ODLTableReadOnly; import com.opendoorlogistics.components.cluster.capacitated.data.Cluster; import com.opendoorlogistics.components.cluster.capacitated.data.Location; import com.opendoorlogistics.components.cluster.capacitated.data.Travel; import com.opendoorlogistics.components.cluster.capacitated.solver.ContinueCallback; import com.opendoorlogistics.components.cluster.capacitated.solver.EvaluatedSolution; import com.opendoorlogistics.components.cluster.capacitated.solver.FilterCallbackEvents; import com.opendoorlogistics.components.cluster.capacitated.solver.Problem; import com.opendoorlogistics.components.cluster.capacitated.solver.Solver; import com.opendoorlogistics.components.cluster.capacitated.solver.Solver.HeuristicType; import com.opendoorlogistics.core.components.ODLWizardTemplateConfig; import com.opendoorlogistics.core.tables.ODLRowReadOnly; import com.opendoorlogistics.core.tables.beans.BeanMapping; import com.opendoorlogistics.core.tables.beans.BeanMapping.BeanDatastoreMapping; import com.opendoorlogistics.core.tables.utils.TableUtils; import com.opendoorlogistics.core.utils.ObjectConverter; import com.opendoorlogistics.core.utils.Time; import com.opendoorlogistics.core.utils.iterators.IterableAdapter; import com.opendoorlogistics.core.utils.strings.Strings; import com.opendoorlogistics.utils.ui.Icons; final public class CapClusterComponent implements ODLComponent { @Override public String getId() { return "com.opendoorlogistics.components.cluster.capacitated"; } @Override public String getName() { return "Cluster using capacitated p-median clusterer"; } @Override public ODLDatastore<? extends ODLTableDefinition> getIODsDefinition(ODLApi api,Serializable configuration) { BeanDatastoreMapping mapping = createBeanMapping((CapClusterConfig)configuration); return mapping.getDefinition(); } private static BeanDatastoreMapping createBeanMapping(CapClusterConfig config) { if(config.isUseInputClusterTable()){ return BeanMapping.buildDatastore(Location.class, Cluster.class ); } return BeanMapping.buildDatastore(Location.class); } @Override public ODLDatastore<? extends ODLTableDefinition> getOutputDsDefinition(ODLApi api,int mode, Serializable configuration) { ODLDatastoreAlterable<? extends ODLTableDefinitionAlterable>ret= BeanMapping.buildDatastore(Cluster.class).getDefinition(); ret.setTableName(ret.getTableAt(0).getImmutableId(), "Capacitated cluster result"); return ret; } @SuppressWarnings("unchecked") @Override public void execute(final ComponentExecutionApi reporter,int mode,Object configuration, ODLDatastore<? extends ODLTable> ioDb, ODLDatastoreAlterable<? extends ODLTableAlterable> outputDb) { final CapClusterConfig config = (CapClusterConfig) configuration; reporter.postStatusMessage("Reading clusterer input information"); // read objects from the tables using bean mapping BeanDatastoreMapping mapping = createBeanMapping((CapClusterConfig)configuration); Object[][] objects = mapping.readObjectsFromDatastore(ioDb); ArrayList<Location> locations = new ArrayList<>(objects[0].length); for(Object obj:objects[0]){ locations.add((Location)obj); } if(locations.size()==0){ throw new RuntimeException("No valid input locations passed into cluster."); } // read clusters table, creating dummy location objects for their centre if needed Cluster[] clusters =null; String[]originalLocationIds = null; if(config.isUseInputClusterTable()){ clusters = Arrays.copyOf(objects[1], objects[1].length, Cluster[].class); originalLocationIds = new String[clusters.length]; for(int i =0 ; i<clusters.length; i++){ Cluster cluster = clusters[i]; originalLocationIds[i] = cluster.getLocationKey(); if(reporter.getApi().values().isTrue(cluster.getFixedLocation())){ // add dummy location object Location dummy = new Location(); dummy.setGlobalRowId(-1); dummy.setLatitude(cluster.getLatitude()); dummy.setLongitude(cluster.getLongitude()); dummy.setQuantity(0); // get unique dummy location id (input id may not be unique) String id = "#Cluster" + i + "_" + UUID.randomUUID().toString(); dummy.setClusterId(id); dummy.setId(id); cluster.setLocationKey(id); locations.add(dummy); } } }else{ clusters = Problem.createClusters(config.getNumberClusters(), config.getClusterCapacity()).toArray(new Cluster[config.getNumberClusters()]); } // get travel table *using appended table* reporter.postStatusMessage("Generating distances"); ODLTableReadOnly travelTable=reporter.calculateDistances(config.getDistancesConfig(), BeanMapping.convertToTable(locations, Location.class)); if(reporter.isCancelled()){ return; } // wrap travel table with an iterator which converts to travel object Iterable<Travel> travel = new IterableAdapter<ODLRowReadOnly, Travel>( TableUtils.readOnlyIterable(travelTable),new ObjectConverter<ODLRowReadOnly, Travel>() { @Override public Travel convert(ODLRowReadOnly o) { Travel travel = new Travel(); travel.setFromLocation(o.get(0).toString()); travel.setToLocation(o.get(1).toString()); travel.setCost((Double)o.get(2)); return travel; } }); // create problem object reporter.postStatusMessage("Initialising clusterer"); Problem problem = new Problem(reporter.getApi(),locations, Arrays.asList(clusters), travel); if(reporter.isCancelled()){ return; } // call solver final Date start = new Date(); final FilterCallbackEvents filter= new FilterCallbackEvents(); Solver solver = new Solver(problem, new ContinueCallback() { @Override public ContinueOption continueOptimisation(int nbSteps,HeuristicType type, EvaluatedSolution best) { if(reporter.isCancelled()){ return ContinueOption.USER_CANCELLED; } if (config.getMaxStepsOptimization() != -1 && nbSteps >= config.getMaxStepsOptimization()) { return ContinueOption.FINISH_NOW; } long ms = new Date().getTime() - start.getTime(); double secs = ms / 1000.0; if (config.getMaxSecondsOptimization() != -1 && secs > config.getMaxSecondsOptimization()) { return ContinueOption.FINISH_NOW; } if (reporter.isCancelled() || reporter.isFinishNow()) { return ContinueOption.FINISH_NOW; } if(filter.hasStateChanged(nbSteps,type, best) && best!=null){ String message = "Running time=" + Time.millisecsToString(ms) + "," + " Step=" + nbSteps + ", Process=" + Strings.convertEnumToDisplayFriendly(type.name()) + System.lineSeparator() + "Capacity violation=" + best.getCost().getCapacityViolation() + System.lineSeparator() + "Travel=" + + best.getCost().getTravel(); // System.out.println(message); reporter.postStatusMessage(message); } return ContinueOption.KEEP_GOING; } }); solver.setUseSwapMoves(config.isUseSwapMoves()); EvaluatedSolution sol = solver.run(); // update cluster objects with solution information if (!reporter.isCancelled() && sol != null) { for(int i = 0 ; i<clusters.length ; i++){ Cluster cluster = clusters[i]; int centreIndx = sol.getClusterCentre(i); // write location info if(reporter.getApi().values().isTrue(cluster.getFixedLocation())==false){ if(centreIndx!=-1){ Location centre = locations.get(centreIndx); cluster.setLatitude(centre.getLatitude()); cluster.setLongitude(centre.getLongitude()); cluster.setLocationKey(centre.getId()); } } else{ // set back original key name cluster.setLocationKey(originalLocationIds[i]); } // write costs etc cluster.setAssignedQuantity(sol.getClusterQuantity(i)); cluster.setAssignedTravelCost(sol.getClusterCost(i).getTravel()); cluster.setAssignedCapacityViolation(sol.getClusterCost(i).getCapacityViolation()); cluster.setAssignedLocationsCount(sol.getClusterLocationCount(i)); if(reporter.getApi().values().isTrue(cluster.getFixedLocation())){ // minus 1 location for the dummy created.. cluster.setAssignedLocationsCount(cluster.getAssignedLocationsCount()-1); } } } // write results back to tables reporter.postStatusMessage("Writing results out"); if (!reporter.isCancelled() && sol != null) { // write locations back int nbOriginalLocations = objects[0].length; for (int row = 0; row < nbOriginalLocations; row++) { Location location = locations.get(row); int clusterIndx = sol.getClusterIndex(row); if(clusterIndx==-1){ location.setClusterId(null); }else{ location.setClusterId(problem.getClusterId(clusterIndx)); } mapping.getTableMapping(0).updateTableRow(location, ioDb.getTableAt(0), location.getGlobalRowId()); } // write cluster information back if table was provided as input if(config.isUseInputClusterTable()){ for (int row = 0; row < problem.getNbClusters(); row++) { mapping.getTableMapping(1).updateTableRow(clusters[row], ioDb.getTableAt(1), clusters[row].getGlobalRowId()); } } // also write clusters as output BeanMapping.buildDatastore(Cluster.class).getTableMapping(0).writeObjectsToTable(clusters, outputDb.getTableAt(0)); } reporter.postStatusMessage("Finished clustering"); } @Override public Class<? extends Serializable> getConfigClass() { return CapClusterConfig.class; } @Override public JPanel createConfigEditorPanel(ComponentConfigurationEditorAPI factory,int mode,Serializable config, boolean isFixedIO) { return new CapClusterPanel((CapClusterConfig)config,factory,isFixedIO); } @Override public long getFlags(ODLApi api,int mode) { // TODO Auto-generated method stub return 0; } // @Override // public Iterable<ODLWizardTemplateConfig> getWizardTemplateConfigs(ODLApi api) { // ArrayList<ODLWizardTemplateConfig> ret = new ArrayList<>(); // // CapClusterConfig config = new CapClusterConfig(); // config.setUseInputClusterTable(false); // config.getDistancesConfig().getOutputConfig().setOutputType(OutputType.DISTANCE); // ret.add(new ODLWizardTemplateConfig(getName(), getName(), getName(),config)); // // config = new CapClusterConfig(); // config.setUseInputClusterTable(true); // String s = " - different capacities per cluster"; // ret.add(new ODLWizardTemplateConfig(getName(), getName() + s, getName() + s, config)); // // return ret; // } @Override public void registerScriptTemplates(ScriptTemplatesBuilder templatesApi) { CapClusterConfig config = new CapClusterConfig(); config.setUseInputClusterTable(false); config.getDistancesConfig().getOutputConfig().setOutputType(OutputType.DISTANCE); templatesApi.registerTemplate(getName(), getName(), getName(),getIODsDefinition(templatesApi.getApi(), config),config); config = new CapClusterConfig(); config.setUseInputClusterTable(true); String s = " - different capacities per cluster"; templatesApi.registerTemplate(getName(), getName() + s, getName() + s, getIODsDefinition(templatesApi.getApi(), config),config); } @Override public Icon getIcon(ODLApi api,int mode) { return Icons.loadFromStandardPath("capacitated-clusterer.png"); } @Override public boolean isModeSupported(ODLApi api,int mode) { return mode==ODLComponent.MODE_DEFAULT; } }