Symbolic computations with functions having a variable number of arguments

I’m looking for a Julia package that can rewrite symbolic expressions involving functions that don’t have a fixed number of arguments. For example, it should be easy to declare that some function f is multilinear (= linear in each argument if all other arguments are kept fixed) without having to create a separate transformation rule for each possible case ("linear in the k-th argument out of n").
The package shouldn’t call Python under the hood. I once tried out SymPy as well as pure Python and found them way too slow. Any suggestions?

1 Like

SymbolicUtils.jl, which underlies Symbolics.jl, has a rewrite engine, but I cannot find support for sequence patterns in the documentation (here the sequence would be the list of arguments). This kind of task is easy using sequence patterns in Wolfram Mathematica, but I’d love to learn about a Julia solution if it exists! (I’m trying to move some of my computing tasks from Mathematica to Julia.) Here’s the Mathematica solution, just to make it clear what I meant by sequence patterns etc.

Input:

f[arguments1___, coeff_?NumericQ * variable_, arguments2___] :=
coeff * f[arguments1, variable, arguments2];                                                    

f[x, 2*y]

Output:

2 f[x, y]

Input:

f[x, 2*y, 3*z, w]                                                       

Output:

Out[3]= 6 f[x, y, z, w]

Just use Symbolics.jl with arrays.

1 Like

Can you give an example? For instance, how would the rule that @greatpet has given in Mathematica above look like in Symbolics.jl? I would be interested in that because it’s exactly of the kind I’m looking for.

1 Like

I’ve cooked up a solution using Symbolics.jl. The function is here, and some example usage follows,

function expand_multilinear(expr)
    # check if expr is a single term before proceeding
    if !isa(expr, Num) || !isa(expr.val, SymbolicUtils.Term)
        return expr
    end
    function_name = expr.val.f
    function_arguments = deepcopy(expr.val.arguments)
    overall_coeff = 1
    for i in 1:length(function_arguments)
        # check if this function argument is a product of things
        if isa(function_arguments[i], SymbolicUtils.Mul)
            current_coeff = function_arguments[i].coeff
            # absorb nontrivial coefficient into an overall factor
            if current_coeff != 1
                overall_coeff *= current_coeff
                function_arguments[i] = function_arguments[i] / current_coeff
            end
        end
    end
    overall_coeff * function_name(function_arguments...)
end

Example usage:

julia> using Symbolics
julia> @variables x,y,z,g(..);
julia> expand_multilinear(g(x, 2*y, 3*z))
6g(x, y, z)

julia> expand_multilinear(g(1/x, 2*y*z))
2g(x^-1, y*z)

The main limitation is that this only works for a single term. It gives up if you run e.g. expand_multilinear(g(2*x,y) + g(3*z, y)) . If you have a general expression, presumably you can look at all sub-expressions (i.e. visiting tree nodes of the interal representation) and apply the above function, but I know far too little about Symbolics.jl to figure out how to do this efficiently.

1 Like

Thanks a lot! Although the code doesn’t quite reach the elegance of Mathematica’s rule-based approach … :wink:

It seems that such patters exist under the name of segment patterns, see this part of the documentation. Example:

using SymbolicUtils
@syms x y z
r = @rule(+(~x,~~y) => +(~~y...))
r(x+y+z)  # gives y+z

This could be the basis of a very clean solution without writing as much code as in the function expand_multilinear above. However, while arithmetic operators like + accept a variable number of arguments, I don’t know if it’s possible to define a new symbolic function with this property. Does anybody else know?

UPDATE: The following seems to work:

using SymbolicUtils
@syms w x y z
f = SymbolicUtils.Sym{(SymbolicUtils.FnType){Tuple, Number}}(:f)
r = @rule(f(~~a, ~x + ~y, ~~b) => f(~~a..., ~x, ~~b...) + f(~~a..., ~y, ~~b...))

r( f(x+y) )        #  f(x) + f(y)
r( f(x+y, z) )     #  f(x, z) + f(y, z)
r( f(w, x+y, z) )  #  f(w, x, z) + f(w, y, z)

The crucial point is that by defining f without the help of @syms, one can specify a general Tuple type instead of one with a specific number of parameters (as in Tuple{Number,Number}). (I’ve got this idea from the @variables f(..) macro of Symbolics.jl.)

2 Likes

@shashi

Nicely done, but on further testing, your code returns nothing when you call it with 3 variables added together in an argument slot, like r( f(w, x+y+z, z) ). I haven’t been able to fix it, but here’s an attempt:

r1 = @rule(f(~~a, ~x + ~~y, ~~b) => f(~~a..., ~x, ~~b...) + f(~~a..., +(~~y...), ~~b...))

r1(f(w, x+y+z, z)) # returns f(w, x, z) + f(w, y + z, z)

Do you known a good way to apply the rule recursively to finish up the job?

An unrelated question / feature request for Symbolics.jl: can associative-commutative pattern matching, i.e. @acrule, be applied to arbitrary user-defined associative / commutative functions, rather than Base.+ and Base.*?