1

I'm trying to perform the following nested summation in python:

enter image description here

so I tried the following code:

import numpy as np
gamma = 17
R = 0.5
H = np.array([0.1,0.2])
alpha = np.array([0.1,0.2])
n = 2

F = 0
for i in range(n):
    for j in range(i+1):
        F = F + 3*gamma*H[i]*(R+H[j]*np.tan(alpha[j]))**2

But of course, this isn't giving me the right answer since it is summing all the terms again in the j loop. My question is how I can solve it? Bear in mind that this is just a small piece of a big expression with several summations for j like the one above inside a summation for i, so it must be something a little optimized. Thank you in advance!

Mr. T
  • 11,960
  • 10
  • 32
  • 54
  • 1
    I have heavily updated my answer. If you are concerned about efficiency, I recommend you take a look at [the updated version](https://stackoverflow.com/a/66475880/3566606) :) – Jonathan Scholbach Mar 04 '21 at 13:52
  • Thank you very much @jonathan.scholbach, I'll look forward to adopting your solution in the complete equation :) – Mateus Forcelini Mar 04 '21 at 13:56

4 Answers4

3

I see at least the following options (order by increase efficiency):

Just the readable Pythonic Way

One nice thing about python is that you can write these expressions in very close analogy to the way it is written in math. In your case, you want to sum over an iterable of numbers:

f = sum(
    3 * gamma * H[i] * (
        R + (
            sum(
                H[j] * np.tan(alpha[j])
                for j in range(i+1)
            )
       )
    )**2
    for i in range(n)
)

Caching the Inner Sum

In your case, the inner sum

sum(
    H[j] * np.tan(alpha[j])
    for j in range(i+1)
)

is calculated multiple times, while it just increments in every iteration. Let's just call this term inner_sum(index). Then inner_sum(index-1) has already been calculated in the previous iteration. So we loose time when we recalculate it in every iteration. One approach could be to make inner_sum a function and cache its previous results. We could use functools.cache for that purpose:

from functools import cache


@cache
def inner_sum(index: int) -> float:
   if not index:
      return H[0] * np.tan(alpha[0])
   return inner_sum(H, index - 1) + H[index] * np.tan(alpha[index])

Now, we can just write:

f = sum(
    3 * gamma * H[i] * (
        R + inner_sum(i)
    )**2
    for i in range(n)
)

Using a Generator for the Partial Sum

This is still not memory-efficient, because we store all the H[i] for i < index in memory, while we actually just need the last one. There are different ways to implement an object which only stores the last value. You could just store it in a variable inner_sum_previous, for example. Or you could make inner_sum a proper generator spitting out (in fact: yielding) the partial sums one after another:

from typing import Generator


def partial_sum() -> Generator[float, None, None]:
    partsum = 0
    index = 0
    while True:
        try:
            partsum += H[index] * np.tan(alpha[index])
            yield partsum
            index += 1
        except IndexError:
            raise StopIteration

With this, we would write;

partial_sum_generator = partial_sum()
f = sum(
    3 * gamma * H[i] * (
        R + next(partial_sum_generator)
    )**2
    for i in range(n)
)
Jonathan Scholbach
  • 4,925
  • 3
  • 23
  • 44
2

for loop, in this case, is very similar to , i.e. everything that is outside the in your formula should be outside the for loop, i.e.:

...
F = 0
for i in range(n):
    inner_sum = 0
    for j in range(i+1):
        inner_sum += H[j] * np.tan(alpha[j])
    F += 3 * gamma * H[i] * (R + inner_sum) ** 2
Yevhen Kuzmovych
  • 10,940
  • 7
  • 28
  • 48
  • Ok, I thought there's another option to do it since I have tons of inner sums in the original expression, but it's ok. Thank you for the help! – Mateus Forcelini Mar 04 '21 at 13:20
1

Calculate the part inside the parenthesis first in the j loop, store it in a variable, then multiply it by the rest of the expression afterwards.

1

Just for compactness and readability, I would go for something like this:

for i in range(n):
     3*gamma*H[i]*(R + np.sum([H[j]*np.tan(alpha[i]) for j in range(i)]))**2

Obviously, you can also convert the first for loop into a sum over a list as I did with the second summation to make the expression more compact, but I think it is more readable this way.