Skip to content

Commit 3b2d94a

Browse files
committed
WIP on local bdim
1 parent 56f513f commit 3b2d94a

File tree

3 files changed

+255
-3
lines changed

3 files changed

+255
-3
lines changed

src/bdim.jl

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,3 +177,150 @@ function bdim_correction(
177177
δD = sparse(Is, Js, Ds, num_trgs, n)
178178
return δS, δD
179179
end
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

src/mesh.jl

Lines changed: 106 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,11 @@ for a given mesh).
5151
function elements end
5252

5353
"""
54-
element_types(msh::AbstractMesh)
54+
elements(msh::AbstractMesh,E::DataType)
5555
56-
Return the element types present in the `msh`.
56+
Return an iterator for all elements of type `E` on a mesh `msh`.
5757
"""
58-
function element_types end
58+
elements(msh::AbstractMesh, E::DataType) = ElementIterator{E,typeof(msh)}(msh)
5959

6060
"""
6161
struct LagrangeMesh{N,T} <: AbstractMesh{N,T}
@@ -548,3 +548,106 @@ end
548548
centers = map(center, els)
549549
return inrange(balltree, centers, tol)
550550
end
551+
552+
"""
553+
topological_neighbors(msh::LagrangeMesh, k=1)
554+
555+
Return the `k` neighbors of each element in `msh`. The one-neighbors are the
556+
elements that share a common vertex with the element, `k` neighbors are the
557+
one-neighbors of the `k-1` neighbors.
558+
"""
559+
function topological_neighbors(msh::LagrangeMesh, k = 1)
560+
@assert k == 1
561+
# dictionary mapping a node index to all elements containing it. Note
562+
# that the elements are stored as a tuple (type, index)
563+
T = Tuple{DataType,Int}
564+
node2els = Dict{Int,Vector{T}}()
565+
for E in element_types(msh)
566+
mat = msh.etype2mat[E]::Matrix{Int} # connectivity matrix
567+
np, Nel = size(mat)
568+
for n in 1:Nel
569+
for i in 1:np
570+
idx = mat[i, n]
571+
els = get!(node2els, idx, Vector{T}())
572+
push!(els, (E, n))
573+
end
574+
end
575+
end
576+
# now revert the map to get the neighbors
577+
one_neighbors = Dict{T,Set{T}}()
578+
for (_, els) in node2els
579+
for el in els
580+
nei = get!(one_neighbors, el, Set{T}())
581+
for el′ in els
582+
push!(nei, el′)
583+
end
584+
end
585+
end
586+
#TODO: for k > 1, recursively compute the neighbors from the one-neighbors
587+
return one_neighbors
588+
end
589+
590+
"""
591+
element_to_near_targets(X,Y::AbstractMesh; tol)
592+
593+
For each element `el` of type `E` in `Y`, return the indices of the points in
594+
`X` which are closer than `tol` to the `center` of `el`.
595+
596+
This function returns a dictionary where e.g. `dict[E][5] --> Vector{Int}` gives
597+
the indices of points in `X` which are closer than `tol` to the center of the
598+
fifth element of type `E`.
599+
600+
If `tol` is a `Dict`, then `tol[E]` is the tolerance for elements of type `E`.
601+
"""
602+
function element_to_near_targets(
603+
X::AbstractVector{<:SVector{N}},
604+
Y::AbstractMesh{N};
605+
tol,
606+
) where {N}
607+
@assert isa(tol, Number) || isa(tol, Dict) "tol must be a number or a dictionary mapping element types to numbers"
608+
# for each element type, build the list of targets close to a given element
609+
dict = Dict{DataType,Vector{Vector{Int}}}()
610+
balltree = BallTree(X)
611+
for E in element_types(Y)
612+
els = elements(Y, E)
613+
tol_ = isa(tol, Number) ? tol : tol[E]
614+
idxs = _element_to_near_targets(balltree, els, tol_)
615+
dict[E] = idxs
616+
end
617+
return dict
618+
end
619+
620+
@noinline function _element_to_near_targets(balltree, els, tol)
621+
centers = map(center, els)
622+
return inrange(balltree, centers, tol)
623+
end
624+
625+
"""
626+
target_to_near_elements(X::AbstractVector{<:SVector{N}}, Y::AbstractMesh{N};
627+
tol)
628+
629+
For each target `x` in `X`, return a vector of tuples `(E, i)` where `E` is the
630+
type of the element in `Y` and `i` is the index of the element in `Y` such that
631+
`x` is closer than `tol` to the center of the element.
632+
"""
633+
function target_to_near_elements(
634+
X::AbstractVector{<:SVector{N}},
635+
Y::AbstractMesh{N};
636+
tol,
637+
) where {N}
638+
@assert isa(tol, Number) || isa(tol, Dict) "tol must be a number or a dictionary mapping element types to numbers"
639+
dict = Dict{Int,Vector{Tuple{DataType,Int}}}()
640+
balltree = BallTree(X)
641+
for E in element_types(Y)
642+
els = elements(Y, E)
643+
tol_ = isa(tol, Number) ? tol : tol[E]
644+
idxs = _target_to_near_elements(balltree, els, tol_)
645+
for (i, idx) in enumerate(idxs)
646+
dict[i] = get!(dict, i, Vector{Tuple{DataType,Int}}())
647+
for j in idx
648+
push!(dict[i], (E, j))
649+
end
650+
end
651+
end
652+
return dict
653+
end

src/quadrature.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ flip_normal(q::QuadratureNode) = QuadratureNode(q.coords, q.weight, -q.normal)
4545

4646
weight(q::QuadratureNode) = q.weight
4747

48+
translate(q::QuadratureNode, x) = QuadratureNode(coords(q) + x, weight(q), normal(q))
49+
4850
# useful for using either a quadrature node or a just a simple point in
4951
# `IntegralOperators`.
5052
coords(x::Union{SVector,Tuple}) = SVector(x)

0 commit comments

Comments
 (0)