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

No comments:

Post a Comment