A = a1*e1+a2*e2
B = b1*e1+b2*e2
expand(A*B) = a1*b1*e1**2+a1*b2*e1*e2+a2*b1*e2*e1+a2*b2*e2**2
Normally no further simplification is possible, but if the user could
define a function q of the e's that gives:
q(e1,e1) = g111*e1+g112*e2
q(e1,e2) = g121*e1+g122*e2
q(e2,e1) = g211*e1+g212*e2
q(e2,e2) = g221*e1+g222*e2
that could be associated with the product of the e's in expand(A*B) to
do automatic expansion rather than use subs and a dictionary.
Especially since then are cases where a dictionary is not the best way
to perform the operation such as in differential forms (let * represent
^) where e1 = dx1 and e2 = dx2 and
q(a,b) = 0 if a == b and q(dx1,dx2) = dx1*dx2 and q(dx2,dx1) = -dx1*dx2
(put the product in normal order)
Likewise consider the function:
F = f1(x,y)*e1+f2(x,y)*e2
Normally:
diff(F,x) = Derivative(f1(x,y),x)*e1+Derivative(f2(x,y),x)*e2
similarly for diff(F,y), but let there be a user defined function:
d(e1) = g11(x,y)*e1+g12(x,y)*e2
d(e2) = g21(x,y)*e1+g22(x,y)*e2
so that diff(F,x) gives
diff(F,x) =
Derivative(f1(x,y),x)*e1+Derivative(f2(x,y),x)*e2+f1(x,y)*(g11(x,y)*e1+g12(x,y)*e2)+f2(x,y)*(g21(x,y)*e1+g22(x,y)*e2)
A generalization of the first type of expansion for binary products
would be for nary products where q(*args) could have a variable number
of arguments so that products of the form e1*e2*...*er could be
automatically evaluated.
Finally there are times when additional products for symbols would be
very useful such as ^ and | operators that different user supplied
evaluation functions could be applied to. The problem here is that
during expansion operations the operator precedence
defined by user supplied parenthesis in the original expression would
have to be respected since the python default precedence for the
operators might have no relation to the precedence requires by the
mathematics.
That sounds very useful.
In fact I see this done in mathematical texts all the time - if the
result of a simplification is too complicated, identify patterns and
push them away into helper functions.
I'm not sure how simplification would work if user-defined functions can
be added.
For example, sometimes you'd want to complicate an expression slightly
so you have more places where you can use such a function.
This might be a second step after the first one. I'm pretty sure that
the ability to put common subexpressions into functions would already be
useful, even withouth the ability to massage expressions to get more
common subexpressions.
#GAsympy.py
from sympy import expand,Mul,Add,Symbol,S,Expr,Wild
ONE = S(1)
ZERO = S(0)
def linear_function(expr,fct):
"""
If a sympy 'Expr' is of the form:
expr = expr_0+expr_1*a_1+...+expr_n*a_n
where all the a_j are noncommuting symbols in basis then
f(expr) = expr_0+expr_1*f(a_1)+...+expr_n*f(a_n)
is returned
"""
if expr.is_commutative:
return(expr)
expr = expand(expr)
if isinstance(expr,Mul):
x = (coefs,bases) = expr.args_cnc()
return(Mul(*coefs)*fct(bases[0]))
elif isinstance(expr,Symbol):
return(fct(expr))
elif isinstance(expr,Add):
result = ZERO
for arg in expr.args:
term = arg.args_cnc()
if term[1] == []:
result += Mul(*term[0])
else:
result += Mul(*term[0])*fct(term[1][0])
return(result)
def bilinear_function(expr,fct):
"""
If a sympy 'Expr' is of the form:
expr = expr_0+expr_1*a_1+...+expr_n*a_n+expr_11*a_1*a_1
+...+expr_ij*a_i*a_j+...+expr_nn*a_n*a_n
where all the a_j are noncommuting symbols in basis then
f(expr) = expr_0+expr_1*fa_1+...+expr_n*a_n+expr_11*fct(a_1,a_1)
+...+expr_ij*fct(a_i,a_j)+...+expr_nn*fct(a_n,a_n)
is returned
"""
p = Wild('p')
if expr.is_commutative:
return(expr)
expr = expand(expr)
if isinstance(expr,Mul): #only one additive term
x = (coefs,bases) = expr.args_cnc()
if len(bases) == 1:
W = bases[1][0].match(p**2)
if W != None: #square test
x = W[p]
return(Mul(*coefs)*fct(x,x))
else: #no bilinear term
return(expr)
else: #nonsquare bilinear term
return(Mul(*coefs)*fct(bases[0],bases[1]))
elif isinstance(expr,Symbol):
return(expr)
elif isinstance(expr,Add): #multiple additive terms
result = ZERO
for arg in expr.args:
term = arg.args_cnc()
if len(term[1]) == 0: #scalar term
result += Mul(*term[0])
elif len(term[1]) == 1: #vector or square term
W = term[1][0].match(p**2)
if W != None: #square test
x = W[p]
result += Mul(*term[0])*fct(x,x)
else: #linear term
result += Mul(*term[0])*term[1][0]
else: #bilinear term
result += Mul(*term[0])*fct(term[1][0],term[1][1])
return(result)
def derivation_function(expr,fct,x):
"""
If a sympy 'Expr' is of the form:
expr = expr_0+expr_1*a_1+...+expr_n*a_n
where all the a_j are noncommuting symbols in basis then
df(expr) = diff(expr_0,x)+diff(expr_1,x)*a_1+...+diff(expr_n,x)*f(a_n)+
expr_1*fct(a_1,x)+...+expr_n*fct(a_n,x)
is returned
"""
if expr.is_commutative:
return(diff(expr,x))
expr = expand(expr)
if isinstance(expr,Mul):
x = (coefs,bases) = expr.args_cnc()
coef = Mul(*coefs)
return(diff(coef,x)*bases[0]+coef*fct(bases[0],x))
elif isinstance(expr,Symbol):
return(fct(expr,x))
elif isinstance(expr,Add):
result = ZERO
for arg in expr.args:
term = arg.args_cnc()
coef = Mul(*term[0])
if term[1] == []:
result += diff(coef,x)
else:
result += diff(coef,x)*term[1][0]+coef*fct(term[1][0],x)
return(result)
from sympy import *
def f(e):
return(e*e)
def f2(e_a,e_b):
if e_a == e_b:
return(e_a)
else:
return(ZERO)
def df(e,x):
return(x*e)
(a1,a2,b1,b2,x) = symbols('a1,a2,b1,b2,x')
(e1,e2) = symbols('e1,e2',commutative=False)
A = e1*a1+e2*a2+a1
B = e1*b1+e2*b2+a2
AB = expand(A*B)
print 'A =',A,
print 'B =',B
print 'AB =',AB
print 'linear_function(A,f) =',linear_function(A,f)
print 'bilinear_function(AB,f2) =',bilinear_function(AB,f2)
f1 = Function('f1')(x)
f2 = Function('f2')(x)
F = f1*e1+f2*e2
print 'F =',F
print 'derivation_function(F,df,x) =',derivation_function(F,df,x)