/*******************************************************************************
* 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.solver;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import com.opendoorlogistics.api.ODLApi;
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.core.utils.iterators.IteratorUtils;
import com.opendoorlogistics.core.utils.strings.StandardisedCache;
import com.opendoorlogistics.core.utils.strings.Strings;
import gnu.trove.map.hash.TObjectIntHashMap;
final public class Problem {
private static final double MAX_TRAVEL_COST_MULTIPLIER = 1000;
private final List<Location>locations;
private final List<Cluster> clusters;
private final double [][]matrix;
//private final int [] matrixIdByCustomerIndex;
private final int [] fixedClusterLocations;
private final int [] fixedClusterIndexByLocationIndex;
public static List<Cluster> createClusters(int nbClusters, double capacity){
List<Cluster> clusters = new ArrayList<>(nbClusters);
for(int i =0 ; i<nbClusters; i++){
Cluster cluster = new Cluster();
clusters.add(cluster);
cluster.setClusterId(Integer.toString(i + 1));
cluster.setCapacity(capacity);
}
return clusters;
}
// /**
// * Create problem, automatically creating the clusters
// * @param customers
// * @param travel
// * @param nbClusters
// * @param capacity
// */
// public Problem(Iterable<Location> locations, Iterable<Travel>travel,int nbClusters, double capacity){
// this(locations, createClusters(nbClusters, capacity), travel);
// }
//
/**
* Create problem passing the available customers
* @param customers
* @param clusters
* @param travel
*/
public Problem(ODLApi api,Iterable<Location> customers,Iterable<Cluster> clusters,Iterable<Travel> travel){
this.locations= IteratorUtils.toList(customers);
this.clusters = IteratorUtils.toList(clusters);
this.fixedClusterIndexByLocationIndex = new int[locations.size()];
Arrays.fill(fixedClusterIndexByLocationIndex, -1);
// turn external location ids into internal
TObjectIntHashMap<String> externalToInternal = new TObjectIntHashMap<>();
int n =locations.size();
for(int i =0 ; i < n ; i++){
Location customer =locations.get(i);
String id = customer.getId();
if(id==null){
throw new RuntimeException("Null location id found.");
}
id = Strings.std(id);
if(externalToInternal.contains(id)){
throw new RuntimeException("Duplication location id found: " + id);
}
externalToInternal.put(id, i);
}
// validate any fixed cluster locations...
int nc = this.clusters.size();
fixedClusterLocations = new int[nc];
Arrays.fill(fixedClusterLocations, -1);
for(int i =0 ; i<nc ; i++){
Cluster cluster = this.clusters.get(i);
if(api.values().isTrue(cluster.getFixedLocation())){
String loc = cluster.getLocationKey();
if(loc==null){
throw new RuntimeException("Found cluster with fixed location but with null location key.");
}
loc = Strings.std(loc);
if(externalToInternal.containsKey(loc)==false){
throw new RuntimeException("Could not match cluster location key to input location: " + loc);
}
int locIndx = externalToInternal.get(loc);
if(fixedClusterIndexByLocationIndex[locIndx]!=-1){
throw new RuntimeException("Fixed cluster location shared by more than one cluster.");
}
fixedClusterIndexByLocationIndex[locIndx] = i;
fixedClusterLocations[i] = locIndx;
}
}
// allocate matrix
matrix = new double[n][];
for(int i = 0 ; i < n ; i++){
matrix[i] = new double[n];
}
// get maximum non-infinite travel cost
double maxTravelCost = 0;
for(Travel t : travel){
double c= t.getCost();
if(!isInfiniteCost(c)){
maxTravelCost = Math.max(maxTravelCost, c);
}
}
maxTravelCost *= MAX_TRAVEL_COST_MULTIPLIER;
// copy matrix across, saving the standardised form of all strings to speed things up
StandardisedCache stdCache = new StandardisedCache();
for(Travel t : travel){
// get from and to locations
String from = t.getFromLocation();
String to = t.getToLocation();
if(from == null || to==null){
continue;
}
from = stdCache.std(from);
to = stdCache.std(to);
// ensure both from and to are known
if(externalToInternal.contains(from)==false || externalToInternal.contains(to)==false){
continue;
}
int internalFrom = externalToInternal.get(from);
int internalTo = externalToInternal.get(to);
// road network graphs can be unconnected, giving an infinite travel cost; convert
// to our maximum cost so the algorithm can still cope with it...
double c = t.getCost();
if(isInfiniteCost(c)){
c = maxTravelCost;
}
matrix[internalFrom][internalTo] = c;
}
}
private boolean isInfiniteCost(double c) {
return Double.isNaN(c) || Double.isInfinite(c) || c == Double.MAX_VALUE;
}
public int getNbLocations(){
return locations.size();
}
public int getNbClusters(){
return clusters.size();
}
public int getFixedClusterIndexByLocationIndex(int locationIndx){
return fixedClusterIndexByLocationIndex[locationIndx];
}
/**
* Get the cluster's fixed location or -1 if not fixed.
* @param clusterIndex
* @return
*/
public int getFixedLocation(int clusterIndex){
return fixedClusterLocations[clusterIndex];
}
public double getClusterCapacity(int clusterIndx){
return clusters.get(clusterIndx).getCapacity();
}
public double getLocationQuantity(int customerIndx){
return locations.get(customerIndx).getQuantity();
}
public double getTravel(int customerId1, int customerId2){
return matrix[ customerId1] [ customerId2];
}
public double getCostPerUnitTravelled(int customerId){
return locations.get(customerId).getCostPerUnitTravel();
}
public String getClusterId(int clusterIndx){
return clusters.get(clusterIndx).getClusterId();
}
}