Does Julia support symbolic computation?

Does Julia support symbolic computation? For example, I would like to perform symbolic matrix operations or simplify algebraic expressions. What is the best way to achieve this?



Regarding Symbolics.jl, I would caution that — while it is quite capable for certain tasks — its expression-manipulation (e.g., simplification) is not yet anywhere close to other CASs like sympy or mathematica. So it might also be worth noting that SymPy.jl and SymPyPythonCall.jl wrap sympy.


Thank you for your kind input! Could you please provide a further overview of their respective advantages? Alternatively, in your opinion, which option would be the most orthodox for a loyal and naive Julia user? Thanks! :smiley:

Thanks! May I ask, what is the correct way to implement the following code using Symbolics.jl?

using Symbolics
@variables p_11 p_12 p_13 p_21 p_22 p_23 p_31 p_32 p_33
P=[p_11 p_12 p_13; p_21 p_22 p_23; p_31 p_32 p_33]

Sure. Also see here and here.

Obviously, Symbolics.jl is nice because it’s pure Julia, and has lots of nice features for being able to turn Julia code into symbolic expressions, and symbolic results back into efficient Julia code. On the other hand, rewriting expressions is quite difficult. In my experience, “simplification” only does anything with the very simplest expressions. It has taken Mathematica and SymPy a long time to get as good at simplifications as they are, and Symbolics.jl is just not there yet. (Also, one of my pet peeves is that Symbolics.jl is constantly introducing Float64s where I want numbers compatible with other precisions.)

So if you want to be able to really manipulate your expressions, I think SymPy is the obvious way to go. It has lots of different functions for manipulating expressions, its basic simplify function works very nicely, and it has a pretty complete “assumptions” model that helps the simplification process do what you want it to. In my experience, at least, the Julia interfaces I mentioned above both work pretty well with Julia code. Basically, the SymPy variables or expressions can be passed around (and into functions) just like other Julia types, which works about as well as similar operations with Symbolics.jl variables. So you can use it almost like it’s native Julia.

These are both pretty orthodox. Symbolics.jl is much newer than SymPy.jl, so even though the latter is a wrapper around some python code, it was quite well developed early on, and gained a lot of Julia users. I think it really comes down to whether you want anything more than basic simplification/manipulation of expressions; if so, use SymPy.

As for the difference between SymPy.jl and SymPyPythonCall.jl, the former uses PyCall while the latter uses PythonCall to handle the Python dependencies. IMHO, PyCall causes more headaches than anything; its design just doesn’t work with how Python is meant to be used. PythonCall is the modern and much better way to use Python from Julia. (It looks like there’s some discussion that SymPy.jl may eventually just become SymPyPythonCall.jl anyway.)


@variables P[1:3, 1:3]

1 Like

Thank you for your patiently answering!


But I discovered that I can’t change any element of P if I declare a matrix like this, for example,

@variables P[1:3, 1:3]
P[1,1] = 1

and it can’t display the detailed expression of every element of the matrix when I need to see them, for example,

@variables P[1:3, 1:3] Q[1:3, 1:3]
P * Q

displays only PQ without detailed content of the product matrix.

julia> using Symbolics

julia> @variables P[1:3, 1:3] Q[1:3, 1:3]
2-element Vector{Symbolics.Arr{Num, 2}}:

julia> P * Q

julia> collect(P * Q)
3Ă—3 Matrix{Num}:
 P[1, 1]*Q[1, 1] + P[1, 2]*Q[2, 1] + P[1, 3]*Q[3, 1]  …  P[1, 1]*Q[1, 3] + P[1, 2]*Q[2, 3] + P[1, 3]*Q[3, 3]
 P[2, 1]*Q[1, 1] + P[2, 2]*Q[2, 1] + P[2, 3]*Q[3, 1]     P[2, 1]*Q[1, 3] + P[2, 2]*Q[2, 3] + P[2, 3]*Q[3, 3]
 P[3, 1]*Q[1, 1] + P[3, 2]*Q[2, 1] + P[3, 3]*Q[3, 1]     P[3, 1]*Q[1, 3] + P[3, 2]*Q[2, 3] + P[3, 3]*Q[3, 3]
1 Like

By default Symbolics uses a representation of arrays as symbolics, rather than simply putting the symbolic scalars into arrays. The reason for this is to be able to expand beyond what most symbolic languages can do and support things like high-level rules for matrix calculus, better code generation for large structured systems, and allow for algebraic manipulations of common non-commutative forms. This is something that you don’t really see in other symbolic languages and it’s not easy so its taking a bit of time to really get right, but it’s progressing quite well over time. It’s a target we want to keep on.

You can always use Julia tools like collect to transform it into the scalar expressions and then perform the operations on scalars, but over time we hope that the array form becomes more and more feature complete and thus simplifies a lot of operations.