SymPy.jl vs SymEngine.jl vs Reduce.jl vs

Has anyone made a serious comparison of the Symbolic Math packages we a currently have?

(others?)

There is a little (off topic) discussion here

12 Likes

SymPy is very feature-filled but slow. Same reason why I stopped using Python: my problems are growing in size. Symbolic equations get big and slow really quickly, so this never worked out well for me.

SymEngine is quite speedly. I love this library and use it a lot. It doesn’t have all that much to it, but its basic symbolic variable type has a lot of differentiation tooling and works with Julia’s generic linear algebra. SymEngine.Basic cannot be precompiled though. I’ve been moving away from it though for the pure-Julia ModelingToolkit.jl to cover these basic needs (and be built more towards a purpose of model encoding and compilation).

Never tried Reduce.jl. The docs scare me though.

14 Likes

Can’t agree with this enough.

I’ve had to apologize for my code being slow for a while now. Little did I know, that swapping out to SymEngine would reduce boot time by like an order of magnitude.


edit: Also, I wish Sylvia.jl would take off. I know everyone’s time is precious, but that would be pretty badass

5 Likes

I have not seen any comparison as thorough as, say, a Julep. I plan to study this a bit and write something up in the not-too distant future. An important point to realize at the outset is that the name “symbolic math” applies to a wide range of programs. The purpose and functionality of two programs or packages is often (roughly speaking) orthogonal. A large amount of opinion that you find in forums is muddled by not taking this into account.

There is also my package Symata.jl, which is mathematica-like. In particular, it is a DSL written in Julia. It has a REPL that parses expressions following legal Julia syntax and then completely reinterprets the AST. It follows closely the Mathematica (Wolfram) evaluation sequence. Because its written in Julia and is open, it is far more flexible. For example, you can use it as a library to manipulate symbolic expressions in Julia code, outside of the evaluation sequence.

5 Likes

Eventually we will have a full-blown symbolic math package on par with mathematica written from the ground up in Julia, it’s just going to take a really long time. It’s an intimidating problem. Maybe Sylvia would be a good base for that, it looks like a good start at least.

Symata.jl is far closer to Mathematica than anything else. For one person (me) with a more-than-full-time job, it is difficult to develop and maintain. And to keep up with changes in Julia is for practical purposes impossible. Using wc, I count 23,000 lines of Julia code. Then there 4,300 lines of test code, written in Symata. It is fairly complicated, uses a lot of features of Julia, is very sensitive to the AST (which has been tweaked and changed quite a bit on the path to 1.0), etc.

The core is written in pure Julia. I finally took advice to call SymPy for much of the functionality. This was the only way to get something more-or-less complete on the time-scale of a lifetime. Replacing SymPy would be great, I’d like to see it in the long run. But, it would be a huge project. There are more important things in the short run, so replacing SymPy now makes no sense. If other developers were to pick up Symata, then internal documentation and organization would be a priority.

Mathematica-like lanaguages are inherently slow. They have very little type-structure at the user level. It has taken a huge amount of money and engineering over the years to improve the efficiency of Mathematica. In the beginning, I compared the speed of Symata with Mathematica, and the results were not bad; good in some cases. But, I soon found that premature optimization was the wrong path, again because Julia has been changing.

5 Likes

I wonder if you can abstract those parts away, or use a package that does. A brief look at your AST conversion code suggests quite a bit of overlap with tools like

3 Likes

I think you may be referring to the underscores and comments about patterns in the AST translation code. Like this:

# f:(_^_), or f:_^_  --> Pattern[x, Power[Blank[], Blank[]]]
...
function parseblank(s::AbstractString)
a = split(s,['_'], keep=true)
...

This code is not doing pattern matching itself. Mathematica (and Symata) do pattern matching. This part of the code is for translating the Julia AST to Symata AST for Symata pattern-matching synax.

The Symata-level pattern matching is done elsewhere. I considered trying to use something like MacroTools to help with this, but it did not look helpful at the time. It is certainly worth reconsidering. On the other hand, complicated (too complicated) macros are used in other places in the Symata code. Of course, this code also breaks when the Julia AST changes. I think there is a good chance that this code could be simplified significantly using MacroTools.

Note that the upstream documentation is also available REDUCE Documentation

Over the past year, my goal has been to only focus on making the basic interface between Reduce and Julia robust, so that it can handle many different scenarios. In the last few weeks, the pace of improvements have picked up in speed, since the generalized functionality that is now in place easily allows lots of new extensions to standard Julia methods with symbolic and matrix computations.

It is still constantly changing and in flux at the moment, so I have not settled on writing a friendly documentation for it yet. Most of the core features have already been implemented, but are not documented yet. However, the intention is to provide an explanation to help other developers extend the packages.

The premise behind Reduce.jl is based on the idea that Symbol and Expr types can be translated into computer algebra rewrite commands and then automatically parsed back into Julia ASTs, essentially extending the Julia language into a fully programable symbolic AST rewrite environment.

When I am confident that the core package is fairly complete in the upcoming weeks, an official announcement will be made.

A new organization called JuliaReducePkg has just been created to handle the additional extensions of Reduce packages that go beyond the core basic features Reduce.jl External Packages · GitHub

5 Likes

The Rewrite.jl package should probably also be under discussion.

4 Likes

Any updates? I am still not sure which one to use…

1 Like

My recommendation is to choose between SymEngine and Reduce. Personally, I use Reduce (i am biased since I worked on it), but SymEngine has better performance. I don’t do any massively complicated symbolic calculation and prefer work with Julia expressions, so Reduce is good enough for me. If Reduce isn’t good enough, try SymEngine. Also, it seems that Reduce is more portable than SymEngine. Each has its tradeoffs. For portability and convenience of casual use with Julia expressions, I recommend Reduce… while for maximum performance: SymEngine might better. Depends on what your needs are.

5 Likes

I am also interested in this. Any updates on recommended packages?
I also found https://github.com/MasonProtter/Symbolics.jl, which wasn’t mentioned before in this thread.

1 Like

Honestly, it may be slow, but there is still an abiss in terms of documentation and features between SymPy(.jl) and anything else available…

3 Likes

Could you elaborate on how you replace SymEngine.Basic with ModelingToolkit.jl?

You can just trace the same way.

Thanks.

I’m new to symbolic math manipulation on Julia. Could you provide a small example where you replace SymEngine.Basic with ModelingToolkit.jl?

The way I tend to use ModelingToolkit is something like

julia> using ModelingToolkit

julia> f( (x, y) ) = x^2 + y
f (generic function with 2 methods)

julia> vars = @variables x, y
(x, y)

julia> ex = f(vars)
x ^ 2 + y

julia> ex
x ^ 2 + y

Here we defined a standard Julia function f using a mathematical expression. But it’s difficult to recover that mathematical expression from inside the function. ModelingToolkit provides symbolic variables; when I evaluate the function using those variables, I extract that mathematical expression into a form that’s now usable. I believe that’s what Chris means by “tracing”.

4 Likes

Thank you!

I tried to play around with it. However, I do not seem to understand how to get it to work. For example, I try to expand a function and to differentiate a function, by slightly modifying your codes:

using ModelingTools
using SymEngine

julia> f( (x,f) ) = x^2 + y
f (generic function with 1 method)

julia> vars = @variables x, y
(x, y)

julia> ex = f(vars)
x ^ 2 + y

julia> diff(ex, x)
ERROR: MethodError: no method matching diff(::Operation, ::Operation)
Stacktrace:
 [1] top-level scope at REPL[48]:1

julia> f( (x,f) ) = x^2 + y*x + (x+1)*(x+2)
f (generic function with 1 method)

julia> vars = @variables x, y
(x, y)

julia> ex = f(vars)
(x ^ 2 + y * x) + (x + 1) * (x + 2)

julia> diff(ex, x)
ERROR: MethodError: no method matching diff(::Operation, ::Operation)
Stacktrace:
 [1] top-level scope at REPL[52]:1

julia> expand(ex, x)
ERROR: MethodError: no method matching expand(::Operation, ::Operation)
Stacktrace:
 [1] top-level scope at REPL[53]:1

I think you could use the D() syntax here.

using ModelingTools

julia> f( (x,f) ) = x^2 + y
f (generic function with 1 method)

julia> vars = @variables x, y
(x, y)

julia> ex = f(vars)
x ^ 2 + y

julia> @derivatives D'~x
((D'~x),)

julia> d1 = D(ex)
derivative(x ^ 2 + y, x)

julia> expand_derivatives(d1)
2x

julia> f( (x,f) ) = x^2 + y*x + (x+1)*(x+2)
f (generic function with 1 method)

julia> vars = @variables x, y
(x, y)

julia> ex = f(vars)
(x ^ 2 + y * x) + (x + 1) * (x + 2)

julia> d2 = D(ex)
derivative((x ^ 2 + y * x) + (x + 1) * (x + 2), x)

julia> expand_derivatives(d2)
3 + 4x + y
5 Likes