sympy "Invalid limits given" when plotting hyperbolic integral - python

I'm trying to do some calculations in sympy but keep getting an "Invalid limits given" error when I try and plot my function. I'm new to python and sympy so I'm sure it's an obvious mistake but I just cant understand how the limits are invalid.
import sympy
x = sympy.symbols('x')
min_x, max_x = -6.0, 6.0
func = x * sympy.integrate(x * sympy.tanh(x), (x, min_x, max_x))
p = sympy.plot(func)

In a few words, the plotting module converts the symbolic expression to a numerical function, evaluates it and create the plot. However, the current plotting module is using out-of-date code to convert the symbolic expression to a numerical function. That error simply means that the plotting module can't plot the expression.
You can plot your expression in two different ways. The first: convert the symbolic expression to a numerical function, use numpy and matplotlib:
import matplotlib.pyplot as plt
import numpy as np
f = sympy.lambdify(x, func)
plt.figure()
xx = np.linspace(-10, 10)
yy = f(xx)
plt.plot(xx, yy)
The second one: you install the new and more advanced plotting module, Sympy Plot Backend. Then:
from spb import *
plot(func)

Related

sympy: plot implicit function of erf

x,y = sympy.symbols("x y")
f = sympy.erf(x+y) - 1
sympy.plot_implicit(f)
Is it possible to plot this in Sympy 1.9 and NumPy 1.21.4, or an upgrade path for either that allows me to plot this, or a workaround I can use?

Implementing derivatives of logs of data in python

We have two lists (vectors) of data, y and x, we can imagine x being time steps (0,1,2,...) and y some system property computed at value each value of x.
I'm interested in calculating the derivative of log of y with respect to log of x, and the question is how to perform such calculations in Python?
We can start off by using numpy to calculate the logs: logy = np.log(y) and logx = np.log(x). Then what method do we use for the differentiation dlog(y)/dlog(x)?
One option that comes to mind is using np.gradient() in the following way:
deriv = np.gradient(logy,np.gradient(logx)).
Is this a valid way of going about this calculation?
Are there better (or equivalent) alternatives without using np.gradient?
Right after looking at the source of np.gradient here and looking around you can see it changed in numpy version 1.14, hence why the docs change.
I have version 1.11. So I think that gradient is defined as def gradient(y, x) -> dy/dx if isinstance(x, np.ndarray) now but isn't in version 1.11. Doing np.gradient(y, np.array(...)) is actually, I think, undefined behaviour!
However, np.gradient(y) / np.gradient(x) works for all numpy versions. Use that!
Proof:
import numpy as np
import matplotlib.pyplot as plt
x = np.sort(np.random.random(10000)) * 2 * np.pi
y = np.sin(x)
dy_dx = np.gradient(y) / np.gradient(x)
plt.plot(x, dy_dx)
plt.show()
Looks an awful lot like a cos wave

Error when trying to graph an arbitrary 3d function

I'm working on a project for my multivariable calculus class, My objective is to graph an arbitrary function f(x,y), use contour plots to graph the partial derivatives (df/dx, df/dy) and a quiver plot for the gradient of the function, but I have an issue while graphing more complicated functions.
For function inputs like f(x,y) = (x+y)**2 the program works fine an outputs the graph, but when I use an input that requires a more complicated mathematical concept (i.e: f(x,y) = sin(x*y). I get an error:
TypeError: only length-1 arrays can be converted to Python scalars.
There are a lot of cases of this on stackoverflow, but they all seem to be isolated incidents involving numpy/sympy conflicts. In my program, I am reliant on sympy for creating arbitrary functions, and numpy for array computation, so I'm not sure how to get around this problem.
'''
Imports
'''
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import axes3d
import numpy as np
from sympy import *
x = Symbol('x')
y = Symbol('y')
lims = [-10, 10]
function = sin(x+y)
lam_function = lambdify((x,y),function)
fig = plt.figure()
ax = fig.gca(projection='3d')
gX, gY = np.meshgrid(np.arange(lims[0], lims[1], 0.05),
np.arange(lims[0], lims[1], 0.05))
z = lam_function(gX, gY)
plot = ax.plot_surface(gX, gY, z, cmap=plt.cm.jet, linewidth=0)
plt.colorbar(plot, cmap=plt.cm.jet)
plt.show()
The solution is easy: You need to specify the which package to use with lambdify
lam_function = sym.lambdify((x,y), function, "numpy")
This will ensure that the resulting functions will be numpy compatible. This works for basic functions like sin, cos, atan, log but may fail for more complicated ones such as sympy.lowergamma.
Now as to why polinamials work without specifiying "numpy" is easy to understand if we have a close look at sympy's documentation. If no package is specified sympy will try python-math, numpy and mpmath in exactly this order. Now a python-math x is not really different from a numpy x, but python-math sin is a lot different as it can not handle numpy arrays.
One last thing: Newer versions of sympy (1.1.1+) behave differently. In these new versions sympy.lambdify will try to use numpy as default if installed if not resort to math, mpmath, sympy.

Scipy: efficiently generate a series of integration (integral function)

I have a function, I want to get its integral function, something like this:
That is, instead of getting a single integration value at point x, I need to get values at multiple points.
For example:
Let's say I want the range at (-20,20)
def f(x):
return x**2
x_vals = np.arange(-20, 21, 1)
y_vals =[integrate.nquad(f, [[0, x_val]]) for x_val in x_vals ]
plt.plot(x_vals, y_vals,'-', color = 'r')
The problem
In the example code I give above, for each point, the integration is done from scratch. In my real code, the f(x) is pretty complex, and it's a multiple integration, so the running time is simply too slow(Scipy: speed up integration when doing it for the whole surface?).
I'm wondering if there is any way of efficient generating the Phi(x), at a giving range.
My thoughs:
The integration value at point Phi(20) is calucation from Phi(19), and Phi(19) is from Phi(18) and so on. So when we get Phi(20), in reality we also get the series of (-20,-19,-18,-17 ... 18,19,20). Except that we didn't save the value.
So I'm thinking, is it possible to create save points for a integrate function, so when it passes a save point, the value would get saved and continues to the next point. Therefore, by a single process toward 20, we could also get the value at (-20,-19,-18,-17 ... 18,19,20)
One could implement the strategy you outlined by integrating only over the short intervals (between consecutive x-values) and then taking the cumulative sum of the results. Like this:
import numpy as np
import scipy.integrate as si
def f(x):
return x**2
x_vals = np.arange(-20, 21, 1)
pieces = [si.quad(f, x_vals[i], x_vals[i+1])[0] for i in range(len(x_vals)-1)]
y_vals = np.cumsum([0] + pieces)
Here pieces are the integrals over short intervals, which get summed to produce y-values. As written, this code outputs a function that is 0 at the beginning of the range of integration which is -20. One can, of course, subtract the y-value that corresponds to x=0 in order to have the same normalization as on your plot.
That said, the split-and-sum process is unnecessary. When you find an indefinite integral of f, you are really solving the differential equation F' = f. And SciPy has a built-in method for that, odeint. Just use it:
import numpy as np
import scipy.integrate as si
def f(x):
return x**2
x_vals = np.arange(-20, 21, 1)
y_vals = si.odeint(lambda y,x: f(x), 0, x_vals)
The output is essential identical to the first version (within tiny computational errors), with less code. The reason for using lambda y,x: f(x) is that the first argument of odeint must be a function taking two arguments, the right-hand side of the equation y' = f(y, x).
For the equivalent version of user3717023's answer using scipy's solve_ivp you need to keep in mind the different ordering of x and y in the function f (different from the odeint version).
Further, keep in mind that you can only compute the solution up to a constant. So you might want to shift the result according to some given condition. In the example here (with the function f(x)=x^2 as given by the OP), I shifted the numeric solution such that it goes through the origin, matching the simplest analytic solution F(x)=x^3/3.
import numpy as np
import matplotlib.pyplot as plt
from scipy.integrate import solve_ivp
def f(x):
return x**2
xs = np.linspace(-20, 20, 1001)
# This is the integration step:
sol = solve_ivp(lambda x, y: f(x), t_span=(xs[0], xs[-1]), y0=[0], t_eval=xs)
plt.plot(sol.t, sol.t**3/3, ls='-', c='C0', label="analytic: $F(x)=x^3/3$")
plt.plot(sol.t, sol.y[0], ls='--', c='C1', label="numeric solution")
plt.plot(sol.t, sol.y[0] - sol.y[0][sol.t.size//2], ls='-.', c='C3', label="shifted solution going through origin")
plt.legend()
In case you don't have an analytical version of the function f, but only xs and ys as data points, then you can use scipy's interp1d function to interpolate between the data points and pass on that interpolating function the same way as before:
from scipy.interpolate import interp1d
f = interp1d(xs, ys)

Using brentq with two lists of data

I am trying to find the root(s) of a line which is defined by data like:
x = [1,2,3,4,5]
y = [-2,4,6,8,4]
I have started by using interpolation but I have been told I can then use the brentq function. How can I use brentq from two lists? I thought continuous functions are needed for it.
As the documentation of brentq says, the first argument must be a continuous function. Therefore, you must first generate, from your data, a function that will return a value for each parameter passed to it. You can do that with interp1d:
import numpy as np
from scipy.interpolate import interp1d
from scipy.optimize import brentq
x, y = np.array([1,2,3,4,5]), np.array([-2,4,6,8,4])
f = interp1d(x,y, kind='linear') # change kind to something different if you want e.g. smoother interpolation
brentq(f, x.min(), x.max()) # returns: 1.33333
You could also use splines to generate the continuous function needed for brentq.

Categories

Resources