@@ -33,11 +33,47 @@ This can cause significant errors in free-slip boundary conditions.
3333
3434---
3535
36- ## Three Approaches
36+ ## Four Approaches
3737
38- ### 1. Raw ` mesh.Gamma ` (Simplest )
38+ ### 1. Nitsche Free-Slip (Recommended )
3939
40- Use the mesh-derived normals directly:
40+ Nitsche's method provides a variationally consistent alternative to penalty
41+ that is insensitive to the penalty magnitude and gives optimal convergence:
42+
43+ ``` python
44+ stokes.add_nitsche_bc(" Upper" , gamma = 10 )
45+ ```
46+
47+ The method automatically constructs penalty, consistency (stress flux),
48+ symmetry, and pressure coupling terms. The ` gamma ` parameter is dimensionless
49+ and mesh-independent — ` gamma=10 ` works for P2 elements regardless of
50+ resolution or viscosity.
51+
52+ ** Prescribed normal velocity:**
53+ ``` python
54+ stokes.add_nitsche_bc(" Inlet" , g = 1.0 , gamma = 10 )
55+ ```
56+
57+ ** Custom constraint direction** (e.g., fault normal different from surface normal):
58+ ``` python
59+ fault_normal = sympy.Matrix([0.6 , 0.8 ])
60+ stokes.add_nitsche_bc(" Fault" , direction = fault_normal, gamma = 10 )
61+ ```
62+
63+ ** When to use:**
64+ - Free-slip on any geometry (boxes, annuli, spherical shells)
65+ - Spherical shell models where penalty is fragile
66+ - When you don't want to tune a penalty parameter
67+ - Basal shear constraints with custom direction
68+
69+ ** Accuracy:** Optimal convergence rate. On a Cartesian box test, Nitsche at
70+ ` gamma=10 ` gives 0.08% velocity error vs the essential BC solution (penalty
71+ at 1e4 gives 0.15%).
72+
73+
74+ ### 2. Penalty Free-Slip (Simple but Fragile)
75+
76+ Use the mesh-derived normals directly with a penalty parameter:
4177
4278``` python
4379Gamma = mesh.Gamma
@@ -46,14 +82,16 @@ stokes.add_natural_bc(penalty * Gamma.dot(v.sym) * Gamma, "Boundary")
4682```
4783
4884** When to use:**
49- - Straight-edged boundaries (boxes, channels)
50- - Circular boundaries (normals are radial, so facet direction is correct)
5185- Quick prototyping where high accuracy isn't critical
86+ - When Nitsche is not yet available for your solver type
5287
53- ** Accuracy:** ~ 25-30% error on elliptical boundaries
88+ ** Limitations:**
89+ - Penalty must be tuned: too small → loose constraint, too large → ill-conditioning
90+ - On spherical shells, penalty can become unstable at moderate resolution
91+ - ~ 25-30% error on elliptical boundaries when using raw facet normals
5492
5593
56- ### 2 . Projected Normals (Recommended for Curved Boundaries)
94+ ### 3 . Projected Normals (For Curved Boundaries with Penalty )
5795
5896Project ` mesh.Gamma ` onto a continuous mesh variable, which interpolates and smooths the normals:
5997
@@ -96,7 +134,7 @@ stokes.add_natural_bc(penalty * n_proj.sym.dot(v.sym) * n_proj.sym, "Boundary")
96134** Why it works:** The projection solves a weak-form problem that naturally smooths the discontinuous facet normals into a continuous field. The finite element basis functions interpolate between facets, approximating the true surface direction.
97135
98136
99- ### 3 . Analytical Normals (Most Accurate)
137+ ### 4 . Analytical Normals (Most Accurate)
100138
101139Derive the surface normal from the mathematical definition of the boundary:
102140
@@ -179,15 +217,18 @@ unit_normal = normal / sympy.sqrt(normal.dot(normal))
179217
180218## Experimental Comparison
181219
182- We tested the three approaches on an elliptical annulus (ellipticity = 1.5) with a free-slip boundary condition:
220+ We tested the approaches on an elliptical annulus (ellipticity = 1.5) with a free-slip boundary condition:
183221
184- | Approach | Error vs Analytical |
185- | ----------| ---------------------|
186- | Raw ` mesh.Gamma ` | 26.88% |
187- | Projected normals | 0.06% |
188- | Analytical | 0% (reference) |
222+ | Approach | Error vs Analytical | Notes |
223+ | ----------| ---------------------| -------|
224+ | Penalty + raw ` mesh.Gamma ` | 26.88% | Penalty-sensitive |
225+ | Penalty + projected normals | 0.06% | Requires projection solve |
226+ | Penalty + analytical normals | 0% (reference) | Requires surface formula |
227+ | Nitsche (default) | ~ 0.1% | No penalty tuning needed |
189228
190- The projected normals provide ** 99.8% improvement** over raw ` mesh.Gamma ` for curved boundaries.
229+ Nitsche is recommended for most use cases — it matches the accuracy of
230+ projected normals without requiring a separate projection solve or penalty
231+ tuning.
191232
192233---
193234
@@ -222,21 +263,59 @@ For complex geometries where the orientation varies spatially, the projection ap
222263
223264---
224265
266+ ## Internal Boundaries
267+
268+ ``` {warning}
269+ Nitsche BCs are designed for **exterior** boundaries only. Do not use
270+ `add_nitsche_bc` on internal boundary labels (e.g., `"Internal"` from
271+ `BoxInternalBoundary` or `AnnulusInternalBoundary`).
272+ ```
273+
274+ On an internal surface, PETSc evaluates boundary residuals from ** both**
275+ adjacent cells with opposite normals. The Nitsche consistency and pressure
276+ terms flip sign between the two sides and partially cancel, injecting
277+ a spurious residual that degrades the solution. Testing on an annulus
278+ with an internal boundary showed that Nitsche produced * worse* constraint
279+ enforcement than no constraint at all.
280+
281+ ** For internal boundary constraints, continue using the penalty approach:**
282+
283+ ``` python
284+ Gamma = mesh.Gamma
285+ stokes.add_natural_bc(penalty * Gamma.dot(v.sym) * Gamma, " Internal" )
286+ ```
287+
288+ The penalty term is quadratic in the normal (` n × n ` ), so both sides
289+ reinforce correctly regardless of normal orientation.
290+
291+ A proper Nitsche formulation for internal surfaces would require an
292+ interior penalty (IP/SIP) method with explicit jump and average operators
293+ across the interface — accessing the solution from both adjacent cells.
294+ PETSc's current pointwise callbacks do not provide cross-cell access,
295+ so this would require a different assembly strategy. This is an area
296+ for future development, particularly for embedded impermeable surfaces
297+ in 3D spherical models where penalty sensitivity becomes problematic.
298+
299+ ---
300+
225301## Tips for Success
226302
227- 1 . ** Always normalize** : Analytical formulas need explicit normalization
228- 2 . ** Check orientation** : Ensure normals point outward (use ` sign(r.dot(normal)) ` )
229- 3 . ** Verify visually** : Plot the normal field to catch errors
230- 4 . ** Start with projection** : It's robust and doesn't require deriving formulas
231- 5 . ** Use analytical for validation** : Compare projected normals against analytical when possible
303+ 1 . ** Start with Nitsche** : ` stokes.add_nitsche_bc("Upper", gamma=10) ` — no penalty tuning needed
304+ 2 . ** For penalty BCs, always normalize** : Analytical formulas need explicit normalization
305+ 3 . ** Check orientation** : Ensure normals point outward (use ` sign(r.dot(normal)) ` )
306+ 4 . ** Verify visually** : Plot the normal field to catch errors
307+ 5 . ** Use analytical normals for validation** : Compare against exact surface geometry when possible
308+ 6 . ** Custom constraint direction** : Use ` direction= ` parameter when the constraint
309+ direction differs from the surface normal (faults, basal shear)
232310
233311---
234312
235313## Further Reading
236314
237315- [ Custom Mesh Creation] ( custom-meshes.md ) — Creating elliptical and complex meshes
238316- [ Stokes Ellipse Example] ( ../examples/fluid_mechanics/intermediate/Ex_Stokes_Ellipse_Cartesian.py ) — Complete worked example
317+ - Sime & Wilson (2020), [ arXiv:2001.10639] ( https://arxiv.org/abs/2001.10639 ) — Nitsche free-slip for geodynamics
239318
240319---
241320
242- * Last updated: January 2026*
321+ * Last updated: April 2026*
0 commit comments