From 1d78c8f6738b1251087440430cc71d850cb904d5 Mon Sep 17 00:00:00 2001
From: Florian Atteneder <florian.atteneder@uni-jena.de>
Date: Mon, 12 Aug 2024 17:49:05 +0000
Subject: [PATCH] dg1d: update `lgl` and `glgl` integration methods
 (https://git.tpi.uni-jena.de/dg/dg1d.jl/-/merge_requests/201)

- put the integrand as the first argument
- add integration methods that can work with a function argument
- add doc strings
- update usage of `integrate` functions in rest of code
---
 src/HRSC/bernstein.jl               |  2 +-
 src/glgl.jl                         | 99 +++++++++++++++++++++--------
 src/lgl.jl                          | 95 ++++++++++++++++++---------
 src/spectralelement.jl              | 12 ++--
 test/UnitTests/src/test_spectral.jl | 17 +++++
 5 files changed, 161 insertions(+), 64 deletions(-)

diff --git a/src/HRSC/bernstein.jl b/src/HRSC/bernstein.jl
index c5873cb8..22c4cfca 100644
--- a/src/HRSC/bernstein.jl
+++ b/src/HRSC/bernstein.jl
@@ -55,7 +55,7 @@ end
 @doc """
     reconstruct_cell!(u!, w, P, V; kwargs...)
 
-Compute inplace a convex combination of `u!` and its Bernstein reconstruction weighted by `w`.  
+Compute inplace a convex combination of `u!` and its Bernstein reconstruction weighted by `w`.
 `P, V` are the project and transfer matrices that transfer data from the sample
 grid to the bernstein grid and back, respectively.
 `m` and `M` keywords allow to enforce lower and upper bound in reconstruction.
diff --git a/src/glgl.jl b/src/glgl.jl
index 980b8e92..47d47cd5 100644
--- a/src/glgl.jl
+++ b/src/glgl.jl
@@ -14,7 +14,7 @@ x ... quadrature nodes, vector
 w ... quadrature weights, vector
 v ... derivative weight, number
 """
-function rule(n_pts)
+function rule(n_pts::Integer)
 
   N = n_pts - 2
   gb_x, gb_w = [], []
@@ -43,14 +43,13 @@ end # function
 
 
 """
-Integrate with Legendre weight w(x) = 1.
-x ... Vector of GLGL nodes.
-w ... Vector of GLGL weights.
-v ... Number, GLGL boundary weight.
-D ... Derivative matrix based on x.
-f1, f2 ... Vector of function values evaluated on grid x.
+  integrate(f, x, w, v, D)
+
+Integrate `f` sampled on GLGL quadrature nodes `x` with quadrature weights `w`,
+boundary weight `v` and derivative matrix `D` based on `x`.
+Use `rule` to obtain `x, w, v` and use `first_derivative_matrix` to obtain `D`.
 """
-function integrate(w, v, D, f)
+function integrate(f::AbstractVector, x::AbstractVector, w::AbstractVector, v::Real, D::AbstractVector)
 
   N = length(w)
   @toggled_assert (N,N) == size(D)
@@ -62,7 +61,43 @@ function integrate(w, v, D, f)
 
   return sum
 end
-function integrate(w, v, D, fn::Function, idxs::AbstractVector{<:Integer})
+
+
+"""
+  integrate(f1, f2, w, v, D)
+
+Similar to `integrate(f, w)`, but computes the inner product of `f1*f2` where both
+`f1, f2` must be sampled on the GLGL quadrature nodes `x` associated with the quadrature
+weights `w`, boundary weight `v` and derivative matrix `D` based on `x`.
+Use `rule` to obtain `x, w, v` and use `first_derivative_matrix` to obtain `D`.
+"""
+function integrate(f1::AbstractVector, f2::AbstractVector, x::AbstractVector, w::AbstractVector,
+    v::Real, D::AbstractMatrix)
+
+  N = length(w)
+  @toggled_assert (N,N) == size(D)
+  @toggled_assert N == length(f1)
+  @toggled_assert N == length(f2)
+
+  sum = dot(f1.*f2, w)
+  vD1, vDN = view(D,1,:), view(D,N,:)
+  sum += v * (f1[1] * dot(vD1, f2) + f2[1] * dot(vD1, f1))
+  sum -= v * (f1[end] * dot(vDN, f2) + f2[end] * dot(vDN, f1))
+
+  return sum
+end
+
+
+"""
+  integrate(f::Function, idxs::AbstractVector{<:Integer}, x, w)
+
+Integrate `f` sampled on GLGL quadrature nodes `x` with quadrature weights `w`,
+boundary weight `v` and derivative matrix `D` based on `x`.
+`f(idxs[i])` shall return the value of the integrand evaluated at `x[i]`.
+Use `rule` to obtain `x, w, v` and use `first_derivative_matrix` to obtain `D`.
+"""
+function integrate(fn::Function, idxs::AbstractVector{<:Integer}, x::AbstractVector, w::AbstractVector,
+    v::Real, D::AbstractMatrix)
 
   N = length(w)
   @toggled_assert (N,N) == size(D)
@@ -78,18 +113,25 @@ function integrate(w, v, D, fn::Function, idxs::AbstractVector{<:Integer})
 end
 
 
-function integrate(w, v, D, f1, f2)
+"""
+  integrate(f::Function, x, w, v, D)
 
-  N = length(w)
-  @toggled_assert (N,N) == size(D)
-  @toggled_assert N == length(f1)
-  @toggled_assert N == length(f2)
-
-  sum = dot(f1.*f2, w)
-  vD1, vDN = view(D,1,:), view(D,N,:)
-  sum += v * (f1[1] * dot(vD1, f2) + f2[1] * dot(vD1, f1))
-  sum -= v * (f1[end] * dot(vDN, f2) + f2[end] * dot(vDN, f1))
+Integrate `f` sampled on GLGL quadrature nodes `x` with quadrature weights `w`,
+boundary weight `v` and derivative matrix `D` based on `x`.
+`f(x[i])` shall return the value of the integrand evaluated at `x[i]`.
+Use `rule` to obtain `x, w, v` and use `first_derivative_matrix` to obtain `D`.
 
+This is similar to `integrate(f, idxs, x, w, v, D)`, but is better suited if the integrand is
+given in closed form.
+"""
+function integrate(f::Function, x::AbstractVector, w::AbstractVector, v::Real, D::AbstractMatrix)
+  @toggled_assert length(w) == length(x) == size(D, 1) == size(D, 2)
+  sum = 0.0
+  N = length(w)
+  for i in 1:N
+    fi = f(x[i])
+    sum += (w[i] + v * (D[1,i] - D[N,i])) * fi
+  end
   return sum
 end
 
@@ -101,12 +143,12 @@ w ... GLGL weights
 v ... GLGL derivative weight
 D ... Derivative matrix based on x.
 """
-function mass_matrix(x, w, v, D)
+function mass_matrix(x::AbstractVector, w::AbstractVector, v::Real, D::AbstractMatrix)
 
   N = length(x)
   @assert N == length(w)
   @assert (N,N) == size(D)
-  
+
   M = diagm(w)
   M[1,:] += v * D[1,:]
   M[:,1] += v * D[1,:]
@@ -128,13 +170,14 @@ v ... GLGL derivative weight
 D ... First derivative matrix based on x.
 D2 ... Second derivative matrix based on x.
 """
-function stiffness_matrix(x, w, v, D, D2)
+function stiffness_matrix(x::AbstractVector, w::AbstractVector, v::Real,
+    D::AbstractMatrix, D2::AbstractMatrix)
 
   N = length(x)
   @assert N == length(w)
   @assert (N,N) == size(D)
   @assert (N,N) == size(D2)
-  
+
   S = w .* D
   S[1,:]    += v * D2[1,:]
   S[end,:]  -= v * D2[end,:]
@@ -145,7 +188,7 @@ function stiffness_matrix(x, w, v, D, D2)
 end
 
 
-# TODO Add versions of mass_matrix, stiffness_matrix that take 
+# TODO Add versions of mass_matrix, stiffness_matrix that take
 # a single argument n_pts.
 
 
@@ -159,14 +202,14 @@ w ... GLGL weights
 v ... GLGL derivative weight
 D ... First derivative matrix based on x.
 """
-function linear_dependency_matrix(x, w, v, D)
+function linear_dependency_matrix(x::AbstractVector, w::AbstractVector, v::Real, D::AbstractMatrix)
   N = length(x)
   L = zeros(N,N)
   for i in 1:N
     Pi = map(x->legendre(x, i-1), x)
     for j in i:N
       Pj = map(x->legendre(x, j-1), x)
-      lij = integrate(w, v, D, Pi, Pj)
+      lij = integrate(Pi, Pj, x, w, v, D)
       L[i,j] = lij
       if i != j
         L[j,i] = L[i,j]
@@ -188,14 +231,14 @@ w ... GLGL weights
 v ... GLGL derivative weight
 D ... First derivative matrix based on x.
 """
-function expansion_matrix(x, w, v, D)
+function expansion_matrix(x::AbstractVector, w::AbstractVector, v::Real, D::AbstractMatrix)
   N = length(x)
   T = zeros(N,N)
   for i in 1:N
     li = map(xi->lagrange_poly(i, xi, x), x)
     for j in 1:N
       Pj = map(x->legendre(x, j-1), x)
-      tij = integrate(w, v, D, li, Pj)
+      tij = integrate(li, Pj, x, w, v, D)
       T[i,j] = tij
     end
   end
diff --git a/src/lgl.jl b/src/lgl.jl
index 493d7f4b..31309a41 100644
--- a/src/lgl.jl
+++ b/src/lgl.jl
@@ -11,7 +11,7 @@ Quadrature nodes and weights for Gauss-Legendre rule.
 n_pts ... number of quadrature points
 Exact up to order 2*n_pts - 1.
 """
-function rule(n_pts)
+function rule(n_pts::Integer)
   α = 0.0; β = 0.0
   x = zglj(n_pts, α, β)
   w = wglj(x, α, β)
@@ -21,16 +21,44 @@ end
 
 
 """
-Integrate with Legendre weight w(x) = 1.
-x ... Vector of LGL nodes
-w ... Vector of LGL weights
-f ... Vector with function values evaluated at nodes x.
+  integrate(f, x, w)
+
+Integrate `f` sampled on LGL quadrature nodes `x` with quadrature weights `w`.
+Use `rule` to obtain `x, w`.
 """
-function integrate(w, f)
+function integrate(f::AbstractVector, x::AbstractVector, w::AbstractVector)
   @toggled_assert length(w) == length(f)
   return dot(w, f)
 end
-function integrate(w, f::Function, idxs::AbstractVector{<:Integer})
+
+
+"""
+  integrate(f1, f2, x, w)
+
+Similar to `integrate(f, w)`, but computes the inner product of `f1*f2` where both
+`f1, f2` must be sampled on the LGL quadrature nodes `x` associated with the quadrature
+weights `w`.
+Use `rule` to obtain `x, w`.
+"""
+function integrate(f1::AbstractVector, f2::AbstractVector, x::AbstractVector, w::AbstractVector)
+  @toggled_assert length(w) == length(f1)
+  @toggled_assert length(w) == length(f2)
+  I = 0.0
+  for i = 1:length(w)
+    I += w[i] * f1[i] * f2[i]
+  end
+  return I
+end
+
+
+"""
+  integrate(f::Function, idxs::AbstractVector{<:Integer}, x, w)
+
+Integrate `f` sampled on LGL quadrature nodes `x` with quadrature weights `w`.
+`f(idxs[i])` shall return the value of the integrand evaluated at `x[i]`.
+Use `rule` to obtain `x, w`.
+"""
+function integrate(f::Function, idxs::AbstractVector{<:Integer}, x::AbstractVector, w::AbstractVector)
   @toggled_assert length(w) == length(idxs)
   sum = 0.0
   for i in 1:length(w)
@@ -40,14 +68,23 @@ function integrate(w, f::Function, idxs::AbstractVector{<:Integer})
 end
 
 
-function integrate(w, f, g)
-  @toggled_assert length(w) == length(f)
-  @toggled_assert length(w) == length(g)
-  I = 0.0
-  for i = 1:length(w)
-    I += w[i] * f[i] * g[i]
+"""
+  integrate(f::Function, x, w)
+
+Integrate `f` sampled on LGL quadrature nodes `x` with quadrature weights `w`.
+`f(x[i])` shall return the value of the integrand evaluated at `x[i]`.
+Use `rule` to obtain `x, w`.
+
+This is similar to `integrate(f, idxs, x, w)`, but is better suited if the integrand is
+given in closed form.
+"""
+function integrate(f::Function, x::AbstractVector, w::AbstractVector)
+  @toggled_assert length(w) == length(x)
+  sum = 0.0
+  for i in 1:length(w)
+    sum += w[i] * f(x[i])
   end
-  return I
+  return sum
 end
 
 
@@ -57,7 +94,7 @@ cf. PhD thesis Marcus Bugner, Notes spectral elements Bernd Bruegmann,
 Kopriva (2013), Teukolsky (2015).
 n_pts ... number of qudrature nodes = number of lagrange polynomials
 """
-function inverse_mass_matrix_exact(n_pts)
+function inverse_mass_matrix_exact(n_pts::Integer)
   z, w = rule(n_pts)
   N = n_pts - 1
   invM = zeros(n_pts, n_pts)
@@ -72,7 +109,7 @@ end
 """
 n_pts ... number of qudrature nodes = number of lagrange polynomials
 """
-function inverse_mass_matrix_lumped(n_pts)
+function inverse_mass_matrix_lumped(n_pts::Integer)
   _, w = rule(n_pts)
   invM = diagm(1.0 ./ w)
   return invM
@@ -84,7 +121,7 @@ end
 M_ij = (li, lj)
 n_pts ... number of qudrature nodes = number of lagrange polynomials
 """
-function mass_matrix_exact(n_pts)
+function mass_matrix_exact(n_pts::Integer)
   z, w = rule(n_pts)
   M = zeros(n_pts, n_pts)
   aux_z, aux_w = rule(n_pts+2)
@@ -94,7 +131,7 @@ function mass_matrix_exact(n_pts)
     li = L.(i, aux_z)
     for j = i:n_pts
       lj = L.(j, aux_z)
-      M[i,j] = integrate(aux_w, li, lj)
+      M[i,j] = integrate(li, lj, aux_w)
       if (i != j)
         M[j,i] = M[i,j]
       end
@@ -111,7 +148,7 @@ M_ij = (li, lj)
 Approximated mass matrix due to under integration of LGL quadrature.
 n_pts ... number of qudrature nodes = number of lagrange polynomials
 """
-function mass_matrix_lumped(n_pts)
+function mass_matrix_lumped(n_pts::Integer)
   z, w = rule(n_pts)
   M = diagm(w)
   return M
@@ -125,7 +162,7 @@ Exactly representable, because product is of order 2N - 1 which can be integrate
 exactly using LGL quadrature.
 n_pts ... number of quadrature nodes = number of lagrange polynomials
 """
-function stiffness_matrix(n_pts)
+function stiffness_matrix(n_pts::Integer)
   z, w = rule(n_pts)
   D = first_derivative_matrix(z)
   S = w .* D
@@ -141,14 +178,14 @@ of the numerical representation of the Legendre basis.
 x ... GLGL nodes
 w ... GLGL weights
 """
-function linear_dependency_matrix_lumped(x::tV, w::tV) where {tV}
+function linear_dependency_matrix_lumped(x::AbstractVector, w::AbstractVector)
   N = length(x)
   L = zeros(N,N)
   for i in 1:N
     Pi = map(x->legendre(x, i-1), x)
     for j in i:N
       Pj = map(x->legendre(x, j-1), x)
-      lij = integrate(w, Pi, Pj)
+      lij = integrate(Pi, Pj, x, w)
       L[i,j] = lij
       if i != j
         L[j,i] = L[i,j]
@@ -168,14 +205,14 @@ reverse.
 x ... GLGL nodes
 w ... GLGL weights
 """
-function expansion_matrix_lumped(x, w)
+function expansion_matrix_lumped(x::AbstractVector, w::AbstractVector)
   N = length(x)
   T = zeros(N,N)
   for i in 1:N
     li = map(xi->lagrange_poly(i, xi, x), x)
     for j in 1:N
       Pj = map(x->legendre(x, j-1), x)
-      tij = integrate(w, li, Pj)
+      tij = integrate(li, Pj, x, w)
       T[i,j] = tij
     end
   end
@@ -188,11 +225,11 @@ end
 """
 L_ij = (P_i, P_j)
 Analogon to mass matrix of Lagrange polynomials, expresses the linear dependency
-of the numerical representation of the Legendre basis. 
+of the numerical representation of the Legendre basis.
 x ... GLGL nodes
 w ... GLGL weights
 """
-function linear_dependency_matrix_exact(x, w)
+function linear_dependency_matrix_exact(x::AbstractVector, w::AbstractVector)
   N = length(x)
   N2 = N+2
   x2, w2 = rule(N2)
@@ -201,7 +238,7 @@ function linear_dependency_matrix_exact(x, w)
     Pi = map(x->legendre(x, i-1), x2)
     for j in i:N
       Pj = map(x->legendre(x, j-1), x2)
-      lij = integrate(w2, Pi, Pj)
+      lij = integrate(Pi, Pj, x2, w2)
       L[i,j] = lij
       if i != j
         L[j,i] = L[i,j]
@@ -221,7 +258,7 @@ reverse.
 x ... GLGL nodes
 w ... GLGL weights
 """
-function expansion_matrix_exact(x, w)
+function expansion_matrix_exact(x::AbstractVector, w::AbstractVector)
   N = length(x)
   N2 = N + 2
   x2, w2 = rule(N2)
@@ -230,7 +267,7 @@ function expansion_matrix_exact(x, w)
     li = map(xi->lagrange_poly(i, xi, x), x2)
     for j in 1:N
       Pj = map(x->legendre(x, j-1), x2)
-      tij = integrate(w2, li, Pj)
+      tij = integrate(li, Pj, x2, w2)
       T[i,j] = tij
     end
   end
diff --git a/src/spectralelement.jl b/src/spectralelement.jl
index 1fbbf23f..dcf0b418 100644
--- a/src/spectralelement.jl
+++ b/src/spectralelement.jl
@@ -93,9 +93,9 @@ end
   @unpack quadr = el
   @toggled_assert quadr in (:LGL, :LGL_lumped, :GLGL)
   if quadr === :LGL || quadr === :LGL_lumped
-    return LGL.integrate(el.w, u1, u2)
+    return LGL.integrate(u1, u2, el.z, el.w)
   else # quadr === :GLGL
-    return GLGL.integrate(el.w, el.v, el.D, u1, u2)
+    return GLGL.integrate(u1, u2, el.z, el.w, el.v, el.D)
   end
 end
 
@@ -104,9 +104,9 @@ end
   @unpack quadr = el
   @toggled_assert quadr in (:LGL, :LGL_lumped, :GLGL)
   if quadr === :LGL || quadr === :LGL_lumped
-    return LGL.integrate(el.w, u)
+    return LGL.integrate(u, el.z, el.w)
   else # quadr === :GLGL
-    return GLGL.integrate(el.w, el.v, el.D, u)
+    return GLGL.integrate(u, el.z, el.w, el.v, el.D)
   end
 end
 
@@ -115,9 +115,9 @@ end
   @unpack quadr = el
   @toggled_assert quadr in (:LGL, :LGL_lumped, :GLGL)
   if quadr === :LGL || quadr === :LGL_lumped
-    return LGL.integrate(el.w, fn, idxs)
+    return LGL.integrate(fn, idxs, el.z, el.w)
   else # quadr === :GLGL
-    return GLGL.integrate(el.w, el.v, el.D, fn, idxs)
+    return GLGL.integrate(fn, idxs, el.z, el.w, el.v, el.D)
   end
 end
 
diff --git a/test/UnitTests/src/test_spectral.jl b/test/UnitTests/src/test_spectral.jl
index 298c3822..3a2a3860 100644
--- a/test/UnitTests/src/test_spectral.jl
+++ b/test/UnitTests/src/test_spectral.jl
@@ -162,4 +162,21 @@ end
 end
 
 
+@testset "More integrator tests" begin
+
+  n_pts = 6
+  p = x -> (x-4)^2 * (x+1)^3
+
+  x, w = dg1d.LGL.rule(n_pts)
+  int = dg1d.LGL.integrate(p, x, w)
+  @test int ≈ 140/3
+
+  x, w, v = dg1d.GLGL.rule(n_pts)
+  D = dg1d.first_derivative_matrix(x)
+  int = dg1d.GLGL.integrate(p, x, w, v, D)
+  @test int ≈ 140/3
+
+end
+
+
 end
-- 
GitLab