Solver
The main symbolic solver for Symbolics.jl is symbolic_solve
. Symbolic solving means that it only uses symbolic (algebraic) methods and outputs exact solutions.
Symbolics.symbolic_solve
— Functionsymbolic_solve(expr, x; dropmultiplicity=true, warns=true)
symbolic_solve
is a function which attempts to solve input equations/expressions symbolically using various methods.
Arguments
expr: Could be a single univar expression in the form of a poly or multiple univar expressions or multiple multivar polys or a transcendental nonlinear function.
x: Could be a single variable or an array of variables which should be solved
dropmultiplicity (optional): Should the output be printed
n
times wheren
is the number of occurrence of the root? Say we have(x+1)^2
, we then have 2 rootsx = -1
, by default the output is[-1]
, If dropmultiplicity is inputted as false, then the output is[-1, -1]
.warns (optional): When invalid expressions or cases are inputted, should the solver warn you of such cases before returning nothing? if this is set to false, the solver returns nothing. By default, warns are set to true.
Supported input
The base solver (symbolic_solve
) has multiple solvers which chooses from depending on the the type of input (multiple/uni var and multiple/single expression) only after ensuring that the input is valid.
The expressions inputted can contain parameters, which are assumed to be transcendental. A parameter "a" is transcendental if there exists no polynomial P with rational coefficients such that P(a) = 0. Check the examples section.
Currently, symbolic_solve
supports
- Linear and polynomial equations (with parameters)
- Systems of linear and polynomials equations (without extra parameters, for now)
- Equations with transcendental functions (with parameters)
Examples
solve_univar
(uses factoring and analytic solutions up to degree 4)
The package Nemo
is needed in order to use solve_univar
as well as solve_multipoly
, so executing using Nemo
as you will see in the following examples is necessary; otherwise, the function will throw an error.
julia> using Symbolics, Nemo;
julia> @variables x a b;
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)
4-element Vector{Any}:
-1
-b
(1//2)*√(4a)
(-1//2)*√(4a)
julia> symbolic_solve(expr, x, dropmultiplicity=false)
5-element Vector{Any}:
-1
-1
-b
(1//2)*√(4a)
(-1//2)*√(4a)
julia> symbolic_solve(x^2 + a*x + 6, x)
2-element Vector{SymbolicUtils.BasicSymbolic{Real}}:
(1//2)*(-a + √(-24 + a^2))
(1//2)*(-a - √(-24 + a^2))
julia> symbolic_solve(x^7 - 1, x)
2-element Vector{Any}:
roots_of((1//1) + x + x^2 + x^3 + x^4 + x^5 + x^6, x)
1
solve_multivar
(uses Groebner basis and solve_univar
to find roots)
Similar to solve_univar
, Groebner
is needed for solve_multivar
or to be fully functional.
julia> using Groebner
julia> @variables x y z
3-element Vector{Num}:
x
y
z
julia> eqs = [x+y^2+z, z*x*y, z+3x+y]
3-element Vector{Num}:
x + z + y^2
x*y*z
3x + y + z
julia> symbolic_solve(eqs, [x,y,z])
3-element Vector{Any}:
Dict{Num, Any}(z => 0, y => 1//3, x => -1//9)
Dict{Num, Any}(z => 0, y => 0, x => 0)
Dict{Num, Any}(z => -1, y => 1, x => 0)
If Nemo
or Groebner
are not imported when needed, the solver throws an error.
julia> using Symbolics
julia> @variables x y z;
julia> symbolic_solve(x+1, x)
ERROR: "Nemo is required. Execute `using Nemo` to enable this functionality."
julia> symbolic_solve([x+1, y], [x, y])
ERROR: "Groebner bases engine is required. Execute `using Groebner` to enable this functionality."
solve_multipoly
(uses GCD between the input polys)
julia> symbolic_solve([x-1, x^3 - 1, x^2 - 1, (x-1)^20], x)
1-element Vector{BigInt}:
1
ia_solve
(solving by isolation and attraction)
julia> symbolic_solve(2^(x+1) + 5^(x+3), x)
1-element Vector{SymbolicUtils.BasicSymbolic{Real}}:
(-slog(2) - log(complex(-1)) + 3slog(5)) / (slog(2) - slog(5))
julia> symbolic_solve(log(x+1)+log(x-1), x)
2-element Vector{SymbolicUtils.BasicSymbolic{BigFloat}}:
(1//2)*√(8.0)
(-1//2)*√(8.0)
julia> symbolic_solve(a*x^b + c, x)
((-c)^(1 / b)) / (a^(1 / b))
Evaluating output (converting to floats)
If you want to evaluate the exact expressions found by symbolic_solve
, you can do the following:
julia> roots = symbolic_solve(2^(x+1) + 5^(x+3), x)
1-element Vector{SymbolicUtils.BasicSymbolic{Real}}:
(-slog(2) - log(complex(-1)) + 3slog(5)) / (slog(2) - slog(5))
julia> Symbolics.symbolic_to_float.(roots)
1-element Vector{Complex{BigFloat}}:
-4.512941594732059759689023145584186058252768936052415430071569066192919491762214 + 3.428598090438030380369414618548038962770087500755160535832807433942464545729382im
One other symbolic solver is symbolic_linear_solve
which is limited compared to symbolic_solve
as it only solves linear equations.
Symbolics.symbolic_linear_solve
— Functionsymbolic_linear_solve(eq, var; simplify, check) -> Any
Solve equation(s) eqs
for a set of variables vars
.
Assumes length(eqs) == length(vars)
Currently only works if all equations are linear. check
if the expr is linear w.r.t vars
.
Examples
julia> @variables x y
2-element Vector{Num}:
x
y
julia> Symbolics.symbolic_linear_solve(x + y ~ 0, x)
-y
julia> Symbolics.symbolic_linear_solve([x + y ~ 0, x - y ~ 2], [x, y])
2-element Vector{Float64}:
1.0
-1.0
symbolic_solve
only supports symbolic, i.e. non-floating point computations, and thus prefers equations where the coefficients are integer, rational, or symbolic. Floating point coefficients are transformed into rational values and BigInt values are used internally with a potential performance loss, and thus it is recommended that this functionality is only used with floating point values if necessary. In contrast, symbolic_linear_solve
directly handles floating point values using standard factorizations.
More technical details and examples
Technical details
The symbolic_solve
function uses 4 hidden solvers in order to solve the user's input. Its base, solve_univar
, uses analytic solutions up to polynomials of degree 4 and factoring as its method for solving univariate polynomials. The function's solve_multipoly
uses GCD on the input polynomials then throws passes the result to solve_univar
. The function's solve_multivar
uses Groebner basis and a separating form in order to create linear equations in the input variables and a single high degree equation in the separating variable [1]. Each equation resulting from the basis is then passed to solve_univar
. We can see that essentially, solve_univar
is the building block of symbolic_solve
. If the input is not a valid polynomial and can not be solved by the algorithm above, symbolic_solve
passes it to ia_solve
, which attempts solving by attraction and isolation [2]. This only works when the input is a single expression and the user wants the answer in terms of a single variable. Say log(x) - a == 0
gives us [e^a]
.
Nice examples
using Symbolics, Nemo;
@variables x;
Symbolics.symbolic_solve(9^x + 3^x ~ 8, x)
2-element Vector{SymbolicUtils.BasicSymbolic{Real}}:
slog(-(1//2) + (1//2)*√(33)) / slog(3)
slog(-(1//2) - (1//2)*√(33)) / slog(3)
@variables x y z;
Symbolics.symbolic_linear_solve(2//1*x + y - 2//1*z ~ 9//1*x, 1//1*x)
\[ \begin{equation} \frac{1}{7} \left( y - 2 z \right) \end{equation} \]
using Groebner;
@variables x y z;
eqs = [x^2 + y + z - 1, x + y^2 + z - 1, x + y + z^2 - 1]
Symbolics.symbolic_solve(eqs, [x,y,z])
5-element Vector{Any}:
Dict{Num, Any}(z => 1, y => 0, x => 0)
Dict{Num, Any}(z => -1 + √(2), y => -1 + √(2), x => -1 + √(2))
Dict{Num, Any}(z => -1 - √(2), y => -1 - √(2), x => -1 - √(2))
Dict{Num, Any}(z => 0, y => 0, x => 1)
Dict{Num, Any}(z => 0, y => 1, x => 0)
Feature completeness
- [x] Linear and polynomial equations
- [x] Systems of linear and polynomial equations
- [x] Some transcendental functions
- [x] Systems of linear equations with parameters (via
symbolic_linear_solve
) - [ ] Equations with radicals
- [x] Systems of polynomial equations with parameters and positive dimensional systems
- [ ] Inequalities
Expressions we can not solve (but aim to)
# Mathematica
In[1]:= Reduce[x^2 - x - 6 > 0, x]
Out[1]= x < -2 || x > 3
In[2]:= Reduce[x+a > 0, x]
Out[2]= a \[Element] Reals && x > -a
In[3]:= Solve[x^(x) + 3 == 0, x]
Out[3]= {{x -> (I \[Pi] + Log[3])/ProductLog[I \[Pi] + Log[3]]}}