Source code for moptipy.operators.signed_permutations.op1_swap_2_or_flip
"""
An operator swapping two elements in a permutation or flipping a sign.
This operator is for :mod:`~moptipy.spaces.signed_permutations`. A similar
operator which *only* swaps elements (for :mod:`~moptipy.spaces.permutations`)
is defined in :mod:`~moptipy.operators.permutations.op1_swap2`.
"""
from typing import Callable, Final
# = import numba # type: ignore
import numpy as np
from numpy.random import Generator
from moptipy.api.operators import Op1
# Temporary fix for https://github.com/numba/numba/issues/9103
[docs]
def swap_2_or_flip(random: Generator, dest: np.ndarray,
x: np.ndarray) -> None:
"""
Copy `x` into `dest` and swap two different values or flip a sign.
:param random: the random number generator
:param dest: the array to receive the modified copy of `x`
:param x: the existing point in the search space
>>> rand = np.random.default_rng(10)
>>> xx = np.array(range(10), int)
>>> out = np.empty(len(xx), int)
>>> swap_2_or_flip(rand, out, xx)
>>> print(out)
[0 1 7 3 4 5 6 2 8 9]
>>> swap_2_or_flip(rand, out, xx)
>>> print(out)
[0 1 8 3 4 5 6 7 2 9]
>>> swap_2_or_flip(rand, out, xx)
>>> print(out)
[ 0 1 2 3 4 -5 6 7 8 9]
>>> swap_2_or_flip(rand, out, xx)
>>> print(out)
[0 8 2 3 4 5 6 7 1 9]
>>> swap_2_or_flip(rand, out, xx)
>>> print(out)
[ 0 -1 2 3 4 5 6 7 8 9]
>>> swap_2_or_flip(rand, out, xx)
>>> print(out)
[ 0 1 2 3 4 5 -6 7 8 9]
"""
dest[:] = x[:] # First, we copy `x` to `dest`.
length: Final[int] = len(dest) # Get the length of `dest`.
rint: Callable[[int, int], int] = random.integers # fast call
i1: Final[int] = rint(0, length) # first random index.
v1: Final = dest[i1] # Get the value at the first index.
if rint(0, 2) == 0: # With p=0.5, we flip the sign of v1.
dest[i1] = -v1 # Flip the sign of v1 and store it back at i1.
return # Quit.
# Swap two values. Now, normally we would repeat the loop until we find a
# different value. However, if we have signed permutations, it may be
# possible that all values currently are the same. To avoid an endless loop,
# we therefore use a sufficiently large range.
for _ in range(10 + length):
i2: int = rint(0, length) # Get the second random index.
v2 = dest[i2] # Get the value at the second index.
if v1 != v2: # If both values different...
dest[i2] = v1 # store v1 where v2 was
dest[i1] = v2 # store v2 where v1 was
return # Exit function: we are finished.
# If we get here, probably all elements are the same, so we just...
dest[i1] = -v1 # ...flip the sign of v1 and store it back at i1.
[docs]
class Op1Swap2OrFlip(Op1):
"""
A search operation that swaps two (different) elements or flips a sign.
In other words, it performs exactly one swap on a permutation or a sign
flip. It spans a neighborhood of a rather limited size but is easy
and fast.
"""
def __init__(self) -> None:
"""Initialize the object."""
super().__init__()
self.op1 = swap_2_or_flip # type: ignore # use function directly
def __str__(self) -> str:
"""
Get the name of this unary operator.
:returns: "swap2orFlip", the name of this operator
:retval "swap2orFlip": always
"""
return "swap2orFlip"