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 13 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/*
4 changes: 4 additions & 0 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,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,6 +48,7 @@ InteractiveUtils = "1"
LinearAlgebra = "1"
LoopVectorization = "0.12.22"
MuladdMacro = "0.2"
Optim = "1"
PolynomialBases = "0.4.15"
PrecompileTools = "1.0.1"
RecursiveArrayTools = "2.11, 3"
Expand All @@ -64,4 +67,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"
132 changes: 132 additions & 0 deletions ext/SummationByPartsOperatorsOptimForwardDiffExt.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
module SummationByPartsOperatorsOptimForwardDiffExt

if isdefined(Base, :get_extension)
using Optim: Options, LBFGS, optimize, minimizer
using ForwardDiff: ForwardDiff
else
using ..Optim: Options, LBFGS, optimize, minimizer
using ..ForwardDiff: ForwardDiff
end

using SummationByPartsOperators: SummationByPartsOperators, GlaubitzNordströmÖffner2023
using LinearAlgebra: Diagonal, LowerTriangular, diag, norm, cond
using SparseArrays: spzeros

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)
k = 1
for i in 1:N
for j in (i + 1):N
S[i, j] = sigma[k]
k += 1
end
end
return S - S'
end

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

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

function SummationByPartsOperators.construct_function_space_operator(basis_functions, x_min, x_max, nodes,
::GlaubitzNordströmÖffner2023)
K = length(basis_functions)
N = length(nodes)
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
p = (W, R, x_length)
@views function optimization_function(x, p)
W, R, x_length = p
(N, _) = size(R)
L = Integer(N*(N - 1)/2)
JoshuaLampert marked this conversation as resolved.
Show resolved Hide resolved
sigma = x[1:L]
rho = x[(L + 1):end]
S = create_S(sigma, N)
P = create_P(rho, x_length)
X = [S P]
# TODO: Which norm to use? XW + R is a NxK matrix. This uses the Frobenius norm.
return norm(X * W + R)^2
end
L = Integer(N*(N - 1)/2)
x0 = zeros(L + N)
result = optimize(x -> optimization_function(x, p), x0, LBFGS(), Options(g_tol = 1e-14, iterations = 10000),
autodiff = :forward)
JoshuaLampert marked this conversation as resolved.
Show resolved Hide resolved
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

end # module
7 changes: 6 additions & 1 deletion src/SummationByPartsOperators.jl
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@ function __init__()
@require BandedMatrices="aae01518-5342-5314-be14-df237901396f" include("../ext/SummationByPartsOperatorsBandedMatricesExt.jl")
@require DiffEqCallbacks="459566f4-90b8-5000-8ac3-15dfb0a30def" include("../ext/SummationByPartsOperatorsDiffEqCallbacksExt.jl")
@require ForwardDiff="f6369f11-7733-5829-9624-2563aa707210" include("../ext/SummationByPartsOperatorsForwardDiffExt.jl")
@require Optim="429524aa-4258-5aef-a3af-852621145aeb" begin
@require ForwardDiff="f6369f11-7733-5829-9624-2563aa707210" include("../ext/SummationByPartsOperatorsOptimForwardDiffExt.jl")
end
@require StructArrays="09ab397b-f2b6-538f-b94a-2f83cf4a842a" include("../ext/SummationByPartsOperatorsStructArraysExt.jl")
end
end
Expand All @@ -109,6 +112,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 @@ -139,7 +143,7 @@ export PeriodicDerivativeOperator, PeriodicDissipationOperator,
FourierPolynomialDerivativeOperator, FourierRationalDerivativeOperator,
FourierDerivativeOperator2D,
LegendreDerivativeOperator, LegendreSecondDerivativeOperator,
MatrixDerivativeOperator,
MatrixDerivativeOperator, FunctionSpaceOperator,
UpwindOperators, PeriodicUpwindOperators
export FilterCallback, ConstantFilter, ExponentialFilter
export SafeMode, FastMode, ThreadedMode
Expand Down Expand Up @@ -169,6 +173,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
49 changes: 49 additions & 0 deletions src/function_space_operators.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@

"""
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.
"""
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

"""
FunctionSpaceOperator(basis_functions, x_min, x_max, nodes, source; accuracy_order = 0)

Construct a `MatrixDerivativeOperator` that represents a derivative operator in a function space.
"""
function FunctionSpaceOperator(basis_functions,
ranocha marked this conversation as resolved.
Show resolved Hide resolved
x_min::T, x_max::T, nodes::Vector{T},
source::SourceOfCoefficients;
accuracy_order = 0) where {T, SourceOfCoefficients}

weights, D = construct_function_space_operator(basis_functions, x_min, x_max, nodes, source)
return MatrixDerivativeOperator(x_min, x_max, nodes, weights, D, accuracy_order, source)
end
ranocha marked this conversation as resolved.
Show resolved Hide resolved

# This function is extended in the package extension SummationByPartsOperatorsOptimExt
function construct_function_space_operator end
JoshuaLampert marked this conversation as resolved.
Show resolved Hide resolved
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
36 changes: 36 additions & 0 deletions test/matrix_operators_test.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using Test
using LinearAlgebra
using SummationByPartsOperators
using Optim
JoshuaLampert marked this conversation as resolved.
Show resolved Hide resolved

# check construction of interior part of upwind operators
@testset "Check against some upwind operators" begin
Expand Down Expand Up @@ -113,3 +114,38 @@ using SummationByPartsOperators
@test SummationByPartsOperators.upper_bandwidth(Dm) == size(Dm, 1) - 1
end
end

@testset "Function space operators" begin
N = 5
nodes = collect(range(-1, 1, length=N))
x_min = -1.0
x_max = 1.0
x = [x_min, -0.5, 0.0, 0.5, x_max]
source = GlaubitzNordströmÖffner2023()
for compact in (true, false)
show(IOContext(devnull, :compact=>compact), source)
end
B = zeros(N, N)
B[1, 1] = -1.0
B[N, N] = 1.0
let basis_functions = [x -> x^i for i in 0:3]
D = FunctionSpaceOperator(basis_functions, x_min, x_max, nodes, source)

@test ≈(D * ones(N), zeros(N); atol = 1e-13)
@test D * x ≈ ones(N)
@test D * (x .^ 2) ≈ 2 * x
@test D * (x .^ 3) ≈ 3 * (x .^ 2)
M = mass_matrix(D)
@test M * D.D + D.D' * M ≈ B
end

let basis_functions = [one, identity, exp]
D = FunctionSpaceOperator(basis_functions, x_min, x_max, nodes, source)

@test ≈(D * ones(N), zeros(N); atol = 1e-13)
@test D * x ≈ ones(N)
@test D * exp.(x) ≈ exp.(x)
M = mass_matrix(D)
@test M * D.D + D.D' * M ≈ B
end
end
Loading