/** * */ package vroom.common.modeling.io; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.math.RoundingMode; import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.Map; import vroom.common.modeling.dataModel.Depot; import vroom.common.modeling.dataModel.DistanceMatrix; import vroom.common.modeling.dataModel.Fleet; import vroom.common.modeling.dataModel.IVRPInstance; import vroom.common.modeling.dataModel.IVRPRequest; import vroom.common.modeling.dataModel.Node; import vroom.common.modeling.dataModel.Request; import vroom.common.modeling.dataModel.StaticInstance; import vroom.common.modeling.dataModel.Vehicle; import vroom.common.modeling.dataModel.VehicleRoutingProblemDefinition; import vroom.common.modeling.dataModel.attributes.PointLocation; import vroom.common.modeling.util.BufferedDistance; import vroom.common.modeling.util.CostCalculationDelegate; import vroom.common.modeling.util.EuclidianDistance; import vroom.common.utilities.dataModel.IDHelper; import vroom.common.utilities.dataModel.ObjectWithIdComparator; /** * <code>TSPLibPersistenceHelper</code> is a specialization of {@link FlatFilePersistenceHelper} for instances written * in the extended TSPLib format (<a href="http://neo.lcc.uma.es/radi-aeb/WebVRP/data/Doc.ps">details</a>). * <p> * Creation date: Jul 6, 2010 - 6:16:13 PM * * @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 TSPLibPersistenceHelper extends FlatFilePersistenceHelper { static { Locale.setDefault(Locale.US); } /** a scale factor to convert fractional values to integer **/ private int mFracScale = 1; /** * Getter for fracScale : a scale factor to convert fractional values to integer * * @return the value of fracScale */ public int getCoordinatesScaleFactor() { return mFracScale; } /** * Setter for fracScale : a scale factor to convert fractional values to integer * * @param fracScale * the value to be set for fracScale */ public void setCoordinatesScaleFactor(int fracScale) { mFracScale = fracScale; } /** a scale factor to convert fractional demands to integers **/ private int mDemandsScaleFactor = 1; /** * Getter for a scale factor to convert fractional demands to integers * * @return the value of demScale */ public int getDemandsScaleFactor() { return this.mDemandsScaleFactor; } /** * Setter for a scale factor to convert fractional demands to integers * * @param demScale * the value to be set for a scale factor to convert fractional demands to integers */ public void setDemandsScaleFactor(int demScale) { this.mDemandsScaleFactor = demScale; } private static final IDHelper sIdHelper = new IDHelper(); public static enum EDGE_WEIGHT_FORMAT { LOWER_ROW } public static enum Specifications { NAME, TYPE, COMMENT, DIMENSION, CAPACITY, EDGE_WEIGHT_TYPE, EDGE_WEIGHT_FORMAT, EDGE_DATA_FORMAT, NODE_COORD_TYPE, DISPLAY_DATA_TYPE }; public static enum DataSection { NODE_COORD_SECTION, DEPOT_SECTION, DEMAND_SECTION, EDGE_DATA_SECTION, FIXED_EDGE_SECTION, DISPLAY_DATA_SECTION, TOUR_SECTION, EDGE_WEIGHT_SECTION, EOF }; private List<Depot> mDepots; private Map<Integer, IVRPRequest> mRequests; private DataSection mCurrentSection; /* * (non-Javadoc) * * @see * vroom.common.modeling.io.FlatFilePersistenceHelper#finalizeInstance(vroom * .modelling.VroomModelling.dataModel.IVRPInstance, java.lang.Object) */ @Override protected void finalizeInstance(IVRPInstance instance, Object... params) { instance.setDepots(mDepots); instance.addRequests(mRequests.values()); instance.setCostHelper(new BufferedDistance(instance.getCostDelegate())); } private int mEdgeI = 0; private int mEdgeJ = 0; private EDGE_WEIGHT_FORMAT mEdgeWeightFormat; /* * (non-Javadoc) * * @see * vroom.common.modeling.io.FlatFilePersistenceHelper#processLine(vroom.modelling * .VroomModelling.dataModel.IVRPInstance, java.lang.String, int, java.lang.Object) */ @Override protected void parseLine(IVRPInstance instance, String line, int lineNumber, Object... params) { boolean ignore = false; try { mCurrentSection = DataSection.valueOf(line.replace(" ", "")); ignore = true; } catch (IllegalArgumentException e) { // DO NOTHING ignore = false; } if (!ignore) { if (line.startsWith(" ")) { line = line.replaceFirst("\\s", ""); } String[] values = line.split("\\s+"); switch (mCurrentSection) { case EDGE_WEIGHT_SECTION: double[] edgesWeights = new double[values.length]; for (int i = 0; i < values.length; i++) { edgesWeights[i] = Double.valueOf(values[i]); } setEdgeWeights(instance, edgesWeights); break; case NODE_COORD_SECTION: int id = Integer.valueOf(values[0]) - 1; double x = Double.valueOf(values[1]) / getCoordinatesScaleFactor(); double y = Double.valueOf(values[2]) / getCoordinatesScaleFactor(); IVRPRequest req = new Request(id, new Node(id, new PointLocation(x, y))); mRequests.put(id, req); break; case DEMAND_SECTION: id = Integer.valueOf(values[0]) - 1; double dem = Double.valueOf(values[1]); req = mRequests.get(id); if (req == null) { req = new Request(id, new Node(id, new PointLocation(Double.NaN, Double.NaN))); mRequests.put(id, req); } req.setDemands(dem); break; case DEPOT_SECTION: id = Integer.valueOf(values[0]) - 1; if (id >= 0) { req = mRequests.get(id); mRequests.remove(id); Depot depot = new Depot(id, req.getNode().getLocation()); mDepots.add(depot); } break; case EOF: break; default: throw new IllegalArgumentException("Unsuported section: " + mCurrentSection); } } } private void setEdgeWeights(IVRPInstance instance, double[] edgesWeights) { boolean symmetrical = false; for (int i = 0; i < edgesWeights.length; i++) { switch (mEdgeWeightFormat) { case LOWER_ROW: if (mEdgeI == mEdgeJ) { mEdgeI++; mEdgeJ = 0; } symmetrical = true; break; default: throw new UnsupportedOperationException("Unsupported Edge Weght Format " + mEdgeWeightFormat); } ((DistanceMatrix) instance.getCostDelegate()).setDistance(mEdgeI, mEdgeJ, edgesWeights[i]); if (symmetrical) ((DistanceMatrix) instance.getCostDelegate()).setDistance(mEdgeJ, mEdgeI, edgesWeights[i]); switch (mEdgeWeightFormat) { case LOWER_ROW: mEdgeJ++; break; } } } /* * (non-Javadoc) * * @see * vroom.common.modeling.io.FlatFilePersistenceHelper#initializeInstance( * java.io.File, java.io.BufferedReader, java.lang.Object) */ @Override protected IVRPInstance initializeInstance(File input, BufferedReader reader, Object... params) throws IOException { String name = "NA"; double capacity = 0; VehicleRoutingProblemDefinition vrpDef = null; CostCalculationDelegate cd = null; String[] line; String key = null, value = null; // Guess the fleet size from the file name int fleetSize = Integer.valueOf(input.getName().substring( input.getName().lastIndexOf("k") + 1, input.getName().lastIndexOf("."))); boolean endSpec = false; int dimension = -1; while (!endSpec) { line = reader.readLine().split(":"); key = line[0].replaceAll(" ", ""); if (line.length > 1) { value = line[1].replaceFirst("\\s", ""); } Specifications spec = null; try { spec = Specifications.valueOf(key); } catch (IllegalArgumentException e) { endSpec = true; continue; } switch (spec) { case NAME: name = value; break; case DIMENSION: dimension = Integer.valueOf(value); break; case CAPACITY: capacity = Double.valueOf(value); break; case TYPE: // TODO select appropriate def vrpDef = VehicleRoutingProblemDefinition.VRP; break; case EDGE_WEIGHT_TYPE: value = value.replaceAll(" ", ""); if (value.contains("EUC_2D") || value.contains("EUC_3D")) { cd = new EuclidianDistance(); cd.setPrecision(0, RoundingMode.HALF_EVEN); } else if (value.contains("CEIL_2D")) { cd = new EuclidianDistance(); cd.setPrecision(0, RoundingMode.CEILING); } else if (value.contains("EXPLICIT")) { cd = new DistanceMatrix(dimension); cd.setPrecision(Integer.MAX_VALUE, RoundingMode.UNNECESSARY); } else { // TODO add specific cost delegate for other distances throw new IllegalArgumentException("Unsupported edge weight type: " + value); } break; case EDGE_WEIGHT_FORMAT: value = value.replaceAll("\\s+", ""); mEdgeWeightFormat = EDGE_WEIGHT_FORMAT.valueOf(value); default: break; } } StaticInstance instance = new StaticInstance(name, sIdHelper.nextId(), vrpDef); instance.setFleet(Fleet.newHomogenousFleet(fleetSize, new Vehicle(0, "Vehicle", capacity))); instance.setCostHelper(cd); mDepots = new LinkedList<Depot>(); mRequests = new HashMap<Integer, IVRPRequest>(); mEdgeI = 0; mEdgeJ = 0; try { mCurrentSection = DataSection.valueOf(key); } catch (IllegalArgumentException e) { endSpec = true; } return instance; } /** * Calculate a scaling factor to convert double coordinates into integer coordinates * * @param instance * @param precision * @return a scaling factor that can convert all coordinates into integer values with the given precision */ public static int calculateCoordFracScale(IVRPInstance instance, double precision) { int frac = 1; for (int d = 0; d < instance.getDepotCount(); d++) { Depot dep = instance.getDepot(d); double x = dep.getLocation().getX(); double y = dep.getLocation().getY(); while (Math.abs(x * frac - Math.round(x * frac)) > precision || Math.abs(y * frac - Math.round(y * frac)) > precision) { frac *= 10; } } for (IVRPRequest r : instance.getRequests()) { double x = r.getNode().getLocation().getX(); double y = r.getNode().getLocation().getY(); while (Math.abs(x * frac - Math.round(x * frac)) > precision * frac || Math.abs(y * frac - Math.round(y * frac)) > precision * frac) { frac *= 10; } } return frac; } /** * Calculate a scaling factor to convert double demands into integer demands * * @param instance * @param precision * @return a scaling factor that can convert all demands into integer values with the given precision */ public static int calculateDemFracScale(IVRPInstance instance, double precision) { int frac = 1; for (IVRPRequest r : instance.getRequests()) { double d = r.getDemand(); while (Math.abs(d * frac - Math.round(d * frac)) > precision * frac) { frac *= 10; } } return frac; } @Override public boolean writeInstance(IVRPInstance instance, File output, Object params) throws IOException { BufferedWriter w = new BufferedWriter(new FileWriter(output)); String comment = params instanceof String ? (String) params : "none"; // Name w.write(String.format("%s : %s\n", Specifications.NAME, instance.getName())); // Comment w.write(String.format("%s : %s\n", Specifications.COMMENT, comment)); // Type w.write(String.format("%s : %s\n", Specifications.TYPE, "CVRP")); // Dimension w.write(String.format("%s : %s\n", Specifications.DIMENSION, instance.getRequestCount() + instance.getDepotCount())); // Edge Weight Type w.write(String.format("%s : %s\n", Specifications.EDGE_WEIGHT_TYPE, instance .getCostDelegate().getDistanceType())); // Capacity w.write(String.format("%s : %s\n", Specifications.CAPACITY, (int) instance.getFleet() .getVehicle().getCapacity() * getDemandsScaleFactor())); // Nodes coordinates w.write(DataSection.NODE_COORD_SECTION.toString() + "\n"); List<IVRPRequest> requests = instance.getRequests(); Collections.sort(requests, new ObjectWithIdComparator()); // Autodetect the scaling factor for nodes coordinates if (getCoordinatesScaleFactor() <= 0) { setCoordinatesScaleFactor(calculateCoordFracScale(instance, 1e-3)); } // Coordinates and demands StringBuilder demands = new StringBuilder(); for (int d = 0; d < instance.getDepotCount(); d++) { Depot dep = instance.getDepot(d); int x = (int) Math.round(dep.getLocation().getX() * getCoordinatesScaleFactor()); int y = (int) Math.round(dep.getLocation().getY() * getCoordinatesScaleFactor()); w.write(String.format("%s %s %s\n", dep.getID() + 1, x, y)); demands.append(String.format("%s %s\n", dep.getID() + 1, 0)); // id++; } for (IVRPRequest r : requests) { int x = (int) Math .round(r.getNode().getLocation().getX() * getCoordinatesScaleFactor()); int y = (int) Math .round(r.getNode().getLocation().getY() * getCoordinatesScaleFactor()); w.write(String.format("%s %s %s\n", r.getID() + 1, x, y)); demands.append(String.format("%s %s\n", r.getID() + 1, (int) Math.round(r.getDemand() * getDemandsScaleFactor()))); // id++; } // Node demands w.write(DataSection.DEMAND_SECTION.toString() + "\n"); w.write(demands.toString()); // Depots w.write(DataSection.DEPOT_SECTION.toString() + "\n"); for (int d = 1; d <= instance.getDepotCount(); d++) { w.write(d + "\n"); } w.write("-1\n"); // End of file w.write(DataSection.EOF.toString()); w.flush(); w.close(); return true; } }