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

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.

13 Likes

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.

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

3 Likes

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!

11 Likes

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

SymPy.jl is probably the closest

3 Likes

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
(x,)

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
Stacktrace:
 [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.

18 Likes

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!

8 Likes

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.

5 Likes

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!

12 Likes

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"
Stacktrace:
 [1] top-level scope at none:1

This is because 0b1011 is syntax for creating an unsigned integer from binary. You need to explicitely write 0*b in this case.

2 Likes

Thanks.

figured out this can be done using r = @rule (0 * ~a) => 0; r(0*a) gives zero. But would it be better to give zero automatically in this case?

On v0.5.0 we stopped simplifying expressions to print them.

@syms a; 0* a gives zero

It wasn’t actually returning zero but printing zero rather confusingly.

You can call simplify(0*a) to get 0.

1 Like

got it. many thanks. but would it be better to automatically simplify such a case?

1 Like

You could define that yes, but it would be a kludge. We would have to define many methods and check that one of the inputs is a number and is zero.