diff --git a/Project.toml b/Project.toml index d83642f33..5f4228de6 100644 --- a/Project.toml +++ b/Project.toml @@ -36,6 +36,7 @@ SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" SpecialFunctions = "276daf66-3868-5448-9aa4-cd146d93841b" StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" SymbolicIndexingInterface = "2efcf032-c050-4f8e-a9bb-153293bab1f5" +SymbolicLimits = "19f23fe9-fdab-4a78-91af-e7b7767979c3" SymbolicUtils = "d1185830-fcd6-423d-90d6-eec64667417b" [weakdeps] @@ -81,6 +82,7 @@ Setfield = "1" SpecialFunctions = "2" StaticArrays = "1.1" SymbolicIndexingInterface = "0.3" +SymbolicLimits = "0.2.0" SymbolicUtils = "1.4" julia = "1.10" diff --git a/docs/make.jl b/docs/make.jl index 62f8c657d..e3aac1f72 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -49,7 +49,8 @@ makedocs( "manual/io.md", "manual/sparsity_detection.md", "manual/types.md", - "manual/faq.md" + "manual/faq.md", + "manual/limits.md", ], "Comparison Against SymPy" => "comparison.md", ] diff --git a/docs/src/manual/limits.md b/docs/src/manual/limits.md new file mode 100644 index 000000000..50d418d28 --- /dev/null +++ b/docs/src/manual/limits.md @@ -0,0 +1,9 @@ +# Symbolic Limits + +Experimental symbolic limit support is provided by the [`limit`](@ref) function, documented +below. See [SymbolicLimits.jl](https://github.com/SciML/SymbolicLimits.jl) for more +information and implementation details. + +```@docs +limit +``` diff --git a/src/Symbolics.jl b/src/Symbolics.jl index cb177dd88..e221c9f99 100644 --- a/src/Symbolics.jl +++ b/src/Symbolics.jl @@ -37,6 +37,8 @@ using PrecompileTools using MacroTools using SymbolicIndexingInterface + + import SymbolicLimits end @reexport using SymbolicUtils RuntimeGeneratedFunctions.init(@__MODULE__) @@ -149,6 +151,9 @@ include("error_hints.jl") include("struct.jl") include("operators.jl") +include("limits.jl") +export limit + # Hacks to make wrappers "nicer" const NumberTypes = Union{AbstractFloat,Integer,Complex{<:AbstractFloat},Complex{<:Integer}} (::Type{T})(x::SymbolicUtils.Symbolic) where {T<:NumberTypes} = throw(ArgumentError("Cannot convert Sym to $T since Sym is symbolic and $T is concrete. Use `substitute` to replace the symbolic unwraps.")) diff --git a/src/limits.jl b/src/limits.jl new file mode 100644 index 000000000..e61ee3670 --- /dev/null +++ b/src/limits.jl @@ -0,0 +1,20 @@ +""" + limit(expr, var, h[, side::Symbol]) + +Compute the limit of `expr` as `var` approaches `h`. + +`side` indicates the direction from which `var` approaches `h`. It may be one of `:left`, +`:right`, or `:both`. If `side` is `:both` and the two sides do not align, an error is +thrown. Side defaults to `:both` for finite `h`, `:left` for `h = Inf`, and `:right` for +`h = -Inf`. + +`expr` must be compoesed of `log`, `exp`, constants, and the rational opperators `+`, `-`, +`*`, and `/`. This limitation may eventually be relaxed. + +!!! warning + Because symbolic limit computation is undecidable, this function necessarily employs + heuristics and may occasionally return wrong answers. Nevertheless, please report wrong + answers as issues as we aim to have heuristics that produce correct answers in all + practical cases. +""" +limit(expr, var, h, side...) = SymbolicLimits.limit(expr, var, h, side...)[1] diff --git a/test/limits.jl b/test/limits.jl new file mode 100644 index 000000000..fd29067c9 --- /dev/null +++ b/test/limits.jl @@ -0,0 +1,13 @@ +using Symbolics, Test + +@testset "limits" begin + @syms x + @test limit(exp(x+exp(-x))-exp(x), x, Inf) == 1 + @test limit(x^7/exp(x), x, Inf) == 0 + @test limit(log(log(x*exp(x*exp(x))+1))-exp(exp(log(log(x))+1/x)), x, Inf) == 0 + @test limit(2exp(-x)/exp(-x), x, 0) == 2 + + @test_throws ArgumentError limit(1/x, x, 0) + @test limit(1/x, x, 0, :left)[1] == -Inf + @test limit(1/x, x, 0, :right)[1] == Inf +end