Monday, March 27, 2017

2-opt with Neighbor Lists

proc One_City_2_Opt_N(tour: var Tour_Array;
                      basePos: Tour_Index;
                      neighbor: Neighbor_Matrix;
                      position: var City_Position_In_Tour;
                      numberOfNeigbors: int): bool =
  ## Optimizes the given tour using 2-opt with neighours lists
  # For each edge looks for improvement considering neigbours
  # in ascending order of their distance
  var
    improved: bool
    pos_Y2, i, j: Tour_Index
    X1, X2, Y1, Y2: City_Number

  improved = false
  block two_loops:
    for direction in [forward, backward]:
      if direction == forward:
        i  = basePos
        X1 = tour[i]
        X2 = tour[(i+1) mod N]
      else:
        i  = (N + basePos - 1) mod N
        X2 = tour[i]
        X1 = tour[(i+1) mod N]  # == tour[basePos]

      for neighbor_number in 0 .. numberOfNeigbors-1:
        # after 2-opt move new tour would contain direct link X1-Y1,
        # so we look for city Y1 among cities that are close to city X1
        Y1 = neighbor[X1][neighbor_number]
        if direction == forward:
          j = position[Y1]
          Y2 = tour[(j+1) mod N]
        else:
          j  = (N + position[Y1] - 1) mod N  # pos[Y1] == j+1
          Y2 = tour[j]

        if (X2 == Y1) or (Y2 == X1):
          # 2-opt is for non-adjacent links only
          # by constuction X1 != Y1
          continue

        if Gain_From_2_Opt(X1, X2, Y1, Y2) > 0:
          Make_2_Opt_Move(tour, i, j, position)
          improved = true
          break two_loops
  result = improved
proc LS_2_Opt_N(tour: var Tour_Array;
                       neighbor: Neighbor_Matrix;
                       neighborListLen: int = N-1) =
  ## Optimizes the given tour using 2-opt with neighbor lists
  var
    locallyOptimal: bool = false
    improved: bool
    position: City_Position_In_Tour

  let numberOfNeigbors = min(neighborListLen, len(neighbor[0]))
  position = Create_Position_In_Tour(tour)

  while not locallyOptimal:
    locallyOptimal = true
    for basePos in 0 .. N-1:
      improved = One_City_2_Opt_N(tour, basePos, neighbor,
                                  position, numberOfNeigbors)
      if improved:
        locallyOptimal = false

Some simple statistics

Relative time, best length, average length and worst length for all tours created by NN heuristic and improved by 2-opt and 2-opt with neighbor lists (number of neighbors in parentheses). Random planar problems, N=100.
Problem # Method Time Best Avg Worst
1NN100100.00113.18126.03
NN + 2opt125392.3095.93101.34
NN + 2opt-N(99)93992.9895.97101.72
NN + 2opt-N(48)52092.8995.89101.72
NN + 2opt-N(24)31392.8995.56101.72
2NN100100.00106.57115.38
NN + 2opt87586.0290.9896.83
NN + 2opt-N(99)107486.1891.2396.04
NN + 2opt-N(48)58786.1891.2494.55
NN + 2opt-N(24)29386.9991.2395.95
3NN100100.00106.76115.62
NN + 2opt156682.1084.8792.41
NN + 2opt-N(99)93382.8085.4190.72
NN + 2opt-N(48)52683.0585.7091.73
NN + 2opt-N(24)30682.1085.6690.70
4NN100100.00108.63115.55
NN + 2opt166688.4891.5798.65
NN + 2opt-N(99)94088.6392.4697.13
NN + 2opt-N(48)52088.6392.6998.92
NN + 2opt-N(24)20688.3792.6697.79
5NN100100.00107.56119.38
NN + 2opt104691.2794.1798.35
NN + 2opt-N(99)93390.9994.80100.14
NN + 2opt-N(48)41991.8594.7398.68
NN + 2opt-N(24)20690.7094.69100.00
6NN100100.00110.60121.69
NN + 2opt156687.9690.8694.66
NN + 2opt-N(99)93387.7992.3396.87
NN + 2opt-N(48)42088.0992.2896.82
NN + 2opt-N(24)31387.7391.8496.76
7NN100100.00111.52123.61
NN + 2opt114690.0694.4597.38
NN + 2opt-N(99)83391.0094.7298.71
NN + 2opt-N(48)41990.6994.6797.86
NN + 2opt-N(24)31390.6494.5297.86
8NN100100.00111.82121.50
NN + 2opt136086.5790.2894.59
NN + 2opt-N(99)104086.7790.2895.18
NN + 2opt-N(48)51986.6490.0994.81
NN + 2opt-N(24)31386.7789.8695.55
9NN100100.00116.59127.95
NN + 2opt146090.1995.30104.15
NN + 2opt-N(99)104090.8296.62105.77
NN + 2opt-N(48)62690.8296.74105.77
NN + 2opt-N(24)31390.8296.60105.77
10NN100100.00108.10118.75
NN + 2opt88189.6894.3598.28
NN + 2opt-N(99)87489.9694.6497.56
NN + 2opt-N(48)48791.8494.7399.41
NN + 2opt-N(24)20091.2394.2399.17

6 comments:

  1. Hello!
    Thanks for all materials you put here, your blog has been very informative.
    I tried implementing this algorithm, but it just did not seem to work.
    Resulting tour was always worse, usually about 5% than just simple improved 2 opt. I used this on 100 cities with 25/50 neighbours.
    Do you have an idea about why it might be happening?
    Your statistics show as if it was a good algorithm for getting quality tours.

    ReplyDelete
  2. Hello!
    Worse resulting tours are not unexpected behavior, because neighbor lists are to speed up the process of optimization, and it can costs slightly worse quality, even if it is not always seen for 2opt and N=100. Please take a look at average and worst tours obtained in examples #4, 6 or 9.

    However, indeed 5% over simple 2opt, for *all* of randomly generated Euclidean instances and for *all* of their initial NN tours, suggests a problem. Please make sure that:
    a) neighbor lists contain nearest neighbors of each city, sorted by ascending distance
    b) array of positions is properly created and updated (in Make_2_Opt_Move)

    ReplyDelete
  3. Why you need direction in this algorithm ?

    ReplyDelete
  4. To consider both cases: breaking a link after or before base city. Note that Y1 is always taken from neigbours of X1.

    ReplyDelete
  5. Note that we can expect shorter tour when
    a) Y1 is near X1
    b) Y2 is near X2
    c) both a) and b)
    Since the algorithm above searches for Y1, and not for Y2, then we should additionally reverse direction to have a mirror image and thus check b).

    ReplyDelete
  6. such a very usefull website.

    please provide us a github/gitlab repo for all the source code listed in this website.

    thank you.

    ReplyDelete