1+ module DarcyBGPflux
2+ # Darcy problem: BGP stabilisation with flux boundary conditions (pure pressure trace).
3+ # A Lagrange multiplier fixes the mean pressure to close the system.
4+ #
5+ # Ref: Badia, Boschman, Martín, Nilsson, Ruiz-Baier, Zahedi (2026)
6+ # "Divergence-free unfitted finite element discretisations for the Darcy problem"
7+ # arXiv:2603.26212
8+
9+ using Test
10+ using Gridap
11+ using GridapEmbedded
12+ using Gridap. Geometry, Gridap. Arrays
13+ using Gridap. FESpaces, Gridap. CellData
14+
15+ function get_aggregates (cutgeo)
16+ geo = get_geometry (cutgeo)
17+ strategy = AggregateAllCutCells ()
18+ aggregates = aggregate (strategy,cutgeo,geo)
19+ return Table (filter (agg -> length (agg) > 1 , inverse_table (aggregates)))
20+ end
21+
22+ function projection_operator (ptopo, V, W, Ω, dΩ)
23+ Π (u,Ω) = change_domain (u,Ω,DomainStyle (u))
24+ mass (u,v) = ∫ (u⋅ Π (v,Ω))dΩ
25+ P = LocalOperator (
26+ LocalSolveMap (), ptopo, W, V, mass, mass
27+ )
28+ return P
29+ end
30+
31+ function div_projection_operator (ptopo, V, W, Ω, dΩ)
32+ Π (u,Ω) = change_domain (u,Ω,DomainStyle (u))
33+ lhs (u,v) = ∫ (u⋅ Π (v,Ω))dΩ
34+ rhs (u,v) = ∫ ((∇⋅ u)⋅ Π (v,Ω))dΩ
35+ P = LocalOperator (
36+ LocalSolveMap (), ptopo, W, V, lhs, rhs
37+ )
38+ return P
39+ end
40+
41+ function generate_mesh (n,R= 0.4 ,x0= 0.835 )
42+ geo1 = disk (R;x0= Point (0.5 ,0.5 ),name= " disk" )
43+ geo2 = plane (x0= Point (x0,0.5 ),v= Point (1.0 ,0.0 ),name= " plane" )
44+ geo = intersect (geo1,geo2,name= " domain" )
45+
46+ bgmodel = CartesianDiscreteModel ((0 ,1 ,0 ,1 ),(n,n))
47+ cutgeo = cut (bgmodel,geo)
48+ return cutgeo
49+ end
50+
51+ function driver (n,order,u_exact,p_exact,η= 1.0 ,γ= 10.0 ,τ= 1.0 )
52+ cutgeo = generate_mesh (n)
53+ bgmodel = get_background_model (cutgeo)
54+ qdegree = 2 * (order+ 1 )
55+
56+ Ω_ac = Triangulation (cutgeo,ACTIVE)
57+ V = FESpace (Ω_ac,ReferenceFE (raviart_thomas,Float64,order); conformity= :Hdiv )
58+ Q = FESpace (Ω_ac,ReferenceFE (lagrangian,Float64,order); conformity= :L2 )
59+ Λ = ConstantFESpace (Ω_ac)
60+ X = MultiFieldFESpace ([V,Q,Λ])
61+
62+ ptopo = PatchTopology (get_grid_topology (bgmodel), get_aggregates (cutgeo))
63+ Ωp = PatchTriangulation (bgmodel, ptopo)
64+
65+ Ω = Triangulation (cutgeo,PHYSICAL)
66+ Γu = EmbeddedBoundary (cutgeo)
67+
68+ dΩ = Measure (Ω, qdegree)
69+ dΩp = Measure (Ωp, qdegree)
70+ dΓu = Measure (Γu, 2 * qdegree)
71+
72+ Wd = FESpaces. PatchFESpace (bgmodel,Ωp,Float64,order;space= :RT )
73+ Πd = projection_operator (ptopo, V, Wd, Ωp, dΩp)
74+ W0 = FESpaces. PatchFESpace (bgmodel,Ωp,Float64,order;space= :Q )
75+ Π0 = projection_operator (ptopo, Q, W0, Ωp, dΩp)
76+ Π0div = div_projection_operator (ptopo, V, W0, Ωp, dΩp)
77+
78+ f (x) = η* u_exact (x) + ∇ (p_exact)(x)
79+ g (x) = - (∇⋅ u_exact)(x)
80+
81+ h = γ/ n
82+ n_Γu = get_normal_vector (Γu)
83+ a (u,v) = ∫ (η* (u⋅ v))dΩ + ∫ (h* (u⋅ n_Γu)* (v⋅ n_Γu))dΓu
84+ b (u,q) = ∫ (- (∇⋅ u)* q)dΩ
85+ btilde (u,q) = b (u,q) + ∫ ((u⋅ n_Γu)* q)dΓu
86+ c (λ,q) = ∫ (λ* q)dΩ
87+ l ((v,q,μ)) = ∫ (v⋅ f + g⋅ q)dΩ + ∫ (h* (v⋅ n_Γu)⋅ (u_exact⋅ n_Γu))dΓu + c (μ,p_exact)
88+
89+ sd (u,v) = ∫ (τ* (u⋅ v))dΩp
90+ s0 (p,q) = ∫ (τ* (p* q))dΩp
91+
92+ function weakform (x,y)
93+ u, p, λ = x
94+ v, q, μ = y
95+ Πu, Πv = Πd (u), Πd (v)
96+ Πp, Πq = Π0 (p), Π0 (q)
97+ divu, divv = ∇⋅ u, ∇⋅ v
98+ Πdivu, Πdivv = Π0div (u), Π0div (v)
99+ Xp = FESpaces. PatchFESpace (X,ptopo)
100+ data = FESpaces. collect_and_merge_cell_matrix_and_vector (
101+ (X, X , a (u,v) + btilde (v,p) + b (u,q) + c (λ,q) + c (μ,p) + sd (u,v) - s0 (divu,q) - s0 (divv,p), l (y)),
102+ (X, Xp , s0 (divu,Πq) + s0 (Πdivv,p) - sd (u,Πv), DomainContribution ()),
103+ (Xp, X , s0 (Πdivu,q) + s0 (divv,Πp) - sd (Πu,v), DomainContribution ()),
104+ (Xp, Xp, sd (Πu,Πv) - s0 (Πdivu,Πq) - s0 (Πdivv,Πp), DomainContribution ()),
105+ )
106+ assem = SparseMatrixAssembler (X,X)
107+ A, B = assemble_matrix_and_vector (assem,data)
108+ return AffineFEOperator (X,X,A,B)
109+ end
110+
111+ op = weakform (get_trial_fe_basis (X),get_fe_basis (X))
112+ uh, ph, λh = solve (op)
113+
114+ eu = uh- u_exact
115+ ep = ph- p_exact
116+ l2_err_u = sqrt (sum (∫ ( eu ⋅ eu )dΩ))
117+ l2_err_p = sqrt (sum (∫ ( ep ⋅ ep )dΩ))
118+
119+ return cutgeo, uh, ph, l2_err_u, l2_err_p
120+ end
121+
122+ function convergence (ns,order,u_exact,p_exact)
123+ l2_u = zeros (Float64,length (ns))
124+ l2_p = zeros (Float64,length (ns))
125+ for (i,n) in enumerate (ns)
126+ _, _, _, l2_u[i], l2_p[i] = driver (n,order,u_exact,p_exact)
127+ end
128+ slope_u = log .(l2_u[1 : end - 1 ]. / l2_u[2 : end ]) ./ log .(ns[2 : end ]. / ns[1 : end - 1 ])
129+ slope_p = log .(l2_p[1 : end - 1 ]. / l2_p[2 : end ]) ./ log .(ns[2 : end ]. / ns[1 : end - 1 ])
130+ return l2_u, l2_p, slope_u, slope_p
131+ end
132+
133+ # Manufactured solution
134+ u_exact (x) = VectorValue (x[1 ], - x[2 ])
135+ p_exact (x) = x[1 ] + x[2 ]
136+ cutgeo, uh, ph, l2_err_u, l2_err_p = driver (8 ,1 ,u_exact,p_exact)
137+ @test l2_err_u < 1.e-10
138+ @test l2_err_p < 1.e-10
139+
140+ u_conv (x) = VectorValue (x[1 ] + sin (π* x[2 ]), - x[2 ] + sin (π* x[1 ]))
141+ p_conv (x) = sin (π* x[1 ]) - sin (π* x[2 ])
142+ l2_u, l2_p, su, sp = convergence ([8 ,16 ,32 ,64 ],1 ,u_conv,p_conv)
143+
144+ writevtk (
145+ Ω, " darcy_BGP_flux" ; append= false ,
146+ cellfields= [
147+ " uh" => uh," ph" => ph," u_exact" => u_exact," p_exact" => p_exact,
148+ " eu" => uh- u_exact," ep" => ph- p_exact
149+ ],
150+ )
151+
152+ cell_to_agg = flatten_partition (get_aggregates (cutgeo),num_cells (bgmodel))
153+ writevtk (
154+ Triangulation (bgmodel), " aggregates" ; append= false ,
155+ celldata = [" agg" => cell_to_agg]
156+ )
157+
158+ function normal_at_centroid (Γ)
159+ n = get_normal_vector (Γ)
160+ x = CellPoint (map (mean,get_cell_coordinates (Γ)),Γ,PhysicalDomain ())
161+ return n (x)
162+ end
163+
164+ Γu = EmbeddedBoundary (cutgeo)
165+ writevtk (
166+ Γu, " boundary_u" ; append= false , celldata= [" n" => normal_at_centroid (Γu)]
167+ )
168+
169+ end # module
0 commit comments