Ask Your Question
2

Symbolic functions without named variables

asked 2013-11-27 15:33:17 +0100

Mike Shulman gravatar image

Is there a way to define a symbolic function that can (e.g.) be differentiated, but doesn't remember the name of its input variable(s)? For instance, consider:

sage: f(x) = x^2
sage: g(x) = x^2
sage: h(t) = t^2

Mathematically, f, g, and h, should all be the same function. However, Sage doesn't think so:

sage: f+g
x |--> 2*x^2
sage: f+h
(t, x) |--> t^2 + x^2

I guess that this is happening because a "function" defined with f(x)=x^2 is actually just a symbolic expression equipped with an ordering on its variables, rather than what a mathematician would call a "function". Is there a way to define an actual mathematical function?

edit retag flag offensive close merge delete

3 Answers

Sort by ยป oldest newest most voted
2

answered 2013-11-29 12:00:11 +0100

niles gravatar image

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 ...
(more)
edit flag offensive delete link more

Comments

I'm trying to understand this. Is the expression tree really necessary? It seems like a wrapper around a callable symbolic expression that knows how to reset the variables as needed might be sufficient for what I want. For instance, could you also overload function application, addition, etc.?

Mike Shulman gravatar imageMike Shulman ( 2013-12-02 23:49:58 +0100 )edit

Sorry for the complexity -- I was learning as I wrote this, and I think it contains at least two independent solutions. But I'm also not sure what you want. A wrapper which changes variables in a symbolic expression seems reasonable too. The expression tree gives a presentation of a symbolic expression as a composite of elementary functions -- that may or may not be useful depending on how you want to work with abstract functions.

niles gravatar imageniles ( 2013-12-03 07:42:31 +0100 )edit
1

answered 2013-11-27 18:35:04 +0100

nbruin gravatar image

It's a peculiarity of sage that object "(print)names" have significance. For instance, QQ['x'] and QQ['t'] are both rings of univariate polynomials over QQ. They are of course isomorphic but in the eyes of sage not canonically so (there are many relations possible between x and t that could establish an isomorphism), and hence sage declines to choose any by default. The same here:

sage: parent(f)
Callable function ring with arguments (x,)
sage: parent(h)
Callable function ring with arguments (t,)

Going from one to the other can be done:

sage: Ct = parent(h)
sage: ft = Ct(f(t))
sage: ft
t |--> t^2
sage: parent(ft)
Callable function ring with arguments (t,)
sage: ft+h
t |--> 2*t^2
sage: parent(ft+h)
Callable function ring with arguments (t,)

Doing it like this might give you a bit of a glimpse "under the hood" into why it works this way.

edit flag offensive delete link more

Comments

Hmm... if it were really consistent about behaving this way, then I would expect `f+h` to be a type error, since you can't add elements of different rings.

Mike Shulman gravatar imageMike Shulman ( 2013-12-02 23:34:28 +0100 )edit

You can if you consider the two rings as subrings of a bigger ring, and in this case there is an (almost) natural candidate: the Callable function ring with arguments (x,t), since a function in either x or t can be considered as a function in both. The fact that the order of the arguments isn't well-defined is problematic, though. Finding a common "covering structure" in which the result of an operation on two elements from distinct structures lives is referred to as a "pushout" in sage.

nbruin gravatar imagenbruin ( 2013-12-04 10:19:01 +0100 )edit

Okay, I guess I see what the developers were thinking, even if I don't agree with it. (Wouldn't "coproduct" be a more appropriate term?)

Mike Shulman gravatar imageMike Shulman ( 2013-12-05 13:29:59 +0100 )edit
1

answered 2013-11-27 17:21:13 +0100

tmonteil gravatar image

You are right, you defined a symbolic expression, which includes the name of the variable:

sage: type(f)
<type 'sage.symbolic.expression.Expression'>

sage: f.parent()
Callable function ring with arguments (x,)

You can survive by noticing that:

sage: i(t) = f(t) + g(t)
sage: i
t |--> 2*t^2

Unfortunately, it is currently not possible to add more semantics to such objects, therefore approaching mathematical functions. Typically, it would be very nice to be able to define a function, including its domain and codomain (this is currently imitated with assumptions on the variables).

edit flag offensive delete link more

Comments

This has a side-effect (the string returned by preparse is what is actually executed): sage: preparse("i(t)=f(t)+g(t)") '__tmp__=var("t"); i = symbolic_expression(f(t)+g(t)).function(t)' In addition to defining `i` as the required symbolic function, the symbol `t` also gets re-defined as a symbolic variable. That's a side-effect you may not always expect: sage: t=1 sage: f(t)=t+1 sage: f t |--> t + 1 sage: t # t has been redefined too t

nbruin gravatar imagenbruin ( 2013-11-28 13:56:56 +0100 )edit

Thanks! Why do you refer to defining mathematical functions as "adding more semantics"?

Mike Shulman gravatar imageMike Shulman ( 2013-12-02 23:31:29 +0100 )edit

Your Answer

Please start posting anonymously - your entry will be published after you log in or create a new account.

Add Answer

Question Tools

Stats

Asked: 2013-11-27 15:33:17 +0100

Seen: 1,114 times

Last updated: Nov 29 '13