I have an application where I need to merge two solutions obtained from the scipy.integrate.solve_ivp in python. The actual application is a bit more complicated, but the following example shows the idea:
from scipy.integrate import solve_ivp import numpy as np
def lotkavolterra(t, z, a, b, c, d):
x, y = z
return [a*x - b*x*y, -c*y + d*x*y]
sol_1 = solve_ivp(lotkavolterra, [0, 10], [10, 5], args=(1.5, 1, 3, 1), dense_output=True).sol
sol_2 = solve_ivp(lotkavolterra, [10, 15], [10, 5], args=(1.5, 1, 3, 1), dense_output=True).sol
def sol_comb(t):
if t <= 10:
return sol_1(t)
else:
return sol_2(t)
I want to be able to use the merged or combined solution sol_comb on numpy arrays. Hence I tried to define a vectorized solution as follows:
sol_comb_vect = np.vectorize(sol_comb)
The following code, where I only call the functions on scalars, works fine:
print("sol_1 for t = 2",sol_1(2))
print("sol_2 for t = 11",sol_2(11))
print("sol_comb for t = 11",sol_comb(11))
print("sol_comb_vect for t = 11",sol_comb_vect(11))
The individual solutions sol_1 and sol_2 are apparently vectorized, since the following works fine:
print("sol_1 for t = [2,3]",sol_1(np.array([2])))
print("sol_2 for t = [11,13]",sol_2(np.array([11,13])))
However, if I call the non-vectorized function sol_comb on an array, as in the following example, I get the expected ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all():
print("sol_comb for t = [11,13]",sol_comb(np.array([11,13])))
I was expecting the vectorized version sol_comb_vect to work. However, in the following, I get the error ValueError: setting an array element with a sequence.
print("sol_comb_vect for t = [11,13]",sol_comb_vect(np.array([11,13])))
Any ideas how to fix this?
I would also be happy to merge the two OdeSolution instances in a cleaner way. In principle I think this should be possible, by using the time values and interpolants for sol_1 and sol_2, respectively.
I think you need to specify the signature for your output when you vectorize your function since by default the pyfunc you pass to np.vectorize() is assumed to take scalars as input and output see doc. And I assume that your ValueError is caused by that. So try this:
sol_comb_vect = np.vectorize(sol_comb, signature='()->(n)')
sol_comb_vect(np.array([2, 11, 13]))
output:
array([[0.60031288, 0.09618044],
[0.21298705, 1.36999868],
[2.58274789, 0.01857732]])
I don't know if this is the expected output tho. I hope this answers your question.
Related
I want to njit a function with numba, where pnt_group_ids_ can be in two types, np.int64 or np.int64[::1]. :
import numpy as np
import numba as nb
sorted_fb_i = np.array([1, 3, 4, 2, 5], np.int64)
fb_groups_ids = nb.typed.List([np.array([4, 2], np.int64), np.array([1, 3, 5], np.int64)])
moved_fb_group_ids = nb.typed.List.empty_list(nb.types.Array(dtype=nb.int64, ndim=1, layout="C"))
ind = 0
#nb.njit
def points_group_ids(sorted_fb_i, fb_groups_ids, moved_fb_group_ids, ind):
pnt_group_ids_ = sorted_fb_i[ind]
for i in range(len(fb_groups_ids)):
if sorted_fb_i[ind] in fb_groups_ids[i]:
pnt_group_ids_ = fb_groups_ids[i]
moved_fb_group_ids.append(fb_groups_ids.pop(i))
break
return pnt_group_ids_, fb_groups_ids, moved_fb_group_ids
which will get error:
Cannot unify array(int64, 1d, C) and int64 for 'pnt_group_ids_.2'
Is there any way to write signature for that which can handle the both types, something like:
((int64, int64[::1]), ListType(int64[::1]), ListType(int64[::1]))(int64[::1], ListType(int64[::1]), ListType(int64[::1]), int64)
If it could not to be handled by signatures, the related line can be substituted by:
pnt_group_ids_ = np.array([sorted_fb_i[ind]], np.int64)
Which will work. But, how to signature this when we have multiple inputs and multiple outputs? Now, it will get the following error when we use such the above signature with just one type:
TypeError: 'tuple' object is not callable
This function will be called in a loop, so moved_fb_group_ids, which was an empty numba list and should have been typed otherwise it get error, will be filled and fb_groups_ids becomes empty; Does emptyness of fb_groups_ids will cause the code to get error?
The main goal of this question was about how to write signatures (for both inputs and outputs besides each other) for this function (I know that it is recommended to let numba find them), when we have multiple input and multiple output (preferring signature that can handle both types without changing the code, if it be possible).
as a single number can be an array with 1 element, a simple solution is to just convert your single number to an array.
pnt_group_ids_ = sorted_fb_i[ind:ind+1]
#nb.njit("Tuple((int64[::1],ListType(int64[::1]),ListType(int64[::1])))(int64[::1], ListType(int64[::1]), ListType(int64[::1]), int64)")
def points_group_ids(sorted_fb_i, fb_groups_ids, moved_fb_group_ids, ind):
pnt_group_ids_ = sorted_fb_i[ind:ind+1]
for i in range(len(fb_groups_ids)):
if sorted_fb_i[ind] in fb_groups_ids[i]:
pnt_group_ids_ = fb_groups_ids[i]
moved_fb_group_ids.append(fb_groups_ids.pop(i))
break
return pnt_group_ids_, fb_groups_ids, moved_fb_group_ids
and it works .... without any context of what this is for ...
...and a suggestion to Use a.any() or a.all().
I am new to python and i am trying to implement a sabr model. I have defined a function with the following parameters:
def haganimpliedvol(a,f,k,B,v,t,p):
if k != f:
z = v/a*math.pow(f*k,(1-B)/2)*math.log(f/k)
xz = math.log((math.sqrt(1-2*p*z+math.pow(z,2))+z-p)/(1-p))
sigma = a/math.pow(f*k,(1-B)/2)*(1 + math.pow(1-B,2)/24* math.pow(math.log(f/k),2)+\
math.pow(1-B,4)/1920* math.pow(math.log(f/k),4))*\
xz*\
(1+(math.pow(1-B,2)/24*math.pow(a,2)/math.pow(f/k,1-B)+1/4*(p*B*v*a)/math.pow(f/k,(1-B)/2)+\
(2-3*math.pow(p,2))/24*math.pow(v,2)))*t
else:
sigma = a/math.pow(f,1-B)*\
(1+(math.pow(1-B,2)/24*math.pow(a,2)/math.pow(f,(2-2*B))+\
1/4*(p*B*a*v)/math.pow(f,1-B)+(2-3*math.pow(p,2))/24*math.pow(v,2)))*t
return(sigma)
Now I define another function to and call the haganimpliedvol() function
params = [0.4,0.6,0.1,-0.4]
def objective(params):
global k,sigma_iv,t,f
a = params[0]
B = params[1]
v = params[2]
p = params[1]
for (i,j,k) in zip(k,t,f):
calc_vols = np.array([haganimpliedvol(a,f,k,B,v,t,p)])
return(calc_vols)
As can be seen, a few parameters in the functions are list. I want to get an array as an output. However, I keep getting the message in the subject line.
Pay attention to the variables in this call:
for (i,j,k) in zip(k,t,f):
calc_vols = np.array([haganimpliedvol(a,f,k,B,v,t,p)])
for the zip to work, k,t, f have to be lists or arrays of matching size;
Done use k for an iteration variable; it is already used in the zip. I think you are just being careless here; or confused.
And the arguments to the hagen... function. Are the f, k, t supposed to be variables used in the zip? It would make more sense to use the iteration variables (i,j,?). Again, this just looks like you are careless, or don't care what happens.
As for the ambiguity error, that most likely arises in the
if k != f:
If either k or f is an array (or both) the k!=f will be a boolean array. That can't be used in if, which requires a simple True or False value. It does not iterate on the conditions. It is a basic Python if - a switch.
This ambiguity error comes up frequently, in various contexts, but all with the same basic issue - using an array in a context that requires a scalar T/F. A simple web search should provide lots of examples.
#hpaulj thank you for leading me on the right path. I vectorized my function and made some edits and now it is working fine.
haganimpliedvol = np.vectorize(haganimpliedvol,excluded = ['a','B','v','p'])
params = [0.2,0.7,0.01,-0.4]
def objective(params):
global k,sigma_iv,t,f
a = params[0]
B = params[1]
v = params[2]
p = params[1]
calc_vols = haganimpliedvol(a,f,k,B,v,t,p)
return(calc_vols)
Are you sure you want to pass arrays into the haganimpliedvol() function?
The general convention is to write functions which take a single input type.
Maybe call it one per item in the array?
Or write the function in a way that, if it sees the input is a list it iterates and if it sees the inputs arent lists then it just calculates it one time.
See this thread for ideas
How to make a function that can handle single inputs or lists of inputs
Dlib has recently released a new method for global optimization, minimizing the number of function calls (http://blog.dlib.net/2017/12/a-global-optimization-algorithm-worth.html)
I would like to use this in Python, and for a simple cost function it works well:
import dlib
import numpy as np
def cost_fn_2_params(x0, x1):
return np.sum([x0, x1])
dlib.find_max_global(f=cost_fn_2_params, bound1=[-1., -1],
bound2=[1., 1], num_function_calls=10)
Now, in my particular use-case I have a variable number of parameters going in to my cost function, and find_global_max expects the cost function to have a static number of arguments. For example, defining the cost function in another way:
def cost_fn_unspecified_params(**params):
return np.sum(params)
dlib.find_max_global(f=cost_fn_unspecified_params, bound1=[-1., -1],
bound2=[1., 1], num_function_calls=10)
I get the following error:
Failing expression was num == args.size().
The function being optimized takes a number of arguments that doesn't agree with the size of the bounds lists you provided to find_max_global()
My question is: Is there any way to dynamically define functions with a certain number of arguments? Excluding the *params syntax as this apparently looks like a single argument to dlib.
I have considered using exec to define a function, but it seems overly complicated. This is my best solution so far:
def get_fn_n_arguments(n):
argument_list = [f'x{ii}' for ii in range(n)]
argument_str = ','.join(argument_list)
def_str = f'def fn({argument_str}): \n\tparams = [{argument_str}]\n\treturn cost_fn_unspecified_params(params)'
print(def_str)
exec(def_str, globals())
This puts a function fn into the global namespace, and now I can do
get_fn_n_arguments(2)
dlib.find_max_global(f=fn, bound1=[-1., -1],
bound2=[1., 1], num_function_calls=10)
It works, but it seems like a very brittle solutions. I would really like to improve it.
First post so I'll try to be specific as possible.
I'm trying to define a term ybar1 in Python. I'm intending for ybar1 to be the average of a list y1
where y1 = [1, 2, 3, 4, 5]
What I'm doing first is:
import numpy as np
then defining a new function
def funk(y1):
print np.sum(y1)/len(y1)
return;
So now when I compute funk(y1) I get a number, 3
Now is where it gets weird. I try to say:
ybar1 = funk(y1)
, which returns the value 3
but then when I type ybar1 alone, I get no output.
Thoughts?
Try this:
def funk(y1):
return np.sum(y1)/len(y1)
You were not actually returning a value from the function, only printing a result on the console. Also, there's a better way to compute the average using numpy:
def funk(y1):
return np.mean(y1)
I'm trying out numpy by porting over some code I wrote in matlab/octave. In matlab, I can define the equivalent of a python slice, and then increment it as needed. For example, in my matlab code I have
HXx_range = 1:NHXx;
HXy_range = 1:NHXy;
blah blah blah
Hx(HXx_range, HXy_range) = Da(Hx_media(HXx_range, HXy_range)).*Hx(HXx_range, HXy_range) + Db(Hx_media(HXx_range, HXy_range)).*(Ez(HXx_range,HXy_range) - Ez(HXx_range,**HXy_range+1**));
Hy(HYx_range, HYy_range) = Da(Hy_media(HYx_range, HYy_range)).*Hy(HYx_range, HYy_range) + Db(Hy_media(HYx_range, HYy_range)).*(Ez(**HYx_range+1**,HYy_range) - Ez(HYx_range,HYy_range));
Ez(EZx_range, EZy_range) = Ca(Ez_media(EZx_range, EZy_range)).*Ez(EZx_range, EZy_range) + Cb(Ez_media(EZx_range, EZy_range)).*(Hy(EZx_range,EZy_range) - Hy(**EZx_range-1**,EZy_range) + Hx(EZx_range,**EZy_range-1**) - Hx(EZx_range,EZy_range));
The terms in '**'s (like 'HXy_range+1') are they key parts; HXy_range+1 is equal to 2:(NHXy+1). In python, I can define a slice in a similar way:
HXx_range = slice(0, NHXx)
However, HXx_range+1 gives me an error. Of course, I can just make a new slice for that, but it's not as clean. Is there a way around this?
Thanks.
If you define your HXy_range as a numpy array, then you can increment it as desired. If and when you wish to use it as a slice, you can form slice(*HXy_range):
In [26]: HXy_range = np.array([1,10])
In [27]: HXy_range+1
Out[27]: array([ 2, 11])
In [28]: slice(*(HXy_range+1))
Out[30]: slice(2, 11, None)
No, Python slice instances are immutable. To use standard slice instances, you must create a new one each time. Unfortunately, you can't subclass slice either.