I am trying to make a complete function, that takes in an expression:
def graph(formula):
fig = plt.figure()
ax = fig.gca(projection='3d')
X = np.arange(-50, 50, 0.5)
X = X[X != 0]
Y = np.arange(-50, 50, 0.5)
Y = Y[Y != 0]
X, Y = np.meshgrid(X, Y)
Z=[[0],[0]]
expression = "Z=" + formula
exec(expression)
Now I want to do graph("X+Y"), and then it should do Z = X + Y. It doesn't do that. I have tried doing the same with eval instead of exec, but no luck.
It sounds like you want to pass a "formula" that computes Z from X and Y. Rather than using exec or eval and running into issues with namespaces, a better way to do that is to pass in a function. As user s3cur3 commented, an easy way to do that is with a lambda expression:
def graph(func):
# set up X and Y up here
Z = func(X, Y)
# do stuff with Z after computing it?
graph(lambda X, Y: X+Y)
If you need more complicated logic that you can fit in a lambda, you can write out a full function if you need to:
def my_func(x, y): # this could be done in a lambda too, but lets pretend it couldn't
if random.random() < 0.5:
return x + y
return x - y
graph(my_func)
I assume you mean to pass to your function like so (to calculate Z),
def graph(formula)
...
graph(X+Y)
...
If so, why not just pass to separate values (or arrays of values)? Such as,
def graph(x, y):
...
graph(4, 5)
...
or,
mypoints = [[1, 3], [4, 8], [8, 1], [10, 3]] # 2-D array
def graph(XY):
for point in XY:
x = point[0]
y = point[1]
.... # everything else
graph(mypoints )
...
For a full example of this, check out Method: Stats.linregress( ) in this article (scroll down a bit).
Otherwise, you could:
pass the data as an array (a table of X, Y values if you will).
if it is a super complex formula that will have a bunch of attributes and methods attached to it (such as including complex numbers), perhaps creating a Formula class.
You could also write a function using the lambda syntax. This would give you the freedom of having an object (like I suggested above) as well as a "function" (of course they are practically synonymous). Read more in the docs.
Related
I am trying to fit 3-dimensional data (that is, 2 independent and 1 dependent variable) using multivariate fitting in scipy curve_fit. I wish to do piecewise fitting for the same problem. I have tried to proceed on the basis of this without any success. The problem is defined below:
import numpy as np
from scipy.optimize import curve_fit
#..........................................................................................................
def F0(X, a, b, c, c0, y0):
x, y = X
value = []
for i in range(0, len(x)):
if y[i] < y0:
lnZ = x[i] + c0*y[i]
else:
lnZ = x[i] + c*y[i]
val = a + (b*lnZ)
value.append(val)
return value
#..........................................................................................................
def F1(X, a, b, c):
x, y = X
lnZ = x + c*y
value = a + (b*lnZ)
return value
#..........................................................................................................
x = [-2.302585093,
-2.302585093,
-2.302585093,
-2.302585093,
-2.302585093,
-2.302585093,
-2.302585093,
0,
0,
0,
0,
0,
0,
0,
2.302585093,
2.302585093,
2.302585093,
2.302585093,
2.302585093,
2.302585093,
2.302585093
]
y = [7.55E-04,
7.85E-04,
8.17E-04,
8.52E-04,
8.90E-04,
9.32E-04,
9.77E-04,
7.55E-04,
7.85E-04,
8.17E-04,
8.52E-04,
8.90E-04,
9.32E-04,
9.77E-04,
7.55E-04,
7.85E-04,
8.17E-04,
8.52E-04,
8.90E-04,
9.32E-04,
9.77E-04
]
z = [4.077424497,
4.358253892,
4.610475878,
4.881769469,
5.153063061,
5.323277142,
5.462023074,
4.610475878,
4.840765517,
5.04864602,
5.235070966,
5.351407761,
5.440090728,
5.540693448,
4.960439843,
5.118257381,
5.266539115,
5.370479367,
5.440090728,
5.528296904,
5.5816974,
]
popt, pcov = curve_fit(F0, (x, y), z, method = 'lm')
print(popt)
popt, pcov = curve_fit(F1, (x, y), z, method = 'lm')
print(popt)
The output is:
[1.34957781e+00 1.05456428e-01 1.00000000e+00 4.14879613e+04
1.00000000e+00]
[1.34957771e+00 1.05456434e-01 4.14879603e+04]
You can see that the values of parameters in the piecewise fitting remain as the initial values. I know I am not doing it in the correct way. Please correct me.
The main source of the problem is the insensitivity of this approach to the value of the variable that defines the switch from one function to another (see this response for a similar explanation). Moreover, the choice of starting parameters isn't good.
Since no starting values are provided, curve_fit chooses a value of 1 for all the fitting parameters (see here the default value for p0). Since the fitting algorithm works by making small variations on the parameters, y0 is varied in small steps around 1, which produces no changes in the output of the function (all y values are much smaller than 1). Since y[i] < y0 is always True and only the first branch is ever evaluated, and the output of the function does not depend on the value of c. That explains why y0 and c stay at the initial values.
One might expect that setting y0 initial value to be inside of the range of values that are evaluated (i.e. around 8E-4) might solve the problem. Indeed, since the second branch is evaluated, the value of c is now optimized. Nevertheless, y0 value will stay unchanged. As the fitting algorithm works testing very small changes to the values, the changes are not large enough to move from the interval between two experimental y values to another one. In this particular case, if one chooses 8E-4, the small variations will never be enough to make it go over 8.17E-04 or below 7.85E-4, that are the values encompassing initial y0 choice.
One can usually circumvent this problem making the function depend explicitly on the value of y0. A smart choice would be to redefine the function so the value at y0 is the same no matter which branch is taken (i.e. ensure that the function is continuous). In this case, the function definition does not ensure so. A reasonable change would be:
def F2(X, a, b, c, c0, y0):
x, y = X
value = []
for i in range(0, len(x)):
lnZ = x[i] + c0 * y[i]
if y[i] >= y0:
lnZ += c * (y[i]-y0)
val = a + (b*lnZ)
value.append(val)
return value
which changes the meaning of the parameter c, and limits the results to only continuous functions. In this case, the value of y0 is indeed the function turning point. Nevertheless, it yields the desired results:
popt2, pcov = curve_fit(F2, (x, y), z, p0=(1, 1, 1E4, 1E4, 9.1E-4), method = 'lm')
print(popt2)
results in:
[-1.93417968e-01 1.05456433e-01 -3.65740192e+04 5.97890809e+04
8.64354057e-04]
A better (pythonic) definition for the function avoids the for loop:
def F3(X, a, b, c, c0, y0):
x, y = X
lnZ = x + c0 * y
idx = np.where(y>=y0)
lnZ[idx] += c * (y[idx] - y0)
rv = a + (b * lnZ)
return rv
which will probably be much faster for larger datasets.
My first py file is the function that I want to find the roots, like this:
def myfun(unknowns,a,b):
x = unknowns[0]
y = unknowns[1]
eq1 = a*y+b
eq2 = x**b
z = x*y + y/x
return eq1, eq2
And my second one is to find the value of x and y from a starting point, given the parameter value of a and b:
a = 3
b = 2
x0 = 1
y0 = 1
x, y = scipy.optimize.fsolve(myfun, (x0,y0), args= (a,b))
My question is: I actually need the value of z after plugging in the result of found x and y, and I don't want to repeat again z = x*y + y/x + ..., which in my real case it's a middle step variable without an explicit expression.
However, I cannot replace the last line of fun with return eq1, eq2, z, since fslove only find the roots of eq1 and eq2.
The only solution now is to rewrite this function and let it return z, and plug in x and y to get z.
Is there a good solution to this problem?
I believe that's the wrong approach. Since you have z as a direct function of x and y, then what you need is to retrieve those two values. In the listed case, it's easy enough: given b you can derive x as the inverse of eqn2; also given a, you can invert eqn1 to get y.
For clarity, I'm changing the names of your return variables:
ret1, ret2 = scipy.optimize.fsolve(myfun, (x0,y0), args= (a,b))
Now, invert the two functions:
# eq2 = x**b
x = ret2**(1/b)
# eq1 = a*y+b
y = (ret1 - b) / a
... and finally ...
z = x*y + y/x
Note that you should remove the z computation from your function, as it serves no purpose.
I have a really simple issue with my Python program -- which isn't finished at all. Right now it's doing everything I want, but not how I want.
There are three things I've been trying to change:
all functions are being plotted using the same color, and I'd like the program to automatically switch to a new color when a new function is added to the plot (it will be more than 2, all on the same plot).
f(x)'s range is 140. How can I decrease that? Maybe to 20/40.
(most important) My code isn't very efficient. f1 and derivative are not associated at all. I declare the function's model in f1, but I have to set up everything again in derivative. Every time I try to do otherwise I end up having some problem with the main function. I'll eventually add more features like integration and whatnot, and if I'm declaring everything from scratch everytime I want to do something with f1 the program will kinda lose its purpose.
Should I use x = Symbol('x') inside f1?
import numpy as np
import matplotlib.pyplot as plt
from sympy import *
x = Symbol('x')
def f1(a, b, c, d):
y = a*x**3 + b*x**2 + x*c + d
return y
###yprime = y.diff(x)
###return yprime
def derivative(a, b, c, d):
y = a*x**3 + b*x**2 + x*c + d
yprime = y.diff(x)
return yprime
def factorail(n):
if n == 0:
return 1
else:
return n * factorial(n-1)
###colors = iter(cm.rainbow(np.linspace(0, 1, len(ys))))
###for y in ys:
###plt.scatter(x, y, color=next(colors))
def main():
###colors = itertools.cycle(["r", "b", "g"])
y = f1(0,1,2,1)
yp = derivative(0,1,2,1)
print(y)
plot(y, yp)
plot(yp)
plt.show()
if __name__ == '__main__':
main()
Vertical window is set by ylim option. I recommend to also use some explicit limits for x, the default -10 to 10 is not necessarily best for you. And I do recommend reading the page on SymPy plotting.
Color is set by line_color option. Different colors require different calls to plot, but those can be combined. Example:
p = plot(y, (x, -5, 5), ylim=(-20, 20), line_color='b', show=False)
p.extend(plot(yp, (x, -5, 5), ylim=(-20, 20), line_color='r', show=False))
p.show()
results in
The function reuse is easy:
def derivative(a, b, c, d):
y = f1(a, b, c, d)
yprime = y.diff(x)
return yprime
Aside: what happens if we try line_color=['b', 'r'], as in plot(y, yp, ylim=(-20, 20), line_color=['b', 'r'])? Funny stuff happens:
I use the following function to get colors that are "far" from each other in a general way. The 2396745 is pretty arbitrary, and defines how far apart the colors are. It seems to give me good results.
def cmap_ints(i):
return "#"+hex(((int(i)+1)*2396745)%(256**3))[2:].rjust(6,"0")
Usage:
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(0,1,100)
y1 = 3*x + 4
y2 = 2*x - 5
plt.plot(x,y1,c=cmap_ints(1))
plt.plot(x,y2,c=cmap_ints(2))
Let's say I have the following python code
y = 2
def f(x, y):
y = y**2
return x*y
for i in range(5):
print(f(2,y))
Is it somehow possible to make the change to y within f global while still passing it to f as an argument?
I know that
y = 2
def f(x, y):
global y
y = y**2
return x*y
for i in range(5):
print(f(2,y))
will not work because y cannot be both global and a function parameter.
The 'ugly solution that I have is simply not to pass y as an argument:
y = 2
def f(x):
global y
y = y**2
return x*y
for i in range(5):
print(f(2,y))
but I am not satisfied with this, as I would like to explicitly pass y to the function and basically call it by reference.
The background of this question is that I would like to use scipy's odeint, and I have to use sparse matrices in the computation of the derivative that also change with time.
If I want to avoid converting these to numpy and back to sparse at every timestep, I have to store them globally and modify them from within the function. Because the output of the function is dictated by odeint (it has to be said derivative) it is not an option to include these matrices in the output (and I don't know how that would work anyway, because I'd have to mix scalars and matrices in the output array).
It would be nice if I could somehow pass them as a parameter but make the changes to them from within the function globally permanent.
Just use a different name for the formal argument to f:
y = 2
def f(x, y2):
global y
y = y2**2
return x*y
for i in range(5):
print(f(2,y))
If I understand your intent, then I believe this should work for you.
You cannot do this exactly, for the reason you have described: a variable cannot be at the same time global and a local argument.
However, one solution would be to do this:
y_default = 2
def f(x, y=None):
if y is None:
y = y_default
y = y**2
return x*y
This will do what you want, as you can now call f(2) or f(2,3)
Essentially the problem is that y is global and local as the error message will suggest. Therefore you avoid the local variable issue by introducing a variable z locally. You can still pass y into z, which then yields the desired result.
y = 2
def f(x, z):
y = z**2
global y
return x*y
for i in range(5):
print f(2,y)
I am trying to define a function of n variables to fit to a data set. The function looks like this.
Kelly Function
I then want to find the optimal ai's and bj's to fit my data set using scipy.optimize.leastsq
Here's my code so far.
from scipy.optimize import leastsq
import numpy as np
def kellyFunc(a, b, x): #Function to fit.
top = 0
bot = 0
a = [a]
b = [b]
for i in range(len(a)):
top = top + a[i]*x**(2*i)
bot = bot + b[i]*x**(2*i)
return(top/bot)
def fitKelly(x, y, n):
line = lambda params, x : kellyFunc(params[0,:], params[1,:], x) #Lambda Function to minimize
error = lambda params, x, y : line(params, x) - y #Kelly - dataset
paramsInit = [[1 for x in range(n)] for y in range(2)] #define all ai and bi = 1 for initial guess
paramsFin, success = leastsq(error, paramsInit, args = (x,y)) #run leastsq optimization
#line of best fit
xx = np.linspace(x.min(), x.max(), 100)
yy = line(paramsFin, xx)
return(paramsFin, xx, yy)
At the moment it's giving me the error:
"IndexError: too many indices" because of the way I've defined my initial lambda function with params[0,:] and params[1,:].
There are a few problems with your approach that makes me write a full answer.
As for your specific question: leastsq doesn't really expect multidimensional arrays as parameter input. The documentation doesn't make this clear, but parameter inputs are flattened when passed to the objective function. You can verify this by using full functions instead of lambdas:
from scipy.optimize import leastsq
import numpy as np
def kellyFunc(a, b, x): #Function to fit.
top = 0
bot = 0
for i in range(len(a)):
top = top + a[i]*x**(2*i)
bot = bot + b[i]*x**(2*i)
return(top/bot)
def line(params,x):
print(repr(params)) # params is 1d!
params = params.reshape(2,-1) # need to reshape back
return kellyFunc(params[0,:], params[1,:], x)
def error(params,x,y):
print(repr(params)) # params is 1d!
return line(params, x) - y # pass it on, reshape in line()
def fitKelly(x, y, n):
#paramsInit = [[1 for x in range(n)] for y in range(2)] #define all ai and bi = 1 for initial guess
paramsInit = np.ones((n,2)) #better
paramsFin, success = leastsq(error, paramsInit, args = (x,y)) #run leastsq optimization
#line of best fit
xx = np.linspace(x.min(), x.max(), 100)
yy = line(paramsFin, xx)
return(paramsFin, xx, yy)
Now, as you see, the shape of the params array is (2*n,) instead of (2,n). By doing the re-reshape ourselves, your code (almost) works. Of course the print calls are only there to show you this fact; they are not needed for the code to run (and will produce bunch of needless output in each iteration).
See my other changes, related to other errors: you had a=[a] and b=[b] in your kellyFunc, for no good reason. This turned the input arrays into lists containing arrays, which made the next loop do something very different from what you intended.
Finally, the sneakiest error: you have input variables named x, y in fitKelly, then you use x and y is loop variables in a list comprehension. Please be aware that this only works as you expect it to in python 3; in python 2 the internal variables of list comprehensions actually leak outside the outer scope, overwriting your input variables named x and y.