package vroom.common.modeling.io;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import umontreal.iro.lecuyer.probdist.Distribution;
import umontreal.iro.lecuyer.probdist.NormalDist;
import umontreal.iro.lecuyer.probdist.UniformIntDist;
import vroom.common.modeling.dataModel.Depot;
import vroom.common.modeling.dataModel.DynamicInstance;
import vroom.common.modeling.dataModel.Fleet;
import vroom.common.modeling.dataModel.IVRPInstance;
import vroom.common.modeling.dataModel.Node;
import vroom.common.modeling.dataModel.Request;
import vroom.common.modeling.dataModel.Vehicle;
import vroom.common.modeling.dataModel.VehicleRoutingProblemDefinition;
import vroom.common.modeling.dataModel.attributes.DeterministicDemand;
import vroom.common.modeling.dataModel.attributes.PointLocation;
import vroom.common.modeling.dataModel.attributes.RequestAttributeKey;
import vroom.common.modeling.dataModel.attributes.StochasticDemand;
import vroom.common.modeling.util.EuclidianDistance;
import vroom.common.utilities.dataModel.IDHelper;
/**
* <code>NovoaPersistenceHelper</code> is a extension of the
* {@link FlatFilePersistenceHelper} dedicated to the parsing and writing of
* instances in the format used by Novoa (2005)
* <p>
* Creation date: Apr 13, 2010 - 4:11:27 PM
* <p>
* <b>References</b>
* <p>
* Novoa, Clara M. (2005), <br/>
* Static and dynamic approaches for solving the vehicle routing problem with
* stochastic demands, <br/>
* Ph.D. dissertation, Lehigh University, United States - Pennsylvania,
* Publication No. AAT 3188502, <br/>
* Available online on the <a href=
* "http://proquest.umi.com/pqdweb?did=994239101&sid=2&Fmt=2&clientId=80016&RQT=309&VName=PQD"
* >Umi ProQuest website</a>
*
* @author Victor Pillac, <a href="http://uniandes.edu.co">Universidad de Los
* Andes</a>-<a href="http://copa.uniandes.edu.co">Copa</a> <a
* href="http://www.emn.fr">Ecole des Mines de Nantes</a>-<a
* href="http://www.irccyn.ec-nantes.fr/irccyn/d/en/equipes/Slp">SLP</a>
* @version 1.0
*/
public class NovoaPersistenceHelper extends FlatFilePersistenceHelper {
public static enum DemandDistribution {
UNIFORM, NORMAL
};
/** The relative path to the Novoa instances files */
public static String RELATIVE_INSTANCE_PATH = "../Instances/vrpsd/novoa";
private final static IDHelper ID_HELPER = new IDHelper();
/**
* Vehicle capacities for Novoa instances Format:
* [n,Set1:Q1,Set1:Q2,Set2:Q1,Set2:Q2]
*/
public static final int[][] CAPACITIES = { { 5, 9, 5, 9, 9 },
{ 8, 14, 9, 13, 10 }, { 20, 91, 58, 60, 45 },
{ 30, 137, 87, 90, 68 }, { 40, 183, 116, 120, 90 },
{ 60, 274, 175, 180, 135 }, { 100, 457, 291, 0, 0 },
{ 150, 686, 436, 0, 0 } };
/**
* Getter for the vehicle capacity
*
* @param size
* the instance size
* @param set
* the set (1 or 2)
* @param cap
* the capacity variant (0 for higher, 1 for lower)
* @return the capacity to set for the specified instance
*/
public static int getCapacity(int size, int set, int cap) {
return CAPACITIES[getInstanceId(size)][1 + 2 * (set - 1) + cap];
}
/**
* Conversion of an actual capacity to a 0-1 number
*
* @param size
* @param set
* @param realCap
* @return
*/
public static int getCapacityIdx(int size, int set, int realCap) {
int[] caps = CAPACITIES[getInstanceId(size)];
for (int id = 1; id < caps.length; id++) {
if (caps[id] == realCap) {
return id - 1 - 2 * (set - 1);
}
}
return -1;
}
/** Mapping between instance sizes and indexes */
public static final int[] SIZE_MAPPING = { 5, 8, 20, 30, 40, 60, 100, 150 };
/**
* Instance id lookup
*
* @param size
* the instance size
* @return an id for this instance, e.g. size 8 instances have id 0
* @see #SIZE_MAPPING
*/
public static int getInstanceId(int size) {
return Arrays.binarySearch(SIZE_MAPPING, size);
}
/**
* Instance name builder
*
* @param size
* the instance size
* @param set
* the set (1 or 2)
* @param rep
* the instance replica (from 1 to 5)
* @return the corresponding instance name
*/
public static String getInstanceName(int size, int set, int rep) {
String sset = "";
switch (set) {
case 1:
sset = "i";
break;
case 2:
sset = "newi";
break;
default:
assert false;
}
return String.format("%s_%sr%s.dat", sset, size, rep);
}
private double mExpectedDemandSum;
private int mMaxDemand;
private int mNumRequests;
private Random mRND = null;
/** The expected filling factor used to calculate the vehicle capacity **/
private double mExpectedFillingFactor = 1;
/**
* Getter for the expected filling factor
*
* @return The expected filling factor used to calculate the vehicle
* capacity
*/
public double getExpectedFillingFactor() {
return mExpectedFillingFactor;
}
/**
* Setter for the expected filling factor used to calculate the vehicle
* capacity
*
* @param fillingFactor
* the value to be set
*/
public void setExpectedFillingFactor(double fillingFactor) {
mExpectedFillingFactor = fillingFactor;
}
/** the conversion to be applied to customer demands **/
private DemandDistribution mDemandDistribution = DemandDistribution.UNIFORM;
/**
* Getter for the conversion to be applied to customer demands
*
* @return the value of name
*/
public DemandDistribution getDemandDistribution() {
return this.mDemandDistribution;
}
/**
* Setter for the conversion to be applied to customer demands
*
* @param name
* the value to be set for the conversion to be applied to
* customer demands
*/
public void setDemandDistribution(DemandDistribution name) {
this.mDemandDistribution = name;
}
/**
* Read the instance defined by the size, set, replica and capacity from
* folder {@link #RELATIVE_INSTANCE_PATH}
*
* @param size
* the instance size (5, 8, 20, 30, 40, 60 or 100)
* @param set
* the set (1 or 2)
* @param rep
* the replica (from 1 to 5)
* @param cap
* the capacity variant (0 for higher, 1 for lower)
* @param seed
* an optional seed to generate sampled demands. If set to
* <code>null</code> then the instance will contain demand
* distribution information.
* @return the corresponding instance.
* @throws IOException
*/
public DynamicInstance readInstance(int size, int set, int rep, int cap,
Long seed) throws IOException {
// Check parameters
// ----------------------------------------------------------------------
if (Arrays.binarySearch(SIZE_MAPPING, size) < 0) {
throw new IllegalArgumentException(String.format(
"Unkonwn size: %s", size));
}
if (set < 1 || set > 2) {
throw new IllegalArgumentException(String.format(
"Unsupported set argument: %s", set));
}
if (rep < 1 || rep > 5) {
throw new IllegalArgumentException(String.format(
"Unsupported instance number: %s", rep));
}
if (cap < 0 || cap > 1) {
throw new IllegalArgumentException(String.format(
"Unsupported vehicle capacity: %s", cap));
}
// ----------------------------------------------------------------------
return readInstance(new File(RELATIVE_INSTANCE_PATH + "/"
+ getInstanceName(size, set, rep)),
getCapacity(size, set, cap), seed);
}
/**
* Read the specified set of instances and return it as a list
*
* @param sizes
* the sizes to be read (5, 8, 20, 30, 40, 60 or 100)
* @param capacities
* the capacities to be read (0 for higher, 1 for lower)
* @param sets
* the sets to be read (1 or 2)
* @param seed
* an optional seed to generate sampled demands. If set to
* <code>null</code> then the instance will contain demand
* distribution information.
* @return a list containing the specified instances
* @throws IOException
*/
public List<DynamicInstance> readInstances(int[] sizes, int[] capacities,
int[] sets, Long seed) throws IOException {
ArrayList<DynamicInstance> instances = new ArrayList<DynamicInstance>(
sizes.length * capacities.length * sets.length * 5);
for (int set : sets) {
for (int s : sizes) {
for (int c : capacities) {
for (int r = 1; r <= 5; r++) {
instances.add(readInstance(s, set, r, c,
seed != null ? seed++ : null));
}
}
}
}
return instances;
}
/**
* Read an instance in the Novoa file format.
*
* @param input
* the file conaining the instance
* @param params
* the first element should be the instance capacity, the second
* is an optional random seed to generate an instance with
* sampled demands.
*/
@Override
public DynamicInstance readInstance(File input, Object... params)
throws IOException {
return (DynamicInstance) super.readInstance(input, params);
}
@Override
protected void parseLine(IVRPInstance instance, String line,
int lineNumber, Object... params) {
if (lineNumber == 0) {
mNumRequests = Integer.parseInt(line);
} else if (lineNumber <= mNumRequests) {
String[] values = line.split("\\s+");
int id = Integer.parseInt(values[0]);
double x = Double.parseDouble(values[1]);
double y = Double.parseDouble(values[2]);
int lD = Integer.parseInt(values[3]);
int uD = Integer.parseInt(values[4]);
Request request = new Request(id, new Node(id, new PointLocation(x,
y)));
if (mRND == null) {
request.setAttribute(RequestAttributeKey.DEMAND,
new StochasticDemand(getDemandDistribution(lD, uD)));
} else {
request.setAttribute(RequestAttributeKey.DEMAND,
new DeterministicDemand(lD + mRND.nextInt(uD - lD + 1)));
}
instance.addRequest(request);
mExpectedDemandSum += (uD + lD) / 2d;
mMaxDemand = Math.max(mMaxDemand, uD);
}
}
/**
* Returns a {@link Distribution} representing the demand
*
* @param low
* the demand lower value
* @param high
* the demand upper value
* @return a {@link Distribution} representing the demand
*/
protected Distribution getDemandDistribution(int low, int high) {
switch (getDemandDistribution()) {
case UNIFORM:
return new UniformIntDist(low, high);
case NORMAL:
// mu is the original expected demand
double mu = (low + high) / 2d;
// sigma such that demand is between low-1 and high+1 with 99.7%
// evaluation
double sigma = (high - low + 2) / 6d;
return new NormalDist(mu, sigma);
default:
return new UniformIntDist(low, high);
}
}
@Override
protected DynamicInstance initializeInstance(File input,
BufferedReader reader, Object... params) {
DynamicInstance instance = new DynamicInstance(String.format("%sc%s",
input.getName().replaceFirst(".dat", ""),
((Number) params[0]).intValue()), ID_HELPER.nextId(),
VehicleRoutingProblemDefinition.VRPSD);
// instance.setCostHelper(new BufferedDistance(new
// EuclidianDistance()));
instance.setCostHelper(new EuclidianDistance());
instance.setSymmetric(true);
List<Depot> depots = new LinkedList<Depot>();
depots.add(new Depot(0, "CentralDepot", new PointLocation(0, 0)));
instance.setDepots(depots);
mMaxDemand = 0;
mExpectedDemandSum = 0;
if (params.length > 1 && params[1] instanceof Number) {
mRND = new Random(((Number) params[1]).longValue());
}
return instance;
}
@Override
protected void finalizeInstance(IVRPInstance instance, Object... params) {
double cap = ((Number) params[0]).doubleValue();
instance.setFleet(Fleet.newHomogenousFleet(1, new Vehicle(0, "Vehicle",
cap)));
}
}