When using optimization (e.g. brentq), the input is always an array of items. However, sometimes it is necessary to use a comparator function like >= in object function. Then, python is not able to evaluate those values.
For example:
def f(x):
if x > 0:
return x
if x <= 0:
return -x
optimize.brentq(f,-1,1)
Then we will have the error: The truth value of an array with more than one element is ambiguous.
In general, how to avoid this error?
If f is what you actually need, use np.abs instead. If it's a dummy example, use something like this instead:
def f(x):
return np.where(x>0, x, -x)
In general change
def f(x):
if condition(x):
return f1(x)
else:
return f2(x)
to
def f(x):
return np.where(condition(x), f1(x), f2(x))
keeping in mind that for non trivial conditions, your implementation should still be able to handle vectors... (i.e. if x is a vector condition(x) should return a vector of the same size as x)
Related
Lets say I have a class Vector2D that take x and y components. Because I have no interest in vectors with both components equal to zero, I want to prevent the object with both parameters passed equal to zero from being created in the first place and return None instead.
You could rename your class to RealVector2D and replace it with a new function:
def Vector2D(x, y):
if x == 0 and y == 0:
return None
return RealVector2D(x, y)
You can use a factory function to verify that your parameters are not zero, thn return an instance of Vector2D, or raise an Error:
As mentioned in the comments by #jasonsharper, returning None is not a good idea, better to return an explicit error.
class NullVectorError(ValueError):
pass
def make_non_null_vector(x: float, y: float) -> vector2D:
if x and y:
return Vector2D(x, y)
raise NullVectorError('the parameters x:{x}, and y:{y}, cannot be both equal to zero')
Updated Question
Following from my original post, with the use of #Attack68 's code, I have created a program that successfully evolved the function with a choice of multiplicative functions based on a random variable. However, now I am receiving an error saying the list indices must be integers (even though I'm fairly sure they are), I'm not sure what has happened, The code is as follows:
import numpy as np
import scipy.integrate as integrate
x=np.linspace(0.0,1.0,100)
n=10 #iterations
d=700.0
def f(x):
return np.sin(x)
def g(x,list_):
return np.cos(x)*apply(x,list_)
base = [f, g]
list_ = list()
for i in range(n):
testvar=np.random.randint(1, 100, 1)
if testvar> 50 and i!=0:
func_idx = 0 # choose a random operation: 0=ten, 1=inv
else:
func_idx= 1
list_.append(func_idx)
# now you have a list of indexes referencing your base functions so you can apply them:
def apply(x,list_):
y = 1
for i in range(len(list_)):
y *= base[list_[i]](x)
return y
print(list_)
#testint=integrate.quad(apply(x,list_),-d,d)[0]
#print(testint)
print(apply(list_, x))
I am now getting the error:
TypeError: list indices must be integers or slices, not numpy.float64
I am also attempting to get this to integrate the new function after each iteration but it seems that the form of this function is not callable by scipys quad integrator, any suggestions on how to integrate the evolving function on each iteration would also be appreciated.
Original:
I am creating a simulation in python where I consider a function that evolves over a loop. This function starts off defined as:
def f(x):
return 1.0
So simply a flat distribution. After each iteration of the loop, I want the function to be redefined depending on certain (random) conditions. It could be multiplied by cos(b*x) or it could be multiplied by some function A(x), the evolution will not be the same each time due to the randomness, so I cannot simply multiply by the same value each time.
The progression in one instance could be:
f(x)----> f(x)*A(x)----> f(x)*A(x)*A(x)...
but in another instance it could be:
f(x)----> f(x)*A(x)----> f(x)*A(x)*cos(x)...
or
f(x)----> f(x)*cos(x)----> f(x)*cos(x)*cos(x)...
etc.
after each, of n iterations of this evolution, I have to compute an integral that is related to the function, so I need to essentially update the function after each iteration to be called by scipys quad integrator.
I have tried to use arrays to manipulate the distribution instead and it works as far as the function evolution goes, but upon integration, it gives the incorrect result with numpy.trapz and I cannot work out why. Sci-pys quad integrator is more accurate anyway and I had managed to get this to work previously for the first iteration only, but it requires a function based input, so without this function evolution I cannot use it.
If someone could show me if/how this function evolution is possible that'd be great. If it is not possible, perhaps someone could try to help me understand what numpy.trapz actually does so I can workout how to fix it?
How about this:
class MyFunction:
def __init__(self):
def f1(x):
return 1.0
self.functions = [f1]
def append_function(self, fn):
self.functions.append(fn)
def __call__(self, x):
product = 1.0
for f in self.functions:
product *= f(x)
return product
This object starts off as simply returning 1.0. Later you add more functions and it returns the product of all of them.
Your description suggests your iterated values are combined through a product and are not in fact a composition of functions. A simple way of recording these is to have a set of base functions:
import numpy as np
import scipy.integrate as int
def two(x):
return x*2
def inv(x):
return 1/x
base = [two, inv]
funcs = np.random.choice(base, size=10)
def apply(x, funcs):
y = 1
for func in funcs:
y *= func(x)
return y
print('function value at 1.5 ', apply(1.5, funcs))
answer = int.quad(apply, 1, 2, args=(funcs,))
print('integration over [1,2]: ', answer)
I have a class MyClass which stores an integer a. I want to define a function inside it that takes a numpy array x of length a, but I want that if the user does not pass in anything, x is set to a random array of the same length. (If they pass in values of the wrong length, I can raise an error). Basically, I would like x to default to a random array of size a.
Here is my attempt at implementing this
import numpy as np
class MyClass():
def __init__(self, a):
self.a = a
def function(self, x = None):
if x == None:
x = np.random.rand(self.a)
# do some more functiony stuff with x
This works if nothing is passed in, but if x is passed in I get ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all() i.e. it seems numpy doesn't like comparing arrays with None.
Defining the default value inline doesn't work because self is not in scope yet.
Is there a nice pythonic way to achieve this? To sum up I would like the parameter x to default to a random array of a specific, class-defined length.
As a rule of thumb, comparisons of anything and None should be done with is and not ==.
Changing if x == None to if x is None solves this issue.
class MyClass():
def __init__(self, a):
self.a = a
def function(self, x=None, y=None):
if x is None:
x = np.random.rand(self.a)
print(x)
MyClass(2).function(np.array([1, 2]))
MyClass(2).function()
# [1 2]
# [ 0.92032119 0.71054885]
I am given a list of functions and asked to define plus(x,y) with add1 and repeated. plus is a function that takes in two numbers and returns the total. However, I cannot get any output with my definition. It just gives the name of the function. Any help is appreciated!
add1 = lambda x: x + 1
def compose(f, g):
return lambda x: f(g(x))
def repeated(f, n):
if n == 0:
return lambda x: x
else:
return compose(f, repeated(f, n - 1))
def plus(x, y):
return repeated(add1, y)
That is an interesting way to do addition. It works quite well, you just missed one thing. Repeated returns a function which will give the sum, not the sum itself. So you just have to call repeated(add1, y) on x like this
def plus(x, y):
return repeated(add1, y)(x)
The rest of the code works fine.
The detail is that plus returns a function and that's why you see the name of the function instead of a numeric value. I think that's why there is the x parameter in plus. Just change this line of code to
return repeated(add1, y)(x)
This will evaluate the return function of repeated with the value in x.
So using
plus(5, 1)
>> 6
How do I tell fmin_cobyla about a matrix constraint Ax-b >= 0? It won't take it as a vector constraint:
cons = lambda x: dot(A,x)-b
thanks.
Since the constraint must return a scalar value, you could dynamically define the scalar constraints like this:
constraints = []
for i in range(len(A)):
def f(x, i = i):
return np.dot(A[i],x)-b[i]
constraints.append(f)
For example, if we lightly modify the example from the docs,
def objective(x):
return x[0]*x[1]
A = np.array([(1,2),(3,4)])
b = np.array([1,1])
constraints = []
for i in range(len(A)):
def f(x, i = i):
return np.dot(A[i],x)-b[i]
constraints.append(f)
def constr1(x):
return 1 - (x[0]**2 + x[1]**2)
def constr2(x):
return x[1]
x = optimize.fmin_cobyla(objective, [0.0, 0.1], constraints+[constr1, constr2],
rhoend = 1e-7)
print(x)
yields
[-0.6 0.8]
PS. Thanks to #seberg for pointing out an earlier mistake.
Actually the documentation says Constraint functions;, it simply expects a list of functions each returning only a single value.
So if you want to do it all in one, maybe just modify the plain python code of the fmin_cobyla, you will find there that it defines a wrapping function around your functions, so it is easy... And the python code is really very short anyways, just small wrapper around scipy.optimize._cobyal.minimize.
On a side note, if the function you are optimizing is linear (or quadratic) like your constraints, there are probably much better solvers out there.