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

1""" 

2An operator swapping two elements in a permutation or flipping a sign. 

3 

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 

9 

10import numba # type: ignore 

11import numpy as np 

12from numpy.random import Generator 

13 

14from moptipy.api.operators import Op1 

15 

16 

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. 

22 

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 

26 

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 

52 

53 i1: Final[int] = rint(0, length) # first random index. 

54 v1: Final = dest[i1] # Get the value at the first index. 

55 

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. 

59 

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. 

73 

74 

75class Op1Swap2OrFlip(Op1): 

76 """ 

77 A search operation that swaps two (different) elements or flips a sign. 

78 

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 """ 

83 

84 def __init__(self) -> None: 

85 """Initialize the object.""" 

86 super().__init__() 

87 self.op1 = swap_2_or_flip # type: ignore # use function directly 

88 

89 def __str__(self) -> str: 

90 """ 

91 Get the name of this unary operator. 

92 

93 :returns: "swap2orFlip", the name of this operator 

94 :retval "swap2orFlip": always 

95 """ 

96 return "swap2orFlip"