Proposal: audit of unsupported/ modules for promotion to Core
## Summary This is a proposal for discussion: an audit of `unsupported/Eigen/` modules with a tiered recommendation for which should be promoted into the supported `Eigen/` Core, which are close-but-not-yet (with the specific gating work named), and which should stay in `unsupported/` indefinitely. The audit was done with a deliberately high bar — when in doubt, the recommendation is "wait one more cycle" rather than promote. ABI/API commitments are forever; the cost of waiting is just time. None of this is meant as a directive; it's a starting point for the maintainers and community to react to. ## Promotion criteria used - Matters to Eigen users (real demand, well-known algorithms, depended-on by downstream code). - Stable for years (no recent semantic bug fixes, low and cosmetic-only churn). - Solid test coverage (decomposed via `CALL_SUBTEST_N`, not monolithic; covers multiple scalar types and edge cases). - Solid documentation (Doxygen API docs at the level of `Eigen/Cholesky` etc., plus an example in `unsupported/doc/examples/`). ## Methodology Quantified four signals per module: - **Source size and TODOs** in `unsupported/Eigen/src/<Module>/` (or umbrella header for single-header modules like NNLS). - **Git churn** in 12 / 24 / 60-month windows, plus inspection of recent commits to distinguish cosmetic from semantic changes. - **Test coverage** in `unsupported/test/` — line counts and `CALL_SUBTEST_N` decomposition counts (the standard Eigen marker for substantive multi-scalar / multi-shape testing). - **Documentation quality** — Doxygen comments in headers, presence of example in `unsupported/doc/examples/`, mention in `doc/`. External-binding modules (`AdolcForward`, `ArpackSupport`, `MPRealSupport`, `OpenGLSupport`, `GPU`) are out of scope by nature — Eigen does not control their stability. They stay in `unsupported/` regardless of quality. --- ## Tier 1 — Promote now (bar genuinely met) ### **MatrixFunctions** — strongest candidate - Algorithms are state-of-the-art (Higham / Al-Mohy) and mathematically frozen — the spec doesn't drift. - 2533 LOC, 194 lifetime commits but only 6 in 12mo and all cosmetic (clang-format, C++14 modernization, flaky-test fixes). - Test coverage is excellent and properly decomposed: `matrix_power.cpp` 215 LOC / 45 subtests, `matrix_function.cpp` 210 / 11, `matrix_exponential.cpp` 128 / 17, `matrix_square_root.cpp` 29 / 6. - Documentation is comprehensive; abundant examples in `unsupported/doc/examples/`: `MatrixExponential.cpp`, `MatrixFunction.cpp`, `MatrixLogarithm.cpp`, `MatrixPower.cpp`, `MatrixPower_optimal.cpp`, `MatrixSine.cpp`, `MatrixSinh.cpp`, `MatrixSquareRoot.cpp`. - Real-world demand: matrix exponential is a workhorse in control theory, ODE integration, quantum simulation. Currently users have to opt into "unsupported" for what is effectively a Core-grade capability. - 5 TODOs in source are micro-optimizations ("Use that A is upper triangular", "Replace by bitshift"), not correctness gaps. **Promotion path:** move `unsupported/Eigen/MatrixFunctions` → `Eigen/MatrixFunctions`; move `unsupported/Eigen/src/MatrixFunctions/*` → `Eigen/src/MatrixFunctions/*`; integrate the `MatrixBase` extensions cleanly into Core's build (currently injected via plugin pattern); add a topic page under `doc/` ("Matrix functions") in the same style as the Cholesky / QR pages. ### **KroneckerProduct** — small, frozen, broadly useful - 288 LOC across 2 headers; the API is `kroneckerProduct(A, B)` — narrow surface, mathematically trivial spec, no API drift risk. - 35 lifetime commits, only 2 in 12mo and 8 in 5yr — all cosmetic (typo fixes, comment cleanup). - 242-line test with 2 subtests covering both dense and sparse paths. - Narrow API surface that good Doxygen coverage covers adequately. - High user value: Kronecker products show up constantly in control, statistics, signal processing, tensor algebra. **Gating work for Tier 1:** add one example file (`unsupported/doc/examples/KroneckerProduct.cpp`) and a sentence in `doc/`. The example could realistically be added in the same MR as the move. --- ## Tier 2 — Promote when specific gating work lands Each has one or two named gaps. None close on their own; each needs an MR. ### **NNLS** — closest to ready in this tier - Single 392-line umbrella header (no `src/` directory), self-contained, depends only on Core QR. - Documentation is excellent with a full algorithm description and math in the header. - Test: 471 LOC with 15 subtests — strong. - Imported as a mature drop in 2021; only cosmetic edits since. **Gating work:** 1. Add `unsupported/doc/examples/NNLS.cpp` — Core precedent is that promoted features ship with an example. 2. Confirm the `Status` / `info()` enum API is what we want to commit to long-term (one design review; likely fine as-is). ### **FFT** — gating is policy, not code - 1021 LOC, 6 headers, 11 commits in 5yr (mostly cosmetic). The bundled kissfft path is mature. - Test architecture is good: `fft_test_shared.h` is 311 lines, and per-backend `.cpp` shims (`FFT.cpp`, `FFTW.cpp`, `pocketfft.cpp`, `duccfft.cpp` — all 2–4 lines) verify the same contract across backends. This is intentional and defensible. **Gating work:** 1. **License clarity.** FFTW is GPL — incompatible with Eigen's MPL2. Decide explicitly whether the FFTW backend stays on promotion (with documentation that linking it makes the user's binary GPL), gets removed, or stays in `unsupported/` while the kissfft / PocketFFT / Ducc backends move to Core. One option: promote with kissfft + PocketFFT + Ducc; leave the FFTW backend in `unsupported/` as a bridge. 2. Polish API docs on `FFT::fwd` / `FFT::inv` overloads — currently terse compared to Core conventions. ### **Polynomials** - 4 headers, 789 LOC, 0 TODOs, 35 lifetime commits. - Tests are properly decomposed (`polynomialsolver` 212/12 subtests, `polynomialutils` 102/16). Has `PolynomialSolver1.cpp` and `PolynomialUtils1.cpp` examples. **Gating work:** 1. Upgrade docs: class-level documentation explaining the QR-on-companion-matrix root finder's accuracy regime (the well-known limitation is that convergence guarantees require distinct moduli) and when to use this vs. building an `EigenSolver` on a companion matrix manually. 2. Topic page in `doc/` — one short page in the style of `doc/TutorialLinearAlgebra.dox`. ### **EulerAngles** — needs a soak period, not just code - 558 LOC, 32 lifetime commits. - Good documentation with `EulerAngles.cpp` example. - Test: 314 LOC / 7 subtests — adequate. - **The blocker is recent correctness history.** Commit `eca5d711c` on 2026-04-26 fixed a real gimbal-lock bug (closing #2617). Earlier history shows a recurring pattern of correctness fixes (`6d991a959` bug #1464, `58f5d7d05` "Fix calc bug, docs and better testing"). The module is finally converging but the bug-find rate is still nonzero this year. **Gating work:** 1. **Stability soak period — minimum 6 months with no new correctness fixes** (i.e., earliest reconsideration window: 2026-10-26). 2. Resolve the 6 TODOs in `unsupported/Eigen/src/EulerAngles/` (or document why each is intentional). 3. Add a property-based / randomized round-trip test (rotation matrix → Euler → rotation matrix, check residual) — this is the test pattern that would have caught the gimbal-lock bug earlier. --- ## Tier 3 — Hold (rework or evidence needed before reconsidering) ### **IterativeSolvers** — riskiest "looks ready" candidate - 2551 LOC, 9 headers (GMRES, DGMRES, MINRES, IDR(s), IDR(s)Stab, BiCGSTAB(L), IncompleteLU, Scaling). - **Two recent semantic fixes:** `31d9a793c` 2026-04-20 "DGMRES: guard against Arnoldi happy breakdown and singular Hessenberg" (real correctness bug in mature Krylov code) and `753a6ac5b` 2026-03-29 "Fix private shadowing of protected base members in iterative solvers" (closes #1859). - **Tests are 26–69 lines each** — `gmres.cpp` 30, `bicgstabl.cpp` 30, `idrs.cpp` 26, `idrstabl.cpp` 28, `minres.cpp` 41, `dgmres.cpp` 69. These are smoke-test driver harnesses, not test suites. **Reconsider when:** 1. Test rewrite: each solver gets multi-RHS, varied conditioning, multiple scalar types, decomposed via `CALL_SUBTEST_N` — pattern after Core `test/conjugate_gradient.cpp` and similar. 2. One full year clear of new correctness fixes after the test rewrite. 3. Decide whether all solvers graduate together or only the well-tested subset (GMRES + MINRES first, others later). ### **AutoDiff** - Forward-mode-only AD via `AutoDiffScalar`. Niche but real (small Jacobians, embedded use, alternative to Ceres' `Jet`). Not competitive with full reverse-mode AD frameworks but doesn't need to be — its value is "header-only, Eigen-native, zero-dependency forward AD". - 971 LOC; 444-line test (`autodiff.cpp` 347/9 subtests + `autodiff_scalar.cpp` 97/5). - No example file; documentation has gaps. **Reconsider when:** 1. Add `unsupported/doc/examples/AutoDiff.cpp` and a clear positioning note ("forward-mode AD for small Jacobians; for reverse-mode or large problems, use X"). 2. Roughly 2× the test coverage with multiple-scalar testing and a sweep of standard math functions. ### **Splines** - 984 LOC, 4 headers, 7 commits in 5yr — low churn is a good signal. - **Test is 205 lines with 0 subtests** — monolithic, doesn't probe multiple scalars or degrees systematically. - No example file. **Reconsider when:** subtest decomposition + an example. Small lift. ### **BVH** - 517 LOC, 3 headers, 23 lifetime commits — very low churn. - Test 247 lines / 3 subtests; has `BVH_Example.cpp`. - The honest concern is demand, not quality. BVHs are a thousand-implementations problem; Eigen's `KdBVH` is fine but is not the canonical implementation in the field. **Reconsider when:** evidence of substantial user demand (issue tracker, mailing list, downstream usage). ### **SparseExtra** — grab-bag, currently active - 2057 LOC, 6 headers, 9 TODOs, 21 commits in 5yr. Active development as of 2026-05-02. Mixed-purpose: MatrixMarket I/O, BlockSparseMatrix, dynamic sparse, RandomSetter, SparseInverse. - 261-line test with 29 subtests — coverage is decent. **Reconsider when:** API stabilizes and the module stops being a grab-bag. Strong recommendation: decide whether to split at promotion time. MatrixMarket I/O is a clear Core-tier candidate (it's a standard format). RandomSetter and SparseInverse have clear cases. BlockSparseMatrix and the dynamic sparse formats are more speculative. ### **SpecialFunctions** — eventual Tier 1, not yet - 5823 LOC, 21 headers, 8 TODOs. 18 commits in 24mo — most active candidate. Recent activity is mostly arch-specific packetmath additions (AVX, AVX512, NEON, GPU/HIP/CUDA). - 425-line `special_functions.cpp` test (4 subtests), 275-line `bessel_functions.cpp` (2 subtests), 160-line `special_packetmath.cpp` (4 subtests). - **Couples to Tensor** — Tensor's umbrella header `#includes` SpecialFunctions. This is a non-trivial logistical issue. **Reconsider when:** 1. Tensor's status is resolved one way or another (so the coupling stops being awkward). 2. Per-architecture packetmath specializations slow down (last 12 months had 10 commits — needs to drop into single-digit territory). This is probably the right call to promote eventually, but moving it before Tensor's situation is settled creates an ugly dependency mismatch. --- ## Tier 4 — Do not promote (permanent unsupported / out of scope) ### **Tensor**, **TensorSymmetry**, **TensorUtil** — self-disqualified The module's own README at `unsupported/Eigen/src/Tensor/README.md` says: > The Tensor module is part of Eigen's unsupported modules. While it is actively used in production (e.g. in TensorFlow), its API may change without notice. That is dispositive. Plus 17 commits in last 12mo, 41 in last 24mo, 160 in last 5yr — including feature additions (`TensorRoll`, gemv fast paths, `TensorConcatenation` packet vectorization), packet-op churn, structural reorgs (recent "Remove CXX11/ directory nesting"). This is an actively evolving codebase with TensorFlow as anchor user. Promoting it would either freeze legitimate evolution or break promised ABI guarantees. **Recommendation:** keep in `unsupported/` indefinitely. If formal status is desired, consider documenting an `Eigen-experimental` tier separate from Core. ### **LevenbergMarquardt** + **NonLinearOptimization** — algorithmically obsolete in this form - Direct MINPACK Fortran→C++ ports. Visible in source filenames: `lmpar.h`, `r1updt.h`, `dogleg.h`, `qrsolv.h`. - Tests: `levenberg_marquardt.cpp` 1523 LOC / 0 subtests, `NonLinearOptimization.cpp` 1841 LOC / 0 subtests — monolithic 1990s-style. - The world has moved past these in this form: Ceres, NLopt, Optim.jl, scipy.optimize, dlib all serve this user base better. **Recommendation:** deprecate over time, do not promote. A future Eigen non-linear optimization story (if there should be one) would be a clean rewrite, not a promotion of these. ### **NumericalDiff** — no independent constituency - 130 LOC, 2 headers; only consumers are `LevenbergMarquardt` and `NonLinearOptimization` (both in Tier 4). - Test 102 LOC / 0 subtests. **Recommendation:** no independent demand. If LM/NLO are deprecated, deprecate this with them. ### **AlignedVector3** — niche curiosity - 212 LOC; 86-line test / 0 subtests. A 3-vector that pretends to be a 4-vector for SIMD alignment. Vanishingly small audience. **Recommendation:** permanent `unsupported/`. ### External-binding modules: **AdolcForward**, **ArpackSupport**, **MPRealSupport**, **OpenGLSupport**, **GPU** Stability is not Eigen's to control — depends on third-party libraries. Out of scope for promotion regardless of quality. --- ## Suggested promotion order (if all gating work happens) 1. **Now (this release cycle):** `MatrixFunctions`, `KroneckerProduct`. Keep the bar visible by promoting only these. 2. **+3 months:** `NNLS` (after example lands), `Polynomials` (after doc upgrade), `FFT` (after backend/license decision). 3. **+6 months:** `EulerAngles` (post-soak; earliest reconsideration 2026-10-26). 4. **+12 months:** revisit `IterativeSolvers`, `AutoDiff`, `Splines`, `BVH`, `SparseExtra`, `SpecialFunctions` — each on its own gating timeline. ## Verification (when individual promotion MRs land) For each promoted module: 1. `ninja BuildOfficial` builds clean (the test moved with the module). 2. `ctest -L Official -R <module>` passes for the moved tests. 3. Continued `BuildUnsupported` passes (no orphaned `#include` paths). 4. `unsupported/doc/examples/<Module>.cpp` either moved to `doc/examples/` or replicated for Core. 5. Doxygen build (`ninja doc`) produces clean module page with `\defgroup <Module>_Module` rendering. 6. Internal-header check (`InternalHeaderCheck.h`) still passes — header layout matches Core convention (each implementation header includes it; users reach it only through the umbrella). ## Discussion prompts A few specific things I'd appreciate maintainer input on: 1. Is the suggested **MatrixFunctions + KroneckerProduct** initial pair the right first step, or is there a maintainer preference for a different module to lead the migration? 2. Is the **FFTW license question** something the project has a settled position on already? If FFTW must come along on promotion, that materially changes the FFT recommendation. 3. **LevenbergMarquardt + NonLinearOptimization deprecation** is the most opinionated call here. Is there a constituency that would push back? If so, the alternative is a clean rewrite-then-promote rather than promotion of the existing MINPACK port. 4. Is **EulerAngles** soak period (6 months no correctness fixes) the right framing? An alternative is a property-based test rewrite as the gate, which would also catch the next class of bugs faster. 5. Is **Tensor**'s permanent-unsupported status uncomfortable for the project, given how heavily it is used? If yes, a separate "Eigen-experimental" tier (between unsupported and Core) might be worth a separate discussion thread. Tagging this `~"unsupported"` for visibility. Happy to follow up with specific MRs for the Tier 1 moves if there's appetite.
issue