logo

Introduction to Julia's Symbolic Computing Package Symbolics.jl 📂Julia

Introduction to Julia's Symbolic Computing Package Symbolics.jl

Overview

This introduces Symbolics.jl, a package that supports symbolic algebra systems in Julia1. This package provides an extremely intuitive and powerful interface along with Julia’s basic syntax.

Difference from SymEngine.jl

Symbolics.jl is implemented natively in Julia and has many better aspects in terms of performance and interface. SymEngine.jl, introduced in the post on how to do symbolic operations in Julia, is originally a library written in C++ and wrapped in Julia. As a wrapper, it was useful at the time without any separate feature development due to its immediacy, but now, unless one is already familiar with the original SymEngine, there is no reason to use SymEngine.jl.

Code

Variable Declaration @variables

julia> using Symbolics

julia> @variables t x y z
4-element Vector{Num}:
 t
 x
 y
 z

julia> w = log(y) + cos(x) + 2t^3
log(y) + cos(x) + 2(t^3)

Variables to be used symbolically can be declared by listing them after @variables. These variables can naturally be used as arguments to transcendental functions, etc.

Expansion expand

julia> (x + y)^3
(x + y)^3

julia> expand((x + y)^3)
x^3 + 3(x^2)*y + 3x*(y^2) + y^3

Expansion can be done through the expand function. If no function is taken, it does not perform expansion unnecessarily for performance reasons, among others.

Simplification simplify

julia> simplify(2x + (cos(x))^2 + 3x + (sin(x))^2)
1 + 5x

The simplify function can be used to eliminate terms that become 0 or to make things simpler by adding coefficients together. As seen in the example, it automatically calculates things like the sum of the squares of trigonometric functions becoming 1.

Substitution and Assignment substitute

The substitute function can be used for substituting or assigning variables. How the substitution is done can be indicated by creating and inserting a dictionary. $$ w = \log y + \cos x + 2t^3 $$ Let’s try substitution and assignment on this $w$.

julia> substitute(w, Dict(t => x))
log(y) + cos(x) + 2(x^3)

Substitution between symbolic variables is of course possible.

julia> substitute(w, Dict(t => 2, x => π, y => exp(1)))
16.0

The results of assignments with $t = 2$, $x = \pi$, $y = e$ are returned after numerical calculation.

Differentiation Differential

julia> Dt = Differential(t)
(::Differential) (generic function with 3 methods)

julia> Dt(w)
Differential(t)(log(y) + cos(x) + 2(t^3))

julia> expand_derivatives(Dt(w))
6(t^2)

Interestingly, in Symbolics, differentiation is done by defining a differential operator for a specific variable to differentiate with respect to, instead of passing the variable to integrate over directly to the function that performs the differentiation. At first glance, the code may seem messy and complicated, but from a mathematician’s perspective, it is very intuitive and a valid interface. The result of the differentiation, like expansion, is not calculated immediately but waits lazily and is returned through the expand_derivatives function.

julia> Dx = Differential(x)
(::Differential) (generic function with 3 methods)

julia> Dy = Differential(y)
(::Differential) (generic function with 3 methods)

julia> Dx(w) |> expand_derivatives
-sin(x)

julia> Dy(w) |> expand_derivatives
1 / y

From the perspective of defining an operator, partial differentiation is also very naturally implemented.

Vectors and Matrices

julia> @variables σ ρ β
3-element Vector{Num}:
 σ
 ρ
 β

julia> Lorenz = [-σ*x + σ*y
                 -x*y + ρ*x - y
                  x*y - β*z]
3-element Vector{Num}:
     -x*σ + y*σ
 -y - x*y + x*ρ
      x*y - z*β

Vectors are created just like in Julia syntax. For example, the right side of the Lorenz attractor can also be simply expressed symbolically as above.

julia> M = [x*y y^2
             2x   9]
2×2 Matrix{Num}:
 x*y  y^2
  2x    9

Matrices are also very intuitively defined.

Jacobian

julia> Symbolics.jacobian(Lorenz, [x, y, z])
3×3 Matrix{Num}:
     -σ       σ   0
 -y + ρ  -1 - x   0
      y       x  -β

The Jacobian matrix can be easily obtained through Symbolics.jacobian.

Complete Code

using Symbolics

@variables t x y z
w = log(y) + cos(x) + 2t^3

(x + y)^3
expand((x + y)^3)

simplify(2x + (cos(x))^2 + 3x + (sin(x))^2)

substitute(w, Dict(t => x))
substitute(w, Dict(t => 2, x => π, y => exp(1)))

Dt = Differential(t)
Dt(w)
expand_derivatives(Dt(w))

Dx = Differential(x)
Dy = Differential(y)
Dx(w) |> expand_derivatives
Dy(w) |> expand_derivatives

@variables σ ρ β
Lorenz = [-σ*x + σ*y
          -x*y + ρ*x - y
           x*y - β*z]

M = [x*y y^2
      2x   9]

Symbolics.jacobian(Lorenz, [x, y, z])

Environment

  • OS: Windows
  • julia: v1.10.0
  • Symbolics v5.16.1