I agree that the footnote reduces it to four ways, not two.
In the following code, you can pass in a permutation p
and it will perform a 3-opt, one of the four possibilities. If you also pass broad=False
, it will allow any of the 8 (including the identity).
However, I would really welcome a review of this code, or a link to any other implementation online.
Note that this code doesn't enforce that the edges we cut are originally non-contiguous. Should it?
Note also that this just does the move, it doesn't carry out the full routine of trying all moves and choosing the best.
def three_opt(p, broad=False):
"""In the broad sense, 3-opt means choosing any three edges ab, cd
and ef and chopping them, and then reconnecting (such that the
result is still a complete tour). There are eight ways of doing
it. One is the identity, 3 are 2-opt moves (because either ab, cd,
or ef is reconnected), and 4 are 3-opt moves (in the narrower
sense)."""
n = len(p)
# choose 3 unique edges defined by their first node
a, c, e = random.sample(range(n+1), 3)
# without loss of generality, sort
a, c, e = sorted([a, c, e])
b, d, f = a+1, c+1, e+1
if broad == True:
which = random.randint(0, 7) # allow any of the 8
else:
which = random.choice([3, 4, 5, 6]) # allow only strict 3-opt
# in the following slices, the nodes abcdef are referred to by
# name. x:y:-1 means step backwards. anything like c+1 or d-1
# refers to c or d, but to include the item itself, we use the +1
# or -1 in the slice
if which == 0:
sol = p[:a+1] + p[b:c+1] + p[d:e+1] + p[f:] # identity
elif which == 1:
sol = p[:a+1] + p[b:c+1] + p[e:d-1:-1] + p[f:] # 2-opt
elif which == 2:
sol = p[:a+1] + p[c:b-1:-1] + p[d:e+1] + p[f:] # 2-opt
elif which == 3:
sol = p[:a+1] + p[c:b-1:-1] + p[e:d-1:-1] + p[f:] # 3-opt
elif which == 4:
sol = p[:a+1] + p[d:e+1] + p[b:c+1] + p[f:] # 3-opt
elif which == 5:
sol = p[:a+1] + p[d:e+1] + p[c:b-1:-1] + p[f:] # 3-opt
elif which == 6:
sol = p[:a+1] + p[e:d-1:-1] + p[b:c+1] + p[f:] # 3-opt
elif which == 7:
sol = p[:a+1] + p[e:d-1:-1] + p[c:b-1:-1] + p[f:] # 2-opt
return sol