1 | initial version |
Depending on what you want, the underlying tree for a symbolic expression might also be useful. A naive function that takes a symbolic expression as input and returns an expression tree is demonstrated here.
Sage has more sophisticated ways of building expression trees for the purposes of creating fast callable functions (for, e.g., plotting graphs or computing numerical integrals). I find them a bit confusing, but they might prove useful. The relevant classes are Expression
and ExpressionTreeBuilder
, both defined in sage.ext.fast_callable
Here is a demonstration of how they might be used, and also a "variable free" version of the naive tree:
from sage.ext.fast_callable import ExpressionTreeBuilder
class AbstractFunction(SageObject):
"""
A class to compute and store information for abstract (variable-free) functions
"""
def __init__(self,f):
self._vars = f.variables()
# an ExpressionTreeBuilder instance must know
# the names and ordering of variables
self.etb = ExpressionTreeBuilder(vars=self._vars)
# store the symbolic expression, since we don't
# know how to rebuild it
self.symbolic_expr = f
# the Expression object
self.expr = self.etb(f)
# the fast callable version of this Expression
# useful for computing values of the function
self.callable_expr = fast_callable(self.expr)
# a naive expression tree, stored as a list of lists
self.naive_tree = self.extract_naive_tree(f)
def vars(self,i=None):
if i is not None:
return lambda V: V[i]
return self._vars
def reset_vars(self,new_vars):
"""
Change the variable names for the underlying symbolic expression
"""
sub_dict = dict(zip(self._vars,new_vars))
self.symbolic_expr = self.symbolic_expr.subs(sub_dict)
self._vars = new_vars
return self.symbolic_expr
def extract_naive_tree(self,expr):
"""
A nested list of operators and operands.
Variables are replaced by coordinate functions which
extract ith value from list of inputs.
"""
if expr.operator() is None:
try:
v0 = expr.variables()[0] # test whether we have non-trivial tuple of variables
return [self.vars,self._vars.index(expr)]
except IndexError:
return expr
else:
return [expr.operator()]+map(self.extract_naive_tree,expr.operands())
def diff(self,*args,**kwds):
"""
Differentiate the symbolic expression and
convert the result to an AbstractFunction
"""
df = self.symbolic_expr.diff(*args,**kwds)
return AbstractFunction(df)
Even though this is getting too long, here are some examples of using this class:
sage: var('x,y,z')
(x, y, z)
Create AbstractFunction
from symbolic expression
sage: f = AbstractFunction(x^2 + 1)
sage: f.symbolic_expr # original symbolic expression
x^2 + 1
sage: f.expr # Expression object
add(ipow(v_0, 2), 1)
sage: f.naive_tree # naive expression tree
[<function operator.add>,
[<function operator.pow>,
[<bound method AbstractFunction.vars of <class '__main__.AbstractFunction'>>,
0],
2],
1]
sage: f.callable_expr(3) # calling the callable version of the expression
10
sage: f.callable_expr(z+x) # calling with symbolic expressions as inputs
(x + z)^2 + 1
Operating on the symbolic expression and converting the result to an AbstractFunction
:
sage: df = f.diff()
sage: df.symbolic_expr
2*x
sage: df.expr
mul(v_0, 2)
Change variable names for the original symbolic expression:
sage: f.reset_vars((z,))
z^2 + 1
sage: f.diff().symbolic_expr
2*z
A multivariable example:
sage: h = AbstractFunction(sin(x + z) + y^2)
sage: h.expr
add(ipow(v_1, 2), sin(add(v_0, v_2)))
sage: h.diff(y).expr
mul(v_0, 2)
The naive tree replaces the variables with coordinate functions. Evaluating AbstractFunction.vars
on an index i returns a function whose output is the ith item in a given list:
sage: h.naive_tree
[<function operator.add>,
[<function operator.pow>,
[<bound method AbstractFunction.vars of <class '__main__.AbstractFunction'>>,
1],
2],
[sin,
[<function operator.add>,
[<bound method AbstractFunction.vars of <class '__main__.AbstractFunction'>>,
0],
[<bound method AbstractFunction.vars of <class '__main__.AbstractFunction'>>,
2]]]]
sage: h.naive_tree[1][1][0](1)(h._vars)
y