As we all know, having for and while loops in Python is bad for performance but is sometimes necessary. E.g. in the case of the
heat2d() code, we have to evaluate
delta > epsilon in order to know when to stop iterating. To address this issue, Bohrium introduces the function
bohrium.loop.do_while(), which takes a function and calls it repeatedly until either a maximum number of calls has been reached or until the function return False.
The function signature:
def do_while(func, niters, *args, **kwargs): """Repeatedly calls the `func` with the `*args` and `**kwargs` as argument. The `func` is called while `func` returns True or None and the maximum number of iterations, `niters`, hasn't been reached. Parameters ---------- func : function The function to run in each iterations. `func` can take any argument and may return a boolean `bharray` with one element. niters: int or None Maximum number of iterations in the loop (number of times `func` is called). If None, there is no maximum. *args, **kwargs : list and dict The arguments to `func` Notes ----- `func` can only use operations supported natively in Bohrium. """
An example where the function doesn’t return anything:
>>> def loop_body(a): ... a += 1 >>> a = bh.zeros(4) >>> bh.do_while(loop_body, 5, a) >>> a array([5, 5, 5, 5])
An example where the function returns a
bharray with one element and of type
>>> def loop_body(a): ... a += 1 ... return bh.sum(a) < 10 >>> a = bh.zeros(4) >>> bh.do_while(loop_body, None, a) >>> a array([3, 3, 3, 3])
Sliding Views Between Iterations¶
It can be useful to increase/decrease the beginning of certain array views between iterations of a loop. This can be achieved using
bohrium.loop.get_iterator(), which returns a special bohrium iterator. The iterator can be given an optional start value (0 by default). The iterator is increased by one for each iteration, but can be changed increase or decrease by multiplying any constant (see example 2).
Iterators only supports addition, subtraction and multiplication.
bohrium.loop.get_iterator() can only be used within Bohrium loops. Views using iterators cannot change shape between iterations. Therefore, views such as
a[i:2*i] are not supported.
Example 1. Using iterators to create a loop-based function for calculating the triangular numbers (from 1 to 10). The loop in numpy looks the following:
>>> a = np.arange(1,11) >>> for i in range(0,9): ... a[i+1] += a[i] >>> a array([1 3 6 10 15 21 28 36 45 55])
The same can be written in Bohrium as:
>>> def loop_body(a): ... i = get_iterator() ... a[i+1] += a[i] >>> a = bh.arange(1,11) >>> bh.do_while(loop_body, 9, a) >>> a array([1 3 6 10 15 21 28 36 45 55])
Example 2. Increasing every second element by one, starting at both ends, in the same loop. As it can be seen: i is increased by 2, while j is descreased by 2 for each iteration:
>>> def loop_body(a): ... i = get_iterator(1) ... a[2*i] += a[2*(i-1)] ... j = i+1 ... a[1-2*j] += a[1-2*(j-1)] >>> a = bh.ones(10) >>> bh.for_loop(loop_body, 4, a) >>> a array([1 5 2 4 3 3 4 2 5 1])
Nested loops is also available in
bohrium.loop.do_while() by using grids. A grid is a set of iterators that depend on each other, just as with nested loops. A grid can have arbitrary size and is available via. the function
bohrium.loop.get_grid(), which is only usable within a
bohrium.loop.do_while() loop body. The function takes an amount of integers as parameters, corresponding to the range of the loops (from outer to inner). It returns the same amount of iterators, which functions as a grid. An example of this can be seen in Example 3 below.
Example 3. Creating a range in an array with multiple dimensions. In Numpy it can be written as:
>>> a = bh.zeros((3,3)) >>> counter = bh.zeros(1) >>> for i in range(3): ... for j in range(3): ... counter += 1 ... a[i,j] += counter >>> a [[1. 2. 3.] [4. 5. 6.] [7. 8. 9.]]
The same can done within a
do_while loop by using a grid:
>>> def kernel(a, counter): ... i, j = get_grid(3,3) ... counter += 1 ... a[i,j] += counter >>> a = bh.zeros((3,3)) >>> counter = bh.zeros(1) >>> bh.do_while(kernel, 3*3, a, counter) >>> a [[1. 2. 3.] [4. 5. 6.] [7. 8. 9.]]