Reputation: 279
I wrote some code using scipy to find the root the following equation:
def equation(x, y):
return (x / y) * np.log((a * x / b) + 1.0) - 2.0 * c * c
with a, b, and c scalars.
I have values for y on a rectangular grid (say Y, shape 300x200), and need to find the corresponding x solving the equation for each point. I also have a starting estimate for the value of x at each point (X0, shape 300x 200)
At the moment I've managed to solve this by looping through each item in the array Y, and calling:
for index, value in np.ndenumerate(Y):
result[index] = scipy.optimize.newton(equation, X0[index], args=(value))
# or other scalar solvers like brentq
This works but too slow to allow me to release my script. Given the fact that the values are organised on a grid and the Y and result array contain "continuous" values e.g. gradually changing from the edge of the array towards the center I am sure there must be a nice array oriented / multi dimensional way to solve that problem, which could also give better performance.
I have tried a number of options but have not succeeded so far. Any idea?
Any help would be appreciated.
Thanks
Upvotes: 4
Views: 2838
Reputation: 26040
(expanding a comment) As @askewchan shows in his answer, the runtime here is dominated by actually solving the equations.
Here's what I'd do here: absorb 2*c*c
as a multiplicative constant into y
, ditto for a/b
. What's left is an equation of the form t log (1 + t) = z
, with t
and z
being related to your x
and y
.
Now tabulate the values of z = t log(1 + t)
over the range you need, interpolate t
vs z
, and you have solutions for your equation. Notice that you only need a 1D interpolation, no matter what are the shapes of the arrays X
and Y
.
For interpolation, scipy
has a range of interpolators. The simplest to use is probably interp1d
, or a UnivariateSpline
. All of them support vectorized operations, so you can probably vectorize the evaluations. If this is enough for you performance-wise, you're all set.
Depending on the range of the values of x
and y
you need, you might or might not want to augment the tabulation with the explicit functional behavior at the boundaries [e.g. t log(1 + t)
approaches t^2
as t->0
etc]. If you need that, have a look at https://bitbucket.org/burovski/tabulations --- this is ugly as hell, but it works [there's some work in scipy to have a better alternative, but that's in process at the moment].
Upvotes: 2
Reputation: 46530
It's tough to test without values of Y
and X0
, but using np.frompyfunc
might be a little faster. It takes a scalar function and turns it into a ufunc, which operates on arrays elementwise.
import numpy as np
import scipy.optimize
a, b, c = np.random.rand(3)
def equation(x, y):
return (x/y)*np.log((a*x/b) + 1.0) - 2.*c*c
def solver(y, x0):
return scipy.optimize.newton(equation, x0, args=(y,))
f = np.frompyfunc(solver, 2, 1) # 2 arrays in, 1 array out, apply solver elementwise
Y, X0 = np.random.rand(2, 300, 200)
res = f(Y, X0)
It still doesn't truly vectorize the process, though, and it doesn't speed it up:
In [51]: timeit f(Y, X0)
1 loops, best of 3: 10.4 s per loop
In [52]: timeit OP(Y, X0, a, b, c)
1 loops, best of 3: 10.5 s per loop
But as far as I can tell, the speed issue does not come from the looping, but rather from the fact that you're solving the equation Y.size
times. I believe this is as fast as it will get if you truly solve for each value in Y
:
In [53]: timeit solver(Y[0,0], X0[0,0])
10000 loops, best of 3: 178 µs per loop
In [54]: Y.size*178e-6
Out[54]: 10.68
Probably you already realized this :P but you have to actually decrease the computation, by making some approximation or assumption. Something like @Zhenya's suggestion is probably necessary, but we'd have to know a bit more about the values of Y, X0, a, b, c
.
Upvotes: 1