Skip to content

Commit 9c9c317

Browse files
committed
Add several helper functions; improve the integration demo
1 parent 3738abd commit 9c9c317

8 files changed

+182
-81
lines changed

Project.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "TaylorDiff"
22
uuid = "b36ab563-344f-407b-a36a-4f200bebf99c"
33
authors = ["Songchen Tan <i@tansongchen.com>"]
4-
version = "0.3.1"
4+
version = "0.3.2"
55

66
[deps]
77
ChainRules = "082447d4-558c-5d27-93f4-14fc19e9eca2"

examples/Project.toml

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[deps]
2+
ODEProblemLibrary = "fdc4e326-1af4-4b90-96e7-779fcce2daa5"
3+
OrdinaryDiffEq = "1dea7af3-3e70-54e6-95c3-0bf5283fa5ed"
4+
SymbolicUtils = "d1185830-fcd6-423d-90d6-eec64667417b"
5+
Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7"
6+
TaylorDiff = "b36ab563-344f-407b-a36a-4f200bebf99c"
7+
TaylorIntegration = "92b13dbe-c966-51a2-8445-caca9f8a7d42"
8+
TaylorSeries = "6aa5eb33-94cf-58f4-a9d0-e4b2c4fc25ea"

examples/integration.jl

+89-78
Original file line numberDiff line numberDiff line change
@@ -1,100 +1,111 @@
1-
using TaylorDiff: TaylorDiff, TaylorScalar, TaylorArray, make_seed, value, partials, flatten
2-
using TaylorSeries
3-
using TaylorIntegration: jetcoeffs!
4-
using BenchmarkTools
1+
using TaylorDiff: TaylorDiff, TaylorScalar, make_seed, flatten, get_coefficient,
2+
set_coefficient, append_coefficient
3+
using TaylorSeries, TaylorIntegration
4+
using ODEProblemLibrary, OrdinaryDiffEq, BenchmarkTools, Symbolics
55

6-
"""
7-
No magic, just a type-stable way to generate a new object since TaylorScalar is immutable
8-
"""
9-
function setindex(x::TaylorScalar{T, P}, index, d) where {T, P}
10-
v = flatten(x)
11-
ntuple(i -> i == index + 1 ? d : v[i], Val(P + 1)) |> TaylorScalar
12-
end
13-
14-
function setindex(x::TaylorArray{T, N, A, P}, index, d) where {T, N, A, P}
15-
v = flatten(x)
16-
ntuple(i -> i == index + 1 ? d : v[i], Val(P + 1)) |> TaylorArray
17-
end
18-
19-
"""
20-
Computes the taylor integration of order P
6+
# There are two ways to compute the Taylor coefficients of a ODE solution
7+
# 1. Using naive repeated differentiation
8+
# 2. First simplify the expansion using Symbolics and then evaluate the expression
219

22-
- `f`: ODE function
23-
- `t`: constructed by TaylorScalar{P}(t0, one(t0))
24-
- `x0`: initial value
25-
- `p`: parameters
2610
"""
27-
function my_jetcoeffs(f, t::TaylorScalar{T, P}, x0, p) where {T, P}
28-
x = x0 isa AbstractArray ? TaylorArray{P}(x0) : TaylorScalar{P}(x0)
29-
for index in 1:P # computes x.partials[index]
30-
fx = f(x, p, t)
31-
d = index == 1 ? value(fx) : partials(fx)[index - 1] / index
32-
x = setindex(x, index, d)
33-
end
34-
x
35-
end
36-
37-
"""
38-
Computes the taylor integration of order P
11+
# The first method
3912
40-
- `f!`: ODE function, in non-allocating form
41-
- `t`: constructed by TaylorScalar{P}(t0, one(t0))
42-
- `x0`: initial value
43-
- `p`: parameters
13+
For ODE u' = f(u, p, t) and initial condition (u0, t0), computes Taylor expansion of the solution `u` up to order `P` using repeated differentiation.
4414
"""
45-
function my_jetcoeffs!(f!, t::TaylorScalar{T, P}, x0, p) where {T, P}
46-
x = x0 isa AbstractArray ? TaylorArray{P}(x0) : TaylorScalar{P}(x0)
47-
out = similar(x)
48-
for index in 1:P # computes x.partials[index]
49-
f!(out, x, p, t)
50-
d = index == 1 ? value(out) : partials(out)[index - 1] / index
51-
x = setindex(x, index, d)
15+
function jetcoeffs(f::ODEFunction{iip}, u0, p, t0, ::Val{P}) where {P, iip}
16+
t = TaylorScalar{P}(t0, one(t0))
17+
u = make_seed(u0, zero(u0), Val(P))
18+
fu = copy(u)
19+
for index in 1:P
20+
if iip
21+
f(fu, u, p, t)
22+
else
23+
fu = f(u, p, t)
24+
end
25+
d = get_coefficient(fu, index - 1) / index
26+
u = set_coefficient(u, index, d)
5227
end
53-
x
28+
u
5429
end
5530

5631
function scalar_test()
57-
f(x, p, t) = x * x
58-
x0 = 0.1
59-
t0 = 0.0
6032
P = 6
61-
33+
prob = prob_ode_linear
34+
t0 = prob.tspan[1]
6235
# TaylorIntegration test
63-
t = t0 + Taylor1(typeof(t0), P)
64-
x = Taylor1(x0, P)
65-
@btime jetcoeffs!($f, $t, $x, nothing)
36+
ts = t0 + Taylor1(typeof(t0), P)
37+
u_ts = Taylor1(prob.u0, P)
38+
@btime TaylorIntegration.jetcoeffs!($prob.f, $ts, $u_ts, $prob.p)
6639

6740
# TaylorDiff test
68-
td = TaylorScalar{P}(t0, one(t0))
69-
@btime my_jetcoeffs($f, $td, $x0, nothing)
70-
71-
result = my_jetcoeffs(f, td, x0, nothing)
72-
@assert x.coeffs collect(flatten(result))
41+
@btime jetcoeffs($prob.f, $prob.u0, $prob.p, $t0, Val($P))
42+
u_td = jetcoeffs(prob.f, prob.u0, prob.p, t0, Val(P))
43+
@assert u_ts.coeffs collect(flatten(u_td))
7344
end
7445

7546
function array_test()
76-
function lorenz(du, u, p, t)
77-
du[1] = 10.0(u[2] - u[1])
78-
du[2] = u[1] * (28.0 - u[3]) - u[2]
79-
du[3] = u[1] * u[2] - (8 / 3) * u[3]
80-
return nothing
81-
end
82-
u0 = [1.0; 0.0; 0.0]
83-
t0 = 0.0
8447
P = 6
85-
48+
prob = prob_ode_lotkavolterra
49+
t0 = prob.tspan[1]
8650
# TaylorIntegration test
87-
t = t0 + Taylor1(typeof(t0), P)
88-
u = [Taylor1(x, P) for x in u0]
89-
du = similar(u)
90-
uaux = similar(u)
91-
@btime jetcoeffs!($lorenz, $t, $u, $du, $uaux, nothing)
51+
ts = t0 + Taylor1(typeof(t0), P)
52+
u_ts = [Taylor1(x, P) for x in prob.u0]
53+
du = similar(u_ts)
54+
uaux = similar(u_ts)
55+
@btime TaylorIntegration.jetcoeffs!($prob.f, $ts, $u_ts, $du, $uaux, $prob.p)
9256

9357
# TaylorDiff test
94-
td = TaylorScalar{P}(t0, one(t0))
95-
@btime my_jetcoeffs!($lorenz, $td, $u0, nothing)
96-
result = my_jetcoeffs!(lorenz, td, u0, nothing)
97-
for i in eachindex(u)
98-
@assert u[i].coeffs collect(flatten(result[i]))
58+
@btime jetcoeffs($prob.f, $prob.u0, $prob.p, $t0, Val($P))
59+
u_td = jetcoeffs(prob.f, prob.u0, prob.p, t0, Val(P))
60+
for i in eachindex(u_ts)
61+
@assert u_ts[i].coeffs collect(flatten(u_td[i]))
62+
end
63+
end
64+
65+
"""
66+
# The second method
67+
68+
For ODE u' = f(u, p, t) and initial condition (u0, t0), symbolically computes Taylor expansion of the solution `u` up to order `P`, and then builds a function to evaluate the expression.
69+
"""
70+
function build_jetcoeffs(f::ODEFunction{iip}, p, ::Val{P}, length = nothing) where {P, iip}
71+
@variables t0::Real
72+
u0 = isnothing(length) ? Symbolics.variable(:u0) : Symbolics.variables(:u0, 1:length)
73+
if iip
74+
@assert length isa Integer
75+
f0 = similar(u0)
76+
f(f0, u0, p, t0)
77+
else
78+
f0 = f(u0, p, t0)
9979
end
80+
u = TaylorDiff.make_seed(u0, f0, Val(1))
81+
for index in 2:P
82+
t = TaylorScalar{index - 1}(t0, one(t0))
83+
if iip
84+
fu = similar(u)
85+
f(fu, u, p, t)
86+
else
87+
fu = f(u, p, t)
88+
end
89+
d = get_coefficient(fu, index - 1) / index
90+
u = append_coefficient(u, d)
91+
end
92+
build_function(u, u0, t0; expression = Val(false), cse = true)
93+
end
94+
95+
function simplify_scalar_test()
96+
P = 6
97+
prob = prob_ode_linear
98+
t0 = prob.tspan[1]
99+
@btime jetcoeffs($prob.f, $prob.u0, $prob.p, $t0, Val($P))
100+
fast_jetcoeffs = build_jetcoeffs(prob.f, prob.p, Val(P))
101+
@btime $fast_jetcoeffs($prob.u0, $t0)
102+
end
103+
104+
function simplify_array_test()
105+
P = 6
106+
prob = prob_ode_lotkavolterra
107+
t0 = prob.tspan[1]
108+
@btime jetcoeffs($prob.f, $prob.u0, $prob.p, $t0, Val($P))
109+
fast_oop, fast_iip = build_jetcoeffs(prob.f, prob.p, Val(P), length(prob.u0))
110+
@btime $fast_oop($prob.u0, $t0)
100111
end

examples/multi.jl

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using TaylorDiff
2+
3+
f(x, y) = exp(x) * exp(y)
4+
x0, y0 = 0.0, 0.0
5+
x_dx = TaylorScalar{2}(x0, one(x0))
6+
x_dxdy = TaylorScalar{2}(x_dx, zero(x_dx))
7+
y_dx = TaylorScalar{2}(y0)
8+
y_dxdy = TaylorScalar{2}(y_dx, one(y_dx))
9+
10+
result = f(x_dxdy, y_dxdy)
11+
map(TaylorDiff.flatten, TaylorDiff.flatten(result))

examples/test.jl

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
using Symbolics, SymbolicUtils
2+
struct A1{T} <: Real
3+
x::T
4+
y::T
5+
function A1(value::T, partials::T) where {T}
6+
new{T}(value, partials)
7+
end
8+
end
9+
10+
struct A2{T, P} <: Real
11+
x::T
12+
y::NTuple{P, T}
13+
function A2(value::T, partials::NTuple{P, T}) where {T, P}
14+
new{T, P}(value, partials)
15+
end
16+
end
17+
18+
@register_symbolic A1(x, y)
19+
@register_symbolic A2(x, y)
20+
@variables x::Real
21+
a1 = A1(x, x^2)
22+
a2 = A2(x, (x^2, x^3))
23+
24+
fa1 = build_function(a1, x; expression = Val(false))
25+
fa1(2.0)
26+
fa2 = build_function(a2, x; expression = Val(false))
27+
fa2(2.0)
28+
29+
function Symbolics.Code.toexpr(a2::A2, st)
30+
:($A2($(Symbolics.Code.toexpr(a2.x, st)),
31+
$(Symbolics.Code.toexpr(Symbolics.Code.MakeTuple(a2.y), st))))
32+
end
33+
fa2 = build_function(a2, x; expression = Val(false))
34+
fa2(2.0)

src/TaylorDiff.jl

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ can_taylorize(::Type) = false
1414
" If the type behaves as a scalar, define TaylorDiff.can_taylorize(::Type{$V}) = true."))
1515
end
1616

17-
include("utils.jl")
1817
include("scalar.jl")
1918
include("array.jl")
19+
include("utils.jl")
2020
include("primitive.jl")
2121
include("codegen.jl")
2222
include("derivative.jl")

src/primitive.jl

+31
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ Taylor = Union{TaylorScalar, TaylorArray}
99

1010
@inline value(t::Taylor) = t.value
1111
@inline partials(t::Taylor) = t.partials
12+
@inline value(ts::AbstractArray{<:Taylor}) = map(value, ts)
13+
@inline partials(ts::AbstractArray{<:Taylor}) = map(partials, ts)
1214
@inline @generated extract_derivative(t::Taylor, ::Val{P}) where {P} = :(t.partials[P] *
1315
$(factorial(P)))
1416
@inline extract_derivative(a::AbstractArray{<:TaylorScalar}, p) = map(
@@ -19,6 +21,35 @@ Taylor = Union{TaylorScalar, TaylorArray}
1921

2022
@inline flatten(t::Taylor) = (value(t), partials(t)...)
2123

24+
get_coefficient(t::TaylorScalar, order) = order == 0 ? value(t) : partials(t)[order]
25+
function get_coefficient(a::AbstractArray{<:TaylorScalar}, order)
26+
map(t -> get_coefficient(t, order), a)
27+
end
28+
29+
function set_coefficient(t::TaylorScalar{T, P}, order, d) where {T, P}
30+
order == 0 && return TaylorScalar(d, partials(t))
31+
new_partials = ntuple(i -> i == order ? d : partials(t)[i], Val(P))
32+
TaylorScalar(value(t), new_partials)
33+
end
34+
35+
function set_coefficient(a::AbstractArray{<:TaylorScalar}, index, d)
36+
for i in eachindex(a)
37+
a[i] = set_coefficient(a[i], index, d[i])
38+
end
39+
a
40+
end
41+
42+
function append_coefficient(x::TaylorScalar{T, P}, d) where {T, P}
43+
TaylorScalar(value(x), ntuple(i -> i == P + 1 ? d : partials(x)[i], Val(P + 1)))
44+
end
45+
46+
append_coefficient(x::AbstractArray{<:TaylorScalar}, d) = map(append_coefficient, x, d)
47+
48+
Base.hash(t::Taylor, h::UInt) = hash(flatten(t), h)
49+
function Base.isequal(a::T, b::T) where {T <: Taylor}
50+
isequal(value(a), value(b)) && isequal(partials(a), partials(b))
51+
end
52+
2253
function Base.promote_rule(::Type{TaylorScalar{T, P}},
2354
::Type{S}) where {T, S, P}
2455
TaylorScalar{promote_type(T, S), P}

src/utils.jl

+7-1
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,17 @@
33

44
using ChainRules
55
using ChainRulesCore
6-
using Symbolics: @variables, @rule, unwrap, isdiv
6+
using Symbolics: Symbolics, @variables, @rule, @register_symbolic, unwrap, isdiv
77
using SymbolicUtils.Code: toexpr
88
using MacroTools
99
using MacroTools: prewalk, postwalk
1010

11+
@register_symbolic TaylorScalar(x, y)
12+
function Symbolics.Code.toexpr(t::TaylorScalar, st)
13+
:($TaylorScalar($(Symbolics.Code.toexpr(t.value, st)),
14+
$(Symbolics.Code.toexpr(Symbolics.Code.MakeTuple(t.partials), st))))
15+
end
16+
1117
"""
1218
Pick a strategy for raising the derivative of a function.
1319
If the derivative is like 1 over something, raise with the division rule;

0 commit comments

Comments
 (0)