Skip to content

Commit 2cfc8f7

Browse files
special integral functions (Ei/Si/Ci/Li) implemented
1 parent ff47411 commit 2cfc8f7

File tree

11 files changed

+248
-118
lines changed

11 files changed

+248
-118
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: 38 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -57,28 +57,43 @@ 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)
7693
h₂ = substitute_x(h₂ * ∂h₁^-1, x, sub)
7794

7895
S += expand((1 + h₁) * (1 + h₂))
7996
end
80-
81-
S = simplify(S)
8297

8398
unique([one(x); [equivalent(t, x) for t in terms(S)]])
8499
end
@@ -91,9 +106,9 @@ function ∂(x)
91106
end
92107

93108
partial_int_rules = [
94-
# trigonometric functions
95-
@rule 𝛷(sin(~x)) => (cos(~x), (~x))
96-
@rule 𝛷(cos(~x)) => (sin(~x), (~x))
109+
# trigonometric functions
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))
@@ -119,12 +134,12 @@ partial_int_rules = [
119134
@rule 𝛷(^(csch(~x), -1)) => (cosh(~x), (~x))
120135
@rule 𝛷(^(sech(~x), -1)) => (sinh(~x), (~x))
121136
@rule 𝛷(^(coth(~x), -1)) => (log(cosh(~x)), (~x))
122-
# inverse trigonometric functions
137+
# inverse trigonometric functions
123138
@rule 𝛷(asin(~x)) => (~x * asin(~x) + sqrt(1 - ~x * ~x), (~x))
124139
@rule 𝛷(acos(~x)) => (~x * acos(~x) + sqrt(1 - ~x * ~x), (~x))
125140
@rule 𝛷(atan(~x)) => (~x * atan(~x) + log(~x * ~x + 1), (~x))
126-
@rule 𝛷(acsc(~x)) => (~x * acsc(~x) + atanh(1 - ^(~x,-2)), (~x))
127-
@rule 𝛷(asec(~x)) => (~x * asec(~x) + acosh(~x), (~x))
141+
@rule 𝛷(acsc(~x)) => (~x * acsc(~x) + atanh(1 - ^(~x, -2)), (~x))
142+
@rule 𝛷(asec(~x)) => (~x * asec(~x) + acosh(~x), (~x))
128143
@rule 𝛷(acot(~x)) => (~x * acot(~x) + log(~x * ~x + 1), (~x))
129144
# inverse hyperbolic functions
130145
@rule 𝛷(asinh(~x)) => (~x * asinh(~x) + sqrt(~x * ~x + 1), (~x))
@@ -134,19 +149,26 @@ partial_int_rules = [
134149
@rule 𝛷(asech(~x)) => (asech(~x), (~x))
135150
@rule 𝛷(acoth(~x)) => (~x * acot(~x) + log(~x + 1), (~x))
136151
# logarithmic and exponential functions
137-
@rule 𝛷(log(~x)) => (~x + ~x * log(~x) + sum(candidate_pow_minus(~x, -1); init = one(~x)), (~x))
138-
@rule 𝛷(exp(~x)) => (exp(~x), (~x))
152+
@rule 𝛷(log(~x)) => (~x + ~x * log(~x) +
153+
sum(candidate_pow_minus(~x, -1); init = one(~x)),
154+
(~x))
155+
@rule 𝛷(^(log(~x), -1)) => (log(log(~x)) + li(~x), (~x))
156+
@rule 𝛷(exp(~x)) => (exp(~x) + ei(~x), (~x))
139157
@rule 𝛷(^(exp(~x), ~k::is_neg)) => (^(exp(-~x), -~k), (~x))
140158
# square-root functions
141-
@rule 𝛷(^(~x, ~k::is_abs_half)) => (sum(candidate_sqrt(~x, ~k); init = one(~x)), 1);
159+
@rule 𝛷(^(~x, ~k::is_abs_half)) => (sum(candidate_sqrt(~x, ~k);
160+
init = one(~x)), 1);
142161
@rule 𝛷(sqrt(~x)) => (sum(candidate_sqrt(~x, 0.5); init = one(~x)), 1);
143-
@rule 𝛷(^(sqrt(~x), -1)) => 𝛷(^(~x, -0.5))
144-
# rational functions
145-
@rule 𝛷(^(~x::is_poly, ~k::is_neg)) => (sum(candidate_pow_minus(~x, ~k); init = one(~x)), 1)
162+
@rule 𝛷(^(sqrt(~x), -1)) => 𝛷(^(~x, -0.5))
163+
# rational functions
164+
@rule 𝛷(^(~x::is_poly, ~k::is_neg)) => (sum(candidate_pow_minus(~x,
165+
~k);
166+
init = one(~x)), 1)
146167
@rule 𝛷(^(~x, -1)) => (log(~x), (~x))
147-
@rule 𝛷(^(~x, ~k::is_neg_int)) => (sum(^(~x, i) for i in (~k + 1):-1), (~x))
168+
@rule 𝛷(^(~x, ~k::is_neg_int)) => (sum(^(~x, i) for i in (~k + 1):-1),
169+
(~x))
148170
@rule 𝛷(1 / ~x) => 𝛷(^(~x, -1))
149-
@rule 𝛷(^(~x, ~k)) => (^(~x, ~k + 1), (~x))
171+
@rule 𝛷(^(~x, ~k)) => (^(~x, ~k + 1), (~x))
150172
@rule 𝛷(1) => (𝑥, 1)
151173
@rule 𝛷(~x) => ((~x + ^(~x, 2)), (~x))]
152174

src/integral.jl

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,11 @@ 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,
40-
homotopy = true, use_optim=false)
40+
homotopy = true, use_optim = false)
4141
eq = expand(eq)
4242
eq = apply_div_rule(eq)
4343

@@ -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
@@ -236,13 +239,12 @@ end
236239
"""
237240
function try_integrate(T, eq, x, basis, radius; kwargs...)
238241
args = Dict(kwargs)
239-
use_optim = args[:use_optim]
242+
use_optim = args[:use_optim]
240243
basis = basis[2:end] # remove 1 from the beginning
241-
242-
if use_optim
243-
return solve_optim(T, eq, x, basis, radius; kwargs...)
244-
else
245-
return solve_sparse(T, eq, x, basis, radius; kwargs...)
246-
end
247-
end
248244

245+
if use_optim
246+
return solve_optim(T, eq, x, basis, radius; kwargs...)
247+
else
248+
return solve_sparse(T, eq, x, basis, radius; kwargs...)
249+
end
250+
end

0 commit comments

Comments
 (0)