Ask Your Question

Revision history [back]

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