jipolanco / BSplineKit.jl

A collection of B-spline tools in Julia
https://jipolanco.github.io/BSplineKit.jl/dev/
MIT License
50 stars 9 forks source link

Galerkin projection of delta functions #48

Closed michakraus closed 1 year ago

michakraus commented 1 year ago

We are using BSplineKit in particle codes and repeatedly require to project particle distributions onto a spline basis. Effectively that amounts to a L2 projection of sums of delta functions, i.e., if a spline is given by

$$ \phi (x) = \sum \limits_{i=1}^{n} \varphi_i \psi_i (x) $$

we have the following L2 projection

$$ \int \psi{i} (x) \phi (x) dx = \int \psi{i} (x) \sum \limits{a=1}^{N} \delta ( x - x{a} ) dx \quad \forall i $$

and thus the degrees of freedom are given by

$$ \varphii = M{ij}^{-1} \sum \limits_{a=1}^{N} \int \psij (x{a}) \quad \forall i $$

What would be a good and efficient way to implement this in BSplineKit?

Currently we evaluate the right-hand side using evaluate_all, but due to the required index arithmetics when distributing the contribution of one particle to the degrees of freedom of the various basis functions, in whose support the particle resides, this is not particularly nice, especially with periodic splines.

Is there a better way to implement this? We'd be happy to contribute and add this functionality to BSplineKit if this makes sense, but we may need a little guidance.

jipolanco commented 1 year ago

Hi, I'm guessing your last equation should be something like:

$$ M_{ij} \varphij = \sum{\alpha = 1}^N \psii(x\alpha) $$

In that case yes, evaluate_all would be the right way to evaluate the right-hand side. I think it would be interesting to have a convenient way of computing this projection and distributing the contribution of each $x_\alpha$.

I will try to come up with an example of how to do this right now for periodic bases, which should help define a high-level interface for this.

michakraus commented 1 year ago

Yes, of course. I forgot the inverse on the mass matrix. Just added that.

An example would be great. We should be able to work our way from there.

jipolanco commented 1 year ago

This seems to work:

using BSplineKit

N = 100
L = 1
breaks = range(0, 1; length = N + 1)[1:N]
B = PeriodicBSplineBasis(BSplineOrder(4), breaks, L)

# Particle locations
xs = rand(5)

# Compute right-hand side
fs = Splines.PeriodicVector(zeros(length(B)))
for x ∈ xs
    ilast, bs = B(x)  # same as `evaluate_all`
    # Iterate over evaluated basis functions.
    # The indices of the evaluated basis functions are ilast:-1:(ilast - k + 1),
    # where k is the spline order.
    for (δi, bi) ∈ pairs(bs)
        i = ilast + 1 - δi
        fs[i] += bi
    end
end

M = galerkin_matrix(B)
cs = M \ parent(fs)  # get coefficients
S = Spline(B, cs)    # construct spline from coefficients

## Plots

using CairoMakie

fig = Figure()
ax = Axis(fig[1, 1]; xgridvisible = false, ygridvisible = false)
lines!(ax, 0..1, x -> S(x))
vlines!(ax, xs; color = :grey)
save("test.svg", fig)

test

Note that in the specific case of periodic splines, it's more convenient to work with the internal PeriodicVector type, which just wraps a regular vector allowing for periodic (cyclic) indexing.

jipolanco commented 1 year ago

Just for fun, note that you can also compute an approximation of the RHS using integration by parts and the galerkin_projection function:

fs_alt = galerkin_projection(B, Derivative(1)) do x
    count(y -> x ≥ y, xs)  # integral of the sum of deltas
end
cs_alt = -1 .* (M \ fs_alt)
S_alt = Spline(B, cs_alt)

This is just an approximation because galerkin_projection uses Gauss-Legendre quadratures, which don't capture the discontinuity in the integral of each delta. But it works quite well:

fig = Figure()
ax = Axis(fig[1, 1]; xgridvisible = false, ygridvisible = false)
lines!(ax, 0..1, x -> S(x))
lines!(ax, 0..1, x -> S_alt(x); linestyle = :dash)
vlines!(ax, xs; color = :grey)
save("test.svg", fig)

test

michakraus commented 1 year ago

Many thanks for the detailed example, @jipolanco ! This was very helpful.

We are integrating this now in our packages PoissonSolvers.jl and ParticleMethods.jl.