问题
We need two matrices of differential operators [B]
and [C]
such as:
B = sympy.Matrix([[ D(x), D(y) ],
[ D(y), D(x) ]])
C = sympy.Matrix([[ D(x), D(y) ]])
ans = B * sympy.Matrix([[x*y**2],
[x**2*y]])
print ans
[x**2 + y**2]
[ 4*x*y]
ans2 = ans * C
print ans2
[2*x, 2*y]
[4*y, 4*x]
This could also be applied to calculate the curl of a vector field like:
culr = sympy.Matrix([[ D(x), D(y), D(z) ]])
field = sympy.Matrix([[ x**2*y, x*y*z, -x**2*y**2 ]])
To solve this using Sympy the following Python class had to be created:
import sympy
class D( sympy.Derivative ):
def __init__( self, var ):
super( D, self ).__init__()
self.var = var
def __mul__(self, other):
return sympy.diff( other, self.var )
This class alone solves when the matrix of differential operators is multiplying on the left. Here diff
is executed only when the function to be differentiated is known.
To workaround when the matrix of differential operators is multiplying on the right, the __mul__
method in the core class Expr
had to be changed in the following way:
class Expr(Basic, EvalfMixin):
# ...
def __mul__(self, other):
import sympy
if other.__class__.__name__ == 'D':
return sympy.diff( self, other.var )
else:
return Mul(self, other)
#...
It works pretty well, but there should be a better native solution in Sympy to handle this. Does anybody know what it might be?
回答1:
This solution applies the tips from the other answers and from here. The D
operator can be defined as follows:
- considered only when multiplied from the left, so that
D(t)*2*t**3 = 6*t**2
but2*t**3*D(t)
does nothing - all the expressions and symbols used with
D
must haveis_commutative = False
- is evaluated in the context of a given expression using
evaluateExpr()
- which goes from the right to the left along the expression finding the
D
opperators and applyingmydiff()
* to the corresponding right portion
- which goes from the right to the left along the expression finding the
*:mydiff
is used instead of diff
to allow a higher order D
to be created, like mydiff(D(t), t) = D(t,t)
The diff
inside __mul__()
in D
was kept for reference only, since in the current solution the evaluateExpr()
actually does the differentiation job. A python mudule was created and saved as d.py
.
import sympy
from sympy.core.decorators import call_highest_priority
from sympy import Expr, Matrix, Mul, Add, diff
from sympy.core.numbers import Zero
class D(Expr):
_op_priority = 11.
is_commutative = False
def __init__(self, *variables, **assumptions):
super(D, self).__init__()
self.evaluate = False
self.variables = variables
def __repr__(self):
return 'D%s' % str(self.variables)
def __str__(self):
return self.__repr__()
@call_highest_priority('__mul__')
def __rmul__(self, other):
return Mul(other, self)
@call_highest_priority('__rmul__')
def __mul__(self, other):
if isinstance(other, D):
variables = self.variables + other.variables
return D(*variables)
if isinstance(other, Matrix):
other_copy = other.copy()
for i, elem in enumerate(other):
other_copy[i] = self * elem
return other_copy
if self.evaluate:
return diff(other, *self.variables)
else:
return Mul(self, other)
def __pow__(self, other):
variables = self.variables
for i in range(other-1):
variables += self.variables
return D(*variables)
def mydiff(expr, *variables):
if isinstance(expr, D):
expr.variables += variables
return D(*expr.variables)
if isinstance(expr, Matrix):
expr_copy = expr.copy()
for i, elem in enumerate(expr):
expr_copy[i] = diff(elem, *variables)
return expr_copy
return diff(expr, *variables)
def evaluateMul(expr):
end = 0
if expr.args:
if isinstance(expr.args[-1], D):
if len(expr.args[:-1])==1:
cte = expr.args[0]
return Zero()
end = -1
for i in range(len(expr.args)-1+end, -1, -1):
arg = expr.args[i]
if isinstance(arg, Add):
arg = evaluateAdd(arg)
if isinstance(arg, Mul):
arg = evaluateMul(arg)
if isinstance(arg, D):
left = Mul(*expr.args[:i])
right = Mul(*expr.args[i+1:])
right = mydiff(right, *arg.variables)
ans = left * right
return evaluateMul(ans)
return expr
def evaluateAdd(expr):
newargs = []
for arg in expr.args:
if isinstance(arg, Mul):
arg = evaluateMul(arg)
if isinstance(arg, Add):
arg = evaluateAdd(arg)
if isinstance(arg, D):
arg = Zero()
newargs.append(arg)
return Add(*newargs)
#courtesy: https://stackoverflow.com/a/48291478/1429450
def disableNonCommutivity(expr):
replacements = {s: sympy.Dummy(s.name) for s in expr.free_symbols}
return expr.xreplace(replacements)
def evaluateExpr(expr):
if isinstance(expr, Matrix):
for i, elem in enumerate(expr):
elem = elem.expand()
expr[i] = evaluateExpr(elem)
return disableNonCommutivity(expr)
expr = expr.expand()
if isinstance(expr, Mul):
expr = evaluateMul(expr)
elif isinstance(expr, Add):
expr = evaluateAdd(expr)
elif isinstance(expr, D):
expr = Zero()
return disableNonCommutivity(expr)
Example 1: curl of a vector field. Note that it is important to define the variables with commutative=False
since their order in Mul().args
will affect the results, see this other question.
from d import D, evaluateExpr
from sympy import Matrix
sympy.var('x', commutative=False)
sympy.var('y', commutative=False)
sympy.var('z', commutative=False)
curl = Matrix( [[ D(x), D(y), D(z) ]] )
field = Matrix( [[ x**2*y, x*y*z, -x**2*y**2 ]] )
evaluateExpr( curl.cross( field ) )
# [-x*y - 2*x**2*y, 2*x*y**2, -x**2 + y*z]
Example 2: Typical Ritz approximation used in structural analysis.
from d import D, evaluateExpr
from sympy import sin, cos, Matrix
sin.is_commutative = False
cos.is_commutative = False
g1 = []
g2 = []
g3 = []
sympy.var('x', commutative=False)
sympy.var('t', commutative=False)
sympy.var('r', commutative=False)
sympy.var('A', commutative=False)
m=5
n=5
for j in xrange(1,n+1):
for i in xrange(1,m+1):
g1 += [sin(i*x)*sin(j*t), 0, 0]
g2 += [ 0, cos(i*x)*sin(j*t), 0]
g3 += [ 0, 0, sin(i*x)*cos(j*t)]
g = Matrix( [g1, g2, g3] )
B = Matrix(\
[[ D(x), 0, 0],
[ 1/r*A, 0, 0],
[ 1/r*D(t), 0, 0],
[ 0, D(x), 0],
[ 0, 1/r*A, 1/r*D(t)],
[ 0, 1/r*D(t), D(x)-1/x],
[ 0, 0, 1],
[ 0, 1, 0]])
ans = evaluateExpr(B*g)
A print_to_file()
function has been created to quickly check big expressions.
import sympy
import subprocess
def print_to_file( guy, append=False ):
flag = 'w'
if append: flag = 'a'
outfile = open(r'print.txt', flag)
outfile.write('\n')
outfile.write( sympy.pretty(guy, wrap_line=False) )
outfile.write('\n')
outfile.close()
subprocess.Popen( [r'notepad.exe', r'print.txt'] )
print_to_file( B*g )
print_to_file( ans, append=True )
回答2:
Differential operators do not exist in the core of SymPy, and even if they existed "multiplication by an operator" instead of "application of an operator" is an abuse of notation that is not supported by SymPy.
[1] Another problem is that SymPy expressions can be build only from subclasses of sympy.Basic
, so it is probable that your class D
simply raises an error when entered as sympy_expr+D(z)
. This is the reason why (expression*D(z)) * (another_expr)
fails. (expression*D(z))
can not be built.
In addition if the argument of D
is not a single Symbol
it is not clear what you expect from this operator.
Finally, diff(f(x), x)
(where f
is a symbolic unknown function) returns an unevaluated expressions as you observed simply because when f
is unknown there is nothing else that can sensibly returned. Later, when you substitute expr.subs(f(x), sin(x))
the derivative will be evaluate (at worst you might need to call expr.doit()
).
[2] There is no elegant and short solution to your problem. A way that I would suggest for solving your problem is to override the __mul__
method of Expr
: instead of just multiplying the expression trees it will check if the left expression tree contains instances of D
and it will apply them. Obviously this does not scale if you want to add new objects. This is a longstanding known issue with the design of sympy.
EDIT: [1] is necessary simply to permit creation of expressions containing D
. [2] is necessary for expressions that containing something more that only one D
to work.
回答3:
If you want right multiplication to work, you'll need to subclass from just object
. That will cause x*D
to fall back to D.__rmul__
. I can't imagine this is high priority, though, as operators are never applied from the right.
回答4:
Making an operator that works automatically always is not currently possible. To really work completely, you would need http://code.google.com/p/sympy/issues/detail?id=1941. See also https://github.com/sympy/sympy/wiki/Canonicalization (feel free to edit that page).
However, you could make a class that works most of the time using the ideas from that stackoverflow question, and for the cases it doesn't handle, write a simple function that goes through an expression and applies the operator where it hasn't been applied yet.
By the way, one thing to consider with a differential operator as "multiplication" is that it's nonassociative. Namely, (D*f)*g
= g*Df
, whereas D*(f*g)
= g*Df + f*Dg
. So you need to be careful when you do stuff that it doesn't "eat" some part of an expression and not the whole thing. For example, D*2*x
would give 0
because of this. SymPy everywhere assumes that multiplication is associative, so it's likely to do that incorrectly at some point.
If that becomes an issue, I would recommend dumping the automatic application, and just working with a function that goes through and applies it (which as I noted above, you will need anyway).
来源:https://stackoverflow.com/questions/15463412/differential-operator-usable-in-matrix-form-in-python-module-sympy