clearseplex
clearseplex

Reputation: 729

Iterative outer addition Numpy

I want to apply outer addition of multiple vectors/matrices. Let's say four times:

import numpy as np 

x = np.arange(100)
B = np.add.outer(x,x)
B = np.add.outer(B,x)
B = np.add.outer(B,x)

I would like best if the number of additions could be a variable, like a=4 --> 4 times the addition. Is this possible?

Upvotes: 2

Views: 137

Answers (3)

Paul Panzer
Paul Panzer

Reputation: 53089

Short and reasonably fast:

n = 4
l = 10
x = np.arange(l)

sum(np.ix_(*n*(x,)))

timeit(lambda:sum(np.ix_(*n*(x,))),number=1000)
# 0.049082988989539444

We can speed this up a little by going back to front:

timeit(lambda:sum(reversed(np.ix_(*n*(x,)))),number=1000)
# 0.03847671199764591

We can also build our own reversed np.ix_:

from operator import getitem
from itertools import accumulate,chain,repeat

sum(accumulate(chain((x,),repeat((slice(None),None),n-1)),getitem))

timeit(lambda:sum(accumulate(chain((x,),repeat((slice(None),None),n-1)),getitem)),number=1000)
# 0.02427654700295534

Upvotes: 1

Divakar
Divakar

Reputation: 221674

Approach #1

Here's one with array-initialization -

n = 4 # number of iterations to add outer versions
l = len(x)
out = np.zeros([l]*n,dtype=x.dtype)
for i in range(n):
    out += x.reshape(np.insert([1]*(n-1),i,l))

Why this approach and not iterative addition to create new arrays at each iteration?

Iteratively creating new arrays at each iteration would require more memory and hence memory-overhead there. With array-initialization, we are adding element off x into an already initialized array. Hence, it tries to be memory-efficient with it.

Alternative #1

We can remove one iteration with initializing with x. Hence, the changes would be -

out = np.broadcast_to(x,[l]*n).copy()
for i in range(n-1):

Approach # 2: With np.add.reduce -

Another way would be with np.add.reduce, which again doesn't create any intermediate arrays, but being a reduction method might be better here as that's what it's implemented for -

l = len(x); n = 4
np.add.reduce([x.reshape(np.insert([1]*(n-1),i,l)) for i in range(n)])

Timings -

In [17]: x = np.arange(100)

In [18]: %%timeit
    ...: n = 4 # number of iterations to add outer versions
    ...: l = len(x)
    ...: out = np.zeros([l]*n,dtype=x.dtype)
    ...: for i in range(n):
    ...:     out += x.reshape(np.insert([1]*(n-1),i,l))
829 ms ± 28.1 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

In [19]: l = len(x); n = 4

In [20]: %timeit np.add.reduce([x.reshape(np.insert([1]*(n-1),i,l)) for i in range(n)])
183 ms ± 2.52 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

Upvotes: 3

Lukas Thaler
Lukas Thaler

Reputation: 2720

I don't think there's a builtin argument to repeat this procedure several times, but you can define a custom function for it fairly easily

def recursive_outer_add(arr, num):
    if num == 1:
        return arr

    x = np.add.outer(arr, arr)

    for i in range(num - 1):
        x = np.add.outer(x, arr)
    return x

Just as a warning: the array gets really big really fast

Upvotes: 1

Related Questions