Expression as single fraction in Symbolics.jl

I am using Symbolics.jl to automate some tedious mathematical operations in a large expression. I would like to express the result as a single fraction rather than the sum of several fractions. I tried to accomplish this with expand and simplify, but with no success. Is this possible? Here is a minimum working example:

using Symbolics

@variables a b c d e f g
x1 = (a+b) / (c + d) + (g+f) / (e + d)
expand(x1)

These all work:

simplify_fractions(a/(c+d) + b/(c+d))
(a + b) / (c + d)

julia> simplify_fractions((a+b)/(c+d) + b/(c+d))
(a + 2.0b) / (c + d)

julia> simplify_fractions((a+b)/(c+d) + (b+d)/(c+d))
(a + d + 2.0b) / (c + d)

julia> simplify_fractions((a+b)/(c+d) + (b+d)/(c+a))
(a^2 + a*b + a*c + b*d + c*d + d^2 + 2b*c) / ((a + c)*(c + d))

You can then call simplify on the result to do more simplification, if that is possible.

However, your example doesn’t work (see below). Maybe a bug? Or maybe simplify_fractions is not meant to be part of the public facing api? It is exported though which normally means it is part of the public api.

julia> x1 = (a+b) / (c + d) + (g+f) / (e + d)
(a + b) / (c + d) + (f + g) / (d + e)

julia> simplify_fractions(x1)
ERROR: AssertionError: i1 == i2
Stacktrace:
  [1] deflated_gcd(p1::DynamicPolynomials.Polynomial{true, Int64}, p2::DynamicPolynomials.Polynomial{true, Int64}, algo::MultivariatePolynomials.GeneralizedEuclideanAlgorithm)
    @ MultivariatePolynomials ~/.julia/packages/MultivariatePolynomials/vqcb5/src/gcd.jl:227
  [2] gcd(p1::DynamicPolynomials.Polynomial{true, Int64}, p2::DynamicPolynomials.Polynomial{true, Int64}, algo::MultivariatePolynomials.GeneralizedEuclideanAlgorithm)
    @ MultivariatePolynomials ~/.julia/packages/MultivariatePolynomials/vqcb5/src/gcd.jl:123
  [3] _simplifier(a::DynamicPolynomials.Polynomial{true, Int64}, b::DynamicPolynomials.Polynomial{true, Int64}, algo::MultivariatePolynomials.GeneralizedEuclideanAlgorithm)
    @ MultivariatePolynomials ~/.julia/packages/MultivariatePolynomials/vqcb5/src/gcd.jl:408
  [4] (::MultivariatePolynomials.var"#107#108"{MultivariatePolynomials.GeneralizedEuclideanAlgorithm})(a::DynamicPolynomials.Polynomial{true, Int64}, b::DynamicPolynomials.Polynomial{true, Int64})
    @ MultivariatePolynomials ~/.julia/packages/MultivariatePolynomials/vqcb5/src/gcd.jl:436
  [5] (::Base.BottomRF{MultivariatePolynomials.var"#107#108"{MultivariatePolynomials.GeneralizedEuclideanAlgorithm}})(acc::DynamicPolynomials.Polynomial{true, Int64}, x::DynamicPolynomials.Polynomial{true, Int64})
    @ Base ./reduce.jl:81
  [6] _foldl_impl(op::Base.BottomRF{MultivariatePolynomials.var"#107#108"{MultivariatePolynomials.GeneralizedEuclideanAlgorithm}}, init::DynamicPolynomials.Polynomial{true, Int64}, itr::Vector{DynamicPolynomials.Polynomial{true, Int64}})
    @ Base ./reduce.jl:62
  [7] foldl_impl(op::Base.BottomRF{MultivariatePolynomials.var"#107#108"{MultivariatePolynomials.GeneralizedEuclideanAlgorithm}}, nt::DynamicPolynomials.Polynomial{true, Int64}, itr::Vector{DynamicPolynomials.Polynomial{true, Int64}})
    @ Base ./reduce.jl:48
  [8] mapfoldl_impl(f::typeof(identity), op::MultivariatePolynomials.var"#107#108"{MultivariatePolynomials.GeneralizedEuclideanAlgorithm}, nt::DynamicPolynomials.Polynomial{true, Int64}, itr::Vector{DynamicPolynomials.Polynomial{true, Int64}})
    @ Base ./reduce.jl:44
  [9] _mapreduce_dim(f::Function, op::Function, nt::DynamicPolynomials.Polynomial{true, Int64}, A::Vector{DynamicPolynomials.Polynomial{true, Int64}}, #unused#::Colon)
    @ Base ./reducedim.jl:315
 [10] #mapreduce#672
    @ ./reducedim.jl:310 [inlined]
 [11] #reduce#674
    @ ./reducedim.jl:359 [inlined]
 [12] content
    @ ~/.julia/packages/MultivariatePolynomials/vqcb5/src/gcd.jl:435 [inlined]
 [13] deflated_gcd(p1::DynamicPolynomials.Polynomial{true, Int64}, p2::DynamicPolynomials.Polynomial{true, Int64}, algo::MultivariatePolynomials.GeneralizedEuclideanAlgorithm)
    @ MultivariatePolynomials ~/.julia/packages/MultivariatePolynomials/vqcb5/src/gcd.jl:213
 [14] gcd(p1::DynamicPolynomials.Polynomial{true, Int64}, p2::DynamicPolynomials.Polynomial{true, Int64}, algo::MultivariatePolynomials.GeneralizedEuclideanAlgorithm)
    @ MultivariatePolynomials ~/.julia/packages/MultivariatePolynomials/vqcb5/src/gcd.jl:123
 [15] deflated_gcd(p1::DynamicPolynomials.Polynomial{true, Int64}, p2::DynamicPolynomials.Polynomial{true, Int64}, algo::MultivariatePolynomials.GeneralizedEuclideanAlgorithm)
    @ MultivariatePolynomials ~/.julia/packages/MultivariatePolynomials/vqcb5/src/gcd.jl:224
 [16] gcd(p1::DynamicPolynomials.Polynomial{true, Int64}, p2::DynamicPolynomials.Polynomial{true, Int64}, algo::MultivariatePolynomials.GeneralizedEuclideanAlgorithm)
    @ MultivariatePolynomials ~/.julia/packages/MultivariatePolynomials/vqcb5/src/gcd.jl:123
 [17] _simplifier(a::DynamicPolynomials.Polynomial{true, Int64}, b::DynamicPolynomials.Polynomial{true, Int64}, algo::MultivariatePolynomials.GeneralizedEuclideanAlgorithm)
    @ MultivariatePolynomials ~/.julia/packages/MultivariatePolynomials/vqcb5/src/gcd.jl:408
 [18] (::MultivariatePolynomials.var"#107#108"{MultivariatePolynomials.GeneralizedEuclideanAlgorithm})(a::DynamicPolynomials.Polynomial{true, Int64}, b::DynamicPolynomials.Polynomial{true, Int64})
    @ MultivariatePolynomials ~/.julia/packages/MultivariatePolynomials/vqcb5/src/gcd.jl:436
 [19] (::Base.BottomRF{MultivariatePolynomials.var"#107#108"{MultivariatePolynomials.GeneralizedEuclideanAlgorithm}})(acc::DynamicPolynomials.Polynomial{true, Int64}, x::DynamicPolynomials.Polynomial{true, Int64})
    @ Base ./reduce.jl:81
 [20] _foldl_impl(op::Base.BottomRF{MultivariatePolynomials.var"#107#108"{MultivariatePolynomials.GeneralizedEuclideanAlgorithm}}, init::DynamicPolynomials.Polynomial{true, Int64}, itr::Vector{DynamicPolynomials.Polynomial{true, Int64}})
    @ Base ./reduce.jl:62
 [21] foldl_impl(op::Base.BottomRF{MultivariatePolynomials.var"#107#108"{MultivariatePolynomials.GeneralizedEuclideanAlgorithm}}, nt::DynamicPolynomials.Polynomial{true, Int64}, itr::Vector{DynamicPolynomials.Polynomial{true, Int64}})
    @ Base ./reduce.jl:48
 [22] mapfoldl_impl(f::typeof(identity), op::MultivariatePolynomials.var"#107#108"{MultivariatePolynomials.GeneralizedEuclideanAlgorithm}, nt::DynamicPolynomials.Polynomial{true, Int64}, itr::Vector{DynamicPolynomials.Polynomial{true, Int64}})
    @ Base ./reduce.jl:44
 [23] _mapreduce_dim(f::Function, op::Function, nt::DynamicPolynomials.Polynomial{true, Int64}, A::Vector{DynamicPolynomials.Polynomial{true, Int64}}, #unused#::Colon)
    @ Base ./reducedim.jl:315
 [24] #mapreduce#672
    @ ./reducedim.jl:310 [inlined]
 [25] #reduce#674
    @ ./reducedim.jl:359 [inlined]
 [26] content
    @ ~/.julia/packages/MultivariatePolynomials/vqcb5/src/gcd.jl:435 [inlined]
 [27] deflated_gcd(p1::DynamicPolynomials.Polynomial{true, Int64}, p2::DynamicPolynomials.Polynomial{true, Int64}, algo::MultivariatePolynomials.GeneralizedEuclideanAlgorithm)
    @ MultivariatePolynomials ~/.julia/packages/MultivariatePolynomials/vqcb5/src/gcd.jl:213
 [28] gcd(p1::DynamicPolynomials.Polynomial{true, Int64}, p2::DynamicPolynomials.Polynomial{true, Int64}, algo::MultivariatePolynomials.GeneralizedEuclideanAlgorithm)
    @ MultivariatePolynomials ~/.julia/packages/MultivariatePolynomials/vqcb5/src/gcd.jl:123
 [29] _simplifier(a::DynamicPolynomials.Polynomial{true, Int64}, b::DynamicPolynomials.Polynomial{true, Int64}, algo::MultivariatePolynomials.GeneralizedEuclideanAlgorithm)
    @ MultivariatePolynomials ~/.julia/packages/MultivariatePolynomials/vqcb5/src/gcd.jl:408
 [30] (::MultivariatePolynomials.var"#107#108"{MultivariatePolynomials.GeneralizedEuclideanAlgorithm})(a::DynamicPolynomials.Polynomial{true, Int64}, b::DynamicPolynomials.Polynomial{true, Int64})
    @ MultivariatePolynomials ~/.julia/packages/MultivariatePolynomials/vqcb5/src/gcd.jl:436
 [31] (::Base.BottomRF{MultivariatePolynomials.var"#107#108"{MultivariatePolynomials.GeneralizedEuclideanAlgorithm}})(acc::DynamicPolynomials.Polynomial{true, Int64}, x::DynamicPolynomials.Polynomial{true, Int64})
    @ Base ./reduce.jl:81
 [32] _foldl_impl(op::Base.BottomRF{MultivariatePolynomials.var"#107#108"{MultivariatePolynomials.GeneralizedEuclideanAlgorithm}}, init::DynamicPolynomials.Polynomial{true, Int64}, itr::Vector{DynamicPolynomials.Polynomial{true, Int64}})
    @ Base ./reduce.jl:62
 [33] foldl_impl(op::Base.BottomRF{MultivariatePolynomials.var"#107#108"{MultivariatePolynomials.GeneralizedEuclideanAlgorithm}}, nt::DynamicPolynomials.Polynomial{true, Int64}, itr::Vector{DynamicPolynomials.Polynomial{true, Int64}})
    @ Base ./reduce.jl:48
 [34] mapfoldl_impl(f::typeof(identity), op::MultivariatePolynomials.var"#107#108"{MultivariatePolynomials.GeneralizedEuclideanAlgorithm}, nt::DynamicPolynomials.Polynomial{true, Int64}, itr::Vector{DynamicPolynomials.Polynomial{true, Int64}})
    @ Base ./reduce.jl:44
 [35] _mapreduce_dim(f::Function, op::Function, nt::DynamicPolynomials.Polynomial{true, Int64}, A::Vector{DynamicPolynomials.Polynomial{true, Int64}}, #unused#::Colon)
    @ Base ./reducedim.jl:315
 [36] #mapreduce#672
    @ ./reducedim.jl:310 [inlined]
 [37] #reduce#674
    @ ./reducedim.jl:359 [inlined]
 [38] content
    @ ~/.julia/packages/MultivariatePolynomials/vqcb5/src/gcd.jl:435 [inlined]
 [39] deflated_gcd(p1::DynamicPolynomials.Polynomial{true, Int64}, p2::DynamicPolynomials.Polynomial{true, Int64}, algo::MultivariatePolynomials.GeneralizedEuclideanAlgorithm)
    @ MultivariatePolynomials ~/.julia/packages/MultivariatePolynomials/vqcb5/src/gcd.jl:223
 [40] gcd(p1::DynamicPolynomials.Polynomial{true, Int64}, p2::DynamicPolynomials.Polynomial{true, Int64}, algo::MultivariatePolynomials.GeneralizedEuclideanAlgorithm)
    @ MultivariatePolynomials ~/.julia/packages/MultivariatePolynomials/vqcb5/src/gcd.jl:123
 [41] gcd
    @ ~/.julia/packages/MultivariatePolynomials/vqcb5/src/gcd.jl:107 [inlined]
 [42] _gcd(x::DynamicPolynomials.Polynomial{true, Int64}, y::DynamicPolynomials.Polynomial{true, Int64})
    @ SymbolicUtils ~/.julia/packages/SymbolicUtils/vKBkE/src/polyform.jl:351
 [43] gcd(x::PolyForm{Real, Nothing}, y::PolyForm{Real, Nothing})
    @ SymbolicUtils ~/.julia/packages/SymbolicUtils/vKBkE/src/polyform.jl:89
 [44] _gcd(x::PolyForm{Real, Nothing}, y::PolyForm{Real, Nothing})
    @ SymbolicUtils ~/.julia/packages/SymbolicUtils/vKBkE/src/polyform.jl:351
 [45] rm_gcds(ns::Vector{PolyForm{Real, Nothing}}, ds::Vector{PolyForm{Real, Nothing}})
    @ SymbolicUtils ~/.julia/packages/SymbolicUtils/vKBkE/src/polyform.jl:464
 [46] simplify_div(d::SymbolicUtils.Div{Real, SymbolicUtils.Add{Real, Int64, Dict{Any, Number}, Nothing}, SymbolicUtils.Mul{Real, Int64, Dict{Any, Number}, Nothing}, Nothing})
    @ SymbolicUtils ~/.julia/packages/SymbolicUtils/vKBkE/src/polyform.jl:260
 [47] (::SymbolicUtils.var"#sdiv#129")(a::SymbolicUtils.Div{Real, SymbolicUtils.Add{Real, Int64, Dict{Any, Number}, Nothing}, SymbolicUtils.Mul{Real, Int64, Dict{Any, Number}, Nothing}, Nothing})
    @ SymbolicUtils ~/.julia/packages/SymbolicUtils/vKBkE/src/polyform.jl:287
 [48] (::ComposedFunction{SymbolicUtils.var"#sdiv#129", typeof(quick_cancel)})(x::SymbolicUtils.Div{Real, SymbolicUtils.Add{Real, Int64, Dict{Any, Number}, Nothing}, SymbolicUtils.Mul{Real, Int64, Dict{Any, Number}, Nothing}, Nothing})
    @ Base ./operators.jl:938
 [49] (::SymbolicUtils.Rewriters.Walk{:post, ComposedFunction{SymbolicUtils.var"#sdiv#129", typeof(quick_cancel)}, typeof(similarterm), false})(x::SymbolicUtils.Div{Real, SymbolicUtils.Add{Real, Int64, Dict{Any, Number}, Nothing}, SymbolicUtils.Mul{Real, Int64, Dict{Any, Number}, Nothing}, Nothing})
    @ SymbolicUtils.Rewriters ~/.julia/packages/SymbolicUtils/vKBkE/src/rewriters.jl:164
 [50] simplify_fractions(x::SymbolicUtils.Add{Real, Int64, Dict{Any, Number}, Nothing}; polyform::Bool)
    @ SymbolicUtils ~/.julia/packages/SymbolicUtils/vKBkE/src/polyform.jl:289
 [51] simplify_fractions(x::SymbolicUtils.Add{Real, Int64, Dict{Any, Number}, Nothing})
    @ SymbolicUtils ~/.julia/packages/SymbolicUtils/vKBkE/src/polyform.jl:283
 [52] simplify_fractions(n::Num; kw::Base.Iterators.Pairs{Union{}, Union{}, Tuple{}, NamedTuple{(), Tuple{}}})
    @ Symbolics ~/.julia/packages/Symbolics/mFWWM/src/Symbolics.jl:127
 [53] simplify_fractions(n::Num)
    @ Symbolics ~/.julia/packages/Symbolics/mFWWM/src/Symbolics.jl:127
2 Likes

I can reproduce the bug on my laptop with Julia 1.6.3. By the way, if you genuinely deal with only rational functions (no trig functions, square roots, etc.), a specialized package Nemo.jl can handle it:

julia> using Nemo

julia> R, (a,b,c,d,e,f,g) = PolynomialRing(QQ, ["a", "b", "c", "d", "e", "f", "g"]);

julia> x1 = (a+b) // (c + d) + (g+f) // (e + d)
(a*d + a*e + b*d + b*e + c*f + c*g + d*f + d*g)//(c*d + c*e + d^2 + d*e)

Floating point numbers cannot be used, as far as I know. Also, both the numerator and denominator are always in the expanded form in Nemo.

3 Likes

Yeah open an issue. We need to collect these kinds of cases.

Thank you for your responses. I opened an issue and also noted that simplify_fractions is not in the documentation.

1 Like