[ANN] SymbolicUtils.jl -- Groundwork for a symbolic ecosystem in Julia

Hello everyone, we (Mason Protter, Yingbo Ma, and I) are pleased to present to you SymbolicUtils.jl. This is the first of hopefully many packages to live in the JuliaSymbolics Github organization, and we hope that SymbolicUtils.jl can lay the groundwork for a vibrant, interacting symbolic programming ecosystem in julia.

Building symbolic programming systems is hard and there are various reasons to think it is unlikely that one person, or even a small, coordinated group of people, will be able to to build a monolithic symbolic programming system in Julia that can compete with something like Mathematica or SymPy. Hence, we think it’s important to build flexible infrastructure that others can build on, and interoperate with. Other packages are welcome to build on top of our symbolic types but do not have to. We provide infrastructure for interfacting with SymbolicUtils so that packages can use their own types if they wish, but plug into our rule rewriting infrastructure.

We’re happy to say that ModelingToolkit.jl from the SciML organization is the first package to depend on and interoperate with SymbolicUtils.jl.

SymbolicUtils.jl is on the general registry and can be added the usual way:

pkg> add SymbolicUtils


julia> using Pkg; pkg"add SymbolicUtils"

Here’s a quick and unexplained code sample:

julia> using SymbolicUtils

julia> @syms x::Real y::Real z::Complex f(::Number)::Real
(x, y, z)

julia> 2x^2 - y + x^2
(3 * (x ^ 2)) + (-1 * y)

julia> f(sin(x)^2 + cos(x)^2) + z
f(1) + z

julia> r = @rule sinh(im * ~x) => sin(~x)
sinh(im * ~x) => sin(~x)

julia> r(sinh(im * y))

julia> simplify(cos(y)^2 + sinh(im*y)^2, [r])

We have a manual with more info.

Collaboration and documentation improvements / suggestions are quite welcome! If you want to chat about SymbolicUtils.jl, you can find us here, on GitHub, or in the symbolic programming Zulip stream.

We want to give a big thanks to Harrison Grodin for doing a lot of pioneering work in this approach with Rewrite.jl and Simplify.jl which we hybridized with a more scmutils like approach and added some flavour of our own.

~ @Mason, @YingboMa and @shashi


This looks very cool!

I noticed there are errors in the manual that probably weren’t intended:


This might be controversial and offtopic (feel free to make this a separate thread), but the first thing that most people will say when they see a Symbolic package is that:
Well, yet another symbolic package.

No one likes redoing the stuff, and code reusability is the king when it comes to sustainable software libraries.

How do you justify the existence of another symbolic package, instead of developing and growing all the previous ones that exist?


uh oh, looks like I broke something earlier when I tried to publish some documentation fixes. I’m a total Franklin.jl noob. :frowning: hoepfully you can follow along by running the code and we can get this fixed asap.

Edit: Looks like it’s now fixed. If you find any other problems, opening an issue on our github would be much appreciated!


I think exactly that was quite well explained in the OP?


The reason basically boils down to the fact that revamping an existing package to meet the needs we saw appeared to be a greater investment of effort than just writing this from scratch.

We hope that the design decisions we made will make future people less likely to do that and more interested in building on top of what we have. I think the fact that ModelingToolKit.jl now uses SymbolicUtils.jl for it’s simplification pipeline is an encouraging sign that we might be on the right track.

I should also add that we would likely not have been able to write this package if we didn’t learn from existing previous attempts at similar things. This is a hard problem and we shouldn’t expect anyone to ‘get it right’ on the first try. We’re optimistic about this attempt, but only time will tell how it turns out.


That’s why I am afraid that the efforts put in the previous packages are ignored.

I hope the interfaces of this new package make it the basis for the future packages, and somehow the features of the previous packages move to this new ecosystem.

If you could provide a table with the available and missing features, people can make extensions easier. For instance, the example in the interface section seems like a feature that I expect to see from SymbolicUtils by default. The interface example is better to be a real-world problem.

1 Like

Thanks Yuri! I fixed this now. I’m still getting to know Franklin :slight_smile:


I assure you that we did not ignore them. Shashi and I both put serious effort towards trying to make Rewrite.jl work for this before Shashi started SymbolicUtils.jl. ModelingToolkit.jl was revamped to rely on SymbolicUtils and I also plan to revive my old package, Symbolics.jl but ripping out most of its guts and replacing them with the infrastructure provided by SymbolicUtils.jl.

I think it could be interesting to eventually try and take over the role SymPy takes in providing simplification for other packages, i.e. things like Symata.jl and if @jlapeyre wants to collaborate on such a project I’d certainly be interested.

The whole idea here is to bring all these packages closer together, not push them away!


This is a off-topic question. Is there any Julia package that offers full functionality of Matlab Symbolic Math Toolbox?

1 Like

SymPy.jl is probably the closest


I find it strange that the symbol is not a subtype of Number.
Is the Idea for another package, using SymbolicUtils, to provide that interface?

julia> using SymbolicUtils

julia> @syms x::Real

julia> f(x::Number) = 2*x
f (generic function with 1 method)

julia> f(x)
ERROR: MethodError: no method matching f(::SymbolicUtils.Sym{Real})
Closest candidates are:
  f(::Number) at REPL[7]:1
 [1] top-level scope at REPL[8]:1
1 Like

This is something we agonized over quite a bit. I think Sym <: Number is just plain not good enough. We want to be able to support symbolic Float64s, Matrixs, Strings or really any datatype. One approach would be to just create a billion SymbolicX types which subtype X, but that has many downsides.

What we really want is Sym{T} <: T, but that’s not supported by Julia’s semantics, however it can actually be done inside an IRTools.jl pass like I show in this proof of concept. I plan to investigate this approach further.

One thing I feel somewhat strongly about is that this package can’t just be for math on numbers. It should have wider applicability than that. Mathematica has many flaws, but I think it demonstrates the value of symbolic programming in all sorts of domains outside simple math.


Yes it is! I think we should mention this in the docs, thank you!

We are looking into tracing tools like Cassette or IRTools to make this kinda thing work for all types.

The basic problem is we need something that behaves like Sym{T} <: T for Sym to take on the role of any type but be symbolic.

Edit: I just added it to the docs!


Is this similar to the wrapper array problem so it would be helpful if T were a trait ?

It’s similar, but it wouldn’t help much if T is a trait since existing code does not dispatch on the trait.

1 Like

Right. Hopefully at some point traits will get first class language support and become more pervasive :slight_smile:

I think this is a type system limitation and it would be great if we didn’t have to rely on compiler transforms to work around it.

However, there are some packages that use traits for dispatch/abstract type. Can this interop with those as well?

This looks great!

One glaring omission from most symbolic systems is a nice way to reason about array manipulations. Any chance there’s anything in the works specific to this? An interface to one of the tensor manipulation libraries would be especially exciting.


Yes, this is indeed one major reason I got involved with this whole project, and why we are pushing so hard for parameterized types, despite the headaches they induce.

Symbolically manipulating tensor expressions instead of arrays of symbolic numbers is an important distinction and something I’d really like to explore.

I think there’s still a bit more ‘plumbing’ that I’d like to work on before getting into a high level application like this, but it’s certainly on my radar and if anyone would be interested in collaborating on it, I’m all ears!


I just started using your package, pretty nice. Thanks!

In version v0.4.3, @syms a; 0* a gives zero, while in the new version v0.5.0, it gives 0 * a. How can I force it to be zero?

Also I observed the following behavior:

@syms a
@syms b::Real

then 0a gives 0 * a, but 0b gives the following error:

ERROR: syntax: invalid numeric constant "0b"
 [1] top-level scope at none:1
1 Like