Coverage for moptipy / operators / bitstrings / op1_m_over_n_flip.py: 57%
42 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"""A unary operator flipping each bit with probability m/n."""
2from typing import Final
4import numba # type: ignore
5import numpy as np
6from numpy.random import Generator
7from pycommons.types import check_int_range, type_error
9from moptipy.api.operators import Op1
10from moptipy.utils.nputils import (
11 fill_in_canonical_permutation,
12 int_range_to_dtype,
13)
16@numba.njit(cache=True, inline="always", fastmath=True, boundscheck=False)
17def _op1_movern(m: int, none_is_ok: bool, permutation: np.ndarray,
18 random: Generator, dest: np.ndarray, x: np.ndarray) -> None:
19 """
20 Copy `x` into `dest` and flip each bit with probability m/n.
22 This method will first copy `x` to `dest`. Then it will flip each bit
23 in `dest` with probability `m/n`, where `n` is the length of `dest`.
24 Regardless of the probability, at least one bit will always be
25 flipped if self.at_least_1 is True.
27 :param m: the value of m
28 :param none_is_ok: is it OK to flip nothing?
29 :param permutation: the internal permutation
30 :param random: the random number generator
31 :param dest: the destination array to receive the new point
32 :param x: the existing point in the search space
33 """
34 dest[:] = x[:] # copy source to destination
35 length: Final[int] = len(dest) # get n
36 p: Final[float] = m / length # probability to flip bit
38 flips: int # the number of bits to flip
39 while True:
40 flips = random.binomial(length, p) # get the number of bits to flip
41 if flips > 0:
42 break # we will flip some bit
43 if none_is_ok:
44 return # we will flip no bit
46 i: int = length
47 end: Final[int] = length - flips
48 while i > end: # we iterate from i=length down to end=length-flips
49 k = random.integers(0, i) # index of next bit index in permutation
50 i -= 1 # decrease i
51 idx = permutation[k] # get index of bit to flip and move to end
52 permutation[i], permutation[k] = idx, permutation[i]
53 dest[idx] = not dest[idx] # flip bit
56class Op1MoverNflip(Op1):
57 """
58 A unary search operation that flips each bit with probability of `m/n`.
60 For bit strings of length `n`, draw the number `z` of bits to flip from a
61 binomial distribution with `p=m/n`. If `at_least_1` is set to `True`, then
62 we repeat drawing `z` until `z>0`.
63 """
65 def __init__(self, n: int, m: int = 1, at_least_1: bool = True):
66 """
67 Initialize the operator.
69 :param n: the length of the bit strings
70 :param m: the factor for computing the probability of flipping
71 the bits
72 :param at_least_1: should at least one bit be flipped?
73 """
74 super().__init__()
75 check_int_range(n, "n", 1)
76 #: the value of m in p=m/n
77 self.__m: Final[int] = check_int_range(m, "m", 1, n)
78 if not isinstance(at_least_1, bool):
79 raise type_error(at_least_1, "at_least_1", bool)
80 #: is it OK to not flip any bit?
81 self.__none_is_ok: Final[bool] = not at_least_1
82 #: the internal permutation
83 self.__permutation: Final[np.ndarray] = np.empty(
84 n, dtype=int_range_to_dtype(0, n - 1))
86 def initialize(self) -> None:
87 """Initialize this operator."""
88 super().initialize()
89 fill_in_canonical_permutation(self.__permutation)
91 def op1(self, random: Generator, dest: np.ndarray, x: np.ndarray) -> None:
92 """
93 Copy `x` into `dest` and flip each bit with probability m/n.
95 This method will first copy `x` to `dest`. Then it will flip each bit
96 in `dest` with probability `m/n`, where `n` is the length of `dest`.
97 Regardless of the probability, at least one bit will always be
98 flipped if self.at_least_1 is True.
100 :param self: the self pointer
101 :param random: the random number generator
102 :param dest: the destination array to receive the new point
103 :param x: the existing point in the search space
104 """
105 _op1_movern(self.__m, self.__none_is_ok, self.__permutation,
106 random, dest, x)
108 def __str__(self) -> str:
109 """
110 Get the name of this unary operator.
112 :return: "fileB" + m + "n" if none-is-ok else ""
113 """
114 return f"flipB{self.__m}{'n' if self.__none_is_ok else ''}"