Showing posts with label 2-opt. Show all posts
Showing posts with label 2-opt. Show all posts

Monday, May 8, 2017

2-opt move speedup

If segment reversing is used as an independent 2-opt move, not as a part of sequence (e.g. to apply 3-opt move), then we can take advantage of the fact that the tour is cyclic. Therefore reversing a segment has the same effect as reversing its complementary segment and changing "natural orientation" of the tour array.

p1 p2 p3 p4
p1 p2 p3 p4

Using array representation of tour causes that reversing a segment is done by swapping elements of array. The number of swaps (thus the amount of time) needed to reverse a segment is proportional to the number of cities the segment consists of. So when the segment to reverse is longer than half of tour, then can we reverse the complementary segment to speed-up the transformation.

proc t_pred(pos: Tour_Index): Tour_Index {.inline.} =
  ## returns position of tour predecessor for given position 
  result = (N + pos - 1) mod N

proc t_succ(pos: Tour_Index): Tour_Index {.inline.} =
  ## returns position of tour successor for given position 
  result = (pos + 1) mod N
proc Make_2_Opt_Move_Fast(tour: var Tour_Array;
                          p1, p2, p3, p4: Tour_Index;
                          position: var City_Position_In_Tour) =
  ## Reverses order of elements in segment [p2, p3] of tour:
  ## if the complementary segment is shorter, reverses the complementary
  # Assumes: p1==t_pred(p2), p4==t_succ(p3)
  var
    city: City_Number
    left, right: Tour_Index
    inversionSize: int

  inversionSize = (p3 - p2 + 1)
  if inversionSize < 0:
    inversionSize = inversionSize + N

  if (2 * inversionSize > N):  # segment longer than half of tour
    left  = p4  # t_succ(p3)
    right = p1  # t_pred(p2)
    inversionSize = N - inversionSize
  else:
    left  = p2
    right = p3

  inversionSize = inversionSize div 2

  for counter in 1 .. inversionSize:
    city        = tour[right]
    tour[right] = tour[left]
    tour[left]  = city

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

    left  = t_succ(left)
    right = t_pred(right)
  #end_for counter loop

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

Sunday, March 26, 2017

2-opt with DLB and Fixed Radius - alternative

While the inequity which a 2-opt move must meet to improve a tour::

holds if at least one of the two conditions is met:

or

The inequity (a) means that for given (X1, X2) we can search for Y2 among those cities that are closer to X2 than is X1.

But alternatively the inequity between the two sums may be considered as a pair of inequities:

or

The inequity (c) means that for given (X1, X2) we can search for Y1 among those cities that are closer to X1 than is X2.

In fixed radius search we can use either (a) or (c), the radius is the same. When fixed radius searching is used with DLB, then probably searching around X1 for Y1 would be better – because we would rather prefer to not miss a good move for base city (X1) before activating its don’t look bit. When we want to continue searching and moving in given direction, then probably searching for Y2 around X2 could be better idea.

proc One_City_2_Opt_DLBR2(tour: var Tour_Array,
                          basePos: Tour_Index,
                          DontLook: var DLB_Array): bool =
  var
    improved: bool
    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[basePos]  # == tour[(i+1) mod N]
      radius = distance(X1, X2)

      for counter_2 in 0 .. N-1:
        j = counter_2
        Y1 = tour[j]
        Y2 = tour[(j+1) mod N]
        if direction == backward:
          swap(Y1, Y2)
        if (X1 == Y1) or (X2 == Y1) or (Y2 == X1):
          continue

        if distance(X1, Y1) > radius:  # NOTE
          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)
          improved = true
          break two_loops
  result = improved

2-opt with DLB and Fixed Radius

Note

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

proc One_City_2_Opt_DLBR(tour: var Tour_Array,
                         basePos: Tour_Index,
                         DontLook: var DLB_Array): bool =
  var
    improved: bool
    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[basePos]  # == tour[(i+1) mod N]
      radius = distance(X1, X2)

      for counter_2 in 0 .. N-1:
        j = counter_2
        Y1 = tour[j]
        Y2 = tour[(j+1) mod N]
        if direction == backward:
          swap(Y1, Y2)
        if (X1 == Y1) or (X2 == Y1) or (Y2 == X1):
          continue

        if distance(X2, Y2) > radius:
          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)
          improved = true
          break two_loops
  result = improved
proc LS_2_Opt_DLBR(tour: var Tour_Array) =
  ## Optimizes the given tour using 2-opt with Don't Look Bits (DLB)
  ## and fixed radius search
  var
    locallyOptimal: bool = false
    improved: bool
    DontLook: DLB_Array
    baseCity: City_Number

  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_DLBR(tour, basePos, DontLook)
      if not improved:
        Set_DLB_on(DontLook, baseCity)
      else:
        locallyOptimal = false

Friday, March 24, 2017

2-opt with Fixed Radius Search

Fixed radius search uses the property of the inequity which a 2-opt move must meet to improve a tour. We can benefit from this even more, but only when we consider links in the two orientations and the two symmetric forms.

forward (ABCD)backward (DCBA)
A B C D A B C D
implementation
look forwardlook backward
base base + 1 j j + 1 j j + 1 base - 1 base
X1 X2 Y1 Y2 Y2 Y1 X2 X1

Using this scheme we cannot miss an improving move even if we restrict possible choices to one inequity.

Example implementation:

type
  Orientation = enum forward, backward
proc One_City_2_Opt_R(tour: var Tour_Array,
                         basePos: Tour_Index): bool =
  var
    improved: bool
    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[basePos]  # == tour[(i+1) mod N]
      radius = distance(X1, X2)

      for counter_2 in 0 .. N-1:
        j = counter_2
        Y1 = tour[j]
        Y2 = tour[(j+1) mod N]
        if direction == backward:
          swap(Y1, Y2)
        if (X1 == Y1) or (X2 == Y1) or (Y2 == X1):
          continue

        if distance(X2, Y2) > radius:
          continue

        if Gain_From_2_Opt(X1, X2, Y1, Y2) > 0:
          Make_2_Opt_Move(tour, i, j)
          improved = true
          break two_loops
  result = improved
proc LS_2_Opt_R(tour: var Tour_Array) =
  ## Optimizes the given tour using 2-opt with fixed radius search
  var
    locallyOptimal: bool = false
    improved: bool

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

Thursday, March 23, 2017

Fixed radius search – general idea

The condition

Suppose that for some X1, X2, Y1, Y2 occurs:

Hence:

Therefore the inequity which a 2-opt move must meet to improve a tour::

holds if at least one of the two conditions is met:

or

Simple radius search with 2-opt

Note that for simplicity of the code below we do not change orientation and instead use both conditions.

proc LS_2_Opt_simpleRadius(tour: var Tour_Array) =
  var
    locallyOptimal: bool = false
    i, j: Tour_Index
    X1, X2, Y1, Y2: City_Number
    radius: Length

  while not locallyOptimal:
    locallyOptimal = true
    block two_loops:
      for counter_1 in 0 .. N-1:
        i  = counter_1
        X1 = tour[i]
        X2 = tour[(i+1) mod N]
        radius = distance(X1, X2)

        for counter_2 in 0 .. N-1:
          j = counter_2
          Y1 = tour[j]
          Y2 = tour[(j+1) mod N]
          if (X1 == Y1) or (X2 == Y1) or (Y2 == X1):
            continue

          if distance(X2, Y2) > radius:
            if distance(X1, Y1) > radius:
              continue

          if Gain_From_2_Opt(X1, X2, Y1, Y2) > 0:
            Make_2_Opt_Move(tour, i, j)
            locallyOptimal = false
            break two_loops

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 simple radius search algorithms. Random planar problems, N=100.
Problem # Method Time Best Avg Worst
1NN100100.00107.63117.95
NN + 2opt94086.9589.1894.09
NN + 2opt-simpleR72685.3788.8693.34
2NN100100.00107.02114.02
NN + 2opt78187.5290.9298.22
NN + 2opt-simpleR58787.9090.6495.50
3NN100100.00108.09121.54
NN + 2opt97487.5390.6796.83
NN + 2opt-simpleR58787.5391.0597.84
4NN100100.00109.31118.36
NN + 2opt87486.4791.6096.67
NN + 2opt-simpleR49386.8590.8897.71
5NN100100.00108.73120.07
NN + 2opt83385.7789.4496.25
NN + 2opt-simpleR62686.1289.3493.99
6NN100100.00106.37114.14
NN + 2opt83388.8391.2795.10
NN + 2opt-simpleR52687.2890.6795.23
7NN100100.00108.36116.70
NN + 2opt107482.5186.7391.88
NN + 2opt-simpleR68182.8186.5391.64
8NN100100.00109.35120.17
NN + 2opt68195.2097.46103.04
NN + 2opt-simpleR48794.3297.70102.40
9NN100100.00112.93125.15
NN + 2opt97590.5194.84100.73
NN + 2opt-simpleR58790.9795.1499.15
10NN100100.00110.36125.80
NN + 2opt83387.3892.3397.47
NN + 2opt-simpleR62687.8092.0297.50

Saturday, March 18, 2017

2-opt with DLB

type
  DLB_Array = array [City_Number, bool]
  Orientation = enum forward, backward
proc isDLB_on(DontLook: DLB_Array,
              city: City_Number): bool =
  return DontLook[city]

proc Set_DLB_on(DontLook: var DLB_Array,
                city: City_Number) =
  DontLook[city] = true

proc Set_DLB_off(DontLook: var DLB_Array,
                 city: City_Number) =
  DontLook[city] = false

proc Set_DLB_off(DontLook: var DLB_Array,
                 cities: openArray[City_Number]) =
  # turns off DLB for given cities
  for city in 0 .. len(cities)-1:
    DontLook[cities[city]] = false
proc One_City_2_Opt_DLB(tour: var Tour_Array,
                        basePos: Tour_Index,
                        DontLook: var DLB_Array): bool =
  var
    improved: bool
    i, j: Tour_Index
    X1, X2, Y1, Y2: City_Number

  improved = false
  block two_loops:
    for direction in [forward, backward]:  # NOTE: both tour neighbors
      # consider link between t[i] and t[i+1],
      # then between t[i-1] and t[i]
      if direction == forward:
        i  = basePos
      else:
        i  = (N + basePos - 1) mod N
      X1 = tour[i]
      X2 = tour[(i+1) mod N]

      for counter_2 in 0 .. N-1:
        j = counter_2
        Y1 = tour[j]
        Y2 = tour[(j+1) mod N]

        if (X1 == Y1) or (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)
          improved = true
          break two_loops
  result = improved
proc LS_2_Opt_DLB(tour: var Tour_Array) =
  ## Optimizes the given tour using 2-opt with Don't Look Bits (DLB)
  var
    locallyOptimal: bool = false
    improved: bool
    DontLook: DLB_Array
    baseCity: City_Number

  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_DLB(tour, basePos, DontLook)
      if not improved:
        Set_DLB_on(DontLook, baseCity)
      else:
        locallyOptimal = false

Friday, March 17, 2017

Don't Look Bits (DLB) – general idea

The idea

A local improvement algorithm can be made faster when we notice that the running time includes the time to needed perform the move, but also the time needed to examine possible good moves and find an improving move (if there is one).

If for a given city X1 we previously did not find any improving move and it still has the same neighbors (none of the links between this city and its neighbors has been changed), then chances that we will find an improving move for this city in current iteration are small (although non-zero, see below).

The concept based on this observation is known as don’t look bits (DLB). We use special flag for each of the cities. Initially all the flags are turned off, which means that we allow searching for improving moves starting from given city. When a search for improving move starting from city C fails, then the bit for this city is turned on. When a move is performed in which this city is involved as one of the endpoints of the exchanged links, then the bit for this city is turned off. When we consider candidates for X1, the first city of an improving move, we skip all cities which have their don’t-look bits set to on.

Comments

Consider also what happens with DLB when we examine moves “in one direction” only (see 2-opt basic algorithn). In such case we consider only one of the two links of X1, that is the link to its successor, the link between tour[i] and tour[i+1]. If we failed to found any improving move, then we turn the don’t-look bit for X1 on. If then in some subsequent iteration a move is performed in which X1 is one of the endpoints of the exchanged links, then we turn the don’t-look bit off. It works as expected. But what if a move changes the orientation of the segment in which X1 is inside, but is not at one of its ends? The don’t-look bit remains unchanged, although now the previous predecessor of X1 is its current successor, and we examined X1 only with its succcessor to verify that there are no improvements for X1. So now there could be some improving move for X1 in “forward” orientation, but it will not be examined, because the don’t-look bit for this city is still on.

That is why in the code below we consider both tour neighbors of a given city.

For the same reason of not missing improving moves we do not limit searching to the cases where j>i, but cosider all possible pairs of (i, j).

Aside from the above, it may seem impossible that new, improving move for given city could appear when both neighbors of this city remain unchanged, but let us take a closer look:

X0 Y1 Z1 X0_pred X0_succ Y2 Z2 no gain found from X0
X0 Y1 Z1 X0_pred X0_succ Y2 Z2 move with gain, not from X0
X0 Y1 Z1 X0_pred X0_succ Y2 Z2 after the move: possible gain from X0 with new links

However we should notice that in this case cities Y1 and Z1 have their don’t-look bits set to off after making the move. Therefore the new, possibly improving move: from X0 and involving the link between Y1 and Z1, will be examined in one of the subsequent iterations, that is as a move from Y1 or as a move from Z1.

Using Dont' Looks Bits with a simple 2-opt

type
  DLB_Array = array[City_Number, bool]
  Orientation = enum forward, backward
proc LS_2_Opt_DLB_General_Idea(tour: var Tour_Array) =
  ## Optimizes the given tour using 2-opt with Don't Look Bits (DLB) 
  var
    locallyOptimal: bool = false
    i, j: Tour_Index
    pos_2_Limit: Tour_Index
    X1, X2, Y1, Y2: City_Number
    DontLook: DLB_Array

  for city in 0 .. N-1:
    DontLook[city] = false

  while not locallyOptimal:
    locallyOptimal = true
    block three_loops:
      for counter_1 in 0 .. N-1:

        if DontLook[tour[counter_1]]:
          continue

        for direction in [forward, backward]:  # NOTE: both tour neighbors
          # consider link between t[i] and t[i+1],
          # then between t[i-1] and t[i]
          if direction == forward:
            i  = counter_1
          else:
            i  = (N + counter_1 - 1) mod N
          X1 = tour[i]
          X2 = tour[(i+1) mod N]

          for counter_2 in 0 .. N-1:
            j = counter_2  # 2nd cut between t[j] and t[j+1]
            Y1 = tour[j]
            Y2 = tour[(j+1) mod N]
            if (X1 == Y1) or (X2 == Y1) or (Y2 == X1):
              # the same city or adjacent cities
              continue

            if Gain_From_2_Opt(X1, X2, Y1, Y2) > 0:
              DontLook[X1] = false
              DontLook[X2] = false
              DontLook[Y1] = false
              DontLook[Y2] = false
              Make_2_Opt_Move(tour, i, j)
              locallyOptimal = false
              break three_loops
        # end_for backward loop
        DontLook[tour[counter_1]] = true
      # end_for counter_1 loop

It is expected that the resulting tours will be different from the ones from 2-opt First Take procedure.

See also an alternative

Instead of don't look bits speedup we can use an array indexed by city numbers, in which each element contains length of tour after improving from given city. This would make implementation simpler, but would require more memory, because of storing numbers (lengths), which are longer than booleans.

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 DLBs algorithms. Random planar problems, N=100.
Problem # Method Time Best Avg Worst
1NN100100.00112.31122.36
NN + 2opt114693.0997.73104.89
NN + 2opt-DLB31394.4299.86104.98
2NN100100.00108.83124.17
NN + 2opt146286.1690.2097.26
NN + 2opt-DLB29387.8291.2696.06
3NN100100.00106.21115.53
NN + 2opt156682.1985.7789.56
NN + 2opt-DLB31382.6687.3992.24
4NN100100.00105.83112.36
NN + 2opt114690.0294.7898.19
NN + 2opt-DLB21391.6494.9199.78
5NN100100.00111.19119.81
NN + 2opt78191.9195.4399.81
NN + 2opt-DLB29391.8196.31102.66
6NN100100.00112.89126.03
NN + 2opt136094.3599.05104.02
NN + 2opt-DLB20696.0299.56105.29
7NN100100.00109.35117.75
NN + 2opt114690.4793.2899.21
NN + 2opt-DLB31390.5593.8099.71
8NN100100.00109.02120.22
NN + 2opt125386.5091.8898.31
NN + 2opt-DLB31388.4592.6495.84
9NN100100.00108.79116.77
NN + 2opt94095.4697.58101.63
NN + 2opt-DLB42095.5098.14102.77
10NN100100.00105.40113.11
NN + 2opt126882.1285.6689.87
NN + 2opt-DLB29381.7886.4090.22

Saturday, March 4, 2017

2-opt: basic algorithm

Naive 2-optimization code

We can apply 2-opt move as an improvement step in general scheme of local optimization.

A naive version:

...
  for i in 0 .. N-1:   # outer loop is for X1-X2 link
    X1 = tour[i]
    X2 = tour[(i+1) mod N]
    for j in 0 .. N-1:   # inner loop is for Y1-Y2 link
      Y1 = tour[j]
      Y2 = tour[(j+1) mod N]
      if (Y1 == X1) or (Y1 == X2) or (Y2 == X1): # not 2-opt
         continue

      if Gain_From_2_Opt(X1, X2, Y1, Y2) > 0:
        # this move will shorten the tour, apply it at once
        Make_2_Opt_Move(tour, i, j)
...

We can save half of iterations by taking an advantage from symmetry of STSP. There is no need to check pair (10,5), since it is the same as pair (5,10) already checked before. Therefore we can safely skip all cases where j is greater than i. Or just start j loop from i+2 value, because X1 and Y1 should not be consecutive cities.

0 1 2 N-1 N-2 N-3 i = 0 j = i+2 .. N-2
0 1 2 N-1 N-2 N-3 i = 1 .. N-3 j = i+2 .. N-1

Using the first improving 2-opt move

proc LS_2_Opt(tour: var Tour_Array) =
  ## Iteratively optimizes given tour using 2-opt moves.
  # Shortens the tour by repeating 2-opt moves until no improvement
  # can by done; in every iteration immediatelly makes permanent
  # change from the first move found that gives any length gain.
  var
    locallyOptimal: bool = false
    i, j: Tour_Index
    counter_2_Limit: Tour_Index 
    X1, X2, Y1, Y2: City_Number
  
  while not locallyOptimal:
    locallyOptimal = true
    block two_loops:
      for counter_1 in 0 .. N-3:
        i = counter_1
        X1 = tour[i]
        X2 = tour[(i+1) mod N]

        if i == 0:
          counter_2_Limit = N-2
        else:
          counter_2_Limit = N-1 

        for counter_2 in (i+2) .. counter_2_Limit:
          j = counter_2
          Y1 = tour[j]
          Y2 = tour[(j+1) mod N]

          if Gain_From_2_Opt(X1, X2, Y1, Y2) > 0:
            # this move will shorten the tour, apply it at once
            Make_2_Opt_Move(tour, i, j)
            locallyOptimal = false
            break two_loops  

Note that this code still could be changed for some more efficiency: for example Gain_From_2_Opt() called many times in inner loop uses distance(X1, X2) to obtain a value which is an invariable in this loop. You may also notice that Make_2_Opt_Move() is a oneliner identical to calling Reverse_Segment() with one parameter incremented...

Using the best improving 2-opt move

Instead of applying the first move shortening the tour in given iteration we can use the move that gives the best improvement in this iteration:

proc LS_2_Opt_Take_Best(tour: var Tour_Array) =
  # Shortens the tour by repeating 2-opt moves until no improvement
  # can by done; in every iteration looks for and applies the move
  # that gives maximal length gain.
  type
    Move2 = object
      i, j: Tour_Index
      gain: Length_Gain
  var
    locallyOptimal: bool = false
    i, j: Tour_Index
    X1, X2, Y1, Y2: City_Number
    counter_2_Limit: Tour_Index
    gainExpected: Length_Gain
    bestMove: Move2

  while not locallyOptimal:
    locallyOptimal = true
    bestMove.gain = 0

    for counter_1 in 0 .. N-3:
      i = counter_1
      X1 = tour[i]
      X2 = tour[(i+1) mod N]

      if i == 0:
        counter_2_Limit = N-2
      else:
        counter_2_Limit = N-1

      for counter_2 in (i+2) .. counter_2_Limit:
        j = counter_2
        Y1 = tour[j]
        Y2 = tour[(j+1) mod N]

        gainExpected = Gain_From_2_Opt(X1, X2, Y1, Y2)
        if gainExpected > bestMove.gain:
          bestMove = Move2(i: i,
                           j: j,
                           gain: gainExpected)
          locallyOptimal = false
    # end_for counter_1 loop 

    if not locallyOptimal:
      Make_2_Opt_Move(tour, bestMove.i, bestMove.j)
  # end_while not locallyOptimal 

Note that this method itself does not guarantee that the resulting tour will be shorter than in the first improvement method (see below).

Some simple statistics

Relative time, best length, average length and worst length for all tours created by NN heuristic and improved by the two 2-opt algorithms. Random planar problems, N=100.
Problem # Method Time Best Avg Worst
1NN100100.00107.58115.79
NN + 2opt125388.3992.5397.95
NN + 2opt Take Best93988.2191.5696.98
2NN100100.00108.67120.52
NN + 2opt116892.3295.77102.48
NN + 2opt Take Best97492.1394.3099.53
3NN100100.00108.36125.23
NN + 2opt114692.1294.7099.51
NN + 2opt Take Best125392.3893.9196.94
4NN100100.00109.82121.53
NN + 2opt146086.3589.6194.47
NN + 2opt Take Best125385.0686.9589.79
5NN100100.00112.23121.84
NN + 2opt114693.0097.32104.07
NN + 2opt Take Best94093.2795.8598.32
6NN100100.00111.97121.54
NN + 2opt136088.7094.2398.62
NN + 2opt Take Best104088.9991.8795.01
7NN100100.00109.72121.54
NN + 2opt156687.1491.5096.69
NN + 2opt Take Best124687.8790.8793.32
8NN100100.00110.66119.32
NN + 2opt117588.4593.3798.67
NN + 2opt Take Best116888.3091.4394.53
9NN100100.00106.16119.52
NN + 2opt126884.1088.2693.68
NN + 2opt Take Best117484.2287.4490.98
10NN100100.00107.81117.47
NN + 2opt78191.3194.2599.95
NN + 2opt Take Best87590.3093.0296.06

Friday, March 3, 2017

2-opt move

Starting from geometry

Considering a TSP on Euclidean plane we notice that the optimal tour must be “intersectionless”, that is the tour cannot cross over itself.

The tour without intersection must be shorter because of triangle inequities:

a b c1 d1 d2 c2

Unfortunatelly, not all TSP cases are Euclidean or even metric... but does it change anything important to the idea focused on “crossings” of two links?

The condition for obtaining an improvement

We usually draw the schematic diagram of a tour as a circle with highlighted points:

X1 X2 Y1 Y2

Note that here we do not use triangle inequity, and still the tour can be shortened by exchanging two links (hence a name: 2-opt) if:

If the above condition is true, then when we remove links X1-X2 and Y1-Y2 and replace them with links X1-Y1 and X2-Y2, then the tour would become shorter.

This means than for any type of symmetric TSP the tour can be shortened if for some cities the following gain is positive:

proc Gain_From_2_Opt(X1, X2, Y1, Y2: City_Number): Length_Gain =
  ## Gain of tour length that can be obtained by performing
  ## given 2-opt move
  # Assumes: X2==successor(X1); Y2==successor(Y1)
  let del_Length = distance(X1, X2) + distance(Y1, Y2)
  let add_Length = distance(X1, Y1) + distance(X2, Y2)
  result = del_Length - add_Length

Beware

Note that lack of “crossings” is a necessary, but not sufficient condition for tour to be optimal. A tour without “crossings”, that is a tour which is already fully 2-opted may still be not optimal and then it could be shortened by some other types of moves.

The move

Suppose that we want to transform a tour by making the 2-opt move for:

var
  i, j: Tour_Index
  X1, X2, Y1, Y2: City_Number

X1 = tour[i]
Y1 = tour[j]

X2 = tour[(i+1) mod N]  # the successor of X1
Y2 = tour[(j+1) mod N]  # the successor of Y1

From the first look at the diagram it's clear that:

pos.     ...  i  i+1 ...  j  j+1 ...
before   ... -X1-X2- ... -Y1-Y2- ...
after    ... -X1-Y1- ... -X2-Y2- ...

But swapping only X2 and Y1 on their positions in tour would be an error.

X1 X2 Y1 Y2 b c d r q p
X1 X2 Y1 Y2 b c d r q p

The order of cities between X2 and Y1 also has been changed:

before   ... -X1-X2-b-c-d-Y1-Y2- ...
after    ... -X1-Y1-d-c-b-X2-Y2- ...

Therefore the proper solution is to reverse all the segment of tour from X2 to Y1 (see: Reversing a segment):

proc Make_2_Opt_Move(tour: var Tour_Array;
                     i, j: Tour_Index) =
  ## Performs given 2-opt move on array representation of the tour
  # We cut the cyclic tour in 2 places, by removing 2 links:
  #   L1: from t[i] to t[i+1]  and  L2: from t[j] to t[j+1]
  # and replacing them with
  #   L1': from t[i] to t[j]   and  L2': from t[i+1] to t[j+1]
  # This is equivalent to reversing order in segment from 'i+1' to 'j'
  Reverse_Segment(tour, (i+1) mod N, j)

Tuesday, February 28, 2017

Reversing a segment

One of the standard transformations of a tour used in local optimization is reversing a segment. In the new tour all the cities of the segment occur in reverse order.

pos.    0 1 2 3 4 5 6 7 ...
before  A-B-C-D-E-F-G-H-...
after   A-B-F-E-D-C-G-H-...

We use the following algorithm: swap the first and the last city of the segment, then the second and one before last, and so on, to the middle of the segment. This way number of swaps is equal half of the segment size.

Beware

We should not forget that a tour is cyclic, and therefore after tour[N-1] city (in ascending order of indices) goes tour[0], to close the tour. Therefore segment from tour[N-2] to tour[2] is 5 cities long — it constists of: tour[N-2], tour[N-1], tour[0], tour[1] and tour[2], in this order. Note that reversing the complementary segment, consisting of the cities on positions from 2 to (N-2), is not the exactly same and in some cases may lead to erroneous results. (But see later reversing a segment given by city numbers, not by positions, more complex and using additional structure, but where this problem does not occur).

proc Reverse_Segment(tour: var Tour_Array;
                     startIndex, endIndex: Tour_Index) =
  ## Reverses order of elements in segment [startIndex, endIndex]
  ## of tour.
  ## NOTE: Final and initial part of tour make one segment.
  ## NOTE: While the order of arguments can be safely changed when
  ## reversing segment in 2-optimization, it makes difference for
  ## 3-opt move - be careful and do not reverse (x,z) instead of (z,x).
  var
    left, right: Tour_Index

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

  for counter in 1 .. inversionSize:
    swap(tour[left], tour[right])
    left  = (left + 1) mod N
    right = (N + right - 1) mod N