Skip to content

Commit b15fb88

Browse files
merging
2 parents c218865 + 2cfc8f7 commit b15fb88

File tree

9 files changed

+151
-23
lines changed

9 files changed

+151
-23
lines changed

Project.toml

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,28 @@
11
name = "SymbolicNumericIntegration"
22
uuid = "78aadeae-fbc0-11eb-17b6-c7ec0477ba9e"
33
authors = ["Shahriar Iravanian <siravan@svtsim.com>"]
4-
version = "1.0.2"
4+
version = "1.1.0"
55

66
[deps]
77
DataDrivenDiffEq = "2445eb08-9709-466a-b3fc-47e12bd697a2"
88
DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
99
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
1010
Optim = "429524aa-4258-5aef-a3af-852621145aeb"
11+
SpecialFunctions = "276daf66-3868-5448-9aa4-cd146d93841b"
1112
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
1213
SymbolicUtils = "d1185830-fcd6-423d-90d6-eec64667417b"
1314
Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7"
1415

1516
[compat]
1617
DataDrivenDiffEq = "0.8"
1718
DataStructures = "0.18"
18-
PyCall = "1"
19-
SymPy = "1"
19+
Optim = "1"
2020
SymbolicUtils = "0.19"
2121
Symbolics = "4"
2222
julia = "1.6"
2323

2424
[extras]
25-
PyCall = "438e738f-606a-5dbb-bf0a-cddfbfd45ab0"
26-
SymPy = "24249f21-da20-56a4-8eb1-6a02cf4ae2e6"
2725
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
2826

2927
[targets]
30-
test = ["PyCall", "SymPy", "Test"]
28+
test = ["Test"]

docs/src/index.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,26 @@ julia> integrate(exp(x^2))
7070
(0, exp(x^2), Inf) # as expected!
7171
```
7272

73+
SymbolicNumericIntegration.jl exports some special integral functions (defined over Complex numbers) and uses them in solving integrals:
74+
75+
- `Ei`: exponential integral (define as ∫ exp(x) / x dx)
76+
- `Si`: sine integral (define as ∫ sin(x) / x dx)
77+
- `Ci`: cosine integral (define as ∫ cos(x) / x dx)
78+
- `Li`: logarithmic integral (define as ∫ 1 / log(x) dx)
79+
80+
For examples:
81+
82+
```
83+
julia> integrate(exp(x + 1) / (x + 1))
84+
(SymbolicNumericIntegration.Ei(1 + x), 0, 1.1796119636642288e-16)
85+
86+
julia> integrate(x * cos(x^2 - 1) / (x^2 - 1))
87+
((1//2)*SymbolicNumericIntegration.Ci(x^2 - 1), 0, 2.7755575615628914e-17)
88+
89+
julia> integrate(1 / (x*log(log(x))))
90+
(SymbolicNumericIntegration.Li(log(x)), 0, 1.1102230246251565e-16)
91+
```
92+
7393
`integrate` has the form `integrate(y; kw...)` or `integrate(y, x; kw...)`, where `y` is the integrand and the optional `x` is the variable of integration. The keyword parameters are:
7494

7595
- `abstol` (default `1e-6`): the error tolerance to accept a solution.

src/SymbolicNumericIntegration.jl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,17 @@ using SymbolicUtils.Rewriters
99
using DataDrivenDiffEq
1010

1111
include("utils.jl")
12+
include("special.jl")
13+
1214
include("cache.jl")
1315
include("roots.jl")
1416

1517
include("rules.jl")
1618
include("candidates.jl")
1719
include("homotopy.jl")
1820

21+
export Ei, Si, Ci, Li
22+
1923
include("numeric_utils.jl")
2024
include("sparse.jl")
2125
include("optim.jl")

src/cache.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ function deriv_fun!(c::ExprCache, xs...)
4141
end
4242

4343
function deriv_fun!(c, xs...)
44-
return build_function(deriv!(c, xs), xs...; expression = false)
44+
return build_function(deriv!(c, xs...), xs...; expression = false)
4545
end
4646

4747
Base.show(io::IO, c::ExprCache) = show(expr(c))

src/candidates.jl

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
using DataStructures
22

33
# this is the main heurisctic used to find the test fragments
4-
function generate_basis(eq, x; homotopy = true)
5-
# if homotopy return generate_homotopy(eq, x) end
4+
function generate_basis(eq, x, try_kernel = true; homotopy = true)
5+
if homotopy && !try_kernel
6+
S = sum(generate_homotopy(expr(eq), x))
7+
return cache.(unique([one(x); [equivalent(t, x) for t in terms(S)]]))
8+
end
9+
10+
S = 0
611
eq = expand(expr(eq))
7-
S = 0 #Set{Any}()
12+
813
for t in terms(eq)
914
q = equivalent(t, x)
1015
f = kernel(q)

src/homotopy.jl

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -57,19 +57,36 @@ end
5757

5858
##############################################################################
5959

60+
Symbolics.@register_symbolic Ei(z)
61+
Symbolics.@register_symbolic Si(z)
62+
Symbolics.@register_symbolic Ci(z)
63+
Symbolics.@register_symbolic Li(z)
64+
65+
Symbolics.derivative(::typeof(Ei), args::NTuple{1, Any}, ::Val{1}) = exp(args[1]) / args[1]
66+
Symbolics.derivative(::typeof(Si), args::NTuple{1, Any}, ::Val{1}) = sin(args[1]) / args[1]
67+
Symbolics.derivative(::typeof(Ci), args::NTuple{1, Any}, ::Val{1}) = cos(args[1]) / args[1]
68+
Symbolics.derivative(::typeof(Li), args::NTuple{1, Any}, ::Val{1}) = 1 / log(args[1])
69+
70+
@syms si(𝑥) ci(𝑥) ei(𝑥) li(𝑥)
71+
72+
##############################################################################
73+
6074
function substitute_x(eq, x, sub)
6175
eq = substitute(eq, sub)
6276
substitute(eq, Dict(𝑥 => x))
6377
end
6478

6579
function generate_homotopy(eq, x)
6680
eq = eq isa Num ? eq.val : eq
81+
x = x isa Num ? x.val : x
82+
6783
q, sub = transform(eq, x)
6884
S = 0
6985

7086
for i in 1:length(sub)
7187
μ = u[i]
7288
h₁, ∂h₁ = apply_partial_int_rules(sub[μ])
89+
h₁ = substitute(h₁, Dict(si => Si, ci => Ci, ei => Ei, li => Li))
7390
h₂ = expand_derivatives(Differential(μ)(q))
7491

7592
h₁ = substitute_x(h₁, x, sub)
@@ -78,8 +95,6 @@ function generate_homotopy(eq, x)
7895
S += expand((1 + h₁) * (1 + h₂))
7996
end
8097

81-
S = simplify(S)
82-
8398
unique([one(x); [equivalent(t, x) for t in terms(S)]])
8499
end
85100

@@ -92,8 +107,8 @@ end
92107

93108
partial_int_rules = [
94109
# trigonometric functions
95-
@rule 𝛷(sin(~x)) => (cos(~x), (~x))
96-
@rule 𝛷(cos(~x)) => (sin(~x), (~x))
110+
@rule 𝛷(sin(~x)) => (cos(~x) + si(~x), (~x))
111+
@rule 𝛷(cos(~x)) => (sin(~x) + ci(~x), (~x))
97112
@rule 𝛷(tan(~x)) => (log(cos(~x)), (~x))
98113
@rule 𝛷(csc(~x)) => (log(csc(~x) + cot(~x)), (~x))
99114
@rule 𝛷(sec(~x)) => (log(sec(~x) + tan(~x)), (~x))
@@ -137,7 +152,8 @@ partial_int_rules = [
137152
@rule 𝛷(log(~x)) => (~x + ~x * log(~x) +
138153
sum(candidate_pow_minus(~x, -1); init = one(~x)),
139154
(~x))
140-
@rule 𝛷(exp(~x)) => (exp(~x), (~x))
155+
@rule 𝛷(^(log(~x), -1)) => (log(log(~x)) + li(~x), (~x))
156+
@rule 𝛷(exp(~x)) => (exp(~x) + ei(~x), (~x))
141157
@rule 𝛷(^(exp(~x), ~k::is_neg)) => (^(exp(-~x), -~k), (~x))
142158
# square-root functions
143159
@rule 𝛷(^(~x, ~k::is_abs_half)) => (sum(candidate_sqrt(~x, ~k);

src/integral.jl

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ Base.signbit(x::SymbolicUtils.Sym{Number, Nothing}) = false
3333
solved is the solved integral and unsolved is the residual unsolved portion of the input
3434
err is the numerical error in reaching the solution
3535
"""
36-
function integrate(eq, x = nothing; abstol = 1e-6, num_steps = 2, num_trials = 5,
36+
function integrate(eq, x = nothing; abstol = 1e-6, num_steps = 2, num_trials = 10,
3737
radius = 1.0,
3838
show_basis = false, opt = STLSQ(exp.(-10:1:0)), bypass = false,
3939
symbolic = true, max_basis = 100, verbose = false, complex_plane = true,
@@ -150,13 +150,14 @@ function integrate_term(eq, x, l; kwargs...)
150150
end
151151

152152
eq = cache(eq)
153-
basis = generate_basis(eq, x; homotopy)
153+
basis1 = generate_basis(eq, x, true; homotopy)
154+
basis2 = generate_basis(eq, x, false; homotopy)
154155

155156
if show_basis
156-
inform(l, "Generating basis (|β| = $(length(basis)))", basis)
157+
inform(l, "Generating basis (|β| = $(length(basis)))", basis1)
157158
end
158159

159-
if length(basis) > max_basis
160+
if length(basis1) > max_basis
160161
result(l, "|β| = $(length(basis)) is too large")
161162
return 0, expr(eq), Inf
162163
end
@@ -170,12 +171,12 @@ function integrate_term(eq, x, l; kwargs...)
170171
yᵣ = 0
171172

172173
for i in 1:num_steps
173-
if length(basis) > max_basis
174+
if length(basis1) > max_basis
174175
break
175176
end
176177

177178
if symbolic
178-
y, ϵ = try_symbolic(Float64, expr(eq), x, expr.(basis), deriv!.(basis, x);
179+
y, ϵ = try_symbolic(Float64, expr(eq), x, expr.(basis1), deriv!.(basis1, x);
179180
kwargs...)
180181

181182
if !isequal(y, 0) && accept_solution(eq, x, y, radius) < abstol
@@ -187,6 +188,7 @@ function integrate_term(eq, x, l; kwargs...)
187188
end
188189

189190
for j in 1:num_trials
191+
basis = isodd(j) ? basis1 : basis2
190192
r = radius #*sqrt(2)^j
191193
y, ϵ = try_integrate(Float64, eq, x, basis, r; kwargs...)
192194

@@ -203,10 +205,11 @@ function integrate_term(eq, x, l; kwargs...)
203205
inform(l, "Failed numeric")
204206

205207
if i < num_steps
206-
basis = expand_basis(basis, x)
208+
basis1 = expand_basis(basis1, x)
209+
basis2 = expand_basis(basis2, x)
207210

208211
if show_basis
209-
inform(l, "Expanding the basis (|β| = $(length(basis)))", basis)
212+
inform(l, "Expanding the basis (|β| = $(length(basis)))", basis1)
210213
elseif verbose
211214
inform(l, "Expanding the basis (|β| = $(length(basis)))")
212215
end

src/special.jl

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
using SpecialFunctions
2+
3+
# Adopted from mpmath (https://mpmath.org/), which is a Python library for real and
4+
# complex floating-point arithmetic with arbitrary precision
5+
6+
E1(z) = Complex(expint(z))
7+
8+
function Ei(z)
9+
if z isa Real
10+
return Complex(-expint(Complex(-z)))
11+
end
12+
13+
v = -expint(-z)
14+
15+
if imag(z) > 0
16+
v += Complex(0, π)
17+
else
18+
v -= Complex(0, π)
19+
end
20+
21+
return v
22+
end
23+
24+
function Ci(z)
25+
if z isa Real
26+
return Complex(cosint(z))
27+
end
28+
29+
v = (Ei(z * im) + Ei(-z * im)) / 2
30+
31+
if real(z) 0
32+
if imag(z) >= 0
33+
v += Complex(0, π / 2)
34+
else
35+
v -= Complex(0, π / 2)
36+
end
37+
end
38+
39+
if real(z) < 0
40+
if imag(z) >= 0
41+
v += Complex(0, π)
42+
else
43+
v -= Complex(0, π)
44+
end
45+
end
46+
return v
47+
end
48+
49+
function Si(z)
50+
if z isa Real
51+
return Complex(sinint(z))
52+
end
53+
54+
v = (Ei(z * im) - Ei(-z * im)) / 2im
55+
56+
if real(z) 0
57+
return v
58+
end
59+
60+
if real(z) > 0
61+
v -= π / 2
62+
else
63+
v += π / 2
64+
end
65+
66+
return v
67+
end
68+
69+
function Li(z)
70+
return Ei(log(z)) - Ei(log(2.0))
71+
end

test/runtests.jl

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,17 @@ basic_integrals = [
186186
x / (x^4 - 4),
187187
x^3 / (x^4 + 1),
188188
1 / (x^4 + 1),
189+
# exponential/trigonometric/logarithmic integral functions
190+
exp(2x) / x,
191+
exp(x + 1) / (x + 1),
192+
x * exp(2x^2 + 1) / (2x^2 + 1),
193+
sin(3x) / x,
194+
sin(x + 1) / (x + 1),
195+
cos(5x) / x,
196+
x * cos(x^2 - 1) / (x^2 - 1),
197+
1 / log(3x - 1),
198+
1 / (x * log(log(x))),
199+
x / log(x^2),
189200
# bypass = true
190201
β, # turn of bypass = true
191202
(log(x - 1) + (x - 1)^-1) * log(x),

0 commit comments

Comments
 (0)