Source code for moptipy.operators.bitstrings.op1_m_over_n_flip
"""A unary operator flipping each bit with probability m/n."""
from typing import Callable, Final
import numpy as np
from numpy.random import Generator
from pycommons.types import check_int_range, type_error
from moptipy.api.operators import Op1
from moptipy.utils.nputils import (
fill_in_canonical_permutation,
int_range_to_dtype,
)
[docs]
class Op1MoverNflip(Op1):
"""
A unary search operation that flips each bit with probability of `m/n`.
For bit strings of length `n`, draw the number `z` of bits to flip from a
binomial distribution with `p=m/n`. If `at_least_1` is set to `True`, then
we repeat drawing `z` until `z>0`.
"""
def __init__(self, n: int, m: int = 1, at_least_1: bool = True):
"""
Initialize the operator.
:param n: the length of the bit strings
:param m: the factor for computing the probability of flipping
the bits
:param at_least_1: should at least one bit be flipped?
"""
super().__init__()
check_int_range(n, "n", 1)
#: the value of m in p=m/n
self.__m: Final[int] = check_int_range(m, "m", 1, n)
if not isinstance(at_least_1, bool):
raise type_error(at_least_1, "at_least_1", bool)
#: is it OK to not flip any bit?
self.__none_is_ok: Final[bool] = not at_least_1
#: the internal permutation
self.__permutation: Final[np.ndarray] = np.empty(
n, dtype=int_range_to_dtype(0, n - 1))
[docs]
def initialize(self) -> None:
"""Initialize this operator."""
super().initialize()
fill_in_canonical_permutation(self.__permutation)
[docs]
def op1(self, random: Generator, dest: np.ndarray, x: np.ndarray) -> None:
"""
Copy `x` into `dest` and flip each bit with probability m/n.
This method will first copy `x` to `dest`. Then it will flip each bit
in `dest` with probability `m/n`, where `n` is the length of `dest`.
Regardless of the probability, at least one bit will always be
flipped if self.at_least_1 is True.
:param self: the self pointer
:param random: the random number generator
:param dest: the destination array to receive the new point
:param x: the existing point in the search space
"""
np.copyto(dest, x) # copy source to destination
length: Final[int] = len(dest) # get n
p: Final[float] = self.__m / length # probability to flip bit
none_is_ok: Final[bool] = self.__none_is_ok
flips: int # the number of bits to flip
rbi: Final[Callable[[int, float], int]] = random.binomial
while True:
flips = rbi(length, p) # compute the number of bits to flip
if flips > 0:
break # we will flip some bit
if none_is_ok:
return # we will flip no bit
permutation: Final[np.ndarray] = self.__permutation
i: int = length
end: Final[int] = length - flips
ri: Final[Callable[[int], int]] = random.integers
while i > end: # we iterate from i=length down to end=length-flips
k = ri(i) # get index of next bit index in permutation
i -= 1 # decrease i
idx = permutation[k] # get index of bit to flip and move to end
permutation[i], permutation[k] = idx, permutation[i]
dest[idx] = not dest[idx] # flip bit
def __str__(self) -> str:
"""
Get the name of this unary operator.
:return: "m_over_n_flip"
"""
return f"flipB{self.__m}{'n' if self.__none_is_ok else ''}"