Coverage for moptipy / operators / signed_permutations / op1_swap_2_or_flip.py: 45%
29 statements
« prev ^ index » next coverage.py v7.12.0, created at 2025-11-24 08:49 +0000
« prev ^ index » next coverage.py v7.12.0, created at 2025-11-24 08:49 +0000
1"""
2An operator swapping two elements in a permutation or flipping a sign.
4This operator is for :mod:`~moptipy.spaces.signed_permutations`. A similar
5operator which *only* swaps elements (for :mod:`~moptipy.spaces.permutations`)
6is defined in :mod:`~moptipy.operators.permutations.op1_swap2`.
7"""
8from typing import Callable, Final
10import numba # type: ignore
11import numpy as np
12from numpy.random import Generator
14from moptipy.api.operators import Op1
17@numba.njit(cache=True, inline="always", fastmath=True, boundscheck=False)
18def swap_2_or_flip(random: Generator, dest: np.ndarray,
19 x: np.ndarray) -> None:
20 """
21 Copy `x` into `dest` and swap two different values or flip a sign.
23 :param random: the random number generator
24 :param dest: the array to receive the modified copy of `x`
25 :param x: the existing point in the search space
27 >>> rand = np.random.default_rng(10)
28 >>> xx = np.array(range(10), int)
29 >>> out = np.empty(len(xx), int)
30 >>> swap_2_or_flip(rand, out, xx)
31 >>> print(out)
32 [0 1 7 3 4 5 6 2 8 9]
33 >>> swap_2_or_flip(rand, out, xx)
34 >>> print(out)
35 [0 1 8 3 4 5 6 7 2 9]
36 >>> swap_2_or_flip(rand, out, xx)
37 >>> print(out)
38 [ 0 1 2 3 4 -5 6 7 8 9]
39 >>> swap_2_or_flip(rand, out, xx)
40 >>> print(out)
41 [0 8 2 3 4 5 6 7 1 9]
42 >>> swap_2_or_flip(rand, out, xx)
43 >>> print(out)
44 [ 0 -1 2 3 4 5 6 7 8 9]
45 >>> swap_2_or_flip(rand, out, xx)
46 >>> print(out)
47 [ 0 1 2 3 4 5 -6 7 8 9]
48 """
49 dest[:] = x[:] # First, we copy `x` to `dest`.
50 length: Final[int] = len(dest) # Get the length of `dest`.
51 rint: Callable[[int, int], int] = random.integers # fast call
53 i1: Final[int] = rint(0, length) # first random index.
54 v1: Final = dest[i1] # Get the value at the first index.
56 if rint(0, 2) == 0: # With p=0.5, we flip the sign of v1.
57 dest[i1] = -v1 # Flip the sign of v1 and store it back at i1.
58 return # Quit.
60# Swap two values. Now, normally we would repeat the loop until we find a
61# different value. However, if we have signed permutations, it may be
62# possible that all values currently are the same. To avoid an endless loop,
63# we therefore use a sufficiently large range.
64 for _ in range(10 + length):
65 i2: int = rint(0, length) # Get the second random index.
66 v2 = dest[i2] # Get the value at the second index.
67 if v1 != v2: # If both values different...
68 dest[i2] = v1 # store v1 where v2 was
69 dest[i1] = v2 # store v2 where v1 was
70 return # Exit function: we are finished.
71 # If we get here, probably all elements are the same, so we just...
72 dest[i1] = -v1 # ...flip the sign of v1 and store it back at i1.
75class Op1Swap2OrFlip(Op1):
76 """
77 A search operation that swaps two (different) elements or flips a sign.
79 In other words, it performs exactly one swap on a permutation or a sign
80 flip. It spans a neighborhood of a rather limited size but is easy
81 and fast.
82 """
84 def __init__(self) -> None:
85 """Initialize the object."""
86 super().__init__()
87 self.op1 = swap_2_or_flip # type: ignore # use function directly
89 def __str__(self) -> str:
90 """
91 Get the name of this unary operator.
93 :returns: "swap2orFlip", the name of this operator
94 :retval "swap2orFlip": always
95 """
96 return "swap2orFlip"