# Symbolic computations with functions having a variable number of arguments

**URL:** <https://discourse.julialang.org/t/symbolic-computations-with-functions-having-a-variable-number-of-arguments/65140>\
**Category:** General Usage\
**Tags:** symbolics\
**Created:** [July 22, 2021, 9:54pm UTC](https://discourse.julialang.org/t/symbolic-computations-with-functions-having-a-variable-number-of-arguments/65140 "2021-07-22T21:54:38Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![matthias314](https://avatars.discourse-cdn.com/v4/letter/m/a88e4f/32.png) [@matthias314](https://discourse.julialang.org/u/matthias314)\
**Post date:** [July 22, 2021, 9:54pm UTC](https://discourse.julialang.org/t/symbolic-computations-with-functions-having-a-variable-number-of-arguments/65140/1 "2021-07-22T21:54:38Z")

</div>

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?

---

<div class="post-metadata">

**Author:** ![greatpet](https://avatars.discourse-cdn.com/v4/letter/g/e495f1/32.png) [@greatpet](https://discourse.julialang.org/u/greatpet)\
**Post date:** [July 22, 2021, 10:44pm UTC](https://discourse.julialang.org/t/symbolic-computations-with-functions-having-a-variable-number-of-arguments/65140/2 "2021-07-22T22:44:36Z")

</div>

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:

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

f[x, 2*y]

```

Output:

```julia
2 f[x, y]

```

Input:

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

```

Output:

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

```

---

<div class="post-metadata">

**Author:** ![ChrisRackauckas](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/chrisrackauckas/32/77_2.png) [@ChrisRackauckas](https://discourse.julialang.org/u/ChrisRackauckas)\
**Post date:** [July 23, 2021, 10:13am UTC](https://discourse.julialang.org/t/symbolic-computations-with-functions-having-a-variable-number-of-arguments/65140/3 "2021-07-23T10:13:52Z")

</div>

Just use Symbolics.jl with arrays.

---

<div class="post-metadata">

**Author:** ![matthias314](https://avatars.discourse-cdn.com/v4/letter/m/a88e4f/32.png) [@matthias314](https://discourse.julialang.org/u/matthias314)\
**Post date:** [July 23, 2021, 11:26am UTC](https://discourse.julialang.org/t/symbolic-computations-with-functions-having-a-variable-number-of-arguments/65140/4 "2021-07-23T11:26:06Z")

</div>

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.

---

<div class="post-metadata">

**Author:** ![greatpet](https://avatars.discourse-cdn.com/v4/letter/g/e495f1/32.png) [@greatpet](https://discourse.julialang.org/u/greatpet)\
**Post date:** [July 23, 2021, 1:14pm UTC](https://discourse.julialang.org/t/symbolic-computations-with-functions-having-a-variable-number-of-arguments/65140/5 "2021-07-23T13:14:53Z")

</div>

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

```julia
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
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.

---

<div class="post-metadata">

**Author:** ![matthias314](https://avatars.discourse-cdn.com/v4/letter/m/a88e4f/32.png) [@matthias314](https://discourse.julialang.org/u/matthias314)\
**Post date:** [July 23, 2021, 4:04pm UTC](https://discourse.julialang.org/t/symbolic-computations-with-functions-having-a-variable-number-of-arguments/65140/6 "2021-07-23T16:04:32Z")

</div>

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

---

<div class="post-metadata">

**Author:** ![matthias314](https://avatars.discourse-cdn.com/v4/letter/m/a88e4f/32.png) [@matthias314](https://discourse.julialang.org/u/matthias314)\
**Post date:** [July 27, 2021, 6:06pm UTC](https://discourse.julialang.org/t/symbolic-computations-with-functions-having-a-variable-number-of-arguments/65140/7 "2021-07-27T18:06:22Z")

</div>

> [@greatpet](#):
>
> SymbolicUtils.jl, which underlies Symbolics.jl, has a rewrite engine, but I cannot find support for sequence patterns in the documentation

It seems that such patters exist under the name of segment patterns, see [this part](https://symbolicutils.juliasymbolics.org/rewrite/#term_rewriting) of the documentation. Example:

```julia
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:

```julia
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.)

---

<div class="post-metadata">

**Author:** ![ChrisRackauckas](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/chrisrackauckas/32/77_2.png) [@ChrisRackauckas](https://discourse.julialang.org/u/ChrisRackauckas)\
**Post date:** [July 27, 2021, 6:35pm UTC](https://discourse.julialang.org/t/symbolic-computations-with-functions-having-a-variable-number-of-arguments/65140/8 "2021-07-27T18:35:04Z")

</div>

@shashi

---

<div class="post-metadata">

**Author:** ![greatpet](https://avatars.discourse-cdn.com/v4/letter/g/e495f1/32.png) [@greatpet](https://discourse.julialang.org/u/greatpet)\
**Post date:** [July 28, 2021, 10:49pm UTC](https://discourse.julialang.org/t/symbolic-computations-with-functions-having-a-variable-number-of-arguments/65140/9 "2021-07-28T22:49:50Z")

</div>

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:

```julia
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.*`?

---

<div class="post-metadata">

**Author:** ![matthias314](https://avatars.discourse-cdn.com/v4/letter/m/a88e4f/32.png) [@matthias314](https://discourse.julialang.org/u/matthias314)\
**Post date:** [July 29, 2021, 12:45am UTC](https://discourse.julialang.org/t/symbolic-computations-with-functions-having-a-variable-number-of-arguments/65140/10 "2021-07-29T00:45:58Z")

</div>

Yes, it was not a full solution. Here is what I have at the moment:

```julia
using SymbolicUtils, SymbolicUtils.Rewriters

f = SymbolicUtils.Sym{SymbolicUtils.FnType{Tuple, Number}}(:f)
r1 = @rule(f(~~a, +(~~b), ~~c) => sum([f(~~a..., x, ~~c...) for x in~~b]))
r2 = @rule(f(~~a, ~c::(x -> x isa Int) * ~x,~~b) => ~c * f( ~~a..., ~x,~~ b...))
r = Fixpoint(Postwalk(Chain([r1, r2])))

```

I haven’t tested it fully, but it seems to work:

```julia
julia> @syms w x y z;
julia> r( f(2*w, x-y+z, f(w-3*z,x)) )
2f(w, x, f(w, x)) + 6f(w, y, f(z, x)) + 2f(w, z, f(w, x)) - (6f(w, x, f(z, x))) - (2f(w, y, f(w, x))) - (6f(w, z, f(z, x)))

```

I’m only beginning to use SymbolicUtils.jl, so I don’t know if I’m using the right combination of rewriters.

This version is about 10 times slower than some Maple code I have (which is similar in spirit to `expand_multilinear` from @greatpet’s example above). Maybe something like `expand_multilinear` is the better approach, for example because the `Fixpoint` rewriter makes the `Postwalk` rewriter unnecessarily traverse the whole expression tree over and over again, instead of moving its way up from the bottom level only once. It would nevertheless be interesting to benchmark this Julia code also against @greatpet’s Mathematica example.

Regarding `@acrule`: From reading the documentation, I don’t fully understand what it does. It is not clear to me which functions are affected if I use `@acrule` for a pattern involving more than one function.

---

<div class="post-metadata">

**Author:** ![greatpet](https://avatars.discourse-cdn.com/v4/letter/g/e495f1/32.png) [@greatpet](https://discourse.julialang.org/u/greatpet)\
**Post date:** [July 29, 2021, 12:28pm UTC](https://discourse.julialang.org/t/symbolic-computations-with-functions-having-a-variable-number-of-arguments/65140/11 "2021-07-29T12:28:42Z")

</div>

Regarding performance, I timed your code with a large-ish input,

```julia
r(f(u+2v+3w+4x+5y+6z, u+2v+3w+4x+5y+6z, u+2v+3w+4x+5y+6z,
u+2v+3w+4x+5y+6z, u+2v+3w+4x+5y+6z))

```

It took about 11 seconds on my laptop after a JIT warmup run. An equivalent Mathematica version runs more than 50 times faster, but interestingly the performance is similar to your Julia code if I run it with an open-source clone of Mathematica, [expreduce](https://github.com/corywalker/expreduce) (the software is very incomplete, but still good enough for this situation). I suppose the conclusion is just that Mathematica’s rewriting engine has been heavily optimized.

My experience with Maple is that due to a lack of built-in pattern matching capabilities, I have resort to custom code a lot, but unfortunately `for` loops are slow in Maple, so Julia is much nicer when you have to code up something from scratch.

---

<div class="post-metadata">

**Author:** ![matthias314](https://avatars.discourse-cdn.com/v4/letter/m/a88e4f/32.png) [@matthias314](https://discourse.julialang.org/u/matthias314)\
**Post date:** [July 29, 2021, 1:00pm UTC](https://discourse.julialang.org/t/symbolic-computations-with-functions-having-a-variable-number-of-arguments/65140/12 "2021-07-29T13:00:21Z")

</div>

OK, so this Julia code is too slow. I need to try something else. For the record, the following Maple code is at least 100 times faster than the Julia code on your example.

```julia
f := proc()
local a, i, x, y;
  a := [args];
  for i to nargs do
    x := a[i];
    if type(x, `+`)
      then return `+`(seq(procname(op(1..i-1, a), y, op(i+1..-1, a)), y = x))
    elif type(x, `*`)
      then return `*`(op(1..-2, x), procname(op(1..i-1, a), op(-1, x), op(i+1..-1, a)))
    fi
  od;
  'procname'(args)
end;

```

It would be great to have a Julia solution with a performance comparable to Maple and Mathematica.

---

<div class="post-metadata">

**Author:** ![greatpet](https://avatars.discourse-cdn.com/v4/letter/g/e495f1/32.png) [@greatpet](https://discourse.julialang.org/u/greatpet)\
**Post date:** [July 29, 2021, 3:21pm UTC](https://discourse.julialang.org/t/symbolic-computations-with-functions-having-a-variable-number-of-arguments/65140/13 "2021-07-29T15:21:44Z")

</div>

Here’s a hand-coded Julia version which is 20 times faster than the version using rewrite rules, though it’ll need further optimization to be competitive. It’s a little long, but I’ll just dump the 60 lines of code here.

```julia
using SymbolicUtils
import SymbolicUtils.Add, SymbolicUtils.Mul, SymbolicUtils.Term
@syms u v w x y z argument_position(a,b)
f = SymbolicUtils.Sym{(SymbolicUtils.FnType){Tuple, Number}}(:f)

function expand1(term)
    if !isa(term, Term) || !(term.f === f)
        return term
    end
    args = deepcopy(term.arguments)
    for i in 1:length(args)
        if isa(args[i], Add)
            added = summands(args[i])
            args[i] = sum([argument_position(i, added[j]) for j in 1:length(added)])
        else
            args[i] = argument_position(i, args[i])
        end
    end
    # For example, for term=f(x+2y, z), so far
    # args == [argument_position(1,x)*argument_position(2,z) + argument_position(1,2y)*argument_position(2,z)
    allterms = summands(expand(*(args...))) # summands() is defined below
    sum(map(expand_multilinear, map(to_function, allterms))) # to_function() and expand_multilinear() are defined below
end

# For example, summands(x+2y) == [x, 2y]
function summands(x::Add)
    d = x.dict
    [term[1]*term[2] for term in d]
end
# Any other type
summands(x) = [x]

# For example, to_function(argument_position(1, x) * argument_position(2, y)) == f(x,y)
function to_function(x::Mul)
    d = x.dict
    args = Vector{Any}(undef, length(d))
    for term in d
        @assert term[2] == 1
        @assert term[1].f == argument_position
        args[term[1].arguments[1]] = term[1].arguments[2]
    end
    f(args...)
end

# For example, expand_multilinear(f(x,2y)) == 2f(x,y)
function expand_multilinear(expr)
    function_arguments = Vector{Any}(deepcopy(expr.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], 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 * f(function_arguments...)
end

@time expand1(f(u+2v+3w+4x+5y+6z, u+2v+3w+4x+5y+6z, u+2v+3w+4x+5y+6z, u+2v+3w+4x+5y+6z, u+2v+3w+4x+5y+6z));
# It takes about 0.64 seconds on my laptop

```

---

<div class="post-metadata">

**Author:** ![matthias314](https://avatars.discourse-cdn.com/v4/letter/m/a88e4f/32.png) [@matthias314](https://discourse.julialang.org/u/matthias314)\
**Post date:** [July 29, 2021, 5:58pm UTC](https://discourse.julialang.org/t/symbolic-computations-with-functions-having-a-variable-number-of-arguments/65140/14 "2021-07-29T17:58:16Z")

</div>

Here is a version that only needs 115ms for your example, just 20% slower than Maple. (My laptop seems to be about as fast as yours.) So one can get a performance similar to Maple and Mathematica, and maybe even better with additional tweaking.  
So far, the multilinear function `f` is hard-coded, and there may be other things one could improve. Note that all occurrences of `f` in an expression are expanded:

```julia
julia> expand_multilinear( f(f(x+2*y), f(3*z-w)) )
3f(f(x), f(z)) + 6f(f(y), f(z)) - f(f(x), f(w)) - (2f(f(y), f(w)))

```

> **Code (updated)**
>
> ```julia
> using SymbolicUtils
> using SymbolicUtils: Add, Mul, Term, Symbolic
> 
> const f = SymbolicUtils.Sym{(SymbolicUtils.FnType){Tuple, Number}}(:f)
> 
> function expand_multilinear_arg(t::Add, i::Int)
> sum(c * expand_multilinear_arg(x, i) for (x, c) in t.dict)
> end
> 
> function expand_multilinear_arg(t::Mul, i::Int)
> a, _ = first(t.dict)
> # by construction, t has only one factor, and that has exponent 1
> t.coeff * expand_multilinear_arg(a, i)
> end
> 
> function expand_multilinear_arg(t::Term{T}, i::Int) where T
> f = t.f
> b = convert(Vector{Any}, t.arguments)
> # this is needed for the assignment of one(T) to b[i] later one
> # note that the type of FnType.arguments is Any anyway
> x = b[i]
> if x isa Add
> d = Dict((b[i] = y; f(b...)) => c for (y, c) in x.dict)   
> c = x.coeff
> if !iszero(c)
> b[i] = one(T)
> d[f(b...)] = c
> end
> Add(T, zero(T), d)
> elseif x isa Mul
> p = x.dict
> b[i] = length(p) == 1 ? first(p).first : Mul(T, one(T), p)
> Mul(T, x.coeff, Dict(f(b...) => one(T)))
> else
> t
> end
> end
> 
> function expand_multilinear_allargs(t)
> # the function f is hard-coded here
> if t isa Term && t.f == f
> foldl(expand_multilinear_arg, 1:length(t.arguments); init = t)
> else
> t
> end
> end
> 
> expand_multilinear = Rewriters.Postwalk(expand_multilinear_allargs)
> 
> ```

---

<div class="post-metadata">

**Author:** ![greatpet](https://avatars.discourse-cdn.com/v4/letter/g/e495f1/32.png) [@greatpet](https://discourse.julialang.org/u/greatpet)\
**Post date:** [July 29, 2021, 8:49pm UTC](https://discourse.julialang.org/t/symbolic-computations-with-functions-having-a-variable-number-of-arguments/65140/15 "2021-07-29T20:49:19Z")

</div>

A minor bug fix for your code: the line

```julia
y = length(p) == 1 ? first(p) : Mul(Number, 1, p)

```

should probably be

```julia
y = length(p) == 1 ? first(p)[1] : Mul(Number, 1, p)

```

That’s needed for getting correct results for `expand_multilinear(f(2x, y))`, at least on my machine. Anyway, I’m really excited about doing symbolic calculations in Julia. No Python package would offer competitive performance for this kind of user-programmed symbolic manipulations, unless someone implements an underlying pattern matching / term rewriting engine in C++, or Julia 😀

---

<div class="post-metadata">

**Author:** ![matthias314](https://avatars.discourse-cdn.com/v4/letter/m/a88e4f/32.png) [@matthias314](https://discourse.julialang.org/u/matthias314)\
**Post date:** [July 29, 2021, 9:02pm UTC](https://discourse.julialang.org/t/symbolic-computations-with-functions-having-a-variable-number-of-arguments/65140/16 "2021-07-29T21:02:31Z")

</div>

Thanks! I’ve updated the code. I’ve thought a bit about making it even faster, but I didn’t see anything.

EDIT: On larger examples the Julia/SymbolicUtils code above is again much slower than Maple. So there is still a long way to go.

---

<div class="post-metadata">

**Author:** ![ChrisRackauckas](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/chrisrackauckas/32/77_2.png) [@ChrisRackauckas](https://discourse.julialang.org/u/ChrisRackauckas)\
**Post date:** [July 30, 2021, 9:38pm UTC](https://discourse.julialang.org/t/symbolic-computations-with-functions-having-a-variable-number-of-arguments/65140/17 "2021-07-30T21:38:44Z")

</div>

Yeah, we’ll follow up in the performance in an issue. Can you share the benchmarks to Maple and Mathematica in the issue as well?

---

<div class="post-metadata">

**Author:** ![matthias314](https://avatars.discourse-cdn.com/v4/letter/m/a88e4f/32.png) [@matthias314](https://discourse.julialang.org/u/matthias314)\
**Post date:** [July 30, 2021, 10:17pm UTC](https://discourse.julialang.org/t/symbolic-computations-with-functions-having-a-variable-number-of-arguments/65140/18 "2021-07-30T22:17:42Z")

</div>

I’ve added Julia/Maple benchmarks to the [GitHub issue](https://github.com/JuliaSymbolics/SymbolicUtils.jl/issues/314).  
@greatpet: Could you add Mathematica timings?

---

<div class="post-metadata">

**Author:** ![greatpet](https://avatars.discourse-cdn.com/v4/letter/g/e495f1/32.png) [@greatpet](https://discourse.julialang.org/u/greatpet)\
**Post date:** [July 30, 2021, 11:27pm UTC](https://discourse.julialang.org/t/symbolic-computations-with-functions-having-a-variable-number-of-arguments/65140/19 "2021-07-30T23:27:39Z")

</div>

I just added the Mathematica timings to your Github issue (my Github name is zengmao).

---

<div class="post-metadata">

**Author:** ![ChrisRackauckas](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/chrisrackauckas/32/77_2.png) [@ChrisRackauckas](https://discourse.julialang.org/u/ChrisRackauckas)\
**Post date:** [July 31, 2021, 12:23am UTC](https://discourse.julialang.org/t/symbolic-computations-with-functions-having-a-variable-number-of-arguments/65140/20 "2021-07-31T00:23:46Z")

</div>

That’s all super helpful, thanks!
