Ask Your Question

Revision history [back]

click to hide/show revision 1
initial version

The trick is to substitute u with a variable, differentiate, then substitute back.

Given the python module called func_diff.py (shown later), one can use it as follows:

goofy@wdeb:temp$ ls
func_diff.py
goofy@wdeb:temp$ sage
----------------------------------------------------------------------
| Sage Version 4.6.2, Release Date: 2011-02-25                       |
| Type notebook() for the GUI, and license() for information.        |
----------------------------------------------------------------------
sage: import func_diff
sage: u(x) = function('u', x)
sage: u
x |--> u(x)
sage: L = u^3 + (1/2)*u.diff(x)^2
sage: L
x |--> u(x)^3 + 1/2*D[0](u)(x)^2
sage: func_diff.func_diff(L, u)
x |--> 3*u(x)^2 - D[0, 0](u)(x)

Note that func_diff as-is does not actually compute the functional derivative. In the context of @cswiercz's definition, it takes $L$, not $I$, as the input. But this should be relatively easy to change. Simply use func_diff(I.diff(x), u)

Here is the module func_diff.py:

from sage.all import *
import sage.symbolic.operators

def is_op_du(expr_op, u):
    is_derivative = isinstance(
        expr_op,
        sage.symbolic.operators.FDerivativeOperator
    )

    if is_derivative:
        # Returns True if the differentiated function is `u`.
        return expr_op.function() == u.operator()

    else:
        return False

def iter_du_orders(expr, u):
    for sub_expr in expr.operands():
        if sub_expr == []:
            # hit end of tree
            continue

        elif is_op_du(sub_expr.operator(), u):
            # yield order of differentiation
            yield len(sub_expr.operator().parameter_set())

        else:
            # iterate into sub expression
            for order in iter_du_orders(sub_expr, u):
                yield order

def func_diff(L, u_in):
    # `u` must be a callable symbolic expression
    # in one variable.
    if len(u_in.variables()) == 1:
        x = u_in.variables()[0]
        u = u_in.function(x)
    else:
        raise TypeError

    # This variable name must not collide
    # with an existing one.
    # I use an empty string in hopes that
    # nobody else does this...
    t = SR.var('')

    result = SR(0)

    # `orders` is the set of all
    # orders of differentiation of `u`
    orders = set(iter_du_orders(L, u)).union((0,))

    for c in orders:
        du = u(x).diff(x, c)
        sign = Integer(-1)**c

        # Temporarily replace all `c`th derivatives of `u` with `t`;
        # differentiate; then substitute back.
        dL_du = L.subs({du:t}).diff(t).subs({t:du})

        # Append intermediate term to `result`
        result += sign * dL_du.diff(x, c)

    return result

The bulk of the code is rather uninteresting. For example, the generator iter_du_orders iterates through the given expression L and returns all of the orders of differention of u in L.

Unfornately, there is a bug which I do not know how to cleanly circumvent. It is due to the line t = SR.var(''), which creates a temporary variable. This variable is used to substitute out all instances of $\frac{d^n u}{d x^n}$ in L when computing the derivatives. The problem is if L itself contains a variable with an empty-string representation (i.e. the same as that of t), then things will get pretty messed up. For example:

sage: b = SR.var('')
sage: b

sage: u = function('u', b).function(b)
sage: u
 |--> u()
sage: L = u^3 + (1/2)*u.diff(b)^2
sage: L
 |--> u()^3 + 1/2*D[0](u)()^2
sage: func_diff.func_diff(L, u)
 |--> -3*u(D[0](u)())^2*D[0, 0](u)()*D[0, 0](u)(D[0](u)()) - 6*u(D[0](u)())*D[0](u)(D[0](u)())^2*D[0, 0](u)() + 3*u()^2 + D[0](u)(u())*D[0, 0](u)(u()) - D[0, 0](u)()