Skip to content

Commit 5de3119

Browse files
committed
Add maxsol and minsol
1 parent 837de33 commit 5de3119

File tree

6 files changed

+134
-1
lines changed

6 files changed

+134
-1
lines changed

docs/pages.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22

33
pages = ["index.md",
44
"Getting Started with BVP solving in Julia" => "tutorials/getting_started.md",
5-
"Tutorials" => Any["tutorials/continuation.md", "tutorials/solve_nlls_bvp.md"],
5+
"Tutorials" => Any["tutorials/continuation.md",
6+
"tutorials/solve_nlls_bvp.md", "tutorials/inequality.md"],
67
"Basics" => Any["basics/bvp_problem.md", "basics/bvp_functions.md",
78
"basics/solve.md", "basics/autodiff.md", "basics/error_control.md"],
89
"Solver Summaries and Recommendations" => Any[

docs/src/tutorials/inequality.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Solve BVP with Inequality Boundary Conditions
2+
3+
When dealing with scenarios that boundary conditions are stronger than just explicit values at specific points—such as inequality boundary conditions, where the solution must satisfy constraints like staying within upper and lower bounds—we need a more flexible approach. In such cases, we can use the `maxsol` and `minsol` functions provided by BoundaryValueDiffEq.jl to specify such inequality conditions.
4+
5+
Let's walk through this functionlity with an intuitive example. We still revisit the simple pendulum example here, but this time, we’ll impose upper and lower bound constraints on the solution, specified as:
6+
7+
```math
8+
lb \leq u \leq ub
9+
```
10+
11+
where `lb=-4.8161991710010925` and `ub=5.0496477654230745`. So the states must be bigger than `lb` but smaller than `ub`. To solve such problems, we can simply use the `minsol` and `maxsol` functions when defining the boundary value problem in BoundaryValueDiffEq.jl.
12+
13+
```@example inequality
14+
tspan = (0.0, pi / 2)
15+
function simplependulum!(du, u, p, t)
16+
θ = u[1]
17+
dθ = u[2]
18+
du[1] = dθ
19+
du[2] = -9.81 * sin(θ)
20+
end
21+
function bc!(residual, u, p, t)
22+
residual[1] = maxsol(u, (0.0, pi / 2)) - 5.0496477654230745
23+
residual[2] = minsol(u, (0.0, pi / 2)) + 4.8161991710010925
24+
end
25+
prob = BVProblem(simplependulum!, bc!, [pi / 2, pi / 2], tspan)
26+
sol = solve(prob, MIRK4(), dt = 0.05)
27+
plot(sol)
28+
```

lib/BoundaryValueDiffEqCore/src/solution_utils.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,6 @@ Base.eltype(e::EvalSol) = eltype(e.u)
2929
function Base.show(io::IO, m::MIME"text/plain", e::EvalSol)
3030
(print(io, "t: "); show(io, m, e.t); println(io); print(io, "u: "); show(io, m, e.u))
3131
end
32+
33+
Base.maximum(sol::EvalSol) = maximum(Iterators.flatten(sol.u))
34+
Base.minimum(sol::EvalSol) = minimum(Iterators.flatten(sol.u))

lib/BoundaryValueDiffEqMIRK/src/BoundaryValueDiffEqMIRK.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,5 +160,6 @@ end
160160

161161
export MIRK2, MIRK3, MIRK4, MIRK5, MIRK6, MIRK6I
162162
export BVPJacobianAlgorithm
163+
export maxsol, minsol
163164

164165
end

lib/BoundaryValueDiffEqMIRK/src/interpolation.jl

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,89 @@ function (s::EvalSol{C})(tval::Number) where {C <: MIRKCache}
158158
return z
159159
end
160160

161+
# Interpolate intermidiate solution at multiple points
162+
function (s::EvalSol{C})(tvals::AbstractArray{<:Number}) where {C <: MIRKCache}
163+
(; t, u, cache) = s
164+
(; alg, stage, k_discrete, mesh_dt) = cache
165+
# Quick handle for the case where tval is at the boundary
166+
zvals = [zero(last(u)) for _ in tvals]
167+
for (i, tval) in enumerate(tvals)
168+
(tval == t[1]) && return first(u)
169+
(tval == t[end]) && return last(u)
170+
ii = interval(t, tval)
171+
dt = mesh_dt[ii]
172+
τ = (tval - t[ii]) / dt
173+
w, _ = evalsol_interp_weights(τ, alg)
174+
K = __needs_diffcache(alg.jac_alg) ? @view(k_discrete[ii].du[:, 1:stage]) :
175+
@view(k_discrete[ii][:, 1:stage])
176+
__maybe_matmul!(zvals[i], K, @view(w[1:stage]))
177+
zvals[i] .= zvals[i] .* dt .+ u[ii]
178+
end
179+
return zvals
180+
end
181+
182+
# Intermidiate derivative solution for evaluating boundry conditions
183+
function (s::EvalSol{C})(tval::Number, ::Type{Val{1}}) where {C <: MIRKCache}
184+
(; t, u, cache) = s
185+
(; alg, stage, k_discrete, mesh_dt) = cache
186+
z′ = zeros(typeof(tval), 2)
187+
ii = interval(t, tval)
188+
dt = mesh_dt[ii]
189+
τ = (tval - t[ii]) / dt
190+
_, w′ = interp_weights(τ, alg)
191+
__maybe_matmul!(z′, @view(k_discrete[ii].du[:, 1:stage]), @view(w′[1:stage]))
192+
return z′
193+
end
194+
195+
"""
196+
n root-finding problems to find the critical points with continuous derivative polynomials
197+
"""
198+
function __construct_then_solve_root_problem(
199+
sol::EvalSol{C}, tspan::Tuple) where {C <: MIRKCache}
200+
(; alg) = sol.cache
201+
n = first(size(sol))
202+
nlprobs = Vector{NonlinearProblem}(undef, n)
203+
for i in 1:n
204+
f = @closure (t, p) -> sol(t, Val{1})[i]
205+
nlprob = NonlinearProblem(f, sol.cache.prob.u0[i], tspan)
206+
nlprobs[i] = nlprob
207+
end
208+
nlsols = Vector{SciMLBase.NonlinearSolution}(undef, length(nlprobs))
209+
nlsolve_alg = __concrete_nonlinearsolve_algorithm(first(nlprobs), alg.nlsolve)
210+
for (i, nlprob) in enumerate(nlprobs)
211+
nlsols[i] = solve(nlprob, nlsolve_alg)
212+
end
213+
return nlsols
214+
end
215+
216+
# It turns out the critical points can't cover all possible maximum/minimum values
217+
# especially when the solution are monotonic, we still need to compare the extremes with
218+
# value at critical points to find the maximum/minimum
219+
220+
"""
221+
maxsol(sol::EvalSol, tspan::Tuple)
222+
223+
Find the maximum of the solution over the time span `tspan`.
224+
"""
225+
function maxsol(sol::EvalSol{C}, tspan::Tuple) where {C <: MIRKCache}
226+
nlsols = __construct_then_solve_root_problem(sol, tspan)
227+
tvals = map(nlsol -> (SciMLBase.successful_retcode(nlsol); return nlsol.u), nlsols)
228+
u = sol(tvals)
229+
return max(maximum(sol), maximum(Iterators.flatten(u)))
230+
end
231+
232+
"""
233+
minsol(sol::EvalSol, tspan::Tuple)
234+
235+
Find the minimum of the solution over the time span `tspan`.
236+
"""
237+
function minsol(sol::EvalSol{C}, tspan::Tuple) where {C <: MIRKCache}
238+
nlsols = __construct_then_solve_root_problem(sol, tspan)
239+
tvals = map(nlsol -> (SciMLBase.successful_retcode(nlsol); return nlsol.u), nlsols)
240+
u = sol(tvals)
241+
return min(minimum(sol), minimum(Iterators.flatten(u)))
242+
end
243+
161244
@inline function evalsol_interp_weights::T, ::MIRK2) where {T}
162245
w = [0, τ * (1 - τ / 2), τ^2 / 2]
163246

lib/BoundaryValueDiffEqMIRK/test/mirk_basic_tests.jl

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,3 +401,20 @@ end
401401
@test SciMLBase.successful_retcode(sol)
402402
end
403403
end
404+
405+
@testset "Test maxsol and minsol" begin
406+
tspan = (0.0, pi / 2)
407+
function simplependulum!(du, u, p, t)
408+
θ = u[1]
409+
= u[2]
410+
du[1] =
411+
du[2] = -9.81 * sin(θ)
412+
end
413+
function bc!(residual, u, p, t)
414+
residual[1] = maxsol(u, (0.0, pi / 2)) - 5.0496477654230745
415+
residual[2] = minsol(u, (0.0, pi / 2)) + 4.8161991710010925
416+
end
417+
prob = BVProblem(simplependulum!, bc!, [pi / 2, pi / 2], tspan)
418+
sol = solve(prob, MIRK4(), dt = 0.05)
419+
@test SciMLBase.successful_retcode(sol)
420+
end

0 commit comments

Comments
 (0)