1

I do not know how to insert the if statement if (i + j) % 2 == 0 into a lambda function. It is not as straightforward as a single variable.

Here is my attempt

# dummy values
two_at = 4.5 
n = 5
m = 6
# fromfunction with lambda function
matrix = np.fromfunction(lambda i, j: 1 / (two_at) ** ((i+j)/2) if (i+j) % 2 == 0 else 0.0, (n+1, m+1))

The error I get is

matrix = np.fromfunction(lambda i, j: 1 / (two_at) ** ((i+j)/2) if (i+j) % 2 == 0 else 0.0, (n + 1, m + 1)) ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()

I would be more than satisfied with any answer that is faster than a double for loop such as

   matrix = np.zeros((n,m))
   for i in range(n + 1):
       for j in range(m + 1):
           m = i + j
           if m % 2 == 0:
               matrix[i,j] = 1 / (two_at) ** (m/2) 
6
  • 1
    Just write a regular def function - lambdas are limited to a single expression, which makes it hard to read anything non-trivial. Commented Jul 8, 2020 at 7:40
  • are you sure you get that error for this code or is there other code besides this one? Commented Jul 8, 2020 at 7:41
  • @NikosM. Too ugly to post here, I updated question. Commented Jul 8, 2020 at 7:44
  • @jonrsharpe I will look into this, thanks for the advice. Part of my problem is how do I pass two_at to the function? I get how to use fromfunction if passing only indices. I will read the docs, it must be in there. numpy docs have never let me down before. I think programcreek has some examples. Looks like it isn't an issue so long as I make the function inside the function that defines two_at. Commented Jul 8, 2020 at 7:44
  • 1
    fromfunction generates all indices and passes then as arrays to your function. Your function expects scalar i,j, not 2d arrays. fromfunction is the wrong function for this. Commented Jul 8, 2020 at 7:55

2 Answers 2

2
In [41]: two_at = 2                                                                     
In [42]: def func(i, j): 
    ...:         m = i + j 
    ...:         return np.where(m % 2 == 0, 1 / (two_at) ** (m / 2), 0) 
    ...:  
    ...: matrix = np.fromfunction(func, (4 + 1, 4 + 1))                                 
In [43]: matrix                                                                         
Out[43]: 
array([[1.    , 0.    , 0.5   , 0.    , 0.25  ],
       [0.    , 0.5   , 0.    , 0.25  , 0.    ],
       [0.5   , 0.    , 0.25  , 0.    , 0.125 ],
       [0.    , 0.25  , 0.    , 0.125 , 0.    ],
       [0.25  , 0.    , 0.125 , 0.    , 0.0625]])

With this func, fromfunction doesn't buy us much; just pass broadcastable 1d arrays instead:

In [44]: func(np.arange(5)[:,None], np.arange(5))                                       
Out[44]: 
array([[1.    , 0.    , 0.5   , 0.    , 0.25  ],
       [0.    , 0.5   , 0.    , 0.25  , 0.    ],
       [0.5   , 0.    , 0.25  , 0.    , 0.125 ],
       [0.    , 0.25  , 0.    , 0.125 , 0.    ],
       [0.25  , 0.    , 0.125 , 0.    , 0.0625]])

In my other answer I used multiplication to apply the cond to the results.

As for the comparison with loops. Timings with a small example often favor list and iterative solutions. numpy fares much better when sizes are in the 1000s.

Some timings:

In [55]: timeit matrix = np.fromfunction(func, (4 + 1, 4 + 1))                          
47.2 µs ± 94.4 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
In [56]: timeit func(np.arange(5)[:,None], np.arange(5))                                
33.7 µs ± 112 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
In [61]: %%timeit 
    ...: res = np.zeros((5,5)) 
    ...: for i in range(5): 
    ...:     for j in range(5): 
    ...:         m=i+j 
    ...:         if m%2==0: 
    ...:             res[i,j] = 1/(two_at)** (m/2) 
    ...:              
    ...:                                                                                

9.77 µs ± 26.1 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

But increase the size:

In [62]: %%timeit 
    ...: res = np.zeros((500,500)) 
    ...: for i in range(500): 
    ...:     for j in range(500): 
    ...:         m=i+j 
    ...:         if m%2==0: 
    ...:             res[i,j] = 1/(two_at)** (m/2) 
    ...:              
    ...:                                                                                             
71.9 ms ± 206 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
In [63]: timeit func(np.arange(500)[:,None], np.arange(500))                                         
33.3 ms ± 65.3 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
Sign up to request clarification or add additional context in comments.

1 Comment

Thanks @hpaulj. Unfortunately these loops are the 7th and 8th loops in a nested set, but will never be passed anything very large. I have been trying to broadcast, but it is to no avail on these little loops. I need to go out one level and broadcast over 4 loops I think, or use JIT. These loops get called alot :S
1

One solution as mentioned by jonrsharpe is to define a function.

def func(i, j):
        m = i + j
        return np.where(m % 2 == 0, 1 / (two_at) ** (m / 2), 0)

matrix = np.fromfunction(func, (n + 1, n + 1))

1 Comment

this is incredibly slow. A double for loop is many times faster.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.