scipy.optimize, IndexError: Invalid Index to Scalar Variable - python

I am trying to implement scipy.optimmize.minimize on a multivariate scalar function using the nelder-mead method. My function definition when called in a print function works perfectly fine. When I turn on minimize it throws the IndexError.
from scipy.optimize import minimize
import math
import numpy as np
c_ = [1.,1.,1.]
d_ = [1.,1.,1.]
x_=[c_,d_]
def hamiltonian(x_):
N=len(c_)
return np.sum([(1/n**3.0)*(n-i)*i*(x_[0][(n-i)]*x_[0][i]+x_[1][(n-i)]*x_[1][i])-(1/n**3.0/alpha**2.0)*np.sum([x_[0][(n-i-j)]*x_[0][i]*x_[1][j] for j in range(0,(n-i+1))]) for n in range(1,N) for i in range(0,(n+1))])
print hamiltonian(x_) #no problem here
res = minimize(hamiltonian, x_, method='nelder-mead') #problem here

That is because x_ is a list of lists:
>>> x_
[[1.0, 1.0, 1.0], [1.0, 1.0, 1.0]]
Thus replace the line x_=[c_,d_] with:
>>> x_ = c_ + d_
You'll also have to modify your hamiltonian for a 1xN or Nx1 list/array.
The simplest hack that I can think of is:
def hamiltonian(x_):
N=len(c_)
if type(x_[0]) == np.array:
x_ = np.concatenate((x_[0:N], x_[N:]), axis=0)
return np.sum([(1/n**3.0)*(n-i)*i*(x_[0][(n-i)]*x_[0][i]+x_[1][(n-i)]*x_[1][i])-(1/n**3.0/alpha**2.0)*np.sum([x_[0][(n-i-j)]*x_[0][i]*x_[1][j] for j in range(0,(n-i+1))]) for n in range(1,N) for i in range(0,(n+1))])

Related

Integrate over 2d polygon quadpy

I am trying to integrate a function on a 2d polygon described by its vertices as follow
import numpy as np
import quadpy
def f(x):
return x[0]
poly = np.array([[0.0, 0.0], [1.0, 0.0], [0, 1], [1,1]])
scheme = quadpy.t2.get_good_scheme(10)
val = scheme.integrate(f, poly)
But I get
QuadpyError: Wrong domain shape.
I really appreciate any kind of help
quad.t2 is for triangles, three points are expected. Your polygon is a square, you have to use quad.c2.
import quadpy
scheme = quadpy.c2.get_good_scheme(7)
val = scheme.integrate(
lambda x : x[0],
[ [[0.0, 0.0], [1.0, 0.0]], [[0.0, 1.0], [1.0, 1.0]] ]
)
val
This gives 0.5, which is easy to get mathematically.
See the link for the way to specify the quadrilateral.

How can I apply a piecewise function to every element of a 2D numpy array

Lets say I have a 2D numpy array, like
arr = array([[0, 0.001 , 0.002], [0.03, 0.04, 0.05], [0.01, 0.002, 0.5], [0.05, 0.8, 0.003]])
and I want to perform a piecewise function on it, say
def gammacor(x):
return np.piecewise(x, [x <= 0.00313, x > 0.00313], [12.92*x, 1.055*x**(1/2.4)-0.055])
gcarr = gammacor(arr)
When I do this, I get an error:
TypeError: NumPy boolean array indexing assignment requires a 0 or 1-dimensional input, input has 2 dimensions
If I try to run the function on the flattened array (with the plan to reshape back to n x 3 after running the function), I get the error:
ValueError: NumPy boolean array indexing assignment cannot assign 3 input values to the 0 output values where the mask is true
Is there an easy way to apply a piecewise function to all elements of a 2D (or ND) array?
The third parameter of np.piecewise is a funclist.
They should be callables:
import numpy as np
arr = np.array([[0, 0.001, 0.002], [0.03, 0.04, 0.05], [0.01, 0.002, 0.5],
[0.05, 0.8, 0.003]])
p = np.piecewise(arr, [arr <= 0.00313, arr > 0.00313],
[lambda v: 12.92 * v,
lambda v: 1.055 * v ** (1 / 2.4) - 0.055])
print(p)
Output:
[[0. 0.01292 0.02584 ]
[0.18974828 0.22091636 0.24780053]
[0.09985282 0.02584 0.73535698]
[0.24780053 0.90633175 0.03876 ]]
def gammacor(x):
return np.piecewise(x, [x <= 0.00313, x > 0.00313],
[lambda v: 12.92 * v,
lambda v: 1.055 * v ** (1 / 2.4) - 0.055])
gcarr = gammacor(arr)

How to use scipy's leastsq to solve for an array?

Is there anyway we can use Scipy's leastsq solver (or any other function in python) to find an array instead of a vector?
Basically, I want to find C that minimizes function my_func. I think one way is to convert C to a vector and rewrite the function my_func such that the unknowns are a vector. But, is there a better way?
import numpy as np
from scipy.optimize import leastsq
def my_func(C, x, y):
return y - C.dot(x)
x_data = np.array([2, 3, 4])
y_data = np.array([20, 30])
starting_guess = np.ones((2, 3))
data = (x_data, y_data)
result = leastsq(my_func, starting_guess, args=data)
print result
solution = result[0]
print solution
You can use flatten() and reshape() from the numpy library to go back and forth between 1d and 2d arrays. As for the minimization itself, I suggest scipy.optimize.minimize().
Please note that the difference between two vectors is a vector (I am referring to v = y - C.x here), therefore you need the norm to convert this into a metric that you can minimize. Below, you find a good example on how to code this:
import numpy as np
from scipy.optimize import minimize
def my_func(C_flat, x, y):
# print(np.linalg.norm(y - np.dot(C_flat.reshape(2, 3), x), 2))
return np.linalg.norm(y - np.dot(C_flat.reshape(2, 3), x), 2)
x_data = np.array([2, 3, 4])
y_data = np.array([20, 30])
C0 = np.ones((2, 3))
data = (x_data, y_data)
result = minimize(my_func, C0.flatten(), args = data)
print(result)
solution = result["x"].reshape(2,3)
print(solution)

how to fix "only length-1 arrays can be converted to Python scalars" when getting integral with an array argument

I am using quad from scipy.integrate to get an integral in a limited range from an object. suppose the target object is in the blow:
∫expm(A*X).expm(B*X)dx
which both A and B are numpy matrix.
To solve this I have used blow code:
from scipy.integrate import quad
from scipy.linalg import expm
import numpy as np
def integrand(X, A, B):
return np.dot(expm(A*X),expm(B*X))
A = np.array([[1, 2], [3, 4]])
B = np.array([[1, 2], [3, 4]])
I= quad(integrand, 0, 1, args=(A,B))
But for the result I get this error:
TypeError: only length-1 arrays can be converted to Python scalars
I know that The error "only length-1 arrays can be converted to Python scalars" is raised when the function expects a single value but you pass an array instead. but my problem is based on array. so how can I fix it.
As pointed in the comments, quad expects a scalar function. You can always pass the function to a scalar by adding the index as an output:
def integrand(X, A, B, ix=None):
""" pass ix=None to return the matrix, ix = 0,1,2,3 to return an element"""
output = np.dot(expm(A*X),expm(B*X))
if ix is None:
return output
i, j = ix//2, ix%2
return output[i,j]
I= np.array([quad(integrand, 0, 1, args=(A,B, i))[0]
for i in range(4)]).reshape(2,2)
I
>>array([[1031.61668602, 1502.47836021],
[2253.71754031, 3285.33422634]])
Note that this is very inefficient since you are calculating the integral 4 times, as long as this doesn't bother you.
Alternatively, use trapz:
x_i = np.linspace(0,1,60)
np.trapz([integrand(x, A, B) for x in x_i], x=x_i, axis=0)
>>array([[1034.46472361, 1506.62915374],
[2259.94373062, 3294.40845422]])
quadpy does vectorized computation. The fact that expm only works on square matrices (and not on lists of square matrices) requires a bit of juggling with the matrix shapes, though.
from quadpy import quad
import numpy as np
from scipy.linalg import expm
A = np.array([[1, 2], [3, 4]])
B = np.array([[1, 2], [3, 4]])
def integrand(X):
expAX = np.array([expm(A * x) for x in X])
expAX = np.moveaxis(expAX, 0, -1)
#
expBX = np.array([expm(B * x) for x in X])
expBX = np.moveaxis(expBX, 0, -1)
return np.einsum("ij...,jk...->ik...", expAX, expBX)
val, err = quad(integrand, 0, 1)
print(val)
[[1031.61668602 1502.47836021]
[2253.71754031 3285.33422633]]

draw random element in numpy

I have an array of element probabilities, let's say [0.1, 0.2, 0.5, 0.2]. The array sums up to 1.0.
Using plain Python or numpy, I want to draw elements proportional to their probability: the first element about 10% of the time, second 20%, third 50% etc. The "draw" should return index of the element drawn.
I came up with this:
def draw(probs):
cumsum = numpy.cumsum(probs / sum(probs)) # sum up to 1.0, just in case
return len(numpy.where(numpy.random.rand() >= cumsum)[0])
It works, but it's too convoluted, there must be a better way. Thanks.
import numpy as np
def random_pick(choices, probs):
'''
>>> a = ['Hit', 'Out']
>>> b = [.3, .7]
>>> random_pick(a,b)
'''
cutoffs = np.cumsum(probs)
idx = cutoffs.searchsorted(np.random.uniform(0, cutoffs[-1]))
return choices[idx]
How it works:
In [22]: import numpy as np
In [23]: probs = [0.1, 0.2, 0.5, 0.2]
Compute the cumulative sum:
In [24]: cutoffs = np.cumsum(probs)
In [25]: cutoffs
Out[25]: array([ 0.1, 0.3, 0.8, 1. ])
Compute a uniformly distributed random number in the half-open interval [0, cutoffs[-1]):
In [26]: np.random.uniform(0, cutoffs[-1])
Out[26]: 0.9723114393023948
Use searchsorted to find the index where the random number would be inserted into cutoffs:
In [27]: cutoffs.searchsorted(0.9723114393023948)
Out[27]: 3
Return choices[idx], where idx is that index.
You want to sample from the categorical distribution, which is not implemented in numpy. However, the multinomial distribution is a generalization of the categorical distribution and can be used for that purpose.
>>> import numpy as np
>>>
>>> def sampleCategory(p):
... return np.flatnonzero( np.random.multinomial(1,p,1) )[0]
...
>>> sampleCategory( [0.1,0.5,0.4] )
1
use numpy.random.multinomial - most efficient
I've never used numpy, but I assume my code below (python only) does the same thing as what you accomplished in one line. I'm putting it here just in case you want it.
Looks very c-ish so apologies for not being very pythonic.
weight_total would be 1 for you.
def draw(probs)
r = random.randrange(weight_total)
running_total = 0
for i, p in enumerate(probs)
running_total += p
if running_total > r:
return i
use bisect
import bisect
import random
import numpy
def draw(probs):
cumsum=numpy.cumsum(probs/sum(probs))
return bisect.bisect_left(cumsum, numpy.random.rand())
should do the trick.

Categories