Skip to content

Commit fe09b22

Browse files
Polish examples (#32)
* initial commit * fix keyword mismatch * improve coverage
1 parent df59c05 commit fe09b22

13 files changed

Lines changed: 382 additions & 85 deletions

Project.toml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,20 @@ BandedMatrices = "aae01518-5342-5314-be14-df237901396f"
99
DiffRules = "b552c78f-8df3-52c6-915a-8e097449b14b"
1010
FillArrays = "1a297f60-69ca-5386-bcde-b61e274b549b"
1111
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
12+
SparseArrayKit = "a9a3c162-d163-4c15-8926-b8794fbefed2"
1213
SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
1314
TensorOperations = "6aa20fa7-93e2-5fca-9bc0-fbd0db3c71a2"
1415

16+
1517
[compat]
1618
DiffRules = "1.15.1"
1719
ForwardDiff = "1.2.2"
1820
Lux = "1.29.1"
1921
Plots = "1.41.1"
22+
SparseArrayKit = "0.4.3"
2023
SparseArrays = "1.10"
21-
TensorOperations = "5.5.2"
24+
TensorOperations = "5.6.1"
25+
Zygote = "0.7.10"
2226

2327
[extras]
2428
BandedMatrices = "aae01518-5342-5314-be14-df237901396f"
@@ -27,7 +31,8 @@ Lux = "b2108857-7c20-44ae-9111-449ecde12c47"
2731
Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
2832
SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
2933
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
34+
Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f"
3035

3136
[targets]
32-
examples = ["Plots", "ForwardDiff", "BandedMatrices", "Lux"]
37+
examples = ["Plots", "ForwardDiff", "BandedMatrices", "Lux", "Zygote"]
3338
test = ["Test", "ForwardDiff", "BandedMatrices", "SparseArrays"]

examples/catenary.jl

Lines changed: 100 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using DualArrays, LinearAlgebra, Plots, BenchmarkTools
1+
using DualArrays, LinearAlgebra, Plots, BenchmarkTools, BandedMatrices, ForwardDiff, Zygote
22

33
"""
44
Solving the Catenary Problem using DualArrays.jl
@@ -11,6 +11,19 @@ The computation of finite differences keeps the Jacobian sparse,
1111
so we can use DualArrays.jl
1212
"""
1313

14+
const plot_args = (
15+
xscale = :log10,
16+
yscale = :log10,
17+
lw = 2.5,
18+
marker = :circle,
19+
color = :steelblue4,
20+
legend = :topleft,
21+
framestyle = :box,
22+
size = (960, 600),
23+
left_margin = 10Plots.mm,
24+
bottom_margin = 10Plots.mm,
25+
)
26+
1427
function L(y, h, alpha, beta)
1528
"""
1629
Evaluate functional with boundary conditions (alpha, beta)
@@ -24,33 +37,109 @@ function L(y, h, alpha, beta)
2437
return y_mid .* sqrt.(1 .+ dy.^2)
2538
end
2639

27-
function learn_catenary(h = 0.1, alpha = cosh(-1), beta = cosh(1), epochs = 2000, lr = 0.01)
40+
function learn_catenary_dualarrays(h = 0.1, alpha = cosh(-1), beta = cosh(1), epochs = 2000, lr = 0.01)
41+
n = Int(2 / h) - 1
42+
y = ones(n) * (alpha + beta) / 2
43+
for _ = 1:epochs
44+
jac = DualArrays.jacobian(y -> L(y, h, alpha, beta), y, BandedMatrix)
45+
grads = h * sum(jac, dims=1)
46+
y -= lr * vec(grads)
47+
end
48+
return y
49+
end
50+
51+
function learn_catenary_forwarddiff(h = 0.1, alpha = cosh(-1), beta = cosh(1), epochs = 2000, lr = 0.01)
52+
n = Int(2 / h) - 1
53+
y = ones(n) * (alpha + beta) / 2
54+
for _ = 1:epochs
55+
jac = ForwardDiff.jacobian(y -> L(y, h, alpha, beta), y)
56+
grads = h * sum(jac, dims=1)
57+
y -= lr * vec(grads)
58+
end
59+
return y
60+
end
61+
62+
function learn_catenary_zygote(h = 0.1, alpha = cosh(-1), beta = cosh(1), epochs = 2000, lr = 0.01)
2863
n = Int(2 / h) - 1
2964
y = ones(n) * (alpha + beta) / 2
3065
for _ = 1:epochs
31-
jac = jacobian(y -> L(y, h, alpha, beta), y, id="banded")
66+
jac = only(Zygote.jacobian(y -> L(y, h, alpha, beta), y))
3267
grads = h * sum(jac, dims=1)
3368
y -= lr * vec(grads)
3469
end
3570
return y
3671
end
3772

38-
function plot_solution(h)
73+
function plot_solution(save=undef;h = 0.1, alpha = cosh(-1), beta = cosh(1), epochs = 5000, lr = 0.02)
3974
x = collect(-1+h:h:1-h)
40-
y = learn_catenary()
41-
plot(x, y, label = "Approximation")
42-
plot!(x, cosh.(x), label = "Exact Solution")
75+
y = learn_catenary_dualarrays(h, alpha, beta, epochs, lr)
76+
plot(x, y, label = "Approximate solution (DualArrays)", title="Catenary Solution", legend=:topleft)
77+
plot!(x, cosh.(x), label = "Exact Solution (cosh(x))", ls = :dash)
78+
if save !== undef
79+
savefig(save)
80+
end
4381
end
4482

45-
function plot_times()
46-
hs = [0.1, 0.05, 0.02, 0.01, 0.005, 0.002]
83+
function plot_times(save=undef; hs = [0.04, 0.02, 0.01, 0.005, 0.0025, 0.00125], epochs = 200, lr = 0.01)
4784
ns = Int.(2 ./ hs) .- 1
4885
dualvector_times = Float64[]
86+
forwarddiff_times = Float64[]
87+
zygote_times = Float64[]
4988

5089
for (h, n) in zip(hs, ns)
5190
println("Computing solution with h = $h, n = $n")
52-
push!(dualvector_times, @belapsed learn_catenary($h, cosh(-1), cosh(1), 2000, 0.01))
91+
push!(dualvector_times, @belapsed learn_catenary_dualarrays($h, cosh(-1), cosh(1), $epochs, $lr))
92+
push!(forwarddiff_times, @belapsed learn_catenary_forwarddiff($h, cosh(-1), cosh(1), $epochs, $lr))
93+
push!(zygote_times, @belapsed learn_catenary_zygote($h, cosh(-1), cosh(1), $epochs, $lr))
94+
end
95+
96+
plot(
97+
ns,
98+
dualvector_times,
99+
;
100+
label = "DualArrays",
101+
title = "Catenary Gradient Descent Runtime",
102+
xlabel = "Number of points (n)",
103+
ylabel = "Runtime (seconds)",
104+
yticks = 10 .^ collect(-3:0.25:2),
105+
xticks = 10 .^ collect(1.5:0.25:3.5),
106+
plot_args...,
107+
)
108+
plot!(ns, forwarddiff_times, label = "ForwardDiff", lw = 2.5, marker = :square, color = :darkorange2)
109+
plot!(ns, zygote_times, label = "Zygote", lw = 2.5, marker = :diamond, color = :forestgreen)
110+
if save !== undef
111+
savefig(save)
53112
end
113+
end
54114

55-
plot(ns, dualvector_times, label="DualArrays")
115+
function plot_memory(save=undef; hs = [0.04, 0.02, 0.01, 0.005, 0.0025, 0.00125], epochs = 200, lr = 0.01)
116+
ns = Int.(2 ./ hs) .- 1
117+
dualvector_memory = Float64[]
118+
forwarddiff_memory = Float64[]
119+
zygote_memory = Float64[]
120+
121+
for (h, n) in zip(hs, ns)
122+
println("Computing solution with h = $h, n = $n")
123+
push!(dualvector_memory, @ballocated learn_catenary_dualarrays($h, cosh(-1), cosh(1), $epochs, $lr))
124+
push!(forwarddiff_memory, @ballocated learn_catenary_forwarddiff($h, cosh(-1), cosh(1), $epochs, $lr))
125+
push!(zygote_memory, @ballocated learn_catenary_zygote($h, cosh(-1), cosh(1), $epochs, $lr))
126+
end
127+
128+
plot(
129+
ns,
130+
dualvector_memory,
131+
;
132+
label = "DualArrays",
133+
title = "Catenary Gradient Descent Memory",
134+
xlabel = "Number of points (n)",
135+
ylabel = "Allocated bytes",
136+
yticks = 10 .^ collect(6:0.5:11),
137+
xticks = 10 .^ collect(1.5:0.25:3.5),
138+
plot_args...,
139+
)
140+
plot!(ns, forwarddiff_memory, label = "ForwardDiff", lw = 2.5, marker = :square, color = :darkorange2)
141+
plot!(ns, zygote_memory, label = "Zygote", lw = 2.5, marker = :diamond, color = :forestgreen)
142+
if save !== undef
143+
savefig(save)
144+
end
56145
end

examples/controlproblem.jl

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ using LinearAlgebra, DualArrays, Plots
1212
# Provided f ∈ L²(Ω), there exists a unique solution u ∈ H₀¹(Ω) to the boundary value problem.
1313
# A problem arises that given a desired solution d, we want to find f such that the solution u
1414
# is approximately d. An intuitive way of asking this is: "given that we want a certain temperature
15-
# field in a room, how do we heat or cool the walls?"
15+
# field in a room, how do we position heat sources/sinks?"
1616
#
1717
# A way of doing this is to define an objective to be minimised given by:
1818
#
@@ -79,7 +79,7 @@ function solve_1D(f0, d, h, kappa, alpha, iters = 30)
7979
K = (kappa / h ^ 2) * Tridiagonal(-ones(n - 1), 2 * ones(n), -ones(n - 1))
8080
for _ = 1:iters
8181
grads = grad_objective(K, DualVector(fvector, I(length(fvector))), d, alpha)
82-
step = grads.jacobian \ grads.value
82+
step = grads.jacobian.data \ grads.value
8383
fvector -= step
8484
end
8585

@@ -106,7 +106,7 @@ function solve_2D(x0, d, h, kappa, alpha, iters = 30)
106106
K = (kappa / h ^ 2) * (kron(I(n), T) + kron(T, I(n)))
107107
for _ = 1:iters
108108
grads = grad_objective(K, DualVector(fvector, I(length(fvector))), d, alpha)
109-
step = grads.jacobian \ grads.value
109+
step = grads.jacobian.data \ grads.value
110110
fvector -= step
111111
end
112112

@@ -115,7 +115,7 @@ end
115115

116116
# Solve the 1D Poisson control on the unit interval and plot the solution.
117117
# We compare it to a known analytical solution.
118-
function plot_solution_1D()
118+
function plot_solution_1D(save=undef)
119119

120120
# setup problem
121121
kappa = 1
@@ -132,13 +132,16 @@ function plot_solution_1D()
132132
fexact = coeff .* sin.(pi .* x[2:end-1])
133133

134134
f = solve_1D(zeros(length(d)), d, h, kappa, alpha)
135-
plot(x[2:end-1], f, label="Computed Solution")
136-
plot!(x[2:end-1], fexact, label="Exact Solution")
135+
plot(x[2:end-1], f, label="Computed Control", title="1D Poisson Control Solution")
136+
plot!(x[2:end-1], fexact, label="Exact Control")
137+
if save !== undef
138+
savefig(save)
139+
end
137140
end
138141

139142
# Solve the 2D Poisson control on the unit square and plot the solution.
140143
# We compare it to a known analytical solution given in the dolfin-adjoint example.
141-
function plot_solution_2D()
144+
function plot_solution_2D(save=undef)
142145

143146
# setup problem
144147
kappa = 1
@@ -164,5 +167,8 @@ function plot_solution_2D()
164167

165168
p1 = heatmap(x, x, F, title="Computed Control", aspect_ratio=1)
166169
p2 = heatmap(x, x, fexact, title="Exact Control", aspect_ratio=1)
167-
plot(p1, p2, layout=(1, 2), size=(900, 400))
170+
plot(p1, p2, layout=(1, 2), size=(900, 400), plot_title="2D Poisson Control Solution", plot_titlevspan=0.12)
171+
if save !== undef
172+
savefig(save)
173+
end
168174
end

examples/jacobians.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
#
1010
###
1111

12-
using DualArrays, ForwardDiff, Plots, BenchmarkTools
12+
using DualArrays, ForwardDiff, Plots, BenchmarkTools, BandedMatrices
1313

1414
f(x) = sin.(x)
1515

@@ -20,7 +20,7 @@ function plot_times()
2020

2121
for n in ns
2222
println("Computing jacobian for n = $n")
23-
push!(dualarray_times, @belapsed DualArrays.jacobian(f, rand($n)))
23+
push!(dualarray_times, @belapsed DualArrays.jacobian(f, rand($n), BandedMatrix))
2424
push!(forwarddiff_times, @belapsed ForwardDiff.jacobian(f, rand($n)))
2525
end
2626

examples/nestedduals.jl

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,14 +42,14 @@
4242
#
4343
# We now implement an example using f(x) = x[1] * x[2] below.
4444

45-
using DualArrays
45+
using DualArrays, SparseArrayKit
4646
f(x) = x[1] * x[2]
4747

4848
# a + Bϵ
4949
x = DualVector([2, 3], [1 0; 0 1])
5050

5151
# C + Dϵ
52-
y = DualMatrix([1 0;0 1], zeros(2,2,2))
52+
y = DualMatrix([1 0;0 1], SparseArray{Float64, 3}(undef, (2, 2, 2)))
5353

5454
# Setup the nested dual vector (a + Bϵ) + (C + Dϵ)ϵ'
5555
z = DualVector(x, y)
@@ -65,7 +65,7 @@ gradient = result.value.partials
6565
# equivalent to c
6666
gradient2 = result.partials.value
6767
# equivalent to d
68-
hessian = result.partials.jacobian.data
68+
_hessian = result.partials.jacobian.data
6969

7070
# We expect this to be 6
7171
print("Value: ", value, "\n")
@@ -74,5 +74,5 @@ print("Gradient: ", gradient, "\n")
7474
# We expect this to be the same
7575
print("Gradient: ", gradient2, "\n")
7676
# We expect this to be [0 1; 1 0]
77-
print("Hessian: ", hessian, "\n")
77+
print("Hessian: ", _hessian, "\n")
7878

0 commit comments

Comments
 (0)