Skip to content

Commit cca55e4

Browse files
author
Sebastien Loisel
committed
Isoparametric FEM2D_P2 element map; positive-weight guard (v0.12.3)
FEM2D_P2 now builds the element geometry from all 7 P2+bubble node positions via the shape functions, so displacing edge/centroid nodes genuinely curves the element (node-varying Jacobian) instead of being silently flattened to the affine corner triangle. The physical-derivative operators use J(node)^{-T} per node and the quadrature weight is the signed det J(node)·R.w. Dropping abs(det) means inverted/folded elements now produce non-positive weights. FEM3D was already isoparametric (per-node J from D_xi/D_eta/D_zeta over all (k+1)^3 nodes); switch its weights from abs(det J) to signed det J for the same reason, and drop a dead duplicate z-derivative block in _fem3d_structured. Both discretizations now refuse to build (hence to solve) when any quadrature weight is non-positive, since the barrier method requires strictly positive weights. Straight elements are bit-unchanged (tests green, dx/dy/dz ~1e-14).
1 parent 417161c commit cca55e4

4 files changed

Lines changed: 63 additions & 16 deletions

File tree

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "MultiGridBarrier"
22
uuid = "9e2c1f1d-9131-4ad4-b32f-bd2a0b0ecd1e"
33
authors = ["Sébastien Loisel"]
4-
version = "0.12.2"
4+
version = "0.12.3"
55

66
[deps]
77
AlgebraicMultigrid = "2169fc97-5a83-5252-b627-83903c6c433c"

src/Mesh3d/Geometry.jl

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -72,10 +72,24 @@ function _geometric_fem3d_mg(::Type{T}=Float64; L::Int=2,
7272
y_xi[i] y_eta[i] y_zeta[i];
7373
z_xi[i] z_eta[i] z_zeta[i]]
7474

75-
detJ = abs(det(J))
75+
# Signed det J: a non-positive value means the isoparametric Q_k
76+
# hex map is folded / inverted at this node (guarded below).
77+
detJ = det(J)
7678
w_physical[start_idx + i - 1] = ref_el.weights_ref[i] * detJ
7779
end
7880
end
81+
82+
# The barrier method requires strictly positive quadrature weights. Refuse
83+
# to build (and hence to solve) on a folded / inverted curved element.
84+
if !all(>(zero(T)), w_physical)
85+
bad = findall(<=(zero(T)), w_physical)
86+
badelems = sort!(unique((bad .- 1) n_nodes_per_elem .+ 1))
87+
error("fem3d: non-positive quadrature weight at $(length(bad)) node(s) " *
88+
"across $(length(badelems)) element(s) (first few: $(first(badelems, 5))). " *
89+
"The isoparametric Q_k element map has det J ≤ 0 there — the curved " *
90+
"hex is folded or has inverted vertex orientation. Supply orientation-" *
91+
"preserving, non-self-intersecting elements.")
92+
end
7993
return w_physical
8094
end
8195

@@ -210,11 +224,6 @@ function _fem3d_structured(disc::FEM3D{T}, meshes, weights, L, k, ref_el) where
210224
y_xi = D_xi_dense * y_elem
211225
y_eta = D_eta_dense * y_elem
212226
y_zeta = D_zeta_dense * y_elem
213-
z_xi = D_zeta_dense * z_elem # NOTE: keep parity with original code (uses D_zeta?). See below.
214-
z_eta = D_eta_dense * z_elem
215-
z_zeta = D_zeta_dense * z_elem
216-
217-
# The original uses D_xi/eta/zeta_dense for z components. Re-implement faithfully.
218227
z_xi = D_xi_dense * z_elem
219228
z_eta = D_eta_dense * z_elem
220229
z_zeta = D_zeta_dense * z_elem

src/fem2d_P2.jl

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,14 @@ legacy `geometric_mg(geom, L)` builds geometric-subdivision transfers instead.)
134134
# Arguments
135135
- `K::Array{T,3}` (`7 × N × 2`): P2+bubble per-triangle mesh; the 7 vertices per
136136
triangle are laid out as
137-
`corner1, midpt(1,2), corner2, midpt(2,3), corner3, midpt(3,1), centroid`.
137+
`corner1, edge(1,2), corner2, edge(2,3), corner3, edge(3,1), centroid`.
138+
139+
The element geometry is **isoparametric**: the map from the reference triangle is
140+
built from all 7 node positions via the P2+bubble shape functions, so displacing
141+
the edge/centroid nodes off the straight midpoints/barycenter genuinely curves the
142+
element (with a node-varying Jacobian). Triangles must be **orientation-preserving
143+
and non-self-intersecting** — construction errors if any element's `det J ≤ 0` at a
144+
quadrature node, since the barrier method requires strictly positive weights.
138145
"""
139146
function fem2d_P2(::Type{T}=Float64;
140147
K::Array{T,3} = _default_K7(T),
@@ -375,18 +382,42 @@ function _fem2d_P2_structured(::Type{T}, K::Array{T,3}, K7::Array{T,3}, L::Int)
375382
dy_block = zeros(T, p, p, N)
376383
w_vec = zeros(T, n)
377384

385+
# Isoparametric P2+bubble map: x(ξ,η) = Σ_i N_i(ξ,η) X_i over all p geometry
386+
# nodes, so the Jacobian J = [∂x/∂ξ ∂x/∂η; ∂y/∂ξ ∂y/∂η] varies node-to-node
387+
# whenever the element is curved (edge nodes off the midpoints, centroid off
388+
# the barycenter). The reference derivative rows give ∂N_i/∂ξ and ∂N_i/∂η at
389+
# each node, so (R_dx*X)[j] = ∂x/∂ξ at node j, etc. The physical-derivative
390+
# operator rows are J(node j)^{-T} applied to (R_dx, R_dy), and the quadrature
391+
# weight at node j is det J(node j) · R.w[j]. For a straight triangle J is
392+
# constant and this reduces to the previous affine map.
378393
for k in 1:N
379-
u = xL[:, 1, k] - xL[:, 5, k]
380-
v = xL[:, 3, k] - xL[:, 5, k]
381-
A = hcat(u, v)
382-
invA = inv(A)'
383-
dx_block[:, :, k] = invA[1, 1] * R_dx + invA[1, 2] * R_dy
384-
dy_block[:, :, k] = invA[2, 1] * R_dx + invA[2, 2] * R_dy
394+
X = @view xL[1, :, k]
395+
Y = @view xL[2, :, k]
396+
x_xi = R_dx * X; x_eta = R_dy * X
397+
y_xi = R_dx * Y; y_eta = R_dy * Y
385398
for j in 1:p
399+
detJ = x_xi[j] * y_eta[j] - x_eta[j] * y_xi[j]
400+
invdet = one(T) / detJ
401+
for m in 1:p
402+
dx_block[j, m, k] = ( y_eta[j] * R_dx[j, m] - y_xi[j] * R_dy[j, m]) * invdet
403+
dy_block[j, m, k] = (-x_eta[j] * R_dx[j, m] + x_xi[j] * R_dy[j, m]) * invdet
404+
end
386405
id_block[j, j, k] = one(T)
406+
w_vec[(k-1)*p + j] = detJ * R.w[j]
387407
end
388-
w_blk = abs(det(A)) * R.w
389-
w_vec[(k-1)*p+1:k*p] = w_blk
408+
end
409+
410+
# The barrier method requires strictly positive quadrature weights. A
411+
# non-positive weight means det J ≤ 0 at some node: the user-supplied curved
412+
# element is folded or has inverted (clockwise) orientation. Refuse to solve.
413+
if !all(>(zero(T)), w_vec)
414+
bad = findall(<=(zero(T)), w_vec)
415+
badelems = sort!(unique((bad .- 1) p .+ 1))
416+
error("fem2d_P2: non-positive quadrature weight at $(length(bad)) node(s) " *
417+
"across $(length(badelems)) element(s) (first few: $(first(badelems, 5))). " *
418+
"The isoparametric element map has det J ≤ 0 there — the curved triangle " *
419+
"is folded or has inverted (clockwise) vertex orientation. Supply " *
420+
"orientation-preserving, non-self-intersecting elements.")
390421
end
391422

392423
id = BlockDiag(id_block)

src/fem3d.jl

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,13 @@ transfers instead.)
4747
`K[v, e, d]` is coordinate `d` of Lagrange node `v` of hex `e` (tensor-product
4848
ordering: x fastest, then y, then z).
4949
50+
The element geometry is **isoparametric**: the map from the reference hex is built
51+
from all `(k+1)^3` node positions via the Q_k Lagrange basis, so displacing the
52+
edge/face/interior nodes genuinely curves the element (node-varying Jacobian).
53+
Hexes must be **orientation-preserving and non-self-intersecting** — construction
54+
errors if any element's `det J ≤ 0` at a quadrature node, since the barrier method
55+
requires strictly positive weights.
56+
5057
The Geometry is intended for Dirichlet boundary conditions.
5158
"""
5259
function fem3d(::Type{T}=Float64;

0 commit comments

Comments
 (0)