/*******************************************************************************
* 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 gnu.trove.list.array.TIntArrayList;
import java.util.ArrayList;
/**
* An evaluated solution which can be incrementally updated.
* @author Phil
*
*/
final public class EvaluatedSolution extends HasCost implements Solution{
private final Problem problem;
private final CustomerRecord [] customers;
private final ClusterRecord [] clusters;
private boolean allCentresImmutable;
private class CustomerRecord{
private final int id;
private double travelSumToSameClusterCustomers;
private ClusterRecord cluster;
private CustomerRecord(int id) {
super();
this.id = id;
}
private double getQuantity(){
return problem.getLocationQuantity(id);
}
private int getInternalClusterIndx(){
return cluster!=null? cluster.id : -1;
}
@Override
public String toString(){
return "id=" + id + ", travelSum=" + travelSumToSameClusterCustomers + ", " + (cluster!=null? "cluster " + cluster.id:"unassigned");
}
// public void clear(){
// travelSumToSameClusterCustomers=0;
// cluster=null;
// }
}
private class ClusterRecord extends HasCost{
private final int id;
private double quantity;
private CustomerRecord centre;
private ArrayList<CustomerRecord> assignedCustomers = new ArrayList<>();
private ClusterRecord(int id) {
super();
this.id = id;
}
// public void clear(){
// cost.setZero();
// quantity=0;
// centre = null;
// customers.clear();
// }
@Override
public String toString(){
return "id=" + id + ", " + cost.toString() + ", quantity=" + quantity +"/" + problem.getClusterCapacity(id) + ", nbCustomers=" + assignedCustomers.size()
+ (centre!=null?", centre=" + centre.id:"");
}
private void getCustomers( TIntArrayList out){
out.clear();
int n = assignedCustomers.size();
out.ensureCapacity(n);;
for(int i =0 ; i < n ; i++){
out.add(assignedCustomers.get(i).id);
}
}
private void updateAll(){
// assert saveCostChecker();
quantity=0;
int n = assignedCustomers.size();
for(int i = 0 ; i < n ; i++){
CustomerRecord ci = assignedCustomers.get(i);
quantity += ci.getQuantity();
ci.travelSumToSameClusterCustomers = 0;
for(int j = 0 ; j < n ; j++){
if(i!=j){
CustomerRecord cj = assignedCustomers.get(j);
ci.travelSumToSameClusterCustomers += getTravelCost(ci, cj);
}
}
}
updateCentreAndCost();
// assert changedCentre || isCostEqualToChecker();
}
private boolean isValidState(){
return (centre==null && assignedCustomers.size()==0) || (centre!=null && assignedCustomers.size()>0);
}
private boolean updateCentreAndCost(){
CustomerRecord oldCentre = centre;
if(!isImmutableCentre(id)){
centre = null;
int n = assignedCustomers.size();
for(int i = 0 ; i < n ; i++){
CustomerRecord rec = assignedCustomers.get(i);
if(centre == null || rec.travelSumToSameClusterCustomers < centre.travelSumToSameClusterCustomers){
centre = rec;
}
}
}
cost.setZero();
if(centre!=null){
cost.setTravel(centre.travelSumToSameClusterCustomers);
}
double capacity = problem.getClusterCapacity(id);
if(quantity > capacity){
cost.setCapacityViolation(quantity - capacity);
}
if(!isValidState()){
throw new RuntimeException();
}
return oldCentre != centre;
}
private void insert(CustomerRecord customer){
if(!isValidState()){
throw new RuntimeException();
}
if(customer.cluster!=null){
throw new RuntimeException();
}
customer.cluster= this;
customer.travelSumToSameClusterCustomers =0;
// update the distances on all customer records if not using immutable centres
if(isImmutableCentre(id)==false){
int n = assignedCustomers.size();
for(int i = 0 ; i < n ; i++){
CustomerRecord other =assignedCustomers.get(i);
other.travelSumToSameClusterCustomers += getTravelCost(other, customer);
customer.travelSumToSameClusterCustomers +=getTravelCost(customer, other);
}
}else if(centre!=null){
centre.travelSumToSameClusterCustomers += getTravelCost(centre, customer);
}
// add the customer including the quantity
assignedCustomers.add(customer);
quantity += customer.getQuantity();
updateCentreAndCost();
}
private double getTravelCost(CustomerRecord cluster, CustomerRecord beingServed){
return problem.getCostPerUnitTravelled(beingServed.id)* problem.getTravel(cluster.id, beingServed.id);
}
private void remove(CustomerRecord customer){
if(!isValidState()){
throw new RuntimeException();
}
// check we're not removing the centre if centres are immutable
if(isImmutableCentre(id) && customer == centre){
throw new RuntimeException();
}
if(customer.cluster != this){
throw new RuntimeException();
}
// find record
int indx = assignedCustomers.indexOf(customer);
if(indx==-1){
throw new RuntimeException();
}
// remove quantity
quantity-= customer.getQuantity();
// remove record
assignedCustomers.remove(indx);
customer.cluster=null;
// update distances for all if not using immutable centres
customer.travelSumToSameClusterCustomers = 0;
if(isImmutableCentre(id)==false){
int n = assignedCustomers.size();
for(int i = 0 ; i < n ; i++){
CustomerRecord other =assignedCustomers.get(i);
other.travelSumToSameClusterCustomers -= getTravelCost(other, customer);
}
}
else if (centre!=null){
centre.travelSumToSameClusterCustomers -= getTravelCost(centre, customer);
}
updateCentreAndCost();
}
}
/**
* Copy constructor
* @param solution
*/
public EvaluatedSolution( EvaluatedSolution solution){
this(solution.problem);
// set customers to clusters
int nc= getNbCustomers();
for(int i =0 ; i< nc ; i++){
int clusterIndx = solution.getClusterIndex(i);
if(clusterIndx!=-1){
CustomerRecord customer = customers[i];
customer.cluster = clusters[clusterIndx];
customer.cluster.assignedCustomers.add(customer);
}
}
// set cluster centres
for(int i =0 ; i<clusters.length;i++){
int customerIndx = solution.getClusterCentre(i);
if(customerIndx!=-1){
clusters[i].centre = customers[customerIndx];
}
}
// update all counts etc
update();
}
/**
* Constructor which does all object allocation
* @param problem
*/
private EvaluatedSolution(Problem problem){
// allocate all objects
this.problem = problem;
this.customers = new CustomerRecord[problem.getNbLocations()];
for(int i =0 ; i < customers.length ; i++){
customers[i] = new CustomerRecord(i);
}
this.clusters = new ClusterRecord[problem.getNbClusters()];
for(int i =0 ; i < clusters.length ; i++){
clusters[i] = new ClusterRecord(i);
}
}
public EvaluatedSolution(Problem problem, int[]centres){
this(problem);
if(centres.length!=problem.getNbClusters()){
throw new RuntimeException();
}
for(int i =0 ; i < centres.length ; i++){
if(centres[i]!=-1){
int customerIndx = centres[i];
if(customers[customerIndx].cluster!=null){
throw new RuntimeException();
}
// assign customer to cluster and set this as its centre
CustomerRecord customer = customers[customerIndx];
customer.cluster = clusters[i];
customer.cluster.assignedCustomers.add(customer);
clusters[i].centre = customer;
}
}
update();
}
// private void debugCheck(){
// update();
// }
// private static int DEBUG_CALL_NB = 0;
/**
* Get the cost of setting the customer to the cluster. If the cluster
* index =-1, then customer is unloaded.
* @param customerIndx
* @param newClusterIndx
* @param out
*/
public void evaluateSet(int customerIndx , int newClusterIndx, Cost out){
assert saveCostChecker();
// set cost to minus the current cost
out.set(cost);
out.negate();
// get the customer record
CustomerRecord customer = customers[customerIndx];
// save original cluster
int originalClusterIndx = customer.getInternalClusterIndx();
// set new position
setCustomerToCluster(customerIndx, newClusterIndx);
// add new cost to the return object
out.add(cost);
// revert
setCustomerToCluster(customerIndx, originalClusterIndx);
assert isCostEqualToChecker();
}
/**
* Evaluate the swap between 2 customers currently on clusters
* @param customerIndx1
* @param customerIndx2
* @param out
*/
public void evaluateSwap(int customerIndx1, int customerIndx2, Cost out){
assert saveCostChecker();
// set cost to minus the current cost
out.set(cost);
out.negate();
// get current clusters
int original1 = customers[customerIndx1].getInternalClusterIndx();
int original2 = customers[customerIndx2].getInternalClusterIndx();
if(original1==original2){
out.setZero();
return;
}
if(original1==-1 || original2==-1){
throw new RuntimeException();
}
// swap
setCustomerToCluster(customerIndx1, original2);
setCustomerToCluster(customerIndx2, original1);
// add new cost to the return object
out.add(cost);
// finally revert
setCustomerToCluster(customerIndx1, original1);
setCustomerToCluster(customerIndx2, original2);
assert isCostEqualToChecker();
}
@Override
public int getClusterIndex(int customerIndx) {
return customers[customerIndx].getInternalClusterIndx();
// if(clusterIndx!=-1){
// return clusters[clusterIndx].centre.id;
// }
// return -1;
}
public int getClusterSize(int clusterIndx){
return clusters[clusterIndx].assignedCustomers.size();
}
public int getCustomer(int clusterIndx, int indx){
return clusters[clusterIndx].assignedCustomers.get(indx).id;
}
public void getCustomers(int clusterIndx, TIntArrayList out){
clusters[clusterIndx].getCustomers(out);
}
@Override
public void setCustomerToCluster(int customerIndx, int cluster) {
//debugCheck();
// get customer record and original and destination records
CustomerRecord customer = customers[customerIndx];
ClusterRecord original = customer.cluster;
ClusterRecord destination = cluster==-1 ? null : clusters[cluster];
if(original==destination){
// do nothing
return;
}
// check we're not moving a fixed centre to a different cluster
int fixedClusterIndx = problem.getFixedClusterIndexByLocationIndex(customerIndx);
if(fixedClusterIndx!=-1 && fixedClusterIndx!=cluster){
throw new RuntimeException();
}
// remove costs of involved clusters
if(original!=null){
cost.subtract(original.cost);
}
if(destination!=null){
cost.subtract(destination.cost);
}
// remove if needed
if(original!=null){
original.remove(customer);
}
// insert if needed
if(destination!=null){
destination.insert(customer);
}
// re-add costs of involved clusters
if(original!=null){
cost.add(original.cost);
}
if(destination!=null){
cost.add(destination.cost);
}
//debugCheck();
}
public Cost getCost(){
return cost;
}
public Cost getClusterCost(int clusterIndx){
return clusters[clusterIndx].cost;
}
public double getClusterQuantity(int clusterIndex){
return clusters[clusterIndex].quantity;
}
public int getClusterLocationCount(int clusterIndex){
return clusters[clusterIndex].assignedCustomers.size();
}
// public ClusterSummary [] createSummary(Problem problem){
// ClusterSummary []ret = new ClusterSummary[clusters.length];
// for(int i =0 ; i<ret.length ; i++){
// ret[i] = new ClusterSummary();
// ret[i].setClusterId(problem.getClusterId(i));
// ret[i].setOvercapacity(clusters[i].cost.getCapacityViolation());
// ret[i].setTravel(clusters[i].cost.getTravel());
// ret[i].setQuantity(clusters[i].quantity);
// ret[i].setCustomers(clusters[i].assignedCustomers.size());
// //ret[i].setPositionId(getc)
// }
// return ret;
// }
// public Cost getCost(int clusterIndx){
// return clusters[clusterIndx].cost;
// }
//
// public double getQuantity(int clusterIndx){
// return clusters[]
// }
@Override
public int getNbCustomers() {
return customers.length;
}
@Override
public int getClusterCentre(int clusterIndx) {
if(clusters[clusterIndx].centre!=null){
return clusters[clusterIndx].centre.id;
}
return -1;
}
@Override
public void setClusterCentre(int clusterIndx, int customerIndx) {
throw new RuntimeException();
}
@Override
public int getNbClusters() {
return clusters.length;
}
/**
* Update all internal variables accept assignment
* of customers to clusters. Cluster centres can change
* and hence cost can change.
*/
public void update(){
//assert countImmutableCentres()==0 || saveCostChecker();
cost.setZero();
for(ClusterRecord cluster : clusters){
cluster.updateAll();
cost.add(cluster.cost);
}
// assert countImmutableCentres()==0 ||isCostEqualToChecker();
}
@Override
public String toString(){
StringBuilder builder = new StringBuilder();
builder.append(cost.toString() + System.lineSeparator());
builder.append(System.lineSeparator());
for(ClusterRecord cluster: clusters){
builder.append(cluster + System.lineSeparator());
builder.append("Centre:" + cluster.centre + System.lineSeparator());
for(CustomerRecord customer : cluster.assignedCustomers){
builder.append("\t" + customer + System.lineSeparator());
}
builder.append(System.lineSeparator());
}
return builder.toString();
}
public void setAllCentresImmutable(boolean immutable) {
this.allCentresImmutable = immutable;
}
private boolean isImmutableCentre(int clusterIndx){
return allCentresImmutable || problem.getFixedLocation(clusterIndx)!=-1;
}
private int countImmutableCentres(){
int p = problem.getNbClusters();
if(allCentresImmutable){
return p;
}
int ret=0;
for(int i=0 ; i<p; i++){
if(isImmutableCentre(i)){
ret++;
}
}
return ret;
}
}