Showing posts with label NL. Show all posts
Showing posts with label NL. Show all posts

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

Wednesday, April 5, 2017

3-opt with Neighbor Lists and DLB – simple

Compare the code below with the code for 2-opt with neighbor lists and DLB and notice that this algorithm is simplified, not truly bi-directional version.

proc One_City_3_Opt_ND(tour: var Tour_Array;
                       basePos: Tour_Index;
                       neighbor: Neighbor_Matrix;
                       position: var City_Position_In_Tour;
                       numberOfNeigbors: int;
                       DontLook: var DLB_Array): 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)

        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:
    Set_DLB_off(DontLook,
                  [ tour[goodMove.i], tour[(goodMove.i + 1) mod N],
                    tour[goodMove.j], tour[(goodMove.j + 1) mod N],
                    tour[goodMove.k], tour[(goodMove.k + 1) mod N] ])
    Make_3_Opt_Move(tour,
                    goodMove.i,  goodMove.j,  goodMove.k,
                    goodMove.optCase,  position)
  result = improved

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=100.
Problem # Method Time Best Avg Worst
1NN100100.00111.12122.44
NN + 2opt136088.9492.5898.91
NN + 2opt Take Best114089.4791.7595.40
NN + 3opt7916687.9989.6192.82
NN + 3opt-ND(24)458687.9989.8694.22
2NN100100.00107.46119.61
NN + 2opt114690.8594.70100.03
NN + 2opt Take Best104688.5992.0997.39
NN + 3opt6947387.2289.8994.23
NN + 3opt-ND(24)458687.2289.8793.04
3NN100100.00106.81117.56
NN + 2opt68188.9293.3497.90
NN + 2opt Take Best88189.3892.2896.22
NN + 3opt6611287.8589.5393.19
NN + 3opt-ND(24)410687.6889.0792.68
4NN100100.00108.58116.96
NN + 2opt146085.6088.5292.28
NN + 2opt Take Best125385.4086.8789.81
NN + 3opt7322683.1484.8087.22
NN + 3opt-ND(24)458683.1585.0187.43
5NN100100.00110.59124.43
NN + 2opt114692.7395.4298.30
NN + 2opt Take Best94091.4393.5897.02
NN + 3opt7239390.9192.3294.66
NN + 3opt-ND(24)416690.9192.2793.67
6NN100100.00107.15118.12
NN + 2opt125389.3592.7198.25
NN + 2opt Take Best93988.7490.8192.73
NN + 3opt6937387.9489.6391.73
NN + 3opt-ND(24)437388.2090.0092.09
7NN100100.00110.20121.48
NN + 2opt125392.3095.3198.94
NN + 2opt Take Best93991.5393.9497.52
NN + 3opt6530690.4191.7494.38
NN + 3opt-ND(24)427390.4191.6393.30
8NN100100.00105.35112.99
NN + 2opt125388.2092.0297.83
NN + 2opt Take Best83388.6990.7295.39
NN + 3opt6698087.6688.7794.75
NN + 3opt-ND(24)427387.6689.3492.54
9NN100100.00107.15115.57
NN + 2opt78192.0895.79100.95
NN + 2opt Take Best87590.9494.0698.73
NN + 3opt7168189.7691.3594.37
NN + 3opt-ND(24)439389.7691.3293.54
10NN100100.00106.56113.89
NN + 2opt125387.0790.8794.98
NN + 2opt Take Best104086.6688.6391.83
NN + 3opt7135386.0187.6490.86
NN + 3opt-ND(24)427386.0187.4191.10

Tuesday, April 4, 2017

2-opt with Neighbor Lists and Best from given City – alternative

Note

This example of code does not contain any new ideas and can be skipped by a reader.

proc One_City_2_Opt_N_Best_From_City(tour: var Tour_Array;
                                     basePos: Tour_Index;
                                     neighbor: Neighbor_Matrix;
                                     position: var City_Position_In_Tour;
                                     numberOfNeigbors: int): bool =
  ## Makes the best of improving moves starting from given city
  type
    Move2 = object
      i, j: Tour_Index
      gain: Length_Gain
  var
    improved: bool
    i, i_pred, i_succ, j, j_pred, j_succ: Tour_Index
    X0, X0_pred, X0_succ, Y0, Y0_pred, Y0_succ: City_Number
    gainExpected: Length_Gain
    bestMove: Move2

  improved = false
  bestMove.gain = 0
  
  i   = basePos
  X0  = tour[i]
  i_pred = (N + i - 1) mod N
  i_succ = (i + 1) mod N
  X0_pred = tour[i_pred]
  X0_succ = tour[i_succ]

  for neighbor_number in 0 .. numberOfNeigbors-1:
    Y0 = neighbor[X0][neighbor_number]    
    j  = position[Y0]
    j_pred = (N + j - 1) mod N
    j_succ = (j + 1) mod N
    Y0_pred = tour[j_pred]
    Y0_succ = tour[j_succ]

    if (X0_succ != Y0) and (Y0_succ != X0):
      gainExpected  = Gain_From_2_Opt(X0, X0_succ, Y0, Y0_succ)
      if gainExpected > bestMove.gain:
        bestMove = Move2(i: i,  j: j,  gain: gainExpected)
        improved = true
    if (X0_pred != Y0) and (Y0_pred != X0):
      gainExpected = Gain_From_2_Opt(X0_pred, X0, Y0_pred, Y0)
      if gainExpected > bestMove.gain:
        bestMove = Move2(i: i_pred,  j: j_pred,  gain: gainExpected)
        improved = true
  if improved:
    Make_2_Opt_Move(tour, bestMove.i, bestMove.j, position)
  result = improved

Monday, April 3, 2017

2-opt with Neighbor Lists and Best from given City

proc One_City_2_Opt_N_Best_From_City(tour: var Tour_Array;
                                     basePos: Tour_Index;
                                     neighbor: Neighbor_Matrix;
                                     position: var City_Position_In_Tour;
                                     numberOfNeigbors: int): bool =
  ## Makes the best of improving moves starting from given city
  type
    Move2 = object
      i, j: Tour_Index
      gain: Length_Gain
  var
    improved: bool
    pos_Y2, i, j: Tour_Index
    X1, X2, Y1, Y2: City_Number
    gainExpected: Length_Gain
    bestMove: Move2

  improved = false
  bestMove.gain = 0
  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:
        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):
          continue

        gainExpected = Gain_From_2_Opt(X1, X2, Y1, Y2)
        if  gainExpected > bestMove.gain:
          bestMove = Move2(i: i,  j: j,  gain: gainExpected)
          improved = true
  if improved:
    Make_2_Opt_Move(tour, bestMove.i, bestMove.j, position)
  result = improved
proc LS_2_Opt_N_BC(tour: var Tour_Array;
                   neighbor: Neighbor_Matrix;
                   neighborListLen: int = N-1) =
  ## Optimizes tour using 2-opt with neighbor lists and fixed radius,
  ## for each city makes the best of improving moves that starts from it
  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_Best_From_City(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 algorithms. Random planar problems, N=200.
Problem # Method Time Best Avg Worst
1NN100100.00109.58116.87
NN + 2opt442189.6692.5096.98
NN + 2opt Take Best495387.9390.7792.79
NN + 2opt-NR(24)26588.9491.3295.01
NN + 2opt-ND(24)23188.7292.7796.39
NN + 2opt-NDR(24)13489.3692.0595.94
NN + 2opt-N-BC(24)49789.2092.1896.75
NN + 2opt-NR-BC(24)23488.4290.9095.06
2NN100100.00106.22113.13
NN + 2opt428988.2791.2095.07
NN + 2opt Take Best505388.5390.1292.64
NN + 2opt-NR(24)26587.7890.2693.83
NN + 2opt-ND(24)26588.9691.5194.93
NN + 2opt-NDR(24)13187.8390.7993.90
NN + 2opt-N-BC(24)49987.8390.7493.97
NN + 2opt-NR-BC(24)23187.1189.9193.20
3NN100100.00110.81122.10
NN + 2opt350390.5894.0999.85
NN + 2opt Take Best425990.3193.0196.21
NN + 2opt-NR(24)20190.7693.9297.50
NN + 2opt-ND(24)17590.5094.74100.37
NN + 2opt-NDR(24)12591.1094.4799.50
NN + 2opt-N-BC(24)40390.6994.0999.06
NN + 2opt-NR-BC(24)17790.2693.5497.36
4NN100100.00108.19115.51
NN + 2opt415589.1692.4897.91
NN + 2opt Take Best482188.6391.1394.83
NN + 2opt-NR(24)33188.1891.5796.27
NN + 2opt-ND(24)23489.3693.0896.79
NN + 2opt-NDR(24)13188.7491.8296.09
NN + 2opt-N-BC(24)59788.4492.3396.40
NN + 2opt-NR-BC(24)19988.2391.6695.12
5NN100100.00105.47111.66
NN + 2opt310088.4491.2094.82
NN + 2opt Take Best312587.9390.8393.19
NN + 2opt-NR(24)19887.5590.3393.73
NN + 2opt-ND(24)19888.0491.8095.37
NN + 2opt-NDR(24)12388.0790.9895.03
NN + 2opt-N-BC(24)32288.0691.2194.08
NN + 2opt-NR-BC(24)17387.4590.1194.13
6NN100100.00106.99115.27
NN + 2opt511988.6191.7095.36
NN + 2opt Take Best555386.7489.3692.40
NN + 2opt-NR(24)29787.6790.3993.91
NN + 2opt-ND(24)23487.9991.1494.90
NN + 2opt-NDR(24)13187.6390.7593.93
NN + 2opt-N-BC(24)50087.4990.5593.80
NN + 2opt-NR-BC(24)23186.6989.7091.95
7NN100100.00109.06119.98
NN + 2opt485387.4390.8695.27
NN + 2opt Take Best475386.1489.4194.34
NN + 2opt-NR(24)26585.7489.6695.14
NN + 2opt-ND(24)26587.5891.1296.98
NN + 2opt-NDR(24)13486.6490.1596.67
NN + 2opt-N-BC(24)56586.8390.7495.90
NN + 2opt-NR-BC(24)33185.8389.6895.53
8NN100100.00106.79113.15
NN + 2opt495387.2691.3095.33
NN + 2opt Take Best565187.6589.5392.11
NN + 2opt-NR(24)23188.0790.2793.96
NN + 2opt-ND(24)23488.3191.6095.67
NN + 2opt-NDR(24)13188.1390.9995.28
NN + 2opt-N-BC(24)50087.8190.1194.61
NN + 2opt-NR-BC(24)23187.3689.2092.07
9NN100100.00104.58109.98
NN + 2opt408987.8691.8095.52
NN + 2opt Take Best501987.1889.6691.63
NN + 2opt-NR(24)39987.9690.7394.13
NN + 2opt-ND(24)23188.5391.9494.86
NN + 2opt-NDR(24)36588.2491.0694.24
NN + 2opt-N-BC(24)92988.0391.0094.26
NN + 2opt-NR-BC(24)53187.7390.2993.77
10NN100100.00106.10113.14
NN + 2opt668288.4690.9094.87
NN + 2opt Take Best558587.8289.7291.80
NN + 2opt-NR(24)29787.9490.7395.79
NN + 2opt-ND(24)23487.7091.5194.74
NN + 2opt-NDR(24)16587.9691.1896.28
NN + 2opt-N-BC(24)56387.8890.5693.03
NN + 2opt-NR-BC(24)23486.4489.7992.67

Saturday, April 1, 2017

2-opt with Neighbor Lists and optional DLB and FR

Note

This example of code does not contain any new ideas and can be skipped by a reader.

proc One_City_2_Opt_NDR(tour: var Tour_Array;
                        basePos: Tour_Index;
                        neighbor: Neighbor_Matrix;
                        position: var City_Position_In_Tour;
                        numberOfNeigbors: int;
                        DontLook: var DLB_Array;
                        useDLB: bool;
                        useFixedRadius: bool): bool =
  var
    improved: bool
    pos_Y2, i, j: Tour_Index
    X1, X2, Y1, Y2: City_Number
    d_X1_X2, d_X1_Y1: Length

  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]
      d_X1_X2 = distance(X1, X2)

      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
          
        d_X1_Y1 = distance(X1, Y1)
          
        if useFixedRadius:
          if d_X1_Y1 > d_X1_X2:
            break

        # the same as: Gain_From_2_Opt(X1, X2, Y1, Y2) > 0
        if (d_X1_X2 + distance(Y1, Y2)) -
           (d_X1_Y1 + distance(X2, Y2)) > 0:
          if useDLB:
            Set_DLB_off(DontLook, [X1, X2, Y1, Y2])
          Make_2_Opt_Move(tour, i, j, position)
          improved = true
          break two_loops
  result = improved
proc LS_2_Opt_NDR(tour: var Tour_Array;
                  neighbor: Neighbor_Matrix;
                  neighborListLen: int = N-1;
                  useDLB: bool = true;
                  useFixedRadius: bool = true) =
  ## Optimizes the given tour using 2-opt with neighbor lists
  ## and with optional Don't Look Bits (DLB) and fixed radis
  var
    locallyOptimal: bool = false
    improved: bool
    DontLook: DLB_Array
    baseCity: City_Number
    position: City_Position_In_Tour

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

  if useDLB:
    Set_DLB_off(DontLook, tour)  
  while not locallyOptimal:
    locallyOptimal = true
    for basePos in 0 .. N-1:
      baseCity = tour[basePos]
      if useDLB and isDLB_on(DontLook, baseCity):
        continue
      improved = One_City_2_Opt_NDR(tour, basePos, neighbor, position,
                                    numberOfNeigbors, DontLook,
                                    useDLB, useFixedRadius)
      if improved:
        locallyOptimal = false
      else:
        if useDLB:
          Set_DLB_on(DontLook, baseCity)

Thursday, March 30, 2017

2-opt with Neighbor Lists and Don't Look Bits

proc One_City_2_Opt_ND(tour: var Tour_Array;
                       basePos: Tour_Index;
                       neighbor: Neighbor_Matrix;
                       position: var City_Position_In_Tour;
                       numberOfNeigbors: int;
                       DontLook: var DLB_Array): bool =
  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:
        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):
          continue

        if Gain_From_2_Opt(X1, X2, Y1, Y2) > 0:
          Set_DLB_off(DontLook, [X1, X2, Y1, Y2])
          Make_2_Opt_Move(tour, i, j, position)
          improved = true
          break two_loops
  result = improved
proc LS_2_Opt_ND(tour: var Tour_Array;
                 neighbor: Neighbor_Matrix;
                 neighborListLen: int = N-1) =
  ## Optimizes the given tour using 2-opt with neighbor lists
  ## and dDon't Look Bits (DLB)
  var
    locallyOptimal: bool = false
    improved: bool
    DontLook: DLB_Array
    baseCity: City_Number
    position: City_Position_In_Tour

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

  Set_DLB_off(DontLook, tour)
  
  while not locallyOptimal:
    locallyOptimal = true
    for basePos in 0 .. N-1:
      baseCity = tour[basePos]
      if isDLB_on(DontLook, baseCity):
        continue
      improved = One_City_2_Opt_ND(tour, basePos, neighbor, position,
                                   numberOfNeigbors, DontLook)
      if not improved:
        Set_DLB_on(DontLook, baseCity)
      else:
        locallyOptimal = false

Wednesday, March 29, 2017

2-opt with Neighbor Lists and Fixed Radius

proc One_City_2_Opt_NR(tour: var Tour_Array;
                       basePos: Tour_Index;
                       neighbor: Neighbor_Matrix;
                       position: var City_Position_In_Tour;
                       numberOfNeigbors: int): bool =
  var
    improved: bool
    pos_Y2, i, j: Tour_Index
    X1, X2, Y1, Y2: City_Number
    radius: Length

  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]
      radius = distance(X1, X2)

      for neighbor_number in 0 .. numberOfNeigbors-1:
        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):
          continue
          
        if distance(X1, Y1) > radius:
          break

        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_NR(tour: var Tour_Array;
                 neighbor: Neighbor_Matrix;
                 neighborListLen: int = N-1) =
  ## Optimizes tour using 2-opt with neighbor lists and fixed radius
  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_NR(tour, basePos, neighbor,
                                   position, numberOfNeigbors)
      if improved:
        locallyOptimal = false

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

Position of given city in tour

Using neighbor lists forces us to use some convenient and quick method to determine for given city (a neighbor) what is its position in current tour. We use an array indexed by city numbers and storing corresponding positions.

type
  City_Position_In_Tour = array[City_Number, Tour_Index]
proc Create_Position_In_Tour(tour: Tour_Array): City_Position_In_Tour =
  ## Creates array with position of each city in tour
  var
    position: City_Position_In_Tour

  for pos in 0 .. N-1:
    position[tour[pos]] = pos
  result = position

Note that array of positions should be updated during any action that changes the tour, be it reversing a segment:

proc Reverse_Segment(tour: var Tour_Array;
                     startIndex, endIndex: Tour_Index;
                     position: var City_Position_In_Tour) =
  ## Reverses order of elements in segment [startIndex, endIndex]
  ## of tour, and updates positon[] array used with neighbour lists
  var
    left, right: Tour_Index

  let inversionSize = ((endIndex - startIndex + 1 + N) mod N) div 2
 
  left  = startIndex
  right = endIndex

  for counter in 1 .. inversionSize:
    swap(tour[left], tour[right])

    position[tour[left]]  = left
    position[tour[right]] = right

    left  = (left + 1) mod N
    right = (N + right - 1) mod N

...or shifting a segment:

proc Shift_Segment(tour: var Tour_Array; i, j, k: Tour_Index;
                   position: var City_Position_In_Tour) =
  ## Shifts the segment of tour:
  ## cities from t[i+1] to t[j] from their current position to position
  ## after current city t[k], that is between cities t[k] and t[k+1].
  ## Accordingly updates positon[] array.
  # Assumes:  k, k+1 are not within the segment [i+1..j]
  let
    segmentSize = (j - i + N) mod N
    shiftSize = ((k - i + N)  - segmentSize + N) mod N
    offset = i + 1 + shiftSize
  var
    pos, posNew: Tour_Index
    segment: seq[City_Number] = newSeq[City_Number](segmentSize)

  # make a copy of the segment before shift
  for pos in 0 .. segmentSize-1:
    segment[pos] = tour[(pos + i+1) mod N]

  # shift to the left by segmentSize all cities between old position
  # of right end of the segment and new position of its left end
  pos  = (i + 1) mod N
  for counter in 1 .. shiftSize:
    tour[pos] = tour[(pos + segmentSize) mod N]
    position[tour[pos]]  = pos
    pos  = (pos + 1) mod N
  #end_for counter loop

  # put the copy of the segment into its new place in the tour
  for pos in 0 .. segmentSize-1:
    posNew = (pos + offset) mod N 
    tour[posNew] = segment[pos]    
    position[tour[posNew]] = posNew

Sunday, March 26, 2017

Nearest neighbor lists

Note that neighbor lists are used to speed up the process of optimization, and using them may cost slightly worse quality of resulting tours, even if it not always occurs.

type
  Neighbor_Matrix = array [City_Number, seq[City_Number]]
proc Build_Neighbors_Matrix(No_Of_Neigbors: int = N-1): Neighbor_Matrix =
  ## Builds array of No_Of_Neigbors nearest neighbors of each city
  # neighbor[i][j] is j-th nearest neighbour of city number i.
  var
    currentCity, otherCity: City_Number
    idx: int
    neighbors_of_currentCity: seq[City_Number]
    neighbor: Neighbor_Matrix

  newSeq(neighbors_of_currentCity, N-1)
  for currentCity in 0 .. N-1:
    # create sequence of all neighbors of current city:
    idx = 0
    for otherCity in 0 .. N-1:
      if otherCity != currentCity:
        neighbors_of_currentCity[idx] = otherCity
        idx = idx + 1

    # Sort elements in neighbors_of_currentCity by ascending distance
    # to currentCity (use any convenient method of sorting), then take
    # first No_Of_Neigbors cities from the result.
    sort(neighbors_of_currentCity) do (x,y: City_Number) -> int:
      result = cmp(distance(currentCity, x), distance(currentCity, y))

    neighbor[currentCity] = @[]
    setlen(neighbor[currentCity], No_Of_Neigbors)
    for idx in 0 .. No_Of_Neigbors-1:
      neighbor[currentCity][idx] = neighbors_of_currentCity[idx]
  result = neighbor