Wednesday, September 20, 2017

Non-sequential 4-opt moves – part 2 of 4

Double Bridge as a mean of perturbation

Sometimes double bridge move is used to perturb a tour. When local optimization method finds no more improving moves, it finishes its work and tour cannot be further improved by this method. But we can slightly perturb resulting tour to obtan a new, very similar, but not fully optimized tour and then try to optimize it in hope to find better result.

When double bridge (or some other non-sequential move) is to be used as a mean to perturb a tour, it does not matter whether it is improving move or not. It is sufficient to use any valid double bridge to obtain perturbed tour. So we can just take any set of cities that can form such move.

c1 c2 d1 d2 d3 d4 c3 c4

Since c2 is always the tour successor of c1, d2 is the successor of d1 and so on, the move is determinated by four cities. Then to make double bridge we can use a set of any four cities. The following proc generates four random city numbers, then sorts these cities by their ascending positions on tour and uses as parameters to make a double bridge move.

proc Double_Bridge_Perturbation() =
    randCity: City_Number
    citiesDblBridge: array[0..3, City_Number]
    forbidden: bool
    i: int
    c1, c2, c3, c4: City_Number
    d1, d2, d3, d4: City_Number

  i = 0
  while i <= 3:
    randCity = random(N)  # returns number from 0 to N-1 inclusive
    forbidden = false
    for j in 0 .. i-1:    
      # check if city is already taken
      if randCity == citiesDblBridge[j]:
        forbidden = true
      # check if city is a tour neighbor of already taken
      if t_succ(randCity) == citiesDblBridge[j] or
         t_pred(randCity) == citiesDblBridge[j]:
        forbidden = true
    if not forbidden:
      citiesDblBridge[i] = randCity
      i = i + 1
  # sort by ascending position
  sort(citiesDblBridge) do (x,y: City_Number) -> int:
       result = cmp(position[x], position[y])

  c1 = citiesDblBridge[0]
  d1 = citiesDblBridge[1]
  c3 = citiesDblBridge[2]
  d3 = citiesDblBridge[3]
  c2 = t_succ(c1)
  c4 = t_succ(c3)  
  d2 = t_succ(d1)
  d4 = t_succ(d3)
  # make the move:
  Make_4opt_NS_Move(c1, c2, c3, c4, d1, d2, d3, d4, TO_ADBC)

Tuesday, September 12, 2017

Non-sequential 4-opt moves – part 1 of 4

Among all 48 types of 4-opt moves there are exactly five non-sequential pure 4-opt moves. They apparently form two distinct groups:

Moves AbCd and AdCb

A b C d A d C b

While it may be suprising to count them as pure 4-opt non-sequential moves, in fact they are moves of this kind. They are 4-opt moves, because they remove four links and then add four links. They are both pure 4-opt moves, because none of them is equivalent of any single 2-opt nor 3-opt move. They are not sequential moves, which can be easily seen from the fact that we cannot show any closed polygonal chain of line segments connecting the consecutive cities. So by definition they are pure 4-opt non-sequential moves.

On the other hand, each of these moves consists of just two completely independent 2-opt moves, changing two non-overlaping ranges. Each 2-opt move of the pair can be improving or not improving and accordingly to this we would applied it or not. Therefore during optimization there is no need to treat these 4-opt non-sequential moves as practically distinct 4-opt moves, but rather as two separate 2-opts, then as two moves of type we consider anyway during the process of optimization by using sequential moves.

Moves ADbc, AcdB and ADCB

A D b c A c d B A D C B

This group includes non-sequential 4-opt moves, semeed to be specific combinations of two submoves, both being 2-exchanges, where one of them is disjoining (see parallel lines on schemes above) and the other is either disjoining or joining (2-opt, see x-crossed lines).

Including these moves into optimization procedure requires, as usual, resolving two issues: how to find (or generate) such moves and how to actually apply them. Let us start with solution of the latter issue.

The last case, so called "double bridge" move, is most known and instructive. It can be perceived as a kind of combination of two disjoining 2-exchanges. Then the first submove would be always disjoining, and on the other hand we cannot represent two disjoined segments when using array representation of tour (because this single array always contains consecutive city numbers of all cities, as there were links between each pair of consecutive cities). So we would not be able to implement even the first submove of double bridge move.

Second approach to this issue is based on observation that double bridge, which is ADCB, can be obtained from original tour ABCD by shifting two segments or swapping them. This would however require to introduce segment shift (or swap) as second basic transformation, distinct from link exchange we used so far as the only needed basic transformation.

While this may not be obvious, neither segment shifting nor swapping is necessary to obtain the same effect. Let symbol prime (') after a letter or letters in parenthesis means that we reverse this segment(s) and lower letter represent reversed segments:

0.  ABCD
1.  A(BC)'D = AcbD
2.  Ac(bD)' = AcdB
3.  A(cd)'B = ADCB

So double bdridge move is just a specific combination of three segment reversals (then three 2-opts), exactly as any pure 4-opt sequential move:

# 'ADBC' (||=), double bridge
Exchange_Links(d1, d2, d4, d3)
Exchange_Links(c1, c2, c4, c3)
Exchange_Links(d1, d3, d4, d2)
A B C D c1 c2 d1 d2 d3 d4 c3 c4
A c b D c1 c2 d1 d2 d3 d4 c3 c4 A c d B c1 c2 d1 d2 d3 d4 c3 c4 A D C B c1 c2 d1 d2 d3 d4 c3 c4

Note that segment configuration before last exchange is AcdB, which is one of the remaining pure 4-opt non-sequential cases. So for this case we have:

# 'AcdB' (||X), crossed vertical bridge
Exchange_Links(d1, d2, d4, d3)
Exchange_Links(c1, c2, c4, c3)

The first case (ADbc) from this group can be implemented by analogy:

0.  ABCD
1.  AB(CD)' = ABdc
2.  A(Bd)'c = ADbc

We can make a general proc for making 4-opt non-sequential move:

  TO_ADBC   = -1  # 4-opt 'ADBC' (||=), double bridge
  TO_AxcxdB = -2  # 4-opt 'AcdB' (||X), crossed vertical bridge
  TO_ADxbxc = -3  # 4-opt 'ADbc' (X=), crossed horizontal bridge
proc Make_4opt_NS_Move(c1, c2, c3, c4, d1, d2, d3, d4: City_Number;
                       tourOrder: int) =

  case tourOrder

  of TO_ADBC:    # 'ADBC' (||=), double bridge
   # c12-d12-c34-d34
    Exchange_Links(d1, d2, d4, d3)
    Exchange_Links(c1, c2, c4, c3)
    Exchange_Links(d1, d3, d4, d2)

  of TO_AxcxdB:  # 'AcdB' (||X), crossed vertical bridge
    # c12-d12-c34-d43
    Exchange_Links(d1, d2, d4, d3)
    Exchange_Links(c1, c2, c4, c3)
  of TO_ADxbxc:  # 'ADbc' (X=), crossed horizontal bridge
    # c12-d12-c43-d34
    Exchange_Links(c1, c2, c4, c3)
    Exchange_Links(d1, d2, d3, d4)
    echo "Unknown 4-opt non-sequential move: ", tourOrder
    quit 1


Proc Exchange_Links() used here takes city numbers, not city positions in tour, as arguments. Confusing these two different ways leads to wrong results.