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"