Remove CG and GMRES; LSMR is the sole iterative solver#35
Merged
Conversation
LSMR subsumes both: unpreconditioned path replaces CG, and the Gramian- side preconditioner path replaces GMRES for non-symmetric preconditioners. Deletes solve::cg, solve::gmres, their tests, and purges stale references from module docs, the README example, example labels, and test strings.
Removes the multiplicative variant along with its ResidualUpdater abstraction. The additive SchwarzPreconditioner remains the only preconditioner. Also strips stale references from crate-level docs and the README, and prunes dead test fixtures (DiagOperator, IdentityOp, SpdMatrix3, JacobiPrecond3, NonsymMatrix3, DiagLocalSolver, path_graph, cycle_graph) left behind by the CG/GMRES and multiplicative removals.
It existed only as test infrastructure for the now-deleted CG dedup path. Now defined as a private struct inside lsmr/tests.rs (its sole legitimate use), with the three trivial integration tests deleted.
The combined mlsmr(Option<&M>) entry point is replaced by two non-optional functions: lsmr (Golub-Kahan, no preconditioner) and mlsmr (Modified Golub-Kahan, &M required). Updates the module docstring, README example, and equivalence-test doc comment to match.
The additive subdirectory existed only to host the additive variant alongside multiplicative; with multiplicative gone, the wrapper module no longer earns its keep. Internal restructure only — public API (SchwarzPreconditioner, AdditiveSchwarzDiagnostics, ReductionStrategy re-exported from the crate root) is unchanged.
With CG and GMRES gone, the solve module wrapped a single solver and its name redundant with the only function inside (solve::lsmr::lsmr). Move src/solve/lsmr.rs and src/solve/lsmr/ up one level to src/lsmr.rs and src/lsmr/, fold the dot/vec_norm helpers from solve.rs into lsmr.rs as pub(crate), and re-export lsmr/mlsmr/LsmrResult/LsmrStopReason from the crate root alongside SchwarzPreconditioner.
The buffer-pooling types (BufferPool, LocalSolveScratch, AdditiveSweepBuffers, SchwarzBuffers, WorkerReductionBuffers) were used only by AdditiveExecutor, so the file boundary served no isolation purpose. Fold them into executor.rs and tighten visibility: all buffer types and helpers become file-private (no pub(super)), since they're only touched by AdditiveExecutor. Only AdditiveExecutor itself plus the seven fields/methods preconditioner.rs actually accesses retain pub(super).
Modified LSMR is now the sole iterative solver. Removes:
- KrylovMethod and OperatorRepr enums (LSMR-only, no Gramian dispatch)
- Preconditioner::Multiplicative + FePreconditioner::Multiplicative variants
- max_refinements field (refinement is unnecessary since LSMR converges on
the observation-space residual directly)
- The fused Gramian/preconditioner build path (dead since LSMR doesn't use
the explicit Gramian): build_preconditioner_fused, from_pair_blocks,
build_domains_and_gramian_blocks, PairBlockData, compose_gramian_from_blocks
- SparseGramianUpdater and the residual_update module (multiplicative-only)
- PyO3 classes PyCG, PyGMRES, PyMultiplicativeSchwarz, PyOperatorRepr;
validate_cg_preconditioner runtime check
- Examples preconditioned_solve, lsmr_vs_cg, memory_profile
- Tests preconditioner_coverage, iterative_refinement, local_solver_properties
- Bench suites that compared CG vs GMRES (preconditioners, ac_comparison,
fixest_comparison, akm_panel, verify)
SolverParams collapses to { tol, maxiter, local_size }.
LSMR works on sqrt(W) D directly via WeightedDesignOperator and never touches the assembled Gramian. The remaining users of Gramian/ GramianOperator were tests asserting equivalence between explicit and implicit forms — both forms are now obsolete. Removes: - Gramian struct (explicit CSR assembly + matvec, diagonal, extract_submatrix) - GramianOperator (implicit D^T W D matvec) - The entire crates/within/src/operator/gramian/explicit.rs file - CrossTab::from_gramian_block (test-only helper to derive a CrossTab from an assembled Gramian) and its test_helpers module - tests/gramian.rs (entire test file) - prop_gramian_symmetry, prop_explicit_equals_implicit_gramian, prop_weighted_explicit_equals_implicit - public re-exports of Gramian and GramianOperator from within:: - bench_matvec rewritten on WeightedDesignOperator (matvec_weighted_design) The operator::gramian module now houses only the per-pair CrossTab machinery used by Schwarz subdomain construction. The DENSE_TABLE_MAX_ENTRIES constant moves into cross_tab.rs and the module becomes pub(crate).
After the LSMR-only refactor several types had no production callers. Removes them and inlines the remaining preconditioner free functions as methods on FePreconditioner. Removed: - WithinError::Overflow — no producer (the Gramian assembly that emitted it is gone) - DesignOperator (unweighted) — tests now exercise WeightedDesignOperator directly - build_schwarz public convenience builder — sole caller was an internal test that now uses build_additive_with_strategy - pub use schwarz_precond::SchwarzPreconditioner re-export — no consumers - Free functions additive_reduction_strategy, resolved_additive_reduction_strategy, and additive_schwarz_diagnostics — inlined as methods on FePreconditioner Marked Preconditioner and FePreconditioner #[non_exhaustive] (BREAKING for downstream match sites) so future variants — e.g. a two-level Schwarz with a coarse space — can be added without a major version bump. The diagnostic methods on FePreconditioner return Option<...> so non-additive variants will return None rather than panic.
Add the public-surface removals that were not previously documented: Gramian/GramianOperator/DesignOperator/build_schwarz/FeSchwarz from the within crate; the schwarz-precond solve module flattening and schwarz::additive namespace flattening; and #[non_exhaustive] markers on Preconditioner / FePreconditioner. Add the LSMR additions that landed but were not noted: local_size windowed reorthogonalization knob, LsmrStopReason / stop_reason, and non-finite input validation. Add a Performance section for parallelized LSMR kernels. Drop the obsolete "iterations sums initial + iterative-refinement" line in Changed — iterative refinement was removed in this same release.
This was referenced May 10, 2026
The four suites were deleted alongside the CG/GMRES removal because their helpers (benchmark_cg, benchmark_gmres, MultiplicativeSchwarz) were gone. The scenarios are still relevant for LSMR: - akm_panel / akm_scaling — four AKM panel pathologies (power-law firms, low mobility, disconnected clusters, combined) and realistic-scaling sweep up to 1M workers. - ac_comparison / graph_backend_comparison — AC vs AC2 (split=1 vs 2) local-solver comparison; the question is orthogonal to outer-solver choice. - verify — LSMR Schwarz correctness across 2-FE / 3-FE topologies. - fixest_comparison — fixest-style 3-FE difficult DGP scaling, exact vs approximate Schur complement on the local solver. The CG-vs-GMRES preconditioner suite is not ported — its purpose evaporated with the solvers.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Modified LSMR replaces CG and GMRES as the sole iterative solver, motivated by the empirical comparison in #27: GMRES disagrees with CG/LSMR by ~1e-3 (invariant under tol → 0), is 1.7×–2.5× slower on large problems, and has higher per-iteration cost; LSMR matches CG on well-conditioned problems and is 9×–88× tighter on demeaning error for ill-conditioned panels.
Drops the supporting infrastructure that was only needed for CG/GMRES:
Gramian(CSR) andGramianOperator(LSMR usessqrt(W) Ddirectly)DesignOperator,build_schwarz,FeSchwarz,WithinError::Overflowschwarz_precond::solvemodule; namespace flattening inschwarz_precond::schwarzCG/GMRES/MultiplicativeSchwarzclassesMarks
PreconditionerandFePreconditioner#[non_exhaustive]to leave room for future variants (e.g. two-level Schwarz with a coarse space) without another major version bump.Net diff: −9,400+ lines across 11 commits, organized as schwarz-precond removals first (one concern per commit), then within-crate removals layered (solvers → Gramian → dead surface), then the changelog backfill.
Closes #27.
Test plan
cargo test --workspace— all suites passpixi run test— Python suite passes\bcg\b|gmres|multiplicative|iterative_refinementmatches in.rs/.pysources[Unreleased]section