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

Function space SBP operators #268

Merged
merged 47 commits into from
Jun 14, 2024
Merged
Show file tree
Hide file tree
Changes from 41 commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
d888354
first draft for function space operators
JoshuaLampert May 24, 2024
7fe3217
some spaces
JoshuaLampert May 24, 2024
72e1522
order alphabetically
JoshuaLampert May 24, 2024
5f85d67
don't force basis_function to be passed as vector
JoshuaLampert May 25, 2024
f7a0076
allow passing accuracy_order
JoshuaLampert May 25, 2024
944046b
first (basically) working version
JoshuaLampert May 28, 2024
18ce612
orthonormalization via Gram-Schmidt
JoshuaLampert May 28, 2024
b576ece
add some basic tests
JoshuaLampert May 28, 2024
f3f7208
some smaller optimizations
JoshuaLampert May 28, 2024
cd92e53
fix import of Options
JoshuaLampert May 28, 2024
8c77cd5
another fix...
JoshuaLampert May 28, 2024
d62d73b
avoid unnecessary anonymous function
JoshuaLampert May 28, 2024
9d7cc4a
use version of spzeros that is compatible with older Julia versions
JoshuaLampert May 28, 2024
a2ef507
FunctionSpaceOperator -> function_space_operator
JoshuaLampert May 28, 2024
b4b4c7d
add proper docstring
JoshuaLampert May 28, 2024
1b67472
extend docstring
JoshuaLampert May 28, 2024
d4266fe
put constructor function in extension
JoshuaLampert May 28, 2024
1b8d85e
allow passing options
JoshuaLampert May 28, 2024
76f7f3f
clarify that the operator is of derivative_order 1
JoshuaLampert May 28, 2024
d52d55c
only Optim has to be loaded
JoshuaLampert May 29, 2024
aa868bd
only for Julia 1.9
JoshuaLampert May 29, 2024
ad62dcc
remove fallback imports with Requires
JoshuaLampert May 29, 2024
2144cd1
use PreallocationTools.jl to reduce allocations
JoshuaLampert May 30, 2024
29f6830
add kwarg derivative-order
JoshuaLampert May 30, 2024
d4ec79e
add analytical gradient (still much slower than ForwardDiff.jl)
JoshuaLampert May 31, 2024
578a7d2
fuse function and gradient evaluation
JoshuaLampert May 31, 2024
fff300b
avoid some type conversions
JoshuaLampert May 31, 2024
9af4c6e
use div instead of sum
JoshuaLampert May 31, 2024
540db12
Apply suggestions from code review
JoshuaLampert May 31, 2024
c133295
change order of loops
JoshuaLampert May 31, 2024
f77df41
Update ext/SummationByPartsOperatorsOptimForwardDiffExt.jl
JoshuaLampert May 31, 2024
519edcd
import dot
JoshuaLampert May 31, 2024
feacf4e
move optimization_function_and_grad to fix type instability introduce…
ranocha May 31, 2024
8ebe174
move optimization_function_and_grad! outside (that was it!)
JoshuaLampert May 31, 2024
b783475
reolve merge conflict
JoshuaLampert May 31, 2024
047051b
Update ext/SummationByPartsOperatorsOptimForwardDiffExt.jl
JoshuaLampert May 31, 2024
0bd5619
some cleanups
JoshuaLampert May 31, 2024
46b15be
Merge branch 'main' into FSBP-extension
JoshuaLampert Jun 13, 2024
914cab2
ignore stale dependency PreallocationTools in Aqua
JoshuaLampert Jun 13, 2024
1a2a376
add test with non-equidistant nodes
JoshuaLampert Jun 14, 2024
03107f9
fix test
JoshuaLampert Jun 14, 2024
3e03c73
remove kwargs x_min and x_max
JoshuaLampert Jun 14, 2024
38081b1
add experimental feature warning
JoshuaLampert Jun 14, 2024
4e9512c
Update test/matrix_operators_test.jl
JoshuaLampert Jun 14, 2024
fd470ee
remove l_hat2 since it this case is impossible
JoshuaLampert Jun 14, 2024
7f54a30
remove PreallocationTools again
JoshuaLampert Jun 14, 2024
b62f40b
throw ArgumentError instead of assertion for wrong derivative_order
JoshuaLampert Jun 14, 2024
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,6 @@ docs/build
docs/src/code_of_conduct.md
docs/src/contributing.md
docs/src/license.md

run
run/*
6 changes: 6 additions & 0 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
LoopVectorization = "bdcacae8-1622-11e9-2a5c-532679323890"
MuladdMacro = "46d2c3a1-f734-5fdb-9937-b9b9aeba4221"
PolynomialBases = "c74db56a-226d-5e98-8bb0-a6049094aeea"
PreallocationTools = "d236fae5-4411-538c-8e31-a6e3d9e00b46"
PrecompileTools = "aea7be01-6a6a-4083-8856-8a6e6704d82a"
RecursiveArrayTools = "731186ca-8d62-57ce-b412-fbd966d074cd"
Reexport = "189a3867-3050-52da-a836-e630ba90ab69"
Expand All @@ -27,12 +28,14 @@ Unrolled = "9602ed7d-8fef-5bc8-8597-8f21381861e8"
BandedMatrices = "aae01518-5342-5314-be14-df237901396f"
DiffEqCallbacks = "459566f4-90b8-5000-8ac3-15dfb0a30def"
ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"
Optim = "429524aa-4258-5aef-a3af-852621145aeb"
StructArrays = "09ab397b-f2b6-538f-b94a-2f83cf4a842a"

[extensions]
SummationByPartsOperatorsBandedMatricesExt = "BandedMatrices"
SummationByPartsOperatorsDiffEqCallbacksExt = "DiffEqCallbacks"
SummationByPartsOperatorsForwardDiffExt = "ForwardDiff"
SummationByPartsOperatorsOptimForwardDiffExt = ["Optim", "ForwardDiff"]
SummationByPartsOperatorsStructArraysExt = "StructArrays"

[compat]
Expand All @@ -46,7 +49,9 @@ InteractiveUtils = "1"
LinearAlgebra = "1"
LoopVectorization = "0.12.22"
MuladdMacro = "0.2"
Optim = "1"
PolynomialBases = "0.4.15"
PreallocationTools = "0.4"
PrecompileTools = "1.0.1"
RecursiveArrayTools = "2.11, 3"
Reexport = "0.2, 1.0"
Expand All @@ -64,4 +69,5 @@ julia = "1.6"
BandedMatrices = "aae01518-5342-5314-be14-df237901396f"
DiffEqCallbacks = "459566f4-90b8-5000-8ac3-15dfb0a30def"
ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"
Optim = "429524aa-4258-5aef-a3af-852621145aeb"
StructArrays = "09ab397b-f2b6-538f-b94a-2f83cf4a842a"
222 changes: 222 additions & 0 deletions ext/SummationByPartsOperatorsOptimForwardDiffExt.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
module SummationByPartsOperatorsOptimForwardDiffExt

using Optim: Optim, Options, LBFGS, optimize, minimizer
using ForwardDiff: ForwardDiff

using SummationByPartsOperators: SummationByPartsOperators, GlaubitzNordströmÖffner2023, MatrixDerivativeOperator
using LinearAlgebra: Diagonal, LowerTriangular, dot, diag, norm, mul!
using PreallocationTools: DiffCache, get_tmp
ranocha marked this conversation as resolved.
Show resolved Hide resolved
using SparseArrays: spzeros

function SummationByPartsOperators.function_space_operator(basis_functions,
x_min::T, x_max::T, nodes::Vector{T},
ranocha marked this conversation as resolved.
Show resolved Hide resolved
source::SourceOfCoefficients;
derivative_order = 1, accuracy_order = 0,
options = Options(g_tol = 1e-14, iterations = 10000)) where {T, SourceOfCoefficients}

@assert derivative_order == 1 "Only first derivative operators are supported"
sort!(nodes)
JoshuaLampert marked this conversation as resolved.
Show resolved Hide resolved
weights, D = construct_function_space_operator(basis_functions, x_min, x_max, nodes, source; options = options)
return MatrixDerivativeOperator(x_min, x_max, nodes, weights, D, accuracy_order, source)
end

function inner_H1(f, g, f_derivative, g_derivative, nodes)
return sum(f.(nodes) .* g.(nodes) + f_derivative.(nodes) .* g_derivative.(nodes))
end
norm_H1(f, f_derivative, nodes) = sqrt(inner_H1(f, f, f_derivative, f_derivative, nodes))

call_orthonormal_basis_function(A, basis_functions, k, x) = sum([basis_functions[i](x)*A[k, i] for i in 1:k])

# This will orthonormalize the basis functions using the Gram-Schmidt process to reduce the condition
# number of the Vandermonde matrix. The matrix A transfers the old basis functions to the new orthonormalized by
# g(x) = A * f(x), where f(x) is the vector of old basis functions and g(x) is the vector of the new orthonormalized
# basis functions. Analogously, we have g'(x) = A * f'(x).
function orthonormalize_gram_schmidt(basis_functions, basis_functions_derivatives, nodes)
K = length(basis_functions)

A = LowerTriangular(zeros(eltype(nodes), K, K))

basis_functions_orthonormalized = Vector{Function}(undef, K)
basis_functions_orthonormalized_derivatives = Vector{Function}(undef, K)

for k = 1:K
A[k, k] = 1
for j = 1:k-1
g(x) = call_orthonormal_basis_function(A, basis_functions, j, x)
g_derivative(x) = call_orthonormal_basis_function(A, basis_functions_derivatives, j, x)
inner_product = inner_H1(basis_functions[k], g, basis_functions_derivatives[k], g_derivative, nodes)
norm_squared = inner_H1(g, g, g_derivative, g_derivative, nodes)
A[k, :] = A[k, :] - inner_product/norm_squared * A[j, :]
end

basis_functions_orthonormalized[k] = x -> call_orthonormal_basis_function(A, basis_functions, k, x)
basis_functions_orthonormalized_derivatives[k] = x -> call_orthonormal_basis_function(A, basis_functions_derivatives, k, x)
# Normalization
r = norm_H1(basis_functions_orthonormalized[k], basis_functions_orthonormalized_derivatives[k], nodes)
A[k, :] = A[k, :] / r
end
return basis_functions_orthonormalized, basis_functions_orthonormalized_derivatives
end

function vandermonde_matrix(functions, nodes)
N = length(nodes)
K = length(functions)
V = zeros(eltype(nodes), N, K)
for i in 1:N
for j in 1:K
V[i, j] = functions[j](nodes[i])
end
end
return V
end

function create_S(sigma, N)
S = zeros(eltype(sigma), N, N)
set_S!(S, sigma, N)
return S
end

function set_S!(S, sigma, N)
k = 1
for i in 1:N
for j in (i + 1):N
S[i, j] = sigma[k]
S[j, i] = -sigma[k]
k += 1
end
end
end

sig(x) = 1 / (1 + exp(-x))
sig_deriv(x) = sig(x) * (1 - sig(x))

function create_P(rho, x_length)
P = Diagonal(sig.(rho))
P *= x_length / sum(P)
return P
end

function construct_function_space_operator(basis_functions, x_min, x_max, nodes,
::GlaubitzNordströmÖffner2023;
options = Options(g_tol = 1e-14, iterations = 10000))
K = length(basis_functions)
N = length(nodes)
L = div(N * (N - 1), 2)
basis_functions_derivatives = [x -> ForwardDiff.derivative(basis_functions[i], x) for i in 1:K]
basis_functions_orthonormalized, basis_functions_orthonormalized_derivatives = orthonormalize_gram_schmidt(basis_functions,
basis_functions_derivatives,
nodes)
V = vandermonde_matrix(basis_functions_orthonormalized, nodes)
V_x = vandermonde_matrix(basis_functions_orthonormalized_derivatives, nodes)
# Here, W satisfies W'*W = I
# W = [V; -V_x]

B = spzeros(eltype(nodes), N, N)
B[1, 1] = -1
B[N, N] = 1

R = B * V / 2
x_length = x_max - x_min
S = zeros(eltype(nodes), N, N)
A = zeros(eltype(nodes), N, K)
SV = zeros(eltype(nodes), N, K)
PV_x = zeros(eltype(nodes), N, K)
S_cache = DiffCache(S)
A_cache = DiffCache(A)
SV_cache = DiffCache(SV)
PV_x_cache = DiffCache(PV_x)
daij_dsigmak = zeros(eltype(nodes), N, K, L)
daij_drhok = zeros(eltype(nodes), N, K, N)
p = (V, V_x, R, x_length, S_cache, A_cache, SV_cache, PV_x_cache, daij_dsigmak, daij_drhok)

x0 = zeros(L + N)
fg!(F, G, x) = optimization_function_and_grad!(F, G, x, p)
result = optimize(Optim.only_fg!(fg!), x0, LBFGS(), options)

x = minimizer(result)
sigma = x[1:L]
rho = x[(L + 1):end]
S = create_S(sigma, N)
P = create_P(rho, x_length)
weights = diag(P)
Q = S + B/2
D = inv(P) * Q
return weights, D
end

@views function optimization_function_and_grad!(F, G, x, p)
V, V_x, R, x_length, S_cache, A_cache, SV_cache, PV_x_cache, daij_dsigmak, daij_drhok = p
S = get_tmp(S_cache, x)
A = get_tmp(A_cache, x)
SV = get_tmp(SV_cache, x)
PV_x = get_tmp(PV_x_cache, x)
(N, _) = size(R)
L = div(N * (N - 1), 2)
sigma = x[1:L]
rho = x[(L + 1):end]
set_S!(S, sigma, N)
P = create_P(rho, x_length)
mul!(SV, S, V)
mul!(PV_x, P, V_x)
@. A = SV - PV_x + R
if !isnothing(G)
fill!(daij_dsigmak, zero(eltype(daij_dsigmak)))
for k in axes(daij_dsigmak, 3)
for j in axes(daij_dsigmak, 2)
for i in axes(daij_dsigmak, 1)
l_tilde = k + i - N * (i - 1) + div(i * (i - 1), 2)
# same as above, but needs more type conversions
# l_tilde = Int(k + i - (i - 1) * (N - i/2))
if i + 1 <= l_tilde <= N
daij_dsigmak[i, j, k] += V[l_tilde, j]
else
C = N^2 - 3*N + 2*i - 2*k + 1/4
if C >= 0
D = sqrt(C)
D_plus_one_half = D + 0.5
D_plus_one_half_trunc = trunc(D_plus_one_half)
if D_plus_one_half == D_plus_one_half_trunc
int_D_plus_one_half = trunc(Int, D_plus_one_half_trunc)
l_hat1 = N + int_D_plus_one_half - 1
l_hat2 = N - int_D_plus_one_half
if 1 <= l_hat1 <= i - 1
daij_dsigmak[i, j, k] -= V[l_hat1, j]
ranocha marked this conversation as resolved.
Show resolved Hide resolved
end
if 1 <= l_hat2 <= i - 1
daij_dsigmak[i, j, k] -= V[l_hat2, j]
end
end
end
end
end
end
end
sig_rho = sig.(rho)
sig_deriv_rho = sig_deriv.(rho)
sum_sig_rho = sum(sig_rho)
for k in axes(daij_drhok, 3)
for j in axes(daij_drhok, 2)
for i in axes(daij_drhok, 1)
factor1 = x_length * V_x[i, j] / sum_sig_rho^2
factor = factor1 * sig_deriv_rho[k]
if k == i
daij_drhok[i, j, k] = -factor * (sum_sig_rho - sig_rho[k])
else
daij_drhok[i, j, k] = factor * sig_rho[i]
end
end
end
end
for k in axes(daij_dsigmak, 3)
G[k] = 2 * dot(daij_dsigmak[:, :, k], A)
end
for k in axes(daij_drhok, 3)
G[L + k] = 2 * dot(daij_drhok[:, :, k], A)
end
end
if !isnothing(F)
return norm(A)^2
end
end

end # module
4 changes: 3 additions & 1 deletion src/SummationByPartsOperators.jl
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ include("fourier_operators.jl")
include("fourier_operators_2d.jl")
include("legendre_operators.jl")
include("matrix_operators.jl")
include("function_space_operators.jl")
include("upwind_operators.jl")
include("SBP_coefficients/MattssonNordström2004.jl")
include("SBP_coefficients/MattssonSvärdNordström2004.jl")
Expand Down Expand Up @@ -154,7 +155,7 @@ export periodic_central_derivative_operator, periodic_derivative_operator, deriv
dissipation_operator, var_coef_derivative_operator,
fourier_derivative_operator,
legendre_derivative_operator, legendre_second_derivative_operator,
upwind_operators
upwind_operators, function_space_operator
export UniformMesh1D, UniformPeriodicMesh1D
export couple_continuously, couple_discontinuously
export mul!
Expand All @@ -169,6 +170,7 @@ export MattssonNordström2004, MattssonSvärdNordström2004, MattssonSvärdShoey
SharanBradyLivescu2022
export Tadmor1989, MadayTadmor1989, Tadmor1993,
TadmorWaagan2012Standard, TadmorWaagan2012Convergent
export GlaubitzNordströmÖffner2023

export BurgersPeriodicSemidiscretization, BurgersNonperiodicSemidiscretization,
CubicPeriodicSemidiscretization, CubicNonperiodicSemidiscretization,
Expand Down
60 changes: 60 additions & 0 deletions src/function_space_operators.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@

"""
GlaubitzNordströmÖffner2023()

Function space SBP (FSBP) operators given in
- Glaubitz, Nordström, Öffner (2023)
Summation-by-parts operators for general function spaces.
SIAM Journal on Numerical Analysis 61, 2, pp. 733-754.

See also
- Glaubitz, Nordström, Öffner (2024)
An optimization-based construction procedure for function space based
summation-by-parts operators on arbitrary grids.
arXiv, arXiv:2405.08770v1.

See [`function_space_operator`](@ref).
"""
struct GlaubitzNordströmÖffner2023 <: SourceOfCoefficients end

function Base.show(io::IO, source::GlaubitzNordströmÖffner2023)
if get(io, :compact, false)
summary(io, source)
else
print(io,
"Glaubitz, Nordström, Öffner (2023) \n",
" Summation-by-parts operators for general function spaces \n",
" SIAM Journal on Numerical Analysis 61, 2, pp. 733-754. \n",
"See also \n",
" Glaubitz, Nordström, Öffner (2024) \n",
" An optimization-based construction procedure for function \n",
" space based summation-by-parts operators on arbitrary grids \n",
" arXiv, arXiv:2405.08770v1.")
end
end

# This function is extended in the package extension SummationByPartsOperatorsOptimExt
"""
function_space_operator(basis_functions, x_min, x_max, nodes, source;
derivative_order = 1, accuracy_order = 0,
options = Optim.Options(g_tol = 1e-14, iterations = 10000))

Construct an operator that represents a first-derivative operator in a function space spanned by
the `basis_functions`, which is an iterable of functions. The operator is constructed on the
interval `[x_min, x_max]` with the nodes `nodes`. The `accuracy_order` is the order of the
accuracy of the operator, which can optionally be passed, but does not have any effect on the
operator. The operator is constructed solving an optimization problem with Optim.jl. You
can specify the options for the optimization problem with the `options` argument, see also
the [documentation of Optim.jl](https://julianlsolvers.github.io/Optim.jl/stable/user/config/).

The operator that is returned follows the general interface. Currently, it is wrapped in a
[`MatrixDerivativeOperator`](@ref), but this might change in the future.
In order to use this function, the package `Optim` must be loaded.

See also [`GlaubitzNordströmÖffner2023`](@ref).

!!! compat "Julia 1.9"
This function requires at least Julia 1.9.
"""
function function_space_operator end

2 changes: 2 additions & 0 deletions test/Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ BandedMatrices = "aae01518-5342-5314-be14-df237901396f"
DiffEqCallbacks = "459566f4-90b8-5000-8ac3-15dfb0a30def"
ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
Optim = "429524aa-4258-5aef-a3af-852621145aeb"
OrdinaryDiffEq = "1dea7af3-3e70-54e6-95c3-0bf5283fa5ed"
SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
SpecialFunctions = "276daf66-3868-5448-9aa4-cd146d93841b"
Expand All @@ -16,6 +17,7 @@ Aqua = "0.8"
BandedMatrices = "0.15, 0.16, 0.17, 1"
DiffEqCallbacks = "2, 3"
ForwardDiff = "0.10"
Optim = "1"
OrdinaryDiffEq = "5, 6"
SpecialFunctions = "1, 2"
StaticArrays = "1.0"
Expand Down
2 changes: 1 addition & 1 deletion test/aqua.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ using SummationByPartsOperators
Aqua.test_all(SummationByPartsOperators;
ambiguities = false, # a lot of false positives from dependencies
unbound_args = false, # TODO: a strange problem I do not understand right now
stale_deps = (; ignore = [:PrecompileTools]),
stale_deps = (; ignore = [:PrecompileTools, :PreallocationTools]),
)
Loading
Loading