71 lines
4.6 KiB
Markdown
71 lines
4.6 KiB
Markdown
|
|
# Sparse direct preconditioner backend
|
||
|
|
|
||
|
|
`strategies::inverse::SparseDirect` factors an assembled preconditioner block once per Newton state with a
|
||
|
|
pivoted sparse LU and applies it with one exact solve. `make_primal_preconditioner(layout, SparseDirect{})`
|
||
|
|
uses it for the gravitational potential; `complete_sandbox` and the profiling drivers use it by default
|
||
|
|
when it is available (`--potential-inverse auto`).
|
||
|
|
|
||
|
|
The factorization library is chosen when SERiF is configured:
|
||
|
|
|
||
|
|
```sh
|
||
|
|
meson setup build -Dsparse_direct=auto # default
|
||
|
|
meson setup build -Dsparse_direct=accelerate # Apple Accelerate (macOS)
|
||
|
|
meson setup build -Dsparse_direct=umfpack # SuiteSparse UMFPACK (any platform)
|
||
|
|
meson setup build -Dsparse_direct=none # no backend
|
||
|
|
```
|
||
|
|
|
||
|
|
| Value | Library | Notes |
|
||
|
|
|---|---|---|
|
||
|
|
| `auto` | Accelerate on macOS when the SDK provides `SparseFactorizationLU`, else UMFPACK when `pkg-config` finds it, else `none`. `feature_profile=minimal` selects `none`. | The configure summary prints the result as `SparseDirect backend`. |
|
||
|
|
| `accelerate` | Apple Accelerate sparse LU, METIS ordering | A system framework: nothing to bundle. Needs a macOS 15.5 SDK at build time and macOS 15.5 at run time (checked at run time; older systems report it unavailable). Its headers are not valid C++26, so only `inverse/direct_lu_accelerate.cpp` includes them, compiled as C++20. |
|
||
|
|
| `umfpack` | SuiteSparse UMFPACK (`dependency('UMFPACK')`), METIS ordering when the SuiteSparse build has it, otherwise its default | Portable. Linux: `libsuitesparse-dev`; macOS: `brew install suite-sparse`. A wheel must bundle `libumfpack` and its SuiteSparse and BLAS dependencies (auditwheel/delocate do this). |
|
||
|
|
| `none` | — | The strategy still exists; preparing it fails with `PRECONDITIONER_INCOMPATIBLE_STRATEGY`. `--potential-inverse auto` then uses AMG. |
|
||
|
|
|
||
|
|
An explicit `accelerate` or `umfpack` that cannot be satisfied stops the configure step instead of falling
|
||
|
|
back. At run time, `serif::solver::preconditioner::backend::mfem::sparse_direct_backend()` returns the
|
||
|
|
compiled backend name and `sparse_direct_available()` says whether it works on this machine.
|
||
|
|
|
||
|
|
## Wheels
|
||
|
|
|
||
|
|
Pass the backend per platform through meson-python, for example with cibuildwheel:
|
||
|
|
|
||
|
|
```toml
|
||
|
|
[tool.cibuildwheel.macos]
|
||
|
|
config-settings = { "setup-args" = "-Dsparse_direct=accelerate" }
|
||
|
|
|
||
|
|
[tool.cibuildwheel.linux]
|
||
|
|
before-all = "yum install -y suitesparse-devel || apt-get install -y libsuitesparse-dev"
|
||
|
|
config-settings = { "setup-args" = "-Dsparse_direct=umfpack" }
|
||
|
|
```
|
||
|
|
|
||
|
|
or `pip wheel . -Csetup-args=-Dsparse_direct=umfpack`. Setting it explicitly keeps a wheel from silently
|
||
|
|
picking up whatever the build machine happens to have; use `none` for a minimal wheel without extra
|
||
|
|
native dependencies.
|
||
|
|
|
||
|
|
## Reuse across Newton steps and batched solves
|
||
|
|
|
||
|
|
- **Symbolic reuse.** Each sparse-direct block keeps the symbolic analysis (fill-reducing ordering) of the last
|
||
|
|
matrix it factored (`SparseDirectCache`, owned by the preconditioner's inverse computation). While the sparsity
|
||
|
|
pattern is unchanged, which is every step after the first in the stellar solves measured, only the numeric
|
||
|
|
factorization is repeated: 0.033 instead of 0.258 s with Accelerate, 0.17 instead of 0.22 s with UMFPACK. The
|
||
|
|
result is bitwise the same as a fresh factorization for both backends (checked on potential blocks from two
|
||
|
|
different Newton states), so it never changes a solve.
|
||
|
|
- **Batched solves.** The bordered preconditioner probes its border columns in chunks of 32 and passes each chunk
|
||
|
|
to the field inverse as one `ArrayMult`; the sparse-direct inverse then solves the chunk in one Accelerate call,
|
||
|
|
reading the factor once (32 columns: 0.08 s for 171 columns instead of 0.26 s). These columns agree with single
|
||
|
|
solves to about 4e-15 relative, not bit for bit, so preconditioned Newton iterates change at that level.
|
||
|
|
UMFPACK has no multiple right-hand-side solve and solves the chunk column by column (bitwise as before).
|
||
|
|
|
||
|
|
## Measured on the n=2, J=0.05 benchmark (16,189-row potential block, M4 Max)
|
||
|
|
|
||
|
|
| Backend | factor | one solve | factorization footprint |
|
||
|
|
|---|---:|---:|---:|
|
||
|
|
| Accelerate, METIS | 0.25 s | 1.5 ms | +271 MB |
|
||
|
|
| UMFPACK, METIS | 0.22 s | 10 ms | +348 MB |
|
||
|
|
|
||
|
|
Accelerate's triangular solves are multithreaded, UMFPACK's are not, so with UMFPACK each Newton step's 83
|
||
|
|
border solves cost more: the benchmark solve takes 21.4 s instead of 18.4 s, with the same 7 Newton steps and
|
||
|
|
the same converged star. On macOS the UMFPACK build's peak footprint also rose by about 200 MB per Newton step
|
||
|
|
(2.1 GiB after 7 steps, no leaks reported by `leaks`): freed factorization memory that the macOS allocator
|
||
|
|
did not return. Measure again on Linux before relying on that number.
|