Saturday, April 15, 2017

Stop search after encountering the successor

Less popular speed-up is using search cut after encountering a successor of given city when iterating on its neighbors. The idea is based on the observation that lead to 2-opt fixed radius search.

We consider neighbours of city X1 to find a good candidate for Y1, the first of endpoints of second link to remove. If we make a 2-opt move, then Y1 would be the new tour successor of X1, so it should not be in distance from X1 greater then is X2, the current successor. Otherwise, the new link would be longer than the current one. Since we consider neighbors of X1 in ascending order of their distance from this city, encountering X2 during search means that all subsequent neighbors would give longer links to X1.

...
  for neighbor_1 in 0 .. numberOfNeigbors-1:
    Y2 = neighbor[X1][neighbor_1]
    j  = (position[Y2] + N - 1) mod N
    Y1 = tour[j]

    if (Y1 == X2):  # speed-up
      break
...

Analogous condition can be used in 3-opt, also for Z1, although justification in the case of 3-opt is not so strong, even when forcing only Y1 != X2.

Note that this condition rejects some valid 3-opt moves, Node Shifts that are used in 2.5-opt:

X1 Y1=X2 Z1
X1 Y1=X2 Z1

This speed-up is very effective and can be used instead of popular don’t look bits solution.

The full code for simple 3-opt and the speed-up in one loop only is as follows:

proc One_City_3_Opt_NC(tour: var Tour_Array;
                       basePos: Tour_Index;
                       neighbor: Neighbor_Matrix;
                       position: var City_Position_In_Tour;
                       numberOfNeigbors: int): bool =
  type
    Move3 = object
      i, j, k: Tour_Index
      optCase: Reconnection_3_optCase
      gain: Length_Gain
  var
    improved: bool
    i, j, k: Tour_Index
    X1, X2, Y1, Y2, Z1, Z2: City_Number
    k_6, k_7:  Tour_Index
    Z1_6, Z2_6, Z1_7, Z2_7: City_Number
    gainExpected: Length_Gain
    goodMove: Move3
  
  improved = false
  goodMove.gain = 0
  block three_loops:
    
    for direction in [forward, backward]:
      if direction == forward:
        i  = basePos
      else:
        i  = (N + basePos - 1) mod N
      X1 = tour[i]
      X2 = tour[(i+1) mod N]
 
      for neighbor_1 in 0 .. numberOfNeigbors-1:
        # new edges in optCase=6: *X1-Y2*, Y1-Z1, X2-Z2
        # new edges in optCase=7: *X1-Y2*, X2-Z1, Y1-Z2
        Y2 = neighbor[X1][neighbor_1]
        j  = (position[Y2] + N - 1) mod N
        Y1 = tour[j]

        if (Y1 != X1) and (Y1 != X2):
          gainExpected = Gain_From_2_Opt(X1, X2, Y1, Y2)
          if gainExpected > goodMove.gain:
            improved = true
            goodMove = Move3(i: i,  j: j,  k: j,
                             optCase: opt3_case_1, gain: gainExpected)

        if (Y1 == X2):  # NOTE
          break

        for neighbor_2 in 0 .. numberOfNeigbors-1:
          # new edges in optCase=6: X1-Y2, *Y1-Z1*, X2-Z2
          Z1_6 = neighbor[Y1][neighbor_2]
          k_6  = position[Z1_6]
          Z2_6 = tour[(k_6 + 1) mod N]
          # new edges in optCase=7: X1-Y2, X2-Z1, *Y1-Z2*
          Z2_7 = neighbor[Y1][neighbor_2]
          k_7  = (position[Z2_7] + N - 1) mod N
          Z1_7 = tour[k_7]
          
          if Between(i, j, k_6):
            gainExpected = Gain_From_3_Opt(X1, X2, Y1, Y2, Z1_6, Z2_6,
                                           opt3_case_6)
            if gainExpected > goodMove.gain:
              improved = true
              goodMove = Move3(i: i,  j: j,  k: k_6,
                               optCase: opt3_case_6, gain: gainExpected)

          if Between(i, j, k_7):
            gainExpected = Gain_From_3_Opt(X1, X2, Y1, Y2, Z1_7, Z2_7,
                                           opt3_case_7)
            if gainExpected > goodMove.gain:
              improved = true
              goodMove = Move3(i: i,  j: j,  k: k_7,
                               optCase: opt3_case_7, gain: gainExpected)

  if improved:
    Make_3_Opt_Move(tour,
                    goodMove.i,  goodMove.j,  goodMove.k,
                    goodMove.optCase,  position)
  result = improved
proc LS_3_Opt_NC(tour: var Tour_Array;
                 neighbor: Neighbor_Matrix;
                 neighborListLen: int = N-1) =
  var
    locallyOptimal: bool = false
    improved: bool
    baseCity: City_Number
    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:
      baseCity = tour[basePos]
      improved = One_City_3_Opt_NC(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 some 2-opt and 3-opt algorithms. Random planar problems, N=200.
Problem # Method Time Best Avg Worst
1NN100100.00106.52114.46
NN + 2opt472188.1790.8994.52
NN + 2opt Take Best511987.5389.0991.25
NN + 3opt-ND(24)585186.4487.9489.65
NN + 3opt-NC(24)358985.7687.8989.86
2NN100100.00104.27110.57
NN + 2opt362390.3092.4595.40
NN + 2opt Take Best388989.0790.6792.78
NN + 3opt-ND(24)555188.3189.5992.20
NN + 3opt-NC(24)295988.3689.4691.41
3NN100100.00110.24122.10
NN + 2opt383991.0394.6197.58
NN + 2opt Take Best445090.3491.7094.47
NN + 3opt-ND(24)580888.7290.6392.96
NN + 3opt-NC(24)363488.8290.7192.56
4NN100100.00106.87114.35
NN + 2opt395587.5390.6494.81
NN + 2opt Take Best452187.1089.3391.84
NN + 3opt-ND(24)578585.5387.3590.17
NN + 3opt-NC(24)342385.9987.4489.56
5NN100100.00106.86113.89
NN + 2opt372390.1693.6196.94
NN + 2opt Take Best442189.7992.1895.17
NN + 3opt-ND(24)598288.6190.6192.40
NN + 3opt-NC(24)369188.5790.6694.46
6NN100100.00106.80114.76
NN + 2opt485385.0388.1692.81
NN + 2opt Take Best508785.0786.9591.02
NN + 3opt-ND(24)588283.1984.8687.59
NN + 3opt-NC(24)382383.6985.1188.18
7NN100100.00104.23110.78
NN + 2opt448786.0088.6792.58
NN + 2opt Take Best528784.9286.9388.92
NN + 3opt-ND(24)588283.9285.5988.45
NN + 3opt-NC(24)392383.6385.6088.26
8NN100100.00109.86116.72
NN + 2opt528586.0189.9293.72
NN + 2opt Take Best595186.0489.0691.54
NN + 3opt-ND(24)598584.5786.8890.18
NN + 3opt-NC(24)352384.8386.7489.65
9NN100100.00104.99110.15
NN + 2opt432187.5090.4293.91
NN + 2opt Take Best472187.0188.9690.70
NN + 3opt-ND(24)595185.9787.4189.39
NN + 3opt-NC(24)405585.5187.3789.05
10NN100100.00106.83115.85
NN + 2opt432190.2992.6896.71
NN + 2opt Take Best472189.2690.5092.66
NN + 3opt-ND(24)571789.0890.3592.09
NN + 3opt-NC(24)382389.1590.2491.92

No comments:

Post a Comment