New symbolic solver for Symbolics.jl!

New symbolic solver in Symbolics.jl

Hi, I am Yassin. A new symbolic solver authored by me has just been merged to Symbolics.jl, and is available in the latest release (v6.2.0). I want to share some of its capabilities here!

Link to PR

The solver features:

  • Multivariate polynomial solving
  • Exact (symbolic) solutions
  • Solving using isolation and attraction (inspired by a paper from R. W. Hamming)

Examples and usage!

Solving with parameters and transcendental functions:

julia> @variables a b c d e x;
 
julia> symbolic_solve(a*log(x)^b + c ~ 0, x)
1-element Vector{SymbolicUtils.BasicSymbolic{Real}}:
 ℯ^(((-c) / a)^(1 / b))

Solving by detecting polynomialization opportunities (9^x + 3^x = 0 and the like also works :smile: )

julia> symbolic_solve(sin(x^2 +1)^2 + sin(x^2 + 1) + 3)
[ Info: var"##426" ϵ Ζ
[ Info: var"##429" ϵ Ζ
4-element Vector{SymbolicUtils.BasicSymbolic{Complex{Real}}}:
 (1//2)*√(-4(1 - asin((1//2)*(-1 + (0 + 1im)*√(11))) - (π*2var"##426")))
 (-1//2)*√(-4(1 - asin((1//2)*(-1 + (0 + 1im)*√(11))) - (π*2var"##426")))
 (1//2)*√(-4(1 - asin((1//2)*(-1 - ((0 + 1im)*√(11)))) - (π*2var"##429")))
 (-1//2)*√(-4(1 - asin((1//2)*(-1 - ((0 + 1im)*√(11)))) - (π*2var"##429")))

Solving high degree polynomials exactly:

julia> expr = expand((x + 2)*(x^2 + 2x + 1)*(x^4 - 3x^3 + x + 5)*(x^4 - 1))
-10 - 27x - 25(x^2) - 3(x^3) + 22(x^4) + 34(x^5) + 24(x^6) + 2(x^7) - 12(x^8) - 7(x^9) + x^10 + x^11

# output is truncated
julia> symbolic_solve(expr, x, dropmultiplicity=false)
9-element Vector{Any}:
 -2
 -1
 ...
 (-1//2)*(0 + 2im)

Also works with parameters:

julia> expr = expand((x + b)*(x^2 + 2x + 1)*(x^2 - a))
-a*b - a*x - 2a*b*x - 2a*(x^2) + b*(x^2) + x^3 - a*b*(x^2) - a*(x^3) + 2b*(x^3) + 2(x^4) + b*(x^4) + x^5

julia> symbolic_solve(expr, x, dropmultiplicity=false)
5-element Vector{Any}:
 -1
 -1
   -b
   (1//2)*√(4a)
   (-1//2)*√(4a)

Exact intersection of a sphere and a line in \mathbb{C}^3:

julia> symbolic_solve(
   [x^2 + y^2 + z^2 - 9, 
   x - 2y + 3,
   y - z]
   ,[x,y,z])
2-element Vector{Any}:
 Dict{Num, Any}(z => 2, y => 2, x => 1)
 Dict{Num, Any}(z => 0, y => 0, x => -3)

We can check our answers by plotting everything, and as expected there are only 2 intersections with the same coordinates our solver found:

System of polynomials with infinite solutions are solved in terms of one of the variables:

julia> symbolic_solve([x*z - y - 1, x + z], [x, y, z])
┌ Warning: Infinite number of solutions
└ @ SymbolicsGroebnerExt ~/code/julia/Symbolics.jl/ext/SymbolicsGroebnerExt.jl:216
1-element Vector{Dict{Num, Any}}:
 Dict(z => z, y => -1 - (z^2), x => -z)

What do i do?

The solver is available in the latest Symbolics.jl release (v6.2.0). Try solving your favorite equations and let us know about any bugs you encounter. You also need Groebner and Nemo depending on which solver symbolic_solve will use internally to solve your input.

You can find more examples in the documentation.

Note and credits

Although the PR is merged, the release is actually still under preparation, as this is a work in progress. We plan to extend the solver to support more types of equations and integrate it with numerical solving.

Many thanks to @sumiya11 and @shashi for being great mentors throughout the project’s length!

77 Likes

Looks like a really great addition to Symbolics!

4 Likes

Is there a webpage called “Julia symbolics for newbies” so that new programmers can get up to speed with symbolics.jl?

With examples on how to use Symbolics to solve common problems.

3 Likes

Awesome! This has been sorely missing, and the API and functionality both look spot on. Great job :grin:

2 Likes

Great news!!! I tried Symbolics a few weeks ago while its solver only supports linear equations.

BTW, Is there a trick to importing the symbolic_solve function?

I encountered the following error in the latest version, V6.2.0

> symbolic_solve(a*log(x)^b + c ~ 0, x)
ERROR: UndefVarError: `symbolic_solve` not defined
Stacktrace:
 [1] top-level scope
   @ REPL[11]:1

pkg> st
Status `~/.julia/environments/symbolic/Project.toml`
  [0c5d862f] Symbolics v6.2.0

Hello @tiZ. Thanks for the kind words! For your problem, please try removing your local repo completely and cloning symbolics once again. I also had this problem once we merged.

1 Like

I had the same problem, and I believe it is just because this functionality was not released yet. It works if you do ]add Symbolics#master.

1 Like

TTFX is not ideal for linear equations. Is it possible to automatically detect linear cases and forward to symbolic_linear_solve instead? See timing below.

julia> using Symbolics, Groebner

julia> @variables x y z;

julia> @time symbolic_linear_solve([x-y, y+2z], [x,y])
  1.606491 seconds (3.11 M allocations: 211.991 MiB, 19.11% gc time, 99.90% compilation time)
2-element Vector{SymbolicUtils.BasicSymbolic{Real}}:
 -2z
 -2z

julia> @time symbolic_solve([x-y, y+2z], [x,y])
 19.045450 seconds (44.65 M allocations: 2.988 GiB, 6.72% gc time, 99.85% compilation time: <1% of which was recompilation)
1-element Vector{Dict{Num, Any}}:
 Dict(y => -2z, x => -2z)

Here’s something symbolic_solve handles better than symbolic_linear_solve, as I want an exact solution without rounding to floats:

julia> symbolic_solve([x-1], [x])
1-element Vector{BigInt}:
 1

julia> symbolic_linear_solve([x-1], [x])
1-element Vector{Float64}:
 1.0

Here’s something only symbolic_linear_solve can handle, i.e. solving for “composite” variables:

julia> @variables f(..), x;

julia> symbolic_linear_solve([f(1)-x], [f(1)])
1-element Vector{SymbolicUtils.BasicSymbolic{Real}}:
 x

julia> symbolic_solve([f(1)-x], [f(1)])
ERROR: MethodError: no method matching solve_multipoly(::Vector{Num}, ::SymbolicUtils.BasicSymbolic{Real}; dropmultiplicity::Bool, warns::Bool)
3 Likes

Thank you for your comments. The release should be out just about now, so this should work:
pkg> add Symbolics@6.3

TTFX is not ideal for linear equations. Is it possible to automatically detect linear cases and forward to symbolic_linear_solve instead?

Sure, we can do that for the linear case. By the way, in general, is it possible to precompile package extensions to reduce TTFX ?

Here’s something only symbolic_linear_solve can handle, i.e. solving for “composite” variables:

Thanks for the report. Yes, handling composition of functions is in the todo for symbolic_solve :^).

3 Likes

There’s the getting started page:

1 Like

I am trying to understand Symbolics.jl
In the code below, why is the position at t_sv=17 given as “Position(t=17.0) is (1085.6,)”
Why is the answer (1085.6,0) in brackets, is it a complex number?

using Symbolics

# Tutorial Symbolics for Newbies
#
# In Symbolics we have two type of variables
#  1. Normal Julia Programming Language variable 
#  2. Symbolic variables
#
# we shall denote symbolic variables with suffix t_sv
# eg.  t_sv

@variables t_sv

# Next we create a lazy function to differentiate with respect
# to the symbolic variable t_sv
D_lazyfunc = Differential(t_sv)

Position = 3.4 * t_sv^2 + 5.6 * t_sv + 7.8
println("Position is ",Position)

# Velocity is the derivative of Position with respect to time
Velocity = expand_derivatives(D_lazyfunc(Position))

# Acceleration is the derivative of Velocity with respect to time
Acceleration = expand_derivatives(D_lazyfunc(Velocity))

# Derivative is 
println("Velocity is ",Velocity)
println("Acceleration is ",Acceleration)
println()

println("Find the varies attributes of Position")
println("The polynomial degree of position wrt t_sv is ",Symbolics.degree(Position))
println("The coefficient of position wrt 1 is ",Symbolics.coeff(Position,1))
println("The coefficient of position wrt t_sv is ",Symbolics.coeff(Position,t_sv))
println("The coefficient of position wrt t_sv^2 is ",Symbolics.coeff(Position,t_sv^2))

println()
println("What is the Position, Velocity and Acceleration at time = 17.0?")

Position_at_t17 = substitute.( Position, (Dict(t_sv => 17.0),) )
println("Position(t=17.0) is ",Position_at_t17)

Velocity_at_t17 = substitute.( Velocity, (Dict(t_sv => 17.0),) )
println("Velocity(t=17.0) is ",Velocity_at_t17)

Acceleration_at_t17 = substitute.( Acceleration, (Dict(t_sv => 17.0),) )
println("Acceleration(t=17.0) is ",Acceleration_at_t17)

with output:

Position is 7.8 + 5.6t_sv + 3.4(t_sv^2)
Velocity is 5.6 + 6.8t_sv
Acceleration is 6.8

Find the varies attributes of Position
The polynomial degree of position wrt t_sv is 2
The coefficient of position wrt 1 is 7.8
The coefficient of position wrt t_sv is 5.6
The coefficient of position wrt t_sv^2 is 3.4

What is the Position, Velocity and Acceleration at time = 17.0?
Position(t=17.0) is (1085.6,)
Velocity(t=17.0) is (121.19999999999999,)
Acceleration(t=17.0) is (6.8,)

I don’t know this package but presumably (1085.6,) indicates a one element tuple.

julia> typeof((1085.6,))
Tuple{Float64}

I don’t know, but here’s my workaround from the user side, i.e. creating a wrapper package which loads both the original package and the extensions, and then using PrecompileTools:

module SymbolicsWithGroebner

import Groebner
import Symbolics: @variables, symbolic_solve
using PrecompileTools: @setup_workload, @compile_workload

export symbolic_solve

@setup_workload begin
    @variables x y z
    @compile_workload begin
        solution = symbolic_solve([x-y, y+2z], [x,y])
    end
end

end # module SymbolicsWithGroebner

It’s used as follows:

import Groebner
import Symbolics: @variables
import SymbolicsWithGroebner: symbolic_solve

@variables x y z
@time symbolic_solve([x-y, y+2z], [x,y])

The last @time measurement is below 0.5 seconds, down from about 20 seconds.

PR that? That’s a good thing to add.

Since my workaround involves loading both Symbolics.jl and Groebner.jl in a super-package, what’s a proper way to add the precompile statements directy into Symbolics.jl? I guess that’s why @sumiya11 hasn’t done it so far.

You can add it to SymbolicsGroebnerExt.jl in the ext dir (i think?)

2 Likes

Yes, that works! I’ve created a PR:

5 Likes

This does not work at all !

I believe that you are referring to the issues posted in Symbolics 'solve' command undefined - #9 by abraemer

I just add the link here so future readers have some context and not just a one line statement.

2 Likes