Skip to content

Commit b81bd99

Browse files
committed
now using numba jit again for many operators
1 parent cb41a40 commit b81bd99

File tree

11 files changed

+151
-245
lines changed

11 files changed

+151
-245
lines changed

moptipy/operators/bitstrings/op1_flip_m.py

+2-3
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,8 @@ def op1(self, random: Generator, dest: np.ndarray, x: np.ndarray,
8080
"""
8181
np.copyto(dest, x) # copy source to destination
8282
n: Final[int] = len(dest) # get the number of bits
83-
flips: Final[np.ndarray] = random.choice( # choose the bits
84-
n, exponential_step_size(step_size, 1, n), False)
85-
dest[flips] ^= True # flip the selected bits via xor
83+
dest[random.choice(n, exponential_step_size(
84+
step_size, 1, n), False)] ^= True # flip the selected bits via xor
8685

8786
def __str__(self) -> str:
8887
"""

moptipy/operators/bitstrings/op1_m_over_n_flip.py

+44-25
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""A unary operator flipping each bit with probability m/n."""
2-
from typing import Callable, Final
2+
from typing import Final
33

4+
import numba # type: ignore
45
import numpy as np
56
from numpy.random import Generator
67
from pycommons.types import check_int_range, type_error
@@ -12,6 +13,46 @@
1213
)
1314

1415

16+
@numba.njit(cache=True, inline="always", fastmath=True, boundscheck=False)
17+
def _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.
21+
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.
26+
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
37+
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
45+
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
54+
55+
1556
class Op1MoverNflip(Op1):
1657
"""
1758
A unary search operation that flips each bit with probability of `m/n`.
@@ -61,30 +102,8 @@ def op1(self, random: Generator, dest: np.ndarray, x: np.ndarray) -> None:
61102
:param dest: the destination array to receive the new point
62103
:param x: the existing point in the search space
63104
"""
64-
np.copyto(dest, x) # copy source to destination
65-
length: Final[int] = len(dest) # get n
66-
p: Final[float] = self.__m / length # probability to flip bit
67-
none_is_ok: Final[bool] = self.__none_is_ok
68-
69-
flips: int # the number of bits to flip
70-
rbi: Final[Callable[[int, float], int]] = random.binomial
71-
while True:
72-
flips = rbi(length, p) # compute the number of bits to flip
73-
if flips > 0:
74-
break # we will flip some bit
75-
if none_is_ok:
76-
return # we will flip no bit
77-
78-
permutation: Final[np.ndarray] = self.__permutation
79-
i: int = length
80-
end: Final[int] = length - flips
81-
ri: Final[Callable[[int], int]] = random.integers
82-
while i > end: # we iterate from i=length down to end=length-flips
83-
k = ri(i) # get index of next bit index in permutation
84-
i -= 1 # decrease i
85-
idx = permutation[k] # get index of bit to flip and move to end
86-
permutation[i], permutation[k] = idx, permutation[i]
87-
dest[idx] = not dest[idx] # flip bit
105+
_op1_movern(self.__m, self.__none_is_ok, self.__permutation,
106+
random, dest, x)
88107

89108
def __str__(self) -> str:
90109
"""

moptipy/operators/permutations/op1_insert1.py

+37-129
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@
5959
2016, Denver, CO, USA, pages 57-58, New York, NY, USA: ACM.
6060
ISBN: 978-1-4503-4323-7. https://doi.org/10.1145/2908961.2909001
6161
"""
62-
from typing import Callable, Final
62+
from typing import Final
6363

6464
import numba # type: ignore
6565
import numpy as np
@@ -69,144 +69,30 @@
6969

7070

7171
@numba.njit(cache=True, inline="always", fastmath=True, boundscheck=False)
72-
def try_single_rotate(arr: np.ndarray, i1: int, i2: int) -> bool: # +book
72+
def rotate(random: Generator, dest: np.ndarray, x: np.ndarray) -> None:
7373
"""
74-
Rotate a portion of an array to the left or right in place.
74+
Copy `x` into `dest` and then rotate a subsequence by one step.
75+
76+
The function repeatedly tries to rotate a portion of an array to the left
77+
or right in place. It will continue trying until something changed.
78+
In each step, it draws two indices `i1` and `i2`.
7579
7680
If `i1 < i2`, then a left rotation by one step is performed. In other
7781
words, the element at index `i1 + 1` goes to index `i1`, the element at
7882
index `i1 + 2` goes to index `i1 + 1`, and so on. The lst element, i.e.,
7983
the one at index `i2` goes to index `i2 - 1`. Finally, the element that
8084
originally was at index `i1` goes to index `i2`. If any element in the
81-
array has changed, this function returns `False`, otherwise `True`.
85+
array has changed, this function is done, otherwise it tries again.
8286
8387
If `i1 > i2`, then a right rotation by one step is performed. In other
8488
words, the element at index `i1 - 1` goes to index `i1`, the element at
8589
index `i1 - 2` goes to index `i1 - 1`, and so on. Finally, the element
8690
that originally was at index `i1` goes to index `i2`. If any element in
87-
the array has changed, this function returns `False`, otherwise `True`.
91+
the array has changed, this function tries again, otherwise it stops.
8892
8993
This corresponds to extracting the element at index `i1` and re-inserting
9094
it at index `i2`.
9195
92-
:param arr: the array to rotate
93-
:param i1: the start index, in `0..len(arr)-1`
94-
:param i2: the end index, in `0..len(arr)-1`
95-
:returns: whether the array was *unchanged*
96-
:retval False: if the array `arr` is now different from before
97-
:retval True: if the array `arr` has not changed
98-
99-
>>> import numpy as npx
100-
>>> dest = npx.array(range(10))
101-
>>> print(dest)
102-
[0 1 2 3 4 5 6 7 8 9]
103-
>>> try_single_rotate(dest, 3, 4)
104-
False
105-
>>> print(dest)
106-
[0 1 2 4 3 5 6 7 8 9]
107-
>>> try_single_rotate(dest, 3, 4)
108-
False
109-
>>> print(dest)
110-
[0 1 2 3 4 5 6 7 8 9]
111-
>>> try_single_rotate(dest, 4, 3)
112-
False
113-
>>> print(dest)
114-
[0 1 2 4 3 5 6 7 8 9]
115-
>>> try_single_rotate(dest, 4, 3)
116-
False
117-
>>> print(dest)
118-
[0 1 2 3 4 5 6 7 8 9]
119-
>>> try_single_rotate(dest, 3, 6)
120-
False
121-
>>> print(dest)
122-
[0 1 2 4 5 6 3 7 8 9]
123-
>>> try_single_rotate(dest, 6, 3)
124-
False
125-
>>> print(dest)
126-
[0 1 2 3 4 5 6 7 8 9]
127-
>>> try_single_rotate(dest, 0, len(dest) - 1)
128-
False
129-
>>> print(dest)
130-
[1 2 3 4 5 6 7 8 9 0]
131-
>>> try_single_rotate(dest, len(dest) - 1, 0)
132-
False
133-
>>> print(dest)
134-
[0 1 2 3 4 5 6 7 8 9]
135-
>>> try_single_rotate(dest, 7, 7)
136-
True
137-
>>> dest = np.array([0, 1, 2, 3, 3, 3, 3, 3, 8, 9])
138-
>>> try_single_rotate(dest, 7, 7)
139-
True
140-
>>> try_single_rotate(dest, 4, 6)
141-
True
142-
>>> print(dest)
143-
[0 1 2 3 3 3 3 3 8 9]
144-
>>> try_single_rotate(dest, 6, 4)
145-
True
146-
>>> print(dest)
147-
[0 1 2 3 3 3 3 3 8 9]
148-
>>> try_single_rotate(dest, 4, 7)
149-
True
150-
>>> print(dest)
151-
[0 1 2 3 3 3 3 3 8 9]
152-
>>> try_single_rotate(dest, 6, 7)
153-
True
154-
>>> print(dest)
155-
[0 1 2 3 3 3 3 3 8 9]
156-
>>> try_single_rotate(dest, 4, 8)
157-
False
158-
>>> print(dest)
159-
[0 1 2 3 3 3 3 8 3 9]
160-
>>> try_single_rotate(dest, 8, 4)
161-
False
162-
>>> print(dest)
163-
[0 1 2 3 3 3 3 3 8 9]
164-
>>> try_single_rotate(dest, 9, 4)
165-
False
166-
>>> print(dest)
167-
[0 1 2 3 9 3 3 3 3 8]
168-
>>> try_single_rotate(dest, 4, 9)
169-
False
170-
>>> print(dest)
171-
[0 1 2 3 3 3 3 3 8 9]
172-
"""
173-
# start book
174-
if i1 == i2: # nothing to be done
175-
return True # array will not be changed
176-
177-
unchanged: bool = True # initially, assume that there is no change
178-
179-
if i1 < i2: # rotate to the left: move elements to lower indices?
180-
first = arr[i1] # get the element to be removed
181-
while i1 < i2: # iterate the indices
182-
i3 = i1 + 1 # get next higher index
183-
cpy = arr[i3] # get next element at that higher index
184-
unchanged = unchanged and (cpy == arr[i1]) # is a change?
185-
arr[i1] = cpy # store next element at the lower index
186-
i1 = i3 # move to next higher index
187-
unchanged = unchanged and (first == arr[i2]) # check if change
188-
arr[i2] = first # store removed element at highest index
189-
return unchanged # return True if something changed, else False
190-
191-
last = arr[i1] # last element; rotate right: move elements up
192-
while i2 < i1: # iterate over indices
193-
i3 = i1 - 1 # get next lower index
194-
cpy = arr[i3] # get element at that lower index
195-
unchanged = unchanged and (cpy == arr[i1]) # is a change?
196-
arr[i1] = cpy # store element at higher index
197-
i1 = i3 # move to next lower index
198-
unchanged = unchanged and (last == arr[i2]) # check if change
199-
arr[i2] = last # store removed element at lowest index
200-
return unchanged # return True if something changed, else False
201-
# end book
202-
203-
204-
# Temporary fix for https://github.com/numba/numba/issues/9103
205-
def rotate(random: Generator, dest: np.ndarray, # +book
206-
x: np.ndarray) -> None: # +book
207-
"""
208-
Copy `x` into `dest` and then rotate a subsequence by one step.
209-
21096
:param random: the random number generator
21197
:param dest: the array to receive the modified copy of `x`
21298
:param x: the existing point in the search space
@@ -227,14 +113,37 @@ def rotate(random: Generator, dest: np.ndarray, # +book
227113
>>> print(out)
228114
[0 1 2 3 4 8 5 6 7 9]
229115
"""
230-
# start book
231116
dest[:] = x[:]
232117
length: Final[int] = len(dest) # Get the length of `dest`.
233-
rint: Callable[[int, int], int] = random.integers # fast call
234-
118+
unchanged: bool = True
235119
# try to rotate the dest array until something changes
236-
while try_single_rotate(dest, rint(0, length), rint(0, length)):
237-
pass # do nothing in the loop, but try rotating again
120+
while unchanged:
121+
i1: int = random.integers(0, length)
122+
i2: int = random.integers(0, length)
123+
if i1 == i2: # nothing to be done
124+
continue # array will not be changed
125+
126+
if i1 < i2: # rotate to the left: move elements to lower indices?
127+
first = dest[i1] # get the element to be removed
128+
while i1 < i2: # iterate the indices
129+
i3 = i1 + 1 # get next higher index
130+
cpy = dest[i3] # get next element at that higher index
131+
unchanged &= (cpy == dest[i1]) # is a change?
132+
dest[i1] = cpy # store next element at the lower index
133+
i1 = i3 # move to next higher index
134+
unchanged &= (first == dest[i2]) # check if change
135+
dest[i2] = first # store removed element at highest index
136+
continue
137+
138+
last = dest[i1] # last element; rotate right: move elements up
139+
while i2 < i1: # iterate over indices
140+
i3 = i1 - 1 # get next lower index
141+
cpy = dest[i3] # get element at that lower index
142+
unchanged &= (cpy == dest[i1]) # is a change?
143+
dest[i1] = cpy # store element at higher index
144+
i1 = i3 # move to next lower index
145+
unchanged &= (last == dest[i2]) # check if change
146+
dest[i2] = last # store removed element at lowest index
238147

239148

240149
class Op1Insert1(Op1):
@@ -254,7 +163,6 @@ def __init__(self) -> None:
254163
"""Initialize the object."""
255164
super().__init__()
256165
self.op1 = rotate # type: ignore # use function directly
257-
# end book
258166

259167
def __str__(self) -> str:
260168
"""

moptipy/operators/permutations/op1_swap2.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,14 @@
4848
"""
4949
from typing import Callable, Final
5050

51+
import numba # type: ignore
5152
import numpy as np
5253
from numpy.random import Generator
5354

5455
from moptipy.api.operators import Op1
5556

5657

57-
# Temporary fix for https://github.com/numba/numba/issues/9103
58+
@numba.njit(cache=True, inline="always", fastmath=True, boundscheck=False)
5859
def swap_2(random: Generator, dest: np.ndarray, # +book
5960
x: np.ndarray) -> None: # +book
6061
"""

moptipy/operators/permutations/op1_swap_exactly_n.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@
118118
"""
119119
from typing import Counter, Final, Iterable
120120

121+
import numba # type: ignore
121122
import numpy as np
122123
from numpy.random import Generator
123124
from pycommons.types import check_int_range, type_error
@@ -239,7 +240,7 @@ def get_max_changes(blueprint: Iterable[int]) -> int:
239240
return changes
240241

241242

242-
# Temporary fix for https://github.com/numba/numba/issues/9103
243+
@numba.njit(cache=True, inline="always", fastmath=True, boundscheck=False)
243244
def find_move(x: np.ndarray, indices: np.ndarray, step_size: int,
244245
random: Generator, max_trials: int,
245246
temp: np.ndarray) -> np.ndarray:
@@ -402,7 +403,7 @@ def find_move(x: np.ndarray, indices: np.ndarray, step_size: int,
402403
return temp[0:best_size]
403404

404405

405-
# Temporary fix for https://github.com/numba/numba/issues/9103
406+
@numba.njit(cache=True, inline="always", fastmath=True, boundscheck=False)
406407
def apply_move(x: np.ndarray, dest: np.ndarray, move: np.ndarray,
407408
random: Generator, max_trials: int) -> None:
408409
"""

moptipy/operators/permutations/op1_swap_try_n.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"""
1919
from typing import Final
2020

21+
import numba # type: ignore
2122
import numpy as np
2223
from numpy.random import Generator
2324
from pycommons.types import type_error
@@ -28,7 +29,7 @@
2829
from moptipy.utils.nputils import DEFAULT_INT, fill_in_canonical_permutation
2930

3031

31-
# Temporary fix for https://github.com/numba/numba/issues/9103
32+
@numba.njit(cache=True, inline="always", fastmath=True, boundscheck=False)
3233
def swap_try_n(random: Generator, dest: np.ndarray, x: np.ndarray,
3334
step_size: float, indices: np.ndarray) -> None:
3435
"""

moptipy/operators/permutations/op1_swapn.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,14 @@
4949
"""
5050
from typing import Callable, Final
5151

52+
import numba # type: ignore
5253
import numpy as np
5354
from numpy.random import Generator
5455

5556
from moptipy.api.operators import Op1
5657

5758

58-
# Temporary fix for https://github.com/numba/numba/issues/9103
59+
@numba.njit(cache=True, inline="always", fastmath=True, boundscheck=False)
5960
def swap_n(random: Generator, dest: np.ndarray, # +book
6061
x: np.ndarray) -> None: # +book
6162
"""

0 commit comments

Comments
 (0)