Weak gravitational lensing mass reconstruction via P3 FEM-BEM coupled boundary value problems, with automatic Morozov-regularised MAP inversion and inverse-scattering support recovery.
FEMMI reconstructs the projected mass density
with shear components
The central methodological claim is that the standard practice of truncating this problem to a finite domain with Dirichlet boundary conditions (
| Feature | Kaiser-Squires (1993) | FEMMI |
|---|---|---|
| Far-field boundary condition | Periodic / Dirichlet (wrong) | Exact exterior via BEM |
| Regularisation parameter | Manual smoothing kernel | Morozov discrepancy principle |
| Mass-sheet degeneracy | Present in |
Resolved ( |
| Inverse method | Direct FFT | MAP + L-BFGS, Matérn prior |
| Masked / missing data | Unreliable near mask | Inpainting via prior covariance |
| Source positions | Binned grid | Catalog-native (raw galaxy positions) |
| Element order | N/A | P3 cubic (required for |
Full derivations are in MATH.md. The key ideas:
FEM-BEM coupling. A P3 FEM interior solves
where
Why P3 elements. Shear is the Hessian of
MAP reconstruction. The estimate minimises
(Morozov 1966; C&L Thm 10.4), using 15-25 MAP solves. The gradient is computed via the adjoint:
Injectivity and the mass-sheet degeneracy. The BEM far-field normalization fixes the
Inverse scattering connection. The forward operator
Synthetic benchmarks on a Gaussian convergence field (examples/generate_figures.py.
The Matérn prior propagates
| Mesh transition |
|
Theory |
|---|---|---|
femmi/
├── __init__.py
├── types.py # Mesh namedtuple
├── mesh.py # Structured and adaptive P3 mesh generation
├── basis.py # P3 Lagrange basis (10 DOF/element)
├── assembly.py # P3 element stiffness/mass assembly
├── bem.py # BEM: V_h, K_h, M_b, Calderon operator
├── operators.py # K, M, S1, S2, A_coupled; FEMOperators dataclass
├── forward.py # DifferentiableForward (JAX custom_vjp)
├── inverse.py # MAPReconstructor, kaiser_squires
├── regularization.py # MorozovSelector, estimate_noise_level
└── svd_analysis.py # SVD of F, Picard diagnostic, FactorizationIndicator, LSM
tests/
├── test_fem_bem_coupling.py # BEM matrices (V_h, K_h, M_b, Calderon)
├── test_coupled_pipeline.py # FEM-BEM pipeline invariants
├── test_morozov.py # Morozov lambda selection, monotonicity
├── test_factorization.py # SVD, Picard, support recovery
├── test_convergence_p3.py # O(h^4) L2 Poisson convergence
├── test_convergence.py # Forward operator gamma convergence
└── test_regression.py # End-to-end NFW reconstruction
examples/
├── generate_figures.py # Preliminary results figures (self-contained)
├── smpy_comparison.py # Full Monte Carlo benchmark vs SMPy KS
└── visualize_results.py # SVD modes, Picard, convergence diagnostics
git clone https://github.com/AdamField118/FEMMI.git
cd FEMMI
pip install -e ".[dev]"Requirements: Python 3.10+, JAX >= 0.4, SciPy >= 1.11, NumPy >= 1.25, matplotlib.
64-bit arithmetic is mandatory. FEMMI enforces this at import time via jax.config.update("jax_enable_x64", True). For a
import numpy as np
from femmi.operators import build_operators
from femmi.forward import DifferentiableForward
from femmi.inverse import MAPReconstructor
from femmi.regularization import estimate_noise_level
# Build mesh and operators (20x20 P3 mesh on [-2.5, 2.5]^2)
ops = build_operators(nx=20, ny=20, xmin=-2.5, xmax=2.5, ymin=-2.5, ymax=2.5)
# Synthetic convergence field and noiseless forward model
nodes = np.array(ops.mesh.nodes)
kappa_true = np.exp(-(nodes[:, 0]**2 + nodes[:, 1]**2) / (2 * 0.5**2))
g1, g2 = ops.forward(kappa_true)
# Add 10% noise
noise = 0.10 * np.std(np.hypot(g1, g2))
rng = np.random.default_rng(42)
g1_obs = g1 + rng.normal(0, noise, g1.shape)
g2_obs = g2 + rng.normal(0, noise, g2.shape)
# MAP reconstruction with automatic lambda (Morozov)
noise_std = estimate_noise_level(np.concatenate([g1_obs, g2_obs]), method='mad')
fwd = DifferentiableForward(ops, lam_reg=1e-3)
rec = MAPReconstructor(fwd, noise_std=noise_std, wiener_length=0.5)
kappa_map, result = rec.reconstruct(g1_obs, g2_obs)# Locally refined mesh near a circular mask (e.g. bright cluster core)
from femmi.operators import build_operators_adaptive
ops = build_operators_adaptive(
nx=20, ny=20, xmin=-2.5, xmax=2.5, ymin=-2.5, ymax=2.5,
mask_center=(0., 0.), mask_radius=0.6, refine_factor=3,
)# SVD and support recovery (Kirsch factorization method)
from femmi.svd_analysis import compute_svd, FactorizationIndicator
svd = compute_svd(ops, n_singular=40)
fi = FactorizationIndicator(ops, svd_result=svd)
import numpy as np
XX, YY = np.meshgrid(np.linspace(-2.5, 2.5, 64), np.linspace(-2.5, 2.5, 64))
test_pts = np.column_stack([XX.ravel(), YY.ravel()])
W = fi.indicator_map(test_pts).reshape(64, 64) # large inside supp(kappa)Forward solve (two SuperLU solves per MAP iteration):
Adjoint gradient (for L-BFGS):
Morozov
BEM assembly: Diagonal blocks of
Poisson solve (P3, smooth manufactured solution, unit square):
| Mesh |
|
Theory |
|---|---|---|
| 3.86 | ||
| 3.90 | ||
| 3.97 |
Forward operator (
| Mesh |
|
Theory |
|---|---|---|
The
- Colton, D. & Kress, R. (2013). Inverse Acoustic and Electromagnetic Scattering Theory, 3rd ed. Springer.
- Steinbach, O. (2008). Numerical Approximation Methods for Elliptic Boundary Value Problems. Springer.
- Sauter, S. & Schwab, C. (2011). Boundary Element Methods. Springer.
- Kirsch, A. (1998). Characterization of the shape of a scattering obstacle using the spectral data of the far-field operator. Inverse Problems, 14, 1489–1512.
- Colton, D. & Kirsch, A. (1996). A simple method for solving inverse scattering problems in the resonance region. Inverse Problems, 12, 383–393.
- Morozov, V. A. (1966). On the solution of functional equations by the method of regularization. Soviet Math. Doklady, 7, 414–417.
- Kaiser, N. & Squires, G. (1993). Mapping the dark matter with weak gravitational lensing. ApJ, 404, 441–450.
- Dunavant, D. A. (1985). High degree efficient symmetrical Gaussian quadrature rules for the triangle. IJNME, 21(6), 1129–1148.
- Brenner, S. & Scott, R. (2008). The Mathematical Theory of Finite Element Methods, 3rd ed. Springer.


