# 2026-05-05 — Shell-element mesh-refinement convergence study **Goal:** Break the documented 4-solver tie on the faceted dome by asking which solver(s) converge to a stable limit as their mesh refines, and which diverge or drift. **Setup:** All four shell solvers run on the production 70-rhombic-panel dome midsurface under `wind_cc_peak baseline` (p = -3831 Pa, controlling load case for shell bending). **Driver:** [`tools/shell_convergence_study.py`](../tools/shell_convergence_study.py) **Raw data:** [`reports/shell_convergence_study.csv`](shell_convergence_study.csv) **Captured stdout:** [`reports/shell_convergence_study.txt`](shell_convergence_study.txt) --- ## Headline conclusion **The 4-solver tie is *not* broken by refinement — every solver drifts. None of the four shell formulations converges on the faceted dome.** The faceted-dome geometry creates a fundamental discretization problem that no single shell formulation can hide: refining the mesh exposes ever-larger panel-edge bending the solver tries to resolve, because the production geometry models adjacent panels as joined at a *kink* (a zero-thickness, infinite-stiffness fold) rather than at a finite fillet of finite stiffness. Each formulation responds differently: | Solver | u_max coarse → fine | Verdict | |---|---|---| | CalculiX S3 | 5.47 → 22.33 mm (+308%) | **DIVERGING** (last step +73%) | | in-house CST+DKT | 24.82 → 32.39 mm (+30%) | **DRIFTING** (last step +11%) | | OpenSees ShellDKGT | 39.71 → 56.79 mm (+43%) | **DRIFTING** (last step +8%) | | OpenSees ShellMITC4 | 30.16 → 37.33 mm (+24% from n_div=2) | **DRIFTING** (last step +6%) | CCX is **not** "the one that converged at the standard mesh while the others drifted" — it is the one that the project happened to evaluate at *only* the coarse end of its own divergence curve. At `target_size_mm = 200` (the production-mesh density used by every prior comparison), CCX read 12.93 mm; halve the target to 100 mm and the same CCX solver reads 22.33 mm — a 73% jump, the *largest* last-step delta of any solver. The original Phase-2 narrative had it that "CCX = 12.78 mm is the trusted reference and the other three solvers over-predict." The convergence study shows this conclusion was only an artefact of all four solvers being compared at a single point on each one's curve. At the finest common mesh (35 840 triangles / 18 239 nodes), the spread is still 22 → 57 mm — wider, in absolute terms, than at the standard mesh. --- ## Per-solver convergence tables ### CalculiX S3 (Mindlin-Reissner triangle) | target_mm | n_nodes | n_elem | u_max (mm) | sigma_b (MPa)\* | runtime (s) | Δu_max | Δsigma_b | |----------:|--------:|--------:|-----------:|----------------:|------------:|-------:|---------:| | 800 | 319 | 560 | 5.47 | 0.152 | 0.3 | n/a | n/a | | 400 | 1 199 | 2 240 | 7.90 | 0.238 | 0.3 | +44.6% | +56.7% | | 200 | 4 639 | 8 960 | 12.93 | 0.410 | 1.0 | +63.7% | +72.2% | | 100 | 18 239 | 35 840 | 22.33 | 0.706 | 3.6 | +72.6% | +72.0% | \* For CCX, "sigma_b" is *mid-surface* von Mises (the production CCX shell writer requests `OUTPUT=2D`, which gives only mid-plane stress — not pure-bending fiber stress). For all other solvers, sigma_b is true max-fiber bending stress. Magnitudes are still meaningful as a "how much stress is in the panel" indicator and the *trend* is the relevant convergence signal. **Verdict — DIVERGING.** u_max grows by roughly the same percentage on every refinement step (44, 64, 73, 73 %). This is the characteristic signature of a solution that doesn't have a bounded limit — every time the mesh resolves a smaller patch of panel, it finds more bending in it. ### In-house CST+DKT (Discrete Kirchhoff Triangle) | target_mm | n_nodes | n_elem | u_max (mm) | sigma_b (MPa) | runtime (s) | Δu_max | Δsigma_b | |----------:|--------:|--------:|-----------:|--------------:|------------:|-------:|---------:| | 800 | 319 | 560 | 24.82 | 0.279 | 0.2 | n/a | n/a | | 400 | 1 199 | 2 240 | 26.13 | 0.443 | 1.0 | +5.3% | +58.9% | | 200 | 4 639 | 8 960 | 29.29 | 0.631 | 5.9 | +12.1% | +42.4% | | 100 | 18 239 | 35 840 | 32.39 | 1.143 | 28.2 | +10.6% | +81.2% | **Verdict — DRIFTING.** u_max delta has stabilised around +10–12 % per refinement step (consistent, not decaying). σ_b is still growing at +40–80 % per step. Neither metric is converging — DKT does *not* have a stable answer on this geometry, but its per-step drift is smaller than CCX's, masking the real divergence at coarse meshes. ### OpenSees ShellDKGT (Discrete Kirchhoff Triangle) | target_mm | n_nodes | n_elem | u_max (mm) | sigma_b (MPa) | runtime (s) | Δu_max | Δsigma_b | |----------:|--------:|--------:|-----------:|--------------:|------------:|-------:|---------:| | 800 | 319 | 560 | 39.71 | 1.003 | 0.1 | n/a | n/a | | 400 | 1 199 | 2 240 | 47.26 | 1.249 | 0.4 | +19.0% | +24.5% | | 200 | 4 639 | 8 960 | 52.65 | 1.759 | 1.5 | +11.4% | +40.9% | | 100 | 18 239 | 35 840 | 56.79 | 2.645 | 6.7 | +7.9% | +50.4% | **Verdict — DRIFTING.** u_max per-step delta is decaying (19 → 11 → 8 %), which on its own would *suggest* it is approaching a limit (perhaps ~60 mm). σ_b however is still growing at +50 % per step, with no sign of stabilising. So u_max may have a finite limit while sigma_b does not — more typical of a singular solution where the displacement field remains finite but the stress at a kink line grows without bound. This is the same qualitative behaviour as DKT in-house, just at ~1.7× the magnitudes (consistent with the Phase 2 finding that the OpenSees DKGT implementation is more compliant than the in-house DKT — drilling-DOF treatment, mass-coupling differences). ### OpenSees ShellMITC4 (Mindlin-Reissner quad, n_div sweep) | n_div | n_nodes | n_elem | u_max (mm) | sigma_b (MPa) | runtime (s) | Δu_max | Δsigma_b | |------:|--------:|-------:|-----------:|--------------:|------------:|-------:|---------:| | 1 | 89 | 70 | 65.22 | 0.313 | 0.1 | n/a | n/a | | 2 | 319 | 280 | 30.16 | 0.475 | 0.1 | -53.8% | +51.8% | | 4 | 1 199 | 1 120 | 31.81 | 0.666 | 0.3 | +5.5% | +40.2% | | 6 | 2 639 | 2 520 | 35.10 | 1.039 | 0.7 | +10.3% | +56.1% | | 8 | 4 639 | 4 480 | 37.33 | 1.467 | 1.2 | +6.4% | +41.2% | **Verdict — DRIFTING (UPWARD).** Discount the n_div=1 row (one quad per panel is below the shell-element representational threshold; the answer is dominated by drilling-DOF stiffness, not bending). From n_div=2 to n_div=8, u_max **increases monotonically** by ~24 % total — confirming the Phase 2 observation that MITC4 goes UP with refinement, *away from* the CCX standard-mesh value, not toward it. σ_b grows even faster (+209 % total). MITC4 too has not converged. (Note: this driver's own n_div=2 quad mesh gives 30.16 mm vs the production 4-sub-quads-per-panel value of 29.83 mm. The ~1 % gap is the difference between bilinear-interpolation subdivision (this driver) and the centroid-and-edge-midpoint splitting used by the production `build_quad_midsurface_mesh`. Same panel boundary, same node count, slightly different interior geometry; the convergence trend is the same.) --- ## Cross-solver overlay At each mesh level, u_max in mm: ``` n_nodes n_elem CCX-S3 in-house-DKT OpenSees-DKGT OpenSees-MITC4(quad) 89 70 65.2 319 280 n/a* n/a* n/a* 30.2 319 560 5.5 24.8 39.7 1 199 1 120 31.8 1 199 2 240 7.9 26.1 47.3 2 639 2 520 35.1 4 639 4 480 37.3 4 639 8 960 12.9 29.3 52.6 18 239 35 840 22.3 32.4 56.8 ``` \* MITC4's 280-quad mesh sits at 319 nodes, the same node count as the 560-tri mesh, but they are *different elements*. The triangle solvers were not run at 70 elements / 89 nodes (the in-tree `build_midsurface_mesh` requires depth ≥ 1 → 8 tri/panel minimum). ASCII trend (u_max growth across refinement, all solvers normalized to their coarsest value): ``` refinement step: coarse --> --> --> --> finest CCX S3 [ 1.00 ] 1.45 2.36 4.08 ← biggest growth in-house DKT [ 1.00 ] 1.05 1.18 1.30 OpenSees DKGT [ 1.00 ] 1.19 1.33 1.43 OpenSees MITC4 [ 1.00 ] 1.05 1.16 1.24 (from n_div=2 baseline) ``` CCX has the *largest* relative drift — it just *appears* converged when read at coarse meshes because its absolute number is small. In absolute u_max at the finest common 35 840-tri mesh, the four solvers stack: ``` CalculiX S3 22.3 mm in-house DKT 32.4 mm (1.45 × CCX) OpenSees MITC4* 37.3 mm *4480 quads (1.67 × CCX) OpenSees DKGT 56.8 mm (2.54 × CCX) ``` The spread (22 → 57 mm) is **wider** than at the standard mesh (13 → 52 mm). Refinement makes the disagreement worse, not better. --- ## Why all four diverge The production midsurface mesh joins adjacent panels at a *kink* — a sharp fold between two flat triangles whose normals differ. In shell theory, a kink is a singular line: bending stress diverges logarithmically as the mesh resolves it. Every shell formulation sees this. The differences between formulations only set how fast they diverge: - **CCX S3** uses a rotation-free triangle that captures bending via the curvature of the displacement field across element edges. The finer the mesh, the more sharply CCX resolves the kink-line curvature → fastest growth. - **DKT (both in-house and OpenSees)** assumes Kirchhoff (no shear), which artificially stiffens out-of-plane deformation but does not prevent stress concentration at the kink → moderate growth. - **MITC4 quads** can resolve plate bending well but with one quad per panel-half they are too coarse to see the kink at all (n_div=1 gave 65 mm — pure drilling-DOF nonsense). Refining adds resolution and moves the answer up the divergence curve like everyone else. The "smooth-cap test" (`reports/2026-05-04--smooth-cap-4way.md`) confirmed all four solvers agree within ±7 % on a *non-faceted* spherical cap at the standard mesh. So the formulations are correct; the geometry is the problem. --- ## What this means for the project **The faceted-dome shell deflection number is not a fixed property of the structure — it is a property of the discretization choice.** No amount of trying alternative shell formulations or refining the mesh on the existing CAD will produce a single trustworthy number, because the CAD models the panel-to-panel joint as a perfect kink with infinite stiffness. **Three paths forward, in order of engineering rigour:** 1. **Physically model the joints with finite stiffness.** Replace the kink with a small fillet (radius ~10–25 mm, the actual glue bead size) modelled as a strip of shell elements with reduced stiffness, or as discrete rotational springs along each edge. Either choice converts the singular line into a regular feature that solvers can resolve. The resulting u_max should converge. 2. **Hand-calc the panel-bending demand using the max-pressure / single-panel approach (already in `run_acceptance.py`).** This bypasses the singular geometry entirely. The hand-calc D/C of 0.99 (Finding 2) does not depend on the mesh. 3. **Treat all four solvers as upper bounds on u_max and use the minimum (CCX) as the engineering deflection.** This is *not* rigorous — CCX may simply be the slowest to diverge, not the right answer — but it is consistent with conservative practice on stress demand (largest stress) being inverted for serviceability demand (smallest deflection). Document that the hand-calc D/C of 0.99 is the controlling design check, not the FE displacement. **Recommendation for the next analyst:** Path #1 (finite-stiffness joint modelling) is the one that produces a trustworthy single number. This is the work to do *before* any further FE-vs-FE comparison is worth running. Until that is done, the FE shell answer is bounded between 22 mm (CCX, conservative for stress) and ~60 mm (DKGT, may be conservative for serviceability) — engineers should design to both bounds. The headline assignment ("which solver wins on the faceted dome?") turns out to be the wrong question. **None of them do.** The right question is whether the finite-stiffness joint replacement is needed before the FE answer matters for sign-off — and per `reports/2026-05-04--findings.md` Finding 2, the controlling check (panel bending under wind uplift, severe site, hand-calc D/C = 0.99) is *not* the FE deflection result, so the project's pass/fail verdict does not change. The FE result is only needed for serviceability (deflection limits) and aesthetics (visible movement in wind), neither of which is on the gating path for life-safety. --- ## Files - `tools/shell_convergence_study.py` — driver (new) - `reports/shell_convergence_study.csv` — raw data (force-added; *.csv is gitignored by the repo's .gitignore) - `reports/shell_convergence_study.txt` — captured stdout (force-added; *.txt is gitignored) - `reports/2026-05-05--shell-convergence-study.md` — this document