1

Is there a way to speed up this code:

import mpmath as mp
import numpy as np
from time import time as epochTime

def func(E):
    f = lambda theta: mp.sin(theta) * mp.exp(E * (mp.cos(theta**2) + \
                                                  mp.cos(theta)**2))
    return f

start = epochTime()
mp.mp.dps = 15
mp.mp.pretty = True

E = np.linspace(0, 10, 200)
ints = [mp.quadgl(func(e), [0, mp.pi]) for e in E] # Main Job
print ('Took:{:.3}s'.format(epochTime() - start))
rowman
  • 1,516
  • 1
  • 16
  • 26

2 Answers2

4

Running your code, I timed it to 5.84s

using Memoize and simplifying expressions:

cos = Memoize(mp.cos)
sin = Memoize(mp.sin)

def func(E):
    def f(t):
        cost = cos(t)
        return sin(t) * mp.exp(E * (cos(t*t) + cost*cost))
    return f

I got it down to 3.25s first time, and ~2.8s in the next iterations.

(An even better approach might be using lru_cache from the standard library, but I did not try to time it).

If you are running similar code many times, it may be sensible to Memoize() both func and f, so the computations become trivial ( ~0.364s ).

Replacing mp with math for cos/sin/exp, I got down to ~1.3s, and now memoizing make the performance worse, for some reason (~1.5s, I guess the lookup time became dominant).

Community
  • 1
  • 1
Elazar
  • 20,415
  • 4
  • 46
  • 67
2

In general, you want to avoid calls to transcendent functions like sin, cos, exp, ln as much as possible, especially in a "hot" function like an integrand.

  • Replace x**2 by x*x (often x**2 calls a generic=slow exponentiation function)
  • use variables for "expensive" intermediate terms which are used more than once
  • transform your equation to reduce or eliminate transcendent functions
  • special-case for typical parameter values. Integer exponents are a frequent candidate.
  • precompute everything that is constant, espc. in parameterized functions

For the particular example you can substitute z=cos(theta). It is dz = -sin(theta)dtheta. Your integrand becomes

-exp(E*(z^2 + cos(arccos(z)^2))

saving you some of the transcendent function calls. The boundaries [0, pi] become [1, -1]. Also avoid x**2, better use x*x.

Complete code:

import mpmath as mp
import numpy as np
from time import time as epochTime

def func(E):
    def f(z):
        acz = mp.acos(z)
        return -mp.exp(E * (mp.cos(acz*acz) + z*z))
    return f

start = epochTime()
mp.mp.dps = 15
mp.mp.pretty = True

E = np.linspace(0, 10, 200)
ints = [mp.quadgl(func(e), [1.0, -1.0]) for e in E] # Main Job
print ('Took:{:.3}s'.format(epochTime() - start))
Torben Klein
  • 2,943
  • 1
  • 19
  • 24