Skip to content

Commit d1c2053

Browse files
Add adjoint-based global error control as a SciMLSensitivity extension
Carries SciML/GlobalDiffEq.jl#56 (GlobalAdjoint, adjoint_error_estimate; Cao and Petzold 2004, closes SciML/GlobalDiffEq.jl#4) into the monorepo, restructured as the package extension GlobalDiffEqSciMLSensitivityExt on SciMLSensitivity + QuadGK weakdeps. sensealg defaults to nothing and resolves to QuadratureAdjoint(autojacvec = true) when the extension loads; solving without the extension raises an instructive error. Public API and semantics otherwise match the original PR. Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
1 parent af88f3d commit d1c2053

10 files changed

Lines changed: 563 additions & 8 deletions

File tree

docs/src/globalerrorcontrol/GlobalDiffEq.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,16 @@ using GlobalDiffEq
1818
extrapolation over whole solves, interpreting `abstol` and `reltol` as global
1919
tolerances. It is the most robust and most expensive option.
2020

21+
To *control* the endpoint global error to a tolerance `gtol`, wrap any
22+
adaptive solver in [`GlobalAdjoint`](@ref) (adjoint-based, for endpoint
23+
functionals; requires SciMLSensitivity and QuadGK to be loaded). It solves the
24+
problem, estimates the endpoint global error, and tightens the local
25+
tolerances until the requested global tolerance is met.
26+
2127
## Global error controlling wrappers
2228

2329
```@docs
2430
GlobalRichardson
31+
GlobalAdjoint
32+
adjoint_error_estimate
2533
```

lib/GlobalDiffEq/Project.toml

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,25 @@
11
name = "GlobalDiffEq"
22
uuid = "1d72d19b-84cc-4cb7-a099-7cbdb9ccc67c"
33
authors = ["Chris Rackauckas <accounts@chrisrackauckas.com> and contributors"]
4-
version = "1.2.1"
4+
version = "1.3.0"
55

66
[deps]
77
DiffEqBase = "2b5f629d-d688-5b77-993f-72d75c75574e"
8+
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
89
OrdinaryDiffEqTsit5 = "b1df2697-797e-41e3-8120-5422d3b24e4a"
910
PrecompileTools = "aea7be01-6a6a-4083-8856-8a6e6704d82a"
11+
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
1012
Reexport = "189a3867-3050-52da-a836-e630ba90ab69"
1113
Richardson = "708f8203-808e-40c0-ba2d-98a6953ed40d"
1214
SciMLBase = "0bca4576-84f4-4d90-8ffe-ffa030f20462"
15+
SciMLStructures = "53ae85a6-f571-4167-b2af-e1d143709226"
16+
17+
[weakdeps]
18+
QuadGK = "1fd47b50-473d-5c70-9696-f719f8f3bcdc"
19+
SciMLSensitivity = "1ed8b502-d754-442c-8d5d-10ac956f44a1"
20+
21+
[extensions]
22+
GlobalDiffEqSciMLSensitivityExt = ["SciMLSensitivity", "QuadGK"]
1323

1424
[sources]
1525
DiffEqBase = {path = "../DiffEqBase"}
@@ -21,10 +31,14 @@ LinearAlgebra = "1"
2131
OrdinaryDiffEqSSPRK = "2"
2232
OrdinaryDiffEqTsit5 = "2"
2333
PrecompileTools = "1.1"
34+
QuadGK = "2.11"
35+
Random = "1"
2436
Reexport = "0.2, 1.0"
2537
Richardson = "1.2"
2638
SafeTestsets = "0.0.1, 0.1"
2739
SciMLBase = "3"
40+
SciMLSensitivity = "7.116"
41+
SciMLStructures = "1.10"
2842
SciMLTesting = "2.1"
2943
Test = "1"
3044
julia = "1.10"
@@ -33,9 +47,12 @@ julia = "1.10"
3347
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
3448
OrdinaryDiffEqSSPRK = "669c94d9-1f4b-4b64-b377-1aa079aa2388"
3549
OrdinaryDiffEqTsit5 = "b1df2697-797e-41e3-8120-5422d3b24e4a"
50+
QuadGK = "1fd47b50-473d-5c70-9696-f719f8f3bcdc"
51+
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
3652
SafeTestsets = "1bc83da4-3b8d-516f-aca4-4fe02f6d838f"
53+
SciMLSensitivity = "1ed8b502-d754-442c-8d5d-10ac956f44a1"
3754
SciMLTesting = "09d9d899-5365-40a9-917a-5f67fddea283"
3855
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
3956

4057
[targets]
41-
test = ["LinearAlgebra", "OrdinaryDiffEqSSPRK", "OrdinaryDiffEqTsit5", "SafeTestsets", "SciMLTesting", "Test"]
58+
test = ["LinearAlgebra", "OrdinaryDiffEqSSPRK", "OrdinaryDiffEqTsit5", "QuadGK", "Random", "SafeTestsets", "SciMLSensitivity", "SciMLTesting", "Test"]

lib/GlobalDiffEq/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ provides:
1212

1313
- `GlobalRichardson`: global Richardson extrapolation of whole solves of any
1414
fixed-step method, interpreting `abstol`/`reltol` as global tolerances.
15+
- `GlobalAdjoint`: adjoint-based a posteriori endpoint error estimation and
16+
control (Cao and Petzold 2004), available as a package extension when
17+
SciMLSensitivity and QuadGK are loaded.
1518

1619
See the [OrdinaryDiffEq.jl documentation](https://docs.sciml.ai/OrdinaryDiffEq/stable/)
1720
for the full API reference.
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
module GlobalDiffEqSciMLSensitivityExt
2+
3+
import GlobalDiffEq, LinearAlgebra, QuadGK, SciMLBase, SciMLSensitivity
4+
5+
function GlobalDiffEq._default_quadrature_sensealg()
6+
return SciMLSensitivity.QuadratureAdjoint(autojacvec = true)
7+
end
8+
9+
GlobalDiffEq._is_quadrature_adjoint(::SciMLSensitivity.QuadratureAdjoint) = true
10+
11+
function GlobalDiffEq._adjoint_solution(
12+
sol, sensealg::SciMLSensitivity.QuadratureAdjoint, adjoint_alg, direction;
13+
abstol, reltol
14+
)
15+
terminal_gradient! = let direction = direction
16+
function (out, u, p, t, i)
17+
copyto!(out, direction)
18+
return nothing
19+
end
20+
end
21+
terminal_time = sol.prob.tspan[2]
22+
adjoint_prob = SciMLSensitivity.ODEAdjointProblem(
23+
sol, sensealg, adjoint_alg, [terminal_time], terminal_gradient!
24+
)
25+
adjoint_sol = SciMLBase.solve(
26+
adjoint_prob, adjoint_alg;
27+
abstol, reltol, dense = true, save_everystep = true
28+
)
29+
SciMLBase.successful_retcode(adjoint_sol) ||
30+
throw(ErrorException("the adjoint solve failed with retcode $(adjoint_sol.retcode)"))
31+
return adjoint_sol
32+
end
33+
34+
function GlobalDiffEq._defect_projection(sol, adjoint_sol; abstol, reltol)
35+
prob = sol.prob
36+
isinplace = SciMLBase.isinplace(prob)
37+
38+
function integrand(t)
39+
u = sol(t, continuity = :right)
40+
du = sol(t, Val{1}, continuity = :right)
41+
rhs = if isinplace
42+
value = similar(u)
43+
prob.f(value, u, prob.p, t)
44+
value
45+
else
46+
prob.f(u, prob.p, t)
47+
end
48+
lambda = adjoint_sol(t, continuity = :right)
49+
return LinearAlgebra.dot(lambda, du - rhs)
50+
end
51+
52+
projection = zero(eltype(prob.u0))
53+
for i in 1:(length(sol.t) - 1)
54+
left = sol.t[i]
55+
right = sol.t[i + 1]
56+
left == right && continue
57+
interval_projection, _ = QuadGK.quadgk(
58+
integrand, left, right; atol = abstol, rtol = reltol
59+
)
60+
projection += interval_projection
61+
end
62+
return projection
63+
end
64+
65+
end

lib/GlobalDiffEq/src/GlobalDiffEq.jl

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,16 @@ module GlobalDiffEq
33
using Reexport: @reexport
44
@reexport using DiffEqBase
55

6-
import OrdinaryDiffEqTsit5, Richardson, SciMLBase
6+
import LinearAlgebra, OrdinaryDiffEqTsit5, Random, Richardson, SciMLBase,
7+
SciMLStructures
78
using PrecompileTools: @setup_workload, @compile_workload
89

910
abstract type GlobalDiffEqAlgorithm <: SciMLBase.AbstractODEAlgorithm end
1011

1112
include("richardson.jl")
13+
include("adjoint.jl")
1214

13-
export GlobalRichardson
15+
export GlobalAdjoint, GlobalRichardson, adjoint_error_estimate
1416

1517
@setup_workload begin
1618
# Simple test ODE: exponential decay du/dt = -u

0 commit comments

Comments
 (0)