I have been using SymPy to expand the terms of a complex partial differential equation and would like to use the collect function to gather terms. However, it seems to have a problem dealing with second (or higher order) derivatives where the variables of differentiation differ.
In the code example below collect(expr6... works, but collect(expr7 ... does not, returning the error message "NotImplementedError: Improve MV Derivative support in collect". The error is clearly related to the psi.diff(x,y) difference in the two cases. Is it obvious to anyone what I need to do to have collect(expr7 ... work?
cheers
Richard
Example:
from sympy import *
psi = Function("psi") (x,y,z,t)
expr6=2*psi.diff(x,x)+3*U*psi.diff(x)+5*psi.diff(y)
expr7=2*psi.diff(x,y)+3*U*psi.diff(x)+5*psi.diff(y)
collect(expr6, psi.diff(x),evaluate=False, exact=False) # works
#collect(expr7, psi.diff(x),evaluate=False, exact=False)
# throws an error: NotImplementedError: Improve MV Derivative support in collect
I've bumped into this issue and my workaround is to perform a substitution with simple dummy variables first, collect based on these simple variables, and then substitute back the more advanced variables. There might be some corner cases, but it seems to work for me.
from sympy import symarray, collect
def mycollect(expr, var_list, evaluate=True, **kwargs):
""" Acts as collect but substitute the symbols with dummy symbols first so that it can work with partial derivatives.
Matrix expressions are also supported.
"""
if not hasattr(var_list, '__len__'):
var_list=[var_list]
# Mapping Var -> Dummy, and Dummy-> Var
Dummies=symarray('DUM', len(var_list))
Var2Dummy=[(var, Dummies[i]) for i,var in enumerate(var_list)]
Dummy2Var=[(b,a) for a,b in Var2Dummy]
# Replace var with dummies and apply collect
expr = expr.expand().doit()
expr = expr.subs(Var2Dummy)
if hasattr(expr, '__len__'):
expr = expr.applyfunc(lambda ij: collect(ij, Dummies, **kwargs))
else:
expr = collect(expr, Dummies, evaluate=evaluate, **kwargs)
# Substitute back
if evaluate:
return expr.subs(Dummy2Var)
d={}
for k,v in expr.items():
k=k.subs(Dummy2Var)
v=v.subs(Dummy2Var)
d[k]=v
return d
For your example:
mycollect(expr6, psi.diff(x), evaluate=False)
mycollect(expr7, psi.diff(x), evaluate=False)
returns:
{Derivative(psi(x, y, z, t), (x, 2)): 2, Derivative(psi(x, y, z, t), x): 3*U, 1: 5*Derivative(psi(x, y, z, t), y)}
{Derivative(psi(x, y, z, t), x, y): 2, Derivative(psi(x, y, z, t), x): 3*U, 1: 5*Derivative(psi(x, y, z, t), y)}
Related
I want to work with generic functions as long as possible, and only substitute functions at the end.
I'd like to define a function as the derivative of another one, define a generic expression with the function and its derivative, and substitute the function at the end.
Right now my attempts is as follows, but I get the error 'Derivative' object is not callable:
from sympy import Function
x, y, z = symbols('x y z')
f = Function('f')
df = f(x).diff(x) # <<< I'd like this to be a function of dummy variable x
expr = f(x) * df(z) + df(y) + df(0) # df is unfortunately not callable
# At the end, substitute with example function
expr.replace(f, Lambda(X, cos(X))) # should return: -cos(x)*sin(z) - sin(y) - sin(0)
I think I got it to work with integrals as follows:
I= Lambda( x, integrate( f(y), (y, 0, x))) but that won't work for derivatives.
If that helps, I'm fine restricting myself to functions of a single variable for now.
As a bonus, I'd like to get this to work with any combination (products, derivatives, integrals) of the original function.
It's pretty disappointing that f.diff(x) doesn't work, as you say. Maybe someone will create support it sometime in the future. In the mean time, there are 2 ways to go about it: either substitute x for your y, z, ... OR lambdify df.
I think the first option will work more consistently in the long run (for example, if you decide to extend to multivariate calculus). But the expr in second option is far more natural.
Using substitution:
from sympy import *
x, y, z = symbols('x y z')
X = Symbol('X')
f = Function('f')
df = f(x).diff(x)
expr = f(x) * df.subs(x, z) + df.subs(x, y) + df.subs(x, 0)
print(expr.replace(f, Lambda(X, cos(X))).doit())
Lambdifying df:
from sympy import *
x, y, z = symbols('x y z')
X = Symbol('X')
f = Function('f')
df = lambda t: f(t).diff(t) if isinstance(t, Symbol) else f(X).diff(X).subs(X, t)
expr = f(x) * df(z) + df(y) + df(0)
print(expr.replace(f, Lambda(X, cos(X))).doit())
Both give the desired output.
I need to calculate the Laplace transform of an integral function. It seems that sympy is not yet able to understand that.
Assuming the following:
from sympy import *
s, t = symbols('s t')
I = Function('I')(t)
eq1 = integrate(I, t)
transforms.laplace_transform(eq1, t, s)
The solution should be: I(s) / s
However, sympy gives: LaplaceTransform(Integral(I(t), t), t, s)
It seems to be an open issue Issue 7219. Is there any work around?
It seems that the issue hasn't been fixed yet.
However, we can give a "crappy workaround" based on the "crappy implementation" of Eric Wieser given for derivatives. Note, however, that the original snippet doesn't seem to work for derivatives either, because the internal representation of higher order derivatives seems to have changed since the snippet was posted.
Here's my "crappy" workaround that catches only the simplest of cases (derivatives only with respect to t, indefinite integrals only with respect to t, where t is the variable on which the Laplace transform acts):
from sympy import *
def laplace(e, t, s):
"""Hacked-up Laplace transform that handles derivatives and integrals
Updated generalization of https://github.com/sympy/sympy/issues/7219#issuecomment-154768904
"""
res = laplace_transform(e, t, s, noconds=True)
wf = Wild('f')
lw = LaplaceTransform(wf, t, s)
for exp in res.find(lw):
e = exp.match(lw)[wf]
args = e.args
if isinstance(e, Derivative):
# for derivative check that there's only d/dt^n with n>0
if len(args) == 2 and args[1][0] == t:
n = args[1][1]
if n > 0:
newexp = s**n * LaplaceTransform(e.args[0], t, s)
res = res.replace(exp, newexp)
elif isinstance(e, Integral):
# for integral check that there's only n consecutive indefinite integrals w.r.t. t
if all(len(arg) == 1 and arg[0] == t for arg in args[1:]):
newexp = s**(-len(args[1:])) * LaplaceTransform(args[0], t, s)
res = res.replace(exp, newexp)
# otherwise don't do anything
return res
x = Function('x')
s,t = symbols('s t')
print(laplace(Derivative(x(t), t, 3), t, s))
print(laplace(Integral(Integral(x(t), t), t), t, s))
The above outputs
s**3*LaplaceTransform(x(t), t, s)
LaplaceTransform(x(t), t, s)/s**2
as expected. Using your specific example:
I = Function('I')(t)
eq1 = integrate(I, t)
LI = laplace(eq1, t, s)
print(LI)
we get
LaplaceTransform(I(t), t, s)/s
which is the correct representation of "I(s)/s" that you expected.
The way the above workaround works is that it matches the arguments of the LaplaceTransform and checks if there's a pure Derivative or Integral inside. For Derivative we check that there's only differentiation with respect to t; this is what Eric's original workaround did, but while his code seems to have expected args of the form Derivative(x(t), t, t, t), the current representation of derivatives is Derivative(x(t), (t,3)). This is why handling this use case had to be changed.
As for Integrals, the representation is similar to the original one: Integral(x(t), t, t) is a double integral. I still had to tweak Eric's original, because the args of this expression contain tuples for each integral rather than a scalar t, in order to accommodate definite integrals. Since we only want to handle the no-brainer case of indefinite integrals, I made sure that there's only indefinite integrals and only with respect to t.
If the argument of the LaplaceTransform is anything else, the expression is left alone.
We know that the Fourier Transform of a derivative is
where k is the fourier variable. Explanation here
My question is, why doesn't sympy use this knowledge? For example:
from sympy import Function, symbols, fourier_transform, Derivative
f = Function('f')
x, k= symbols('x, k')
G = fourier_transform(Derivative(f(x), x, x) + f(x), x, k)
print(G)
This prints
FourierTransform(f(x), x, k) + FourierTransform(Derivative(f(x), x, x), x, k)
But I expected it to print (up to some factors of 2 pi i)
FourierTransform(f(x), x, k) + k**2 FourierTransform(f(x), x, k)
Is there a way to tell sympy it's save to make this simplification because I expect f(x) -> 0 as x goes to infinity?
If not, what would be the cleanest way to make the substitution?
The simple reason Sympy doesn't do this is that it's not implemented yet. As a workaround for now, you can manually replace the FourierTransform of the derivative with a multiplication:
from sympy import Wild, FourierTransform, Derivative
a, b, c = symbols('a b c', cls=Wild)
G.replace(
FourierTransform(Derivative(a, b, b), b, c),
c**2 * FourierTransform(a, b, c)
)
As far as I know, Sympy doesn't offer a pattern that matches an arbitrary number of arguments, so you can't have a single pattern that matches Derivative(f(x), x), Derivative(f(x), x, x), Derivative(f(x), x, x, x), and so on. You could get around that by using the function-function form of replace(), but if you know what order of derivative you're dealing with, it's probably simpler to just put in that many bs explicitly, as I did in the example.
I currently have a system of odes with a time-dependent constant. E.g.
def fun(u, t, a, b, c):
x = u[0]
y = u[1]
z = u[2]
dx_dt = a * x + y * z
dy_dt = b * (y-z)
dz_dt = -x*y+c*y-z
return [dx_dt, dy_dt, dz_dt]
The constants are "a", "b" and "c". I currently have a list of "a"s for every time-step which I would like to insert at every time-step, when using the scipy ode solver...is this possible?
Thanks!
Yes, this is possible. In the case where a is constant, I guess you called scipy.integrate.odeint(fun, u0, t, args) where fun is defined as in your question, u0 = [x0, y0, z0] is the initial condition, t is a sequence of time points for which to solve for the ODE and args = (a, b, c) are the extra arguments to pass to fun.
In the case where a depends on time, you simply have to reconsider a as a function, for example (given a constant a0):
def a(t):
return a0 * t
Then you will have to modify fun which computes the derivative at each time step to take the previous change into account:
def fun(u, t, a, b, c):
x = u[0]
y = u[1]
z = u[2]
dx_dt = a(t) * x + y * z # A change on this line: a -> a(t)
dy_dt = b * (y - z)
dz_dt = - x * y + c * y - z
return [dx_dt, dy_dt, dz_dt]
Eventually, note that u0, t and args remain unchanged and you can again call scipy.integrate.odeint(fun, u0, t, args).
A word about the correctness of this approach. The performance of the approximation of the numerical integration is affected, I don't know precisely how (no theoretical guarantees) but here is a simple example which works:
import matplotlib.pyplot as plt
import numpy as np
import scipy as sp
import scipy.integrate
tmax = 10.0
def a(t):
if t < tmax / 2.0:
return ((tmax / 2.0) - t) / (tmax / 2.0)
else:
return 1.0
def func(x, t, a):
return - (x - a(t))
x0 = 0.8
t = np.linspace(0.0, tmax, 1000)
args = (a,)
y = sp.integrate.odeint(func, x0, t, args)
fig = plt.figure()
ax = fig.add_subplot(111)
h1, = ax.plot(t, y)
h2, = ax.plot(t, [a(s) for s in t])
ax.legend([h1, h2], ["y", "a"])
ax.set_xlabel("t")
ax.grid()
plt.show()
I Hope this will help you.
No, that is not possible in the literal sense of
"I currently have a list of "a"s for every time-step which I would like to insert at every time-step"
as the solver has adaptive step size control, that is, it will use internal time steps that you have no control over, and each time step uses several evaluations of the function. Thus there is no connection between the solver time steps and the data time steps.
In the extended sense that the given data defines a piecewise constant step function however, there are several approaches to get to a solution.
You can integrate from jump point to jump point, using the ODE function with the constant parameter for this time segment. After that use numpy array operations like concatenate to assemble the full solution.
You can use interpolation functions like numpy.interp or scipy.interpolate.interp1d. The first gives a piecewise linear interpolation, which may not be desired here. The second returns a function object that can be configured to be a "zero-order hold", which is a piecewise constant step function.
You could implement your own logic to go from the time t to the correct values of those parameters. This mostly applies if there is some structure to the data, for instance, if they have the form f(int(t/h)).
Note that the approximation order of the numerical integration is not only bounded by the order of the RK (solve_ivp) or multi-step (odeint) method, but also by the differentiability order of the (parts of) the differential equation. If the ODE is much less smooth than the order of the method, the implicit assumptions for the step size control mechanism are violated, which may result in a very small step size requiring a huge number of integration steps.
I also encountered similar problem. In my case, parameters a, b, and c are not in direct function with time, but determined by x, y, and z at that time. So I have to get x, y, z at time t, and calculate a, b, c for the integration calculation for x, y, z at t+dt. It turns out that if I change dt value, the whole integration result will change dramatically, even to something unreasonable.
I guess I am missing something in this code:
integrand = lambda t,x,y: (1/(Tiempo-t))*np.exp(-((x-U*(Tiempo-t))**2+y**2)/(4*a*(Tiempo-t)))
def z_func(x,y,Rate,Conductivity):
integral, err = integrate.quad(integrand,0,Tiempo,args=(x,y,))
return ((Rate/(2*math.pi* Conductivity))*integral)
Z = z_func(X, Y, Ql, k)
cs = plt.contour(X, Y, Z,[IncT])
I have an implicit function with an integral, something like f(x,y,t)=A*Integral, where A is constant. It integrates over t. I need to calculate the contour for an specific value of t. But I get several errors such as "Supplied function does not return a valid float", which is the actual error when evaluating the z_func.
What am I doing wrong? Is there another way to solve it?
I should add I'm working with a meshgrid:
x = arange(-1.0,10.0,0.1)
y = arange(-1.0,10.0,0.1)
X,Y = meshgrid(x, y)
Thanks in advance!
To avoid this error, z_func must be vectorized:
vz_func = np.vectorize(z_func)
Z = vz_func(X, Y, Ql, k)