@@ -177,3 +177,150 @@ function bdim_correction(
177177 δD = sparse (Is, Js, Ds, num_trgs, n)
178178 return δS, δD
179179end
180+
181+ function local_bdim_correction (
182+ pde,
183+ target,
184+ source:: Quadrature ,
185+ Sop,
186+ Dop;
187+ green_multiplier:: Vector{<:Real} ,
188+ parameters = DimParameters (),
189+ derivative:: Bool = false ,
190+ maxdist = Inf ,
191+ )
192+ imat_cond = imat_norm = res_norm = rhs_norm = - Inf
193+ T = eltype (Sop)
194+ N = ambient_dimension (source)
195+ @assert eltype (Dop) == T " eltype of S and D must match"
196+ m, n = length (target), length (source)
197+ msh = source. mesh
198+ qnodes = source. qnodes
199+ neighbors = topological_neighbors (msh)
200+ dict_near = etype_to_nearest_points (target, source; maxdist)
201+ # find first an appropriate set of source points to center the monopoles
202+ qmax = sum (size (mat, 1 ) for mat in values (source. etype2qtags)) # max number of qnodes per el
203+ ns = ceil (Int, parameters. sources_oversample_factor * qmax)
204+ # compute a bounding box for source points
205+ low_corner = reduce ((p, q) -> min .(coords (p), coords (q)), source)
206+ high_corner = reduce ((p, q) -> max .(coords (p), coords (q)), source)
207+ xc = (low_corner + high_corner) / 2
208+ R = parameters. sources_radius_multiplier * norm (high_corner - low_corner) / 2
209+ xs = if N === 2
210+ uniform_points_circle (ns, R, xc)
211+ elseif N === 3
212+ fibonnaci_points_sphere (ns, R, xc)
213+ else
214+ error (" only 2D and 3D supported" )
215+ end
216+ # figure out if we are dealing with a scalar or vector PDE
217+ σ = if T <: Number
218+ 1
219+ else
220+ @assert allequal (size (T))
221+ size (T, 1 )
222+ end
223+ # compute traces of monopoles on the source mesh
224+ G = SingleLayerKernel (pde, T)
225+ γ₁G = DoubleLayerKernel (pde, T)
226+ γ₁ₓG = AdjointDoubleLayerKernel (pde, T)
227+ γ₀B = Matrix {T} (undef, length (source), ns)
228+ γ₁B = Matrix {T} (undef, length (source), ns)
229+ for k in 1 : ns
230+ for j in 1 : length (source)
231+ γ₀B[j, k] = G (source[j], xs[k])
232+ γ₁B[j, k] = γ₁ₓG (source[j], xs[k])
233+ end
234+ end
235+ Is, Js, Ss, Ds = Int[], Int[], T[], T[]
236+ for (E, qtags) in source. etype2qtags
237+ els = elements (msh, E)
238+ near_list = dict_near[E]
239+ nq, ne = size (qtags)
240+ @assert length (near_list) == ne
241+ # preallocate a local matrix to store interpolant values resulting
242+ # weights. To benefit from Lapack, we must convert everything to
243+ # matrices of scalars, so when `T` is an `SMatrix` we are careful to
244+ # convert between the `Matrix{<:SMatrix}` and `Matrix{<:Number}` formats
245+ # by viewing the elements of type `T` as `σ × σ` matrices of
246+ # `eltype(T)`.
247+ M_ = Matrix {eltype(T)} (undef, 2 * nq * σ, ns * σ)
248+ W_ = Matrix {eltype(T)} (undef, 2 * nq * σ, σ)
249+ W = T <: Number ? W_ : Matrix {T} (undef, 2 * nq, 1 )
250+ Θi_ = Matrix {eltype(T)} (undef, σ, ns * σ)
251+ Θi = T <: Number ? Θi_ : Matrix {T} (undef, 1 , ns)
252+ K = derivative ? γ₁ₓG : G
253+ # for each element, we will solve Mᵀ W = Θiᵀ, where W is a vector of
254+ # size 2nq, and Θi is a row vector of length(ns)
255+ for n in 1 : ne
256+ # if there is nothing near, skip immediately to next element
257+ isempty (near_list[n]) && continue
258+ el = els[n]
259+ # copy the monopoles/dipoles for the current element
260+ jglob = @view qtags[:, n]
261+ M0 = @view γ₀B[jglob, :]
262+ M1 = @view γ₁B[jglob, :]
263+ _copyto! (view (M_, 1 : (nq* σ), :), M0)
264+ _copyto! (view (M_, (nq* σ+ 1 ): 2 * nq* σ, :), M1)
265+ F_ = qr! (transpose (M_))
266+ @debug (imat_cond = max (cond (M_), imat_cond)) maxlog = 0
267+ @debug (imat_norm = max (norm (M_), imat_norm)) maxlog = 0
268+ # quadrature for auxiliary surface. In global dim, this is the same
269+ # as the source quadrature, and independent of element. In local
270+ # dim, this is constructed for each element using its neighbors.
271+ function translate (q:: QuadratureNode , x, s)
272+ return QuadratureNode (coords (q) + x, weight (q), s * normal (q))
273+ end
274+ nei = neighbors[(E, n)]
275+ qtags_nei = Int[]
276+ for (E, n) in nei
277+ append! (qtags_nei, source. etype2qtags[E][:, n])
278+ end
279+ qnodes_nei = source. qnodes[qtags_nei]
280+ jac = jacobian (el, 0.5 )
281+ ν = _normal (jac)
282+ h = sum (qnodes[i]. weight for i in jglob)
283+ qnodes_op = map (q -> translate (q, h * ν, - 1 ), qnodes_nei)
284+ a, b = external_boundary ()
285+ # qnodes_aux = source.qnodes[jglob]
286+ qnodes_aux = source. qnodes # this is the global dim
287+ for i in near_list[n]
288+ # integrate the monopoles/dipoles over the auxiliary surface
289+ # with target x: Θₖ <-- S[γ₁Bₖ](x) - D[γ₀Bₖ](x) + μ * Bₖ(x)
290+ x = target[i]
291+ μ = green_multiplier[i]
292+ for k in 1 : ns
293+ Θi[k] = μ * K (x, xs[k])
294+ end
295+ for q in qnodes_aux
296+ SK = G (x, q)
297+ DK = γ₁G (x, q)
298+ for k in 1 : ns
299+ Θi[k] += (SK * γ₁ₓG (q, xs[k]) - DK * G (q, xs[k])) * weight (q)
300+ end
301+ end
302+ Θi_ = _copyto! (Θi_, Θi)
303+ @debug (rhs_norm = max (rhs_norm, norm (Θi))) maxlog = 0
304+ W_ = ldiv! (W_, F_, transpose (Θi_))
305+ @debug (res_norm = max (norm (Matrix (F_) * W_ - transpose (Θi_)), res_norm)) maxlog =
306+ 0
307+ W = T <: Number ? W_ : _copyto! (W, W_)
308+ for k in 1 : nq
309+ push! (Is, i)
310+ push! (Js, jglob[k])
311+ push! (Ss, - W[nq+ k]) # single layer corresponds to α=0,β=-1
312+ push! (Ds, W[k]) # double layer corresponds to α=1,β=0
313+ end
314+ end
315+ end
316+ end
317+ @debug """ Condition properties of bdim correction:
318+ |-- max interp. matrix cond.: $imat_cond
319+ |-- max interp. matrix norm : $imat_norm
320+ |-- max residual error: $res_norm
321+ |-- max norm of source term: $rhs_norm
322+ """
323+ δS = sparse (Is, Js, Ss, m, n)
324+ δD = sparse (Is, Js, Ds, m, n)
325+ return δS, δD
326+ end
0 commit comments