|
| 1 | +Base.@kwdef struct DimParameters |
| 2 | + sources_oversample_factor::Float64 = 3 |
| 3 | + sources_radius_multiplier::Float64 = 5 |
| 4 | +end |
| 5 | + |
| 6 | +""" |
| 7 | + dim_correction(pde,X,Y,S,D[;location,p,derivative]) |
| 8 | +
|
| 9 | +Given a `pde` and a (possibly innacurate) discretizations of its single and |
| 10 | +double-layer operators `S` and `D` with domain `Y` and range `X`, compute |
| 11 | +corrections `δS` and `δD` such that `S + δS` and `D + δD` are more accurate |
| 12 | +approximations of the underlying integral operators. |
| 13 | +
|
| 14 | +The following optional keyword arguments are available: |
| 15 | +- `location`: whether the target `X` lies on, inside, or outside the domain `Y`. |
| 16 | +- `p::DimParameters`: parameters associated with the density interpolation |
| 17 | + method |
| 18 | +- `derivative`: if true, compute the correction to the adjoint double-layer and |
| 19 | + hypersingular operators instead. In this case, `S` and `D` should contain a |
| 20 | + discretization of adjoint double-layer and hypersingular operators, |
| 21 | + respectively. |
| 22 | +""" |
| 23 | +function dim_correction(pde, X, Y, S, D; location=:onsurface, p=DimParameters(), |
| 24 | + derivative=false) |
| 25 | + T = eltype(S) |
| 26 | + isvectorial = T <: SMatrix |
| 27 | + msg = "unrecognized value for kw `location`: received $location. |
| 28 | + Valid options are `:onsurface`, `:inside` and `:outside`." |
| 29 | + σ = location === :onsurface ? -0.5 : |
| 30 | + location === :inside ? -1 : location === :outside ? 0 : error(msg) |
| 31 | + dict_near = nearest_point_to_element(X, Y) |
| 32 | + # find first an appropriate set of source points to center the monopoles |
| 33 | + qmax = sum(size(etype2qtags(Y, E), 1) for E in keys(Y)) |
| 34 | + ns = ceil(Int, p.sources_oversample_factor * qmax) |
| 35 | + pts = qcoords(Y) |
| 36 | + bbox = HyperRectangle(pts) |
| 37 | + xc = center(bbox) |
| 38 | + r = p.sources_radius_multiplier * radius(bbox) |
| 39 | + if ambient_dimension(Y) == 2 |
| 40 | + xs = uniform_points_circle(ns, r, xc) |
| 41 | + elseif ambient_dimension(Y) == 3 |
| 42 | + xs = fibonnaci_points_sphere(ns, r, xc) |
| 43 | + else |
| 44 | + notimplemented() |
| 45 | + end |
| 46 | + # compute traces of monopoles on the source mesh |
| 47 | + Xnodes = qnodes(X) |
| 48 | + Ynodes = qnodes(Y) |
| 49 | + G = SingleLayerKernel(pde, T) |
| 50 | + γ₁G = AdjointDoubleLayerKernel(pde, T) |
| 51 | + γ₀B = Matrix{T}(undef, length(Ynodes), ns) |
| 52 | + γ₁B = Matrix{T}(undef, length(Ynodes), ns) |
| 53 | + for k in 1:ns |
| 54 | + for j in 1:length(Ynodes) |
| 55 | + γ₀B[j, k] = G(Ynodes[j], xs[k]) |
| 56 | + γ₁B[j, k] = γ₁G(Ynodes[j], xs[k]) |
| 57 | + end |
| 58 | + end |
| 59 | + # integrate the monopoles/dipoles over Y with target on X. This is the |
| 60 | + # slowest step, and passing a custom S,D can accelerate this computation |
| 61 | + Θ = S * γ₁B - D * γ₀B |
| 62 | + for k in 1:ns |
| 63 | + for i in 1:length(Xnodes) |
| 64 | + if derivative |
| 65 | + Θ[i, k] += σ * γ₁G(Xnodes[i], xs[k]) |
| 66 | + else |
| 67 | + Θ[i, k] += σ * G(Xnodes[i], xs[k]) |
| 68 | + end |
| 69 | + end |
| 70 | + end |
| 71 | + # finally compute the corrected weights as sparse matrices |
| 72 | + Is, Js, Ss, Ds = Int[], Int[], T[], T[] |
| 73 | + dict_near = nearest_point_to_element(X, Y) |
| 74 | + for E in keys(Y) |
| 75 | + qtags = etype2qtags(Y, E) |
| 76 | + near_list = dict_near[E] |
| 77 | + nq, ne = size(qtags) |
| 78 | + @assert length(near_list) == ne |
| 79 | + M = Matrix{T}(undef, 2nq, ns) |
| 80 | + for n in 1:ne # loop over elements of type E |
| 81 | + # if there is nothing near, skip immediately to next element |
| 82 | + isempty(near_list[n]) && continue |
| 83 | + # the weights for points in near_list[n] must be corrected when |
| 84 | + # integrating over the current element |
| 85 | + jglob = @view qtags[:, n] |
| 86 | + M0 = @view γ₀B[jglob, :] |
| 87 | + M1 = @view γ₁B[jglob, :] |
| 88 | + copy!(view(M, 1:nq, :), M0) |
| 89 | + copy!(view(M, (nq + 1):(2nq), :), M1) |
| 90 | + F = qr!(blockmatrix_to_matrix(M)) |
| 91 | + for i in near_list[n] |
| 92 | + Θi = @view Θ[i:i, :] |
| 93 | + W_ = (blockmatrix_to_matrix(Θi) / F.R) * adjoint(F.Q) |
| 94 | + W = matrix_to_blockmatrix(W_, T) |
| 95 | + for k in 1:nq |
| 96 | + push!(Is, i) |
| 97 | + push!(Js, jglob[k]) |
| 98 | + push!(Ss, -W[nq + k]) # single layer corresponds to α=0,β=-1 |
| 99 | + push!(Ds, W[k]) # double layer corresponds to α=1,β=0 |
| 100 | + end |
| 101 | + end |
| 102 | + end |
| 103 | + end |
| 104 | + δS = sparse(Is, Js, Ss) |
| 105 | + δD = sparse(Is, Js, Ds) |
| 106 | + return δS, δD |
| 107 | +end |
| 108 | + |
| 109 | +""" |
| 110 | + nearest_point_to_element(X::NystroMesh,Y::NystromMesh; tol) |
| 111 | +
|
| 112 | +For each element `el`, return a list with the indices of all points in `X` for |
| 113 | +which `el` is the nearest element. Ignore indices for which the distance exceeds `tol`. |
| 114 | +""" |
| 115 | +function nearest_point_to_element(X, Y::NystromMesh; tol=Inf) |
| 116 | + y = collect(qcoords(Y)) |
| 117 | + kdtree = KDTree(y) |
| 118 | + dict = Dict(j => Int[] for j in 1:length(y)) |
| 119 | + for i in eachindex(X) |
| 120 | + qtag, d = nn(kdtree, X[i]) |
| 121 | + d > tol || push!(dict[qtag], i) |
| 122 | + end |
| 123 | + # dict[j] now contains indices in X for which the j quadrature node in Y is |
| 124 | + # the closest. Next we reverse the map |
| 125 | + etype2nearlist = Dict{DataType,Vector{Vector{Int}}}() |
| 126 | + for E in keys(Y) |
| 127 | + tags = etype2qtags(Y, E) |
| 128 | + nq, ne = size(tags) |
| 129 | + etype2nearlist[E] = nearlist = [Int[] for _ in 1:ne] |
| 130 | + for j in 1:ne # loop over each element of type E |
| 131 | + for q in 1:nq # loop over qnodes in the element |
| 132 | + qtag = tags[q, j] |
| 133 | + append!(nearlist[j], dict[qtag]) |
| 134 | + end |
| 135 | + end |
| 136 | + end |
| 137 | + return etype2nearlist |
| 138 | +end |
| 139 | +function nearest_point_to_element(X::NystromMesh, Y::NystromMesh) |
| 140 | + if X === Y |
| 141 | + # when both surfaces are the same, the "near points" of an element are |
| 142 | + # simply its own quadrature points |
| 143 | + dict = Dict{DataType,Vector{Vector{Int}}}() |
| 144 | + for E in keys(Y) |
| 145 | + idx_dofs = etype2qtags(Y, E) |
| 146 | + dict[E] = map(i -> collect(i), eachcol(idx_dofs)) |
| 147 | + end |
| 148 | + else |
| 149 | + dict = nearest_point_to_element(collect(qcoords(X)), Y) |
| 150 | + end |
| 151 | + return dict |
| 152 | +end |
0 commit comments