Monalisa TSP Tour Length/Distance always wrong

139 views Asked by At

the tour length of www.math.uwaterloo.ca/tsp/data/ml/tour/monalisa_5757191.tour is supposed to be 5757191 yet my code gives a result of 5759929.877458311. Assuming that the claim of being willing to pay $1000 for any improvement to be true, then the below code MUST be wrong, and yet I see no flaw. At first I assumed it was a numerical precision issue so I started using arbitrary precision arithmetic and I got almost the same results.

 - double:                            5759929.877458459
 - BigDecimal(precision: 8 digits):   5759756.8
 - BigDecimal(precision: 16 digits):  5759929.877458103
 - BigDecimal(precision: 32 digits):  5759929.877458311
 - BigDecimal(precision: 64,128,256,512 digits): no change

all of these are WAY OFF from the stated 5757191. I even removed the cyclic edge from the beginning to the end on the assumption that it was a TSP path instead of a cycle, but it is still way to high.

The datafile clearly states "EDGE_WEIGHT_TYPE: EUC_2D" so it's not a matter of it being a different distance metric... (i.e. euclidean metric (i.e. sqrt(sum of squares)))

Here is the dataset of the points

in this format

nodeId integerCoordinate1 integerCoordinate2

Here is the code that reads thoses files and only returns the given tour length:

import java.util.HashMap;
import java.util.ArrayList;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import java.io.File;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.PrintWriter;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.MathContext;

public class TSPTest {
  //map from NodeId from the input mona-lisa100K.tsp file to it's 2d coordinates
  HashMap<Integer,int[]> pointMap;
  //the order of NodeId's visited in a tour (first in the order of `mona-lisa100K.tsp' then replaced with the NodeId's in the best known 'monalisa_5757191.tour' file)
  ArrayList<Integer> initState = new ArrayList<Integer>();
  File file;
  File tour;
  int dimension;//i.e. 2 in this case
  public TSPTest(File file, File tour) throws FileNotFoundException,IOException {
    this.file = file;
    this.tour = tour;
    readFromFile();
    loadTour();
  }
  public static void main(String... args) throws Throwable {
    TSPTest tester = new TSPTest(new File("mona-lisa100K.tsp"),new File("monalisa_5757191.tour"));
    int[] state = tester.initState();//the same values in 'monalisa_5757191.tour'
    System.out.println("state.length: "+state.length);
    double distance = tester.evaluate(state);
    System.out.println("TOUR DISTANCE="+distance);
    return;
  }
  synchronized void loadTour() throws FileNotFoundException,IOException {
    if (tour == null) return;
    BufferedReader in = new BufferedReader(new FileReader(tour), 1000000);
    String line;
    Pattern pLine = Pattern.compile("^\\d+$");
    initState.clear();
    while ((line = in.readLine()) != null) {
      Matcher matcher = pLine.matcher(line);
      if (!matcher.find()) {continue;}
      initState.add(Integer.valueOf(matcher.group()));
    }
    in.close();
    return;
  }
  synchronized void readFromFile() throws FileNotFoundException,IOException {
    pointMap = new HashMap<Integer, int[]>();
    BufferedReader in = new BufferedReader(new FileReader(file), 1000000);
    String line;
    Pattern pLine = Pattern.compile("\\d+( \\d+(?:\\.\\d*)?|\\.\\d+)+");
    Pattern p = Pattern.compile("\\d+(?:\\.\\d*)?|\\.\\d+");
    while ((line = in.readLine()) != null) {
      Matcher matcher = pLine.matcher(line);
      if (!matcher.find()) {continue;}
      matcher = p.matcher(line);
      if (!matcher.find()) {continue;}
      dimension = 0;
      while (matcher.find()) {++dimension;}
      Integer index;
      int[] cords = new int[dimension];
      matcher.reset();
      matcher.find();
      index = Integer.valueOf(matcher.group());
      initState.add(index);
      for (int i = 0; i < dimension; ++i) {
        matcher.find();
        cords[i] = Integer.parseInt(matcher.group());
        if (Math.abs(Double.parseDouble(matcher.group())-cords[i])>0.00001) {
          //I'm assuming all integer coordinates
          throw new RuntimeException(line);
        }
      }
      pointMap.put(index, cords);
    }
    in.close();
    return;
  }
  public int[] initState() {
    int[] state = new int[initState.size()];
    for (int i = 0; i < state.length; ++i) {
      state[i] = initState.get(i);
    }
    return state;
  }
  public double evaluate(int[] state) {
    int[] p1;
    int[] p2;
    java.math.MathContext mc = new java.math.MathContext(512,java.math.RoundingMode.HALF_EVEN);
    //double distance = 0.;
    java.math.BigDecimal distance = BigDecimal.ZERO;

    //get first point in TSP tour
    p1=pointMap.get(state[0]);
    for (int i = 1; i<state.length; ++i) {
      p2=pointMap.get(state[i]);//get the next point
      //distance += distance(p1,p2,null);
      distance = distance.add(distance(p1,p2,mc), mc);
      p1=p2;//next point is now the first point
    }
    //next two lines would make it a distance over a TSP cycle instead of a path
    //p2=pointMap.get(state[0]);
    //distance += distance(p1,p2);

    //return distance;
    return distance.doubleValue();
  }
  //commented out double version of code that is replaced with BigDecimal
  //private double distance(int[] p1, int[] p2, MathContext mc2) {
  private BigDecimal distance(int[] p1, int[] p2, MathContext mc2) {
    /*//old double based code
    double distance = 0;
    for (int i = 0; i < p1.length; ++i) {
      distance += Math.pow(p1[i]-p2[i],2);
    }
    distance = Math.sqrt(distance);
    return distance;
    */
    //I thought this might improve precision... (no difference)
    //return Math.hypot(p1[0]-p2[0],p1[1]-p2[1]);
    //+8 just to be safe :)
    MathContext mc = new MathContext(mc2.getPrecision()+8, java.math.RoundingMode.HALF_EVEN);
    //euclidean distance
    return sqrt(new BigDecimal(p1[0],mc).subtract(new BigDecimal(p2[0],mc)).pow(2,mc).add(new BigDecimal(p1[1],mc).subtract(new BigDecimal(p2[1],mc)).pow(2,mc)), mc);
  }
  //Babylonian method (aka Hero's method) (Newton's method)
  private java.math.BigDecimal sqrt(java.math.BigDecimal A, java.math.MathContext mc2) {
    MathContext mc = new MathContext(mc2.getPrecision()+8, java.math.RoundingMode.HALF_EVEN);
    BigDecimal TWO = new BigDecimal(2);
    BigDecimal x0 = new BigDecimal("0");
    BigDecimal x1 = new BigDecimal(Math.sqrt(A.doubleValue()));
    BigDecimal xp = null;
    while (!x0.equals(x1) && (xp==null || x1.compareTo(xp)!=0)) {//xp is used to detect a convergence loop (x1 swaps with x0 in an infinite loop)
      xp = x0;
      x0 = x1;
      x1 = A.divide(x0,mc);
      x1 = x1.add(x0,mc);
      x1 = x1.divide(TWO,mc);
    }
    return x1;
  }
}
0

There are 0 answers