Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add BVDAE solvers #217

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/Tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ jobs:
- "SHOOTING"
- "FIRK(EXPANDED)"
- "FIRK(NESTED)"
- "ASCHER"
- "WRAPPERS"
version: ['1.10']
uses: "SciML/.github/.github/workflows/tests.yml@v1"
Expand Down
21 changes: 21 additions & 0 deletions lib/BoundaryValueDiffEqAscher/LICENSE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
The BoundaryValueDiffEq.jl package is licensed under the MIT "Expat" License:

> Copyright (c) 2017: ChrisRackauckas.
>
> Permission is hereby granted, free of charge, to any person obtaining a copy
> of this software and associated documentation files (the "Software"), to deal
> in the Software without restriction, including without limitation the rights
> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
> copies of the Software, and to permit persons to whom the Software is
> furnished to do so, subject to the following conditions:
>
> The above copyright notice and this permission notice shall be included in all
> copies or substantial portions of the Software.
>
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
> SOFTWARE.
75 changes: 75 additions & 0 deletions lib/BoundaryValueDiffEqAscher/Project.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
name = "BoundaryValueDiffEqAscher"
uuid = "7227322d-7511-4e07-9247-ad6ff830280e"
authors = ["Qingyu Qu <erikqqy123@gmail.com>"]
version = "1.0.0"

[deps]
ADTypes = "47edcb42-4c32-4615-8424-f2b9edc5f35b"
Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e"
AlmostBlockDiagonals = "a95523ee-d6da-40b5-98cc-27bc505739d5"
ArrayInterface = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9"
BandedMatrices = "aae01518-5342-5314-be14-df237901396f"
BoundaryValueDiffEqCore = "56b672f2-a5fe-4263-ab2d-da677488eb3a"
ConcreteStructs = "2569d6c7-a4a2-43d3-a901-331e8e4be471"
DiffEqBase = "2b5f629d-d688-5b77-993f-72d75c75574e"
FastClosures = "9aa1b823-49e4-5ca5-8b0f-3971ec8bab6a"
ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
LinearSolve = "7ed4a6bd-45f5-4d41-b270-4a48e9bafcae"
Logging = "56ddb016-857b-54e1-b83d-db4d58db5568"
NonlinearSolve = "8913a72c-1f9b-4ce2-8d82-65094dcecaec"
PreallocationTools = "d236fae5-4411-538c-8e31-a6e3d9e00b46"
PrecompileTools = "aea7be01-6a6a-4083-8856-8a6e6704d82a"
Preferences = "21216c6a-2e73-6563-6e65-726566657250"
RecursiveArrayTools = "731186ca-8d62-57ce-b412-fbd966d074cd"
Reexport = "189a3867-3050-52da-a836-e630ba90ab69"
SciMLBase = "0bca4576-84f4-4d90-8ffe-ffa030f20462"
Setfield = "efcf1570-3423-57d1-acb7-fd33fddbac46"
SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
SparseDiffTools = "47a9eef4-7e08-11e9-0b38-333d64bd3804"

[compat]
ADTypes = "1.2"
Adapt = "4"
AlmostBlockDiagonals = "0.1.10"
ArrayInterface = "7.7"
BandedMatrices = "1.4"
BoundaryValueDiffEqCore = "1.0.0"
ConcreteStructs = "0.2.3"
DiffEqBase = "6.146"
DiffEqDevTools = "2.44"
FastClosures = "0.3"
ForwardDiff = "0.10.36"
JET = "0.8"
LinearAlgebra = "1.10"
LinearSolve = "2.21"
Logging = "1.10"
NonlinearSolve = "3.8.1"
PreallocationTools = "0.4.24"
PrecompileTools = "1.2"
Preferences = "1.4"
Random = "1.10"
ReTestItems = "1.23.1"
RecursiveArrayTools = "3.27.0"
Reexport = "1.2"
SciMLBase = "2.40"
Setfield = "1"
SparseArrays = "1.10"
SparseDiffTools = "2.14"
StaticArrays = "1.8.1"
Test = "1.10"
julia = "1.10"

[extras]
Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595"
DiffEqDevTools = "f3b72e0c-5b89-59e1-b016-84e28bfd966d"
JET = "c3a54625-cd67-489e-a8e7-0a5a0ff4e31b"
LinearSolve = "7ed4a6bd-45f5-4d41-b270-4a48e9bafcae"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
ReTestItems = "817f1d60-ba6b-4fd5-9520-3cf149f6a823"
RecursiveArrayTools = "731186ca-8d62-57ce-b412-fbd966d074cd"
StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[targets]
test = ["Aqua", "DiffEqDevTools", "JET", "LinearSolve", "Random", "ReTestItems", "RecursiveArrayTools", "StaticArrays", "Test"]
39 changes: 39 additions & 0 deletions lib/BoundaryValueDiffEqAscher/src/BoundaryValueDiffEqAscher.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
module BoundaryValueDiffEqAscher

using ADTypes
using AlmostBlockDiagonals
using BoundaryValueDiffEqCore
using ConcreteStructs
using FastClosures
using ForwardDiff
using LinearAlgebra
using NonlinearSolve
using PreallocationTools
using RecursiveArrayTools
using Reexport
using SciMLBase
using Setfield

import BoundaryValueDiffEqCore: BVPJacobianAlgorithm, __extract_problem_details,
concrete_jacobian_algorithm, __Fix3,
__concrete_nonlinearsolve_algorithm,
__unsafe_nonlinearfunction, BoundaryValueDiffEqAlgorithm,
__sparse_jacobian_cache, __vec, __vec_f, __vec_f!, __vec_bc,
__vec_bc!

import SciMLBase: AbstractDiffEqInterpolation, StandardBVProblem, __solve, _unwrap_val

@reexport using ADTypes, DiffEqBase, NonlinearSolve, SparseDiffTools, SciMLBase

include("types.jl")
include("utils.jl")
include("algorithms.jl")
include("alg_utils.jl")
include("ascher_tableaus.jl")
include("ascher.jl")
include("adaptivity.jl")
include("collocation.jl")

export Ascher1, Ascher2, Ascher3, Ascher4, Ascher5, Ascher6, Ascher7

end # module BoundaryValueDiffEqAscher
222 changes: 222 additions & 0 deletions lib/BoundaryValueDiffEqAscher/src/adaptivity.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
# mesh_selector refine nd redistribute according to the initial mesh
function mesh_selector!(
cache::AscherCache{iip, T}, z, dmz, mesh, mesh_dt, abstol) where {iip, T}
(; k, ncomp) = cache
# weights for mesh selection
cnsts2 = [1.25e-1, 2.604e-3, 8.019e-3, 2.170e-5, 7.453e-5, 5.208e-4, 9.689e-8,
3.689e-7, 3.100e-6, 2.451e-5, 2.691e-10, 1.120e-9, 1.076e-8, 9.405e-8,
1.033e-6, 5.097e-13, 2.290e-12, 2.446e-11, 2.331e-10, 2.936e-9, 3.593e-8,
7.001e-16, 3.363e-15, 3.921e-14, 4.028e-13, 5.646e-12, 7.531e-11, 1.129e-9]

koff = Int(k * (k + 1) / 2)
wgtmsh = 10 * cnsts2[koff]
root = 1 / (k + 1)
n = length(mesh) - 1
slope = Vector{T}(undef, n)
accum = Vector{T}(undef, n + 1)
d = Vector{T}(undef, ncomp)
d1 = similar(d)
d2 = similar(d)
# the first interval has to be treated separately from the
# other intervals (generally the solution on the (i-1)st and ith
# intervals will be used to approximate the needed derivative, but
# here the 1st and second intervals are used.)
hiold = mesh_dt[1]
@views horder(cache, d1, hiold, dmz[1])
hiold = mesh_dt[2]
@views horder(cache, d2, hiold, dmz[2])
oneovh = 2.0 / (mesh[3] - mesh[1])
slp = @. (abs(d2 - d1) * wgtmsh * oneovh / (abstol * (T(1) + abs(z[1]))))^root
slope[1] = maximum(slp)
slphmx = slope[1] * mesh_dt[1]
accum[2] = slphmx
iflip = 1

# go through the remaining intervals generating slope
# and accum
for i in 2:n
hiold = mesh_dt[i]
iflip == -1 && @views horder(cache, d1, hiold, dmz[i])
iflip == 1 && @views horder(cache, d2, hiold, dmz[i])
oneovh = 2.0 / (mesh[i + 1] - mesh[i - 1])

# evaluate function to be equidistributed
slp = @. (abs(d2 - d1) * wgtmsh * oneovh / (abstol * (T(1) + abs(z[i]))))^root
slope[i] = maximum(slp)

# accumulate approximate integral of function to be equidistributed
temp = slope[i] * hiold
slphmx = max(slphmx, temp)
accum[i + 1] = accum[i] + temp
iflip = -iflip
end

avrg = accum[n + 1] / n
# `degequ` is the degree of equidistribution which then is used to
# determine whether the mesh redistribution is worthwhile or just
# halving the mesh is enough.
degequ = avrg / max(slphmx, eps(T))

# expected n to achieve 0.1x user requested tolerances
naccum = floor(Int, accum[n + 1] + 1)

# decide if mesh selection is worthwhile (otherwise, directly halving mesh is enough)
if (avrg < eps(T)) || (degequ >= 0.5)
# Just continue with utilizing the halved mesh which was used for error estimation
return
else
redistribute!(cache, length(cache.original_mesh), naccum, slope, accum)
end
end

function redistribute!(cache::AscherCache{iip, T}, nold::I, naccum::I,
slope::Vector{T}, accum::Vector{T}) where {iip, T, I <: Integer}
(; prob, fixpnt, mesh, mesh_dt) = cache
n::Int = length(slope)
nmax = copy(n)
mesh_old = copy(cache.original_mesh)
# nmx assures mesh has at least half as many subintervals as the
# previous mesh
nmx = max(nold + 1, naccum) / 2

# assures that halving will be possible later
nmax2 = nmax / 2

# the mesh is at most halved
n = min(nmax2, nold, nmx)

noldp1 = nold + 1
nfxp1 = length(fixpnt) + 1
# ensure that fixpnt is included in the new mesh
(n < nfxp1) && (n = nfxp1)

# having decided to generate a new mesh with n subintervals we now
# do so, taking into account that the nfxpnt points in the array
# fixpnt must be included in the new mesh.
inn = 1
accl = T(0)
lold = 2
lcarry = 0
lnew = 0
resize!(mesh, n + 1)
mesh[1] = first(prob.tspan)
mesh[n + 1] = last(prob.tspan)

for i in 1:nfxp1
if i !== nfxp1
for j in lold:noldp1
lnew = j
(fixpnt[i] <= mesh_old[j]) && break
end
accr = accum[lnew] + (fixpnt[i] - mesh_old[lnew]) * slope[lnew - 1]
nregn = (accr - accl) / accum[noldp1] * n - T(0.5)
nregn = min(nregn, n - inn - nfxp1 + i)
mesh[inn + nregn + 1] = fixpnt[i]
else
accr = accum[noldp1]
lnew = noldp1
nregn = n - inn
end
if nregn !== 0
temp = accl
tsum = (accr - accl) / (nregn + 1)
for j in 1:nregn
inn = inn + 1
temp = temp + tsum
for l in lold:lnew
lcarry = l
(temp <= accum[l]) && break
end
lold = lcarry
mesh[inn] = mesh_old[lold - 1] + (temp - accum[lold - 1]) / slope[lold - 1]
end
end
inn = inn + 1
accl = accr
lold = lnew
end
mesh_dt = diff(mesh)
end

function halve_mesh!(cache::AscherCache)
(; mesh, mesh_dt, valstr) = cache
n = length(mesh) - 1
old_mesh = copy(mesh)
for i in 1:n
x = mesh[i]
hd6 = mesh_dt[i] / 6.0
for j in 1:4
x = x + hd6
(j == 3) && (x = x + hd6)
@views approx(cache, x, valstr[i][j])
end
end

# halve the current mesh
N = 2 * n
resize!(mesh, N + 1)
resize!(mesh_dt, N)
mesh[1] = old_mesh[1]

for i in 1:n
mesh[2i] = (old_mesh[i] + old_mesh[i + 1]) / 2.0
mesh[2i + 1] = old_mesh[i + 1]
end
mesh_dt[1:end] = diff(mesh)[1:end]
end

# determine the error estimate and test to see if the
# error tolerances are satisfied
function error_estimate!(cache::AscherCache)
(; k, valstr, mesh, mesh_dt, error) = cache
# weights for extrapolation error estimate
cnsts1 = [0.25e0, 0.625e-1, 7.2169e-2, 1.8342e-2, 1.9065e-2, 5.8190e-2, 5.4658e-3,
5.3370e-3, 1.8890e-2, 2.7792e-2, 1.6095e-3, 1.4964e-3, 7.5938e-3, 5.7573e-3,
1.8342e-2, 4.673e-3, 4.150e-4, 1.919e-3, 1.468e-3, 6.371e-3, 4.610e-3,
1.342e-4, 1.138e-4, 4.889e-4, 4.177e-4, 1.374e-3, 1.654e-3, 2.863e-3]
# assign weights for error estimate
koff = Int(k * (k + 1) / 2)
wgterr = cnsts1[koff]
n = length(mesh) - 1

# error estimates are to be generated and tested
# to see if the tolerance requirements are satisfied.
for i in n:-1:1
# the error estimates are obtained by combining values of the numerical solutions for two meshes.
# for each value of iback we will consider the two approximation at 2 points in each of
# the new subintervals. we work backwards through the subinterval so that new values can be stored
# in valstr in case they prove to be needed later for an error estimate.
x = mesh[i] + (mesh_dt[i]) * 2.0 / 3.0
@views approx(cache, x, valstr[i][3])
error[i] .= wgterr .*
abs.(valstr[i][3] .-
(isodd(i) ? valstr[Int((i + 1) / 2)][2] : valstr[Int(i / 2)][4]))

x = mesh[i] + (mesh_dt[i]) / 3.0
@views approx(cache, x, valstr[i][2])
error[i] .= error[i] .+
wgterr .*
abs.(valstr[i][2] .-
(isodd(i) ? valstr[Int((i + 1) / 2)][1] : valstr[Int(i / 2)][3]))
end
return maximum(reduce(hcat, error), dims = 2)
end

# determine highest order (piecewise constant) derivatives
# of the current collocation solution
function horder(cache::AscherCache, uhigh, hi, dmzi)
(; ncomp, k, TU) = cache
(; coef) = TU
dn = 1.0 / hi^(k - 1)
uhigh[1:ncomp] .= 0.0

idmz = 1
vdmzi = reduce(vcat, dmzi)
for j in 1:k
fact = dn * coef[1, j]
for id in 1:ncomp
uhigh[id] = uhigh[id] + fact * vdmzi[idmz]
idmz = idmz + 1
end
end
end
6 changes: 6 additions & 0 deletions lib/BoundaryValueDiffEqAscher/src/alg_utils.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
for stage in (1, 2, 3, 4, 5, 6, 7)
alg = Symbol("Ascher$(stage)")
@eval alg_stage(::$(alg)) = $stage
end

SciMLBase.isadaptive(alg::AbstractAscher) = true
Loading
Loading