Skip to content

Commit 80635ea

Browse files
authored
Two files are addeed: (#473)
* README-trilinos.md: provides a complete and detailed documentation about the trilinos implementation * mueleOptions.xml: [Advanced] provides an example of xml file for giving parameters to the 'trilinos-ml' preconditioner
1 parent 3e2e2ba commit 80635ea

File tree

2 files changed

+432
-0
lines changed

2 files changed

+432
-0
lines changed
Lines changed: 395 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,395 @@
1+
# Trilinos Linear Solver Implementation
2+
3+
Developer guide for the Trilinos-based parallel linear solver used in svMultiPhysics.
4+
5+
## Overview
6+
7+
This implementation uses the **Trilinos** framework (specifically **Tpetra**, **Belos**, **Ifpack2**, and **MueLu** packages) to solve large-scale sparse linear systems arising from finite element discretizations in parallel MPI environments. For detailed instructions on the Trilinos built used for this project, please refer to the svMultiPhysics/Docker/ubuntu/dockerfile. The instructions found in the dockerfile can be easily adapted for a Linux--based installation.
8+
9+
## User guide
10+
The Trilinos interface of svMultiPhysics allows users to select linear solvers and preconditioners from the Trilinos linear algebra package for solving large-scale sparse linear systems. This section describes the solvers and preconditioners available through the interface, using the exact names that can be specified in the input .xml file. The linear solvers available are:
11+
- `GMRES`: Generalized Minimal Residual method, a robust and widely used Krylov--subspace solver suitable for general non-symmetric sparse systems. It is typically used for CFD and FSI applications due to its stability and flexibility.
12+
- `CG`: Conjugate Gradient method, designed for symmetric positive-definite systems. Within svMultiPhysics, this method should be used for solving the mesh motion equation in the FSI ALE framework, where the system satisfies these mathematical properties.
13+
- `BiCGS`: Bi--Conjugate Gradient Stabilized, an iterative solver for general non--symmetric problems. It is often used when GMRES becomes too expensive (e.g., due to memory growth with restart size).
14+
The preconditioners available are:
15+
- `trilinos-diagonal`: the simplest option, performing a row and column scaling using the inverse square root of the diagonal entries. This preconditioner is inexpensive and can be useful for mildly ill-conditioned systems.
16+
- `trilinos-blockjacobi`: applies block-Jacobi scaling using the diagonal block submatrices. This often provides better conditioning than simple diagonal scaling, especially for block-structured systems.
17+
- `trilinos-ilu`: computes an Incomplete LU (ILU) factorization within an additive Schwarz framework. The ILU has no additional fill beyond the original sparsity pattern, making it relatively efficient while still improving solver robustness.
18+
- `trilinos-ilut`: similar to ILU but with a controlled threshold (set to 1.0e-2) that determines the amount of fill. A higher threshold generates a more accurate (but more expensive) preconditioner. Implemented using the additive Schwarz method.
19+
- `trilinos-riluk0`: a reduced ILU(0) factorization with no fill. Implemented with additive Schwarz. It is fast to compute and is typically an excellent choice for FSI problems using velocity--based structural formulations (`ustruct` equation for solid).
20+
- `trilinos-riluk1`: similar to riluk0 but includes one level of fill, improving accuracy at the cost of additional computation.
21+
- `trilinos-ml`: an algebraic multigrid (AMG) preconditioner from the MueLu package. This is generally the preferred choice for displacement--based structural problems in FSI (`struct` equation), and it has also proven effective for large--scale CFD systems. AMG preconditioners can be expensive to build but scale very well for large problems, making them ideal for high-resolution simulations.
22+
### Notes on the trilinos-ml
23+
AMG can be extremely powerful, but only when configured appropriately for the specific PDE, mesh size, and physical model. Because of the large number of tunable parameters and because optimal settings differ between CFD, FSI, and structural mechanics, exposing everything through the standard .xml input file would be cumbersome and hard--coding problem specific AMG parameters in the source code requires solver rebuilding everytime a change is implemented.
24+
Instead, svMultiPhysics allows a dedicated AMG parameters file, letting advanced users:
25+
- override defaults (hard--coded in the solver already) for smoother and coarsening choices
26+
- tailor AMG behavior to their problem class
27+
- experiment with performance tuning without modifying the main simulation input files
28+
This approach keeps the main input format clean while still providing expert--level control for those who need it. In order to use the AMG parameters from file, a file named exactly `mueluOptions.xml` must be in the same folder where the simulation input file is. An example `mueluOptions.xml` file is included in the svMultiPhysics/Code/Source/solver directory.
29+
30+
**Files:**
31+
- `trilinos_impl.h` — type definitions, function declarations, and key data structures
32+
- `trilinos_impl.cpp` — implementation of assembly, matrix construction, and solve routines
33+
34+
---
35+
36+
## Key Data Structures
37+
38+
### 1. `Trilinos` Struct
39+
40+
Central container holding all Tpetra/Trilinos objects for a linear system solve:
41+
42+
```cpp
43+
struct Trilinos {
44+
Teuchos::RCP<const Tpetra_Map> Map; // DOF ownership map (owned nodes only)
45+
Teuchos::RCP<const Tpetra_Map> ghostMap; // DOF map including ghost nodes
46+
Teuchos::RCP<Tpetra_MultiVector> F; // RHS vector (owned DOFs)
47+
Teuchos::RCP<Tpetra_MultiVector> ghostF; // RHS vector (owned + ghost DOFs)
48+
Teuchos::RCP<Tpetra_CrsMatrix> K; // Global stiffness matrix
49+
Teuchos::RCP<Tpetra_Vector> X; // Solution vector (owned DOFs)
50+
Teuchos::RCP<Tpetra_Vector> ghostX; // Solution vector (owned + ghost)
51+
Teuchos::RCP<Tpetra_Import> Importer; // Import object (ghost communication)
52+
std::vector<Teuchos::RCP<Tpetra_MultiVector>> bdryVec_list; // Coupled Neumann BCs
53+
Teuchos::RCP<const Teuchos::Comm<int>> comm; // MPI communicator
54+
Teuchos::RCP<Tpetra_CrsGraph> K_graph; // Sparse graph structure
55+
56+
Teuchos::RCP<Tpetra_Operator> MueluPrec; // MueLu (algebraic multigrid) preconditioner
57+
Teuchos::RCP<Ifpack2_Preconditioner> ifpackPrec; // Ifpack2 preconditioner
58+
};
59+
```
60+
61+
**Ownership model:**
62+
- `Map`: defines distribution of DOFs across MPI ranks (owned DOFs only, non-overlapping).
63+
- `ghostMap`: extends `Map` to include ghost DOFs (nodes shared with neighboring ranks).
64+
- Vectors (`F`, `X`) use `Map`; ghost vectors (`ghostF`, `ghostX`) use `ghostMap`.
65+
66+
---
67+
68+
### 2. Tpetra Type Aliases
69+
70+
Defined in `trilinos_impl.h` for clarity and portability:
71+
72+
| Alias | Trilinos Type | Description |
73+
|-------|---------------|-------------|
74+
| `Tpetra_Map` | `Tpetra::Map<LO, GO, Node>` | Parallel distribution map |
75+
| `Tpetra_CrsMatrix` | `Tpetra::CrsMatrix<Scalar_d, LO, GO, Node>` | Sparse matrix (CSR format) |
76+
| `Tpetra_CrsGraph` | `Tpetra::CrsGraph<LO, GO, Node>` | Sparse graph (topology) |
77+
| `Tpetra_MultiVector` | `Tpetra::MultiVector<Scalar_d, LO, GO, Node>` | Multi-vector (RHS, etc.) |
78+
| `Tpetra_Vector` | `Tpetra::Vector<Scalar_d, LO, GO, Node>` | Single vector (solution) |
79+
| `Tpetra_Operator` | `Tpetra::Operator<Scalar_d, LO, GO, Node>` | Abstract linear operator |
80+
| `Belos_LinearProblem` | `Belos::LinearProblem<...>` | Belos problem wrapper |
81+
| `Belos_SolverFactory` | `Belos::TpetraSolverFactory<...>` | Creates Belos solvers |
82+
83+
**Ordinals:**
84+
- `LO` (local ordinal): `int` — indices within a rank
85+
- `GO` (global ordinal): `int` — global node/DOF indices across all ranks
86+
- `Node`: Kokkos node type (typically default)
87+
88+
---
89+
90+
### 3. `TrilinosMatVec` Operator
91+
92+
Custom `Tpetra::Operator` that applies the linear operator:
93+
94+
$$
95+
A x = K x + \sum_i v_i (v_i^T x)
96+
$$
97+
98+
where:
99+
- $K$ is the global stiffness matrix
100+
- $v_i$ are coupled Neumann boundary vectors (rank-1 updates for resistance BCs)
101+
102+
**Purpose:** enables efficient matrix-free application of boundary terms without explicitly forming outer products $v_i v_i^T$.
103+
104+
**Key method:**
105+
```cpp
106+
void TrilinosMatVec::apply(const Tpetra_MultiVector& x, Tpetra_MultiVector& y, ...)
107+
```
108+
- Computes `y = K*x` via `trilinos_->K->apply(...)`
109+
- Adds coupled Neumann contributions: `y += v_i * (v_i^T * x)` for each boundary vector
110+
111+
---
112+
113+
## Workflow
114+
115+
### Phase 1: Initialization and Graph Construction
116+
117+
**Function:** `trilinos_lhs_create(...)`
118+
119+
**Inputs:**
120+
- `numGlobalNodes`: total mesh nodes (across all ranks)
121+
- `numLocalNodes`: nodes owned by this rank
122+
- `numGhostAndLocalNodes`: owned + ghost nodes
123+
- `nnz`: number of nonzero entries in CSR node-adjacency structure
124+
- `ltgSorted`: local-to-global map (sorted/reordered for better partitioning)
125+
- `ltgUnsorted`: local-to-global map (original CSR ordering)
126+
- `rowPtr`, `colInd`: CSR row pointers and column indices (node-level connectivity)
127+
- `Dof`: degrees of freedom per node (e.g., 3 for 3D velocity, 4 for velocity+pressure)
128+
129+
**Steps:**
130+
131+
1. **Create DOF-based maps:**
132+
- Expand node GIDs to DOF GIDs: `dofGID = nodeGID * dof + d`
133+
- Build `Map` (owned DOFs) and `ghostMap` (owned + ghost DOFs)
134+
135+
2. **Build sparse graph (`K_graph`):**
136+
- Compute `nnzPerDofRow`: for each DOF row (using Map ordering), determine number of nonzeros by mapping node GID → unsorted index → `rowPtr` to get node neighbor count, then multiply by `dof`.
137+
- Construct `Tpetra_CrsGraph` with pre-allocated `nnzPerDofRow`.
138+
- Insert global column indices via `insertGlobalIndices(rowGID, rowCols)` for each DOF row.
139+
- Call `fillComplete()` to finalize graph (communication, optimization).
140+
141+
3. **Create matrix and vectors:**
142+
- Matrix `K` built from finalized graph.
143+
- Vectors `F`, `ghostF`, `X`, `ghostX` created from respective maps.
144+
- `Importer` created for ghost node communication (`Map``ghostMap`).
145+
146+
**Key detail:** DOF expansion — node-level CSR connectivity is expanded to DOF-level by iterating over `dof × dof` blocks per node-node connection.
147+
148+
---
149+
150+
### Phase 2: Assembly
151+
152+
Two assembly modes are supported:
153+
154+
#### A. Element-by-Element Assembly (Trilinos-native)
155+
156+
**Function:** `trilinos_doassem_(...)`
157+
158+
**Inputs:**
159+
- `numNodesPerElement`: nodes in current finite element
160+
- `eqN`: element connectivity (local node indices → proc-local indices)
161+
- `lK`: element stiffness matrix (dense, size `dof*dof × numNodesPerElement × numNodesPerElement`)
162+
- `lR`: element force vector (size `dof × numNodesPerElement`)
163+
164+
**Steps:**
165+
1. Map element nodes to global node IDs via `localToGlobalUnsorted`.
166+
2. For each element node pair `(a, b)`:
167+
- Compute global row/col DOF indices: `rowGID = nodeGID_a * dof + i`, `colGID = nodeGID_b * dof + j`.
168+
- Extract block `lK[a,b]` (size `dof × dof`).
169+
- Call `K->sumIntoGlobalValues(rowGID, dof, vals, cols)` to accumulate into global matrix.
170+
3. Sum force vector entries into `ghostF` via `sumIntoGlobalValue(...)`.
171+
172+
**Note:** assembly uses `ghostF` (includes ghost nodes); final communication done later.
173+
174+
#### B. Global Assembly (FSILS-assembled)
175+
176+
**Function:** `trilinos_global_solve_(...)`
177+
178+
**Inputs:**
179+
- `Val`: preassembled matrix values (CSR format, already assembled by FSILS)
180+
- `RHS`: preassembled RHS vector
181+
182+
**Steps:**
183+
1. Loop over all nodes (owned + ghost), extract CSR rows from `Val` and `RHS`.
184+
2. Insert/sum values into `K` and `ghostF` using global indices.
185+
3. Proceed to solve (next phase).
186+
187+
**Use case:** when matrix/RHS are assembled externally (e.g., legacy FSILS assembly), this path avoids redundant element loops.
188+
189+
---
190+
191+
### Phase 3: Solve
192+
193+
**Function:** `trilinos_solve_(...)`
194+
195+
**Inputs:**
196+
- `dirW`: Dirichlet boundary condition weights (1 for free DOFs, 0 for constrained)
197+
- Solver parameters: `lsType` (GMRES, BiCGStab, CG), `relTol`, `maxIters`, `kspace` (Krylov space size)
198+
- `precondType`: preconditioner selection (diagonal, block Jacobi, ILU, ILUT, RILUk, ML)
199+
200+
**Steps:**
201+
202+
1. **Finalize matrix:**
203+
- `K->fillComplete()` — finalizes parallel assembly (ghost communication, CRS optimization).
204+
205+
2. **Export RHS:**
206+
- `F->doExport(*ghostF, exporter, Tpetra::ADD)` — sum contributions from ghost nodes to owned nodes (if using element assembly).
207+
- Or `REPLACE` mode if RHS already assembled correctly.
208+
209+
3. **Construct Jacobi scaling (diagonal preconditioning + Dirichlet BCs):**
210+
- Extract diagonal of `K`.
211+
- Modify diagonal: set zero entries to 1, compute `D^{-1/2}`.
212+
- Apply Dirichlet weights from `dirW`.
213+
- Left-scale and right-scale `K` by diagonal: `K ← D^{-1/2} K D^{-1/2}`.
214+
- Scale `F` and boundary vectors similarly.
215+
216+
4. **Setup Belos linear problem:**
217+
- Operator: `TrilinosMatVec` (applies `K` + coupled Neumann terms due to resistance or RCR BCs).
218+
- Solution: `X`, RHS: `F`.
219+
- Preconditioner: set via `setPreconditioner(...)` (creates Ifpack2 or MueLu preconditioner).
220+
221+
5. **Create and configure Belos solver:**
222+
- Solver type: Block GMRES, BiCGStab, or Pseudoblock CG.
223+
- Parameters: convergence tolerance, max iterations, Krylov space dimension, verbosity.
224+
- Factory pattern: `Belos::TpetraSolverFactory` creates solver manager.
225+
226+
6. **Solve:**
227+
- `solverManager->solve()` — iterative solve.
228+
- Returns convergence status, iteration count, residual norms.
229+
230+
7. **Post-process solution:**
231+
- Unscale `X` by multiplying with diagonal.
232+
- Import solution to ghost nodes: `ghostX->doImport(*X, *Importer, Tpetra::INSERT)`.
233+
- Copy `ghostX` to output array `x`.
234+
235+
8. **Cleanup:**
236+
- Zero out `F`, `ghostF`, `X`, boundary vectors.
237+
- Nullify preconditioners and matrix.
238+
239+
---
240+
241+
## Preconditioners
242+
243+
Implemented in `setPreconditioner(...)`:
244+
245+
| Preconditioner | Type | Description |
246+
|----------------|------|-------------|
247+
| `NO_PRECONDITIONER` | None | No preconditioning (may converge slowly) |
248+
| `TRILINOS_DIAGONAL_PRECONDITIONER` | Diagonal | Jacobi scaling only (handled separately) |
249+
| `TRILINOS_BLOCK_JACOBI_PRECONDITIONER` | Ifpack2 Jacobi | Block Jacobi relaxation (1 sweep) |
250+
| `TRILINOS_ILU_PRECONDITIONER` | Ifpack2 Schwarz+ILU(0) | Overlapping domain decomposition with ILU(0) |
251+
| `TRILINOS_ILUT_PRECONDITIONER` | Ifpack2 Schwarz+ILUT | ILU with threshold dropping |
252+
| `TRILINOS_RILUK0_PRECONDITIONER` | Ifpack2 RILUK(0) | Relaxed ILU (level 0) |
253+
| `TRILINOS_RILUK1_PRECONDITIONER` | Ifpack2 RILUK(1) | Relaxed ILU (level 1) |
254+
| `TRILINOS_ML_PRECONDITIONER` | MueLu | Algebraic multigrid (smoothed aggregation) |
255+
256+
**MueLu (ML) preconditioner:**
257+
- Configured in `setMueLuPreconditioner(...)`.
258+
- Parameters tuned for large FSI problems: 6 levels, V-cycle, Gauss-Seidel smoother, KLU coarse solver.
259+
- Can load custom parameters from `mueluOptions.xml` if present.
260+
261+
---
262+
263+
## Coupled Neumann Boundary Conditions
264+
265+
**Motivation:** resistance boundary conditions add low-rank updates to the stiffness matrix:
266+
267+
$$
268+
A = K + \sum_i v_i v_i^T
269+
$$
270+
271+
where $v_i$ are boundary vectors (normal vectors scaled by resistance coefficients).
272+
273+
**Implementation:**
274+
- Vectors stored in `trilinos_->bdryVec_list` (one per coupled BC face).
275+
- Populated in `trilinos_bc_create_(...)` from user-provided BC data.
276+
- Applied during matrix-vector multiply via `TrilinosMatVec::apply(...)`:
277+
- Compute dot product: $\alpha_i = v_i^T x$
278+
- Add contribution: $y \leftarrow y + \alpha_i v_i$
279+
280+
**Advantages:**
281+
- Avoids explicitly forming outer product matrices (memory-intensive).
282+
- Efficient vectorized operations.
283+
284+
---
285+
286+
## Graph and Matrix Construction Details
287+
288+
### DOF Ordering
289+
290+
- **Node-based CSR:** input adjacency (`rowPtr`, `colInd`) is node-to-node connectivity.
291+
- **DOF expansion:** each node has `dof` degrees of freedom; global DOF index = `nodeGID * dof + d`.
292+
- **Map ordering vs CSR ordering:**
293+
- `ltgSorted`: node ordering used to build Tpetra `Map` (may differ from CSR order for better partitioning).
294+
- `ltgUnsorted`: original CSR ordering.
295+
- Graph construction uses a hash map (`gidToUnsortedIndex`) to map node GID → unsorted index → `rowPtr` for computing neighbor counts.
296+
297+
### Allocation Strategy
298+
299+
- Pre-allocate `nnzPerDofRow` for each DOF row (reduces memory overhead).
300+
- For each DOF `d` of node `n`:
301+
- Find node's neighbor count: `rowPtr[unsortedIdx+1] - rowPtr[unsortedIdx]`.
302+
- Allocate `neighborCount * dof` entries (because each node-node connection expands to `dof × dof` block).
303+
304+
---
305+
306+
## Debugging and Diagnostics
307+
308+
### Matrix/Vector Printing
309+
310+
Functions available for debugging (write ASCII files):
311+
- `printMatrixToFile(trilinos_)` → writes `K.txt` (global row/col/value triples).
312+
- `printRHSToFile(trilinos_)` → writes `F.txt`.
313+
- `printSolutionToFile(trilinos_)` → writes `X.txt`.
314+
315+
---
316+
317+
## Solver Types
318+
319+
Defined in `trilinos_impl.h`:
320+
321+
| Macro | Belos Solver | Use Case |
322+
|-------|--------------|----------|
323+
| `TRILINOS_GMRES_SOLVER` | Block GMRES | General nonsymmetric systems (default) |
324+
| `TRILINOS_BICGSTAB_SOLVER` | BiCGStab | Faster for some nonsymmetric problems |
325+
| `TRILINOS_CG_SOLVER` | Pseudoblock CG | Symmetric positive definite systems |
326+
327+
**GMRES parameters:**
328+
- `kspace`: Krylov subspace dimension (typically 50-300).
329+
- `maxRestarts`: computed as `maxIters / kspace + 1`.
330+
- Orthogonalization: DGKS (recommended for robustness).
331+
332+
---
333+
334+
## Memory Management
335+
336+
- **Reference-counted pointers:** all Trilinos objects use `Teuchos::RCP` (similar to `std::shared_ptr`).
337+
- **Cleanup:** at end of solve, set `K`, preconditioners to `Teuchos::null` to free memory.
338+
- **Kokkos:** initialized once (`Kokkos::initialize()`) and finalized at program exit.
339+
340+
---
341+
342+
## Integration Points
343+
344+
### Called from svMultiPhyisics
345+
346+
Functions bridge C++ Trilinos to svMultiPhysics implementation:
347+
- `trilinos_lhs_create(...)` — graph/matrix setup (once per timestep/Newton iteration).
348+
- `trilinos_doassem_(...)` — default element assembly (called per element).
349+
- `trilinos_global_solve_(...)` — solve with pre-assembled data.
350+
- `trilinos_bc_create_(...)` — setup coupled Neumann BCs.
351+
352+
### TrilinosLinearAlgebra Class
353+
354+
Higher-level C++ wrapper (`TrilinosLinearAlgebra::TrilinosImpl`) provides:
355+
- `alloc(...)` — allocate data structures.
356+
- `assemble(...)` — element-by-element assembly.
357+
- `solve(...)` / `solve_assembled(...)` — solve linear system.
358+
- `init_dir_and_coup_neu(...)` — setup boundary conditions.
359+
360+
---
361+
362+
## Tips for new development
363+
364+
1. **Adding a new preconditioner:**
365+
- Define a new macro in `trilinos_impl.h` (e.g., `#define TRILINOS_CUSTOM_PREC 709`).
366+
- Add case in `setPreconditioner(...)` to configure Ifpack2/MueLu parameters.
367+
- If the preconditioner is user--defined, then it should be implemented as a new Tpetra::Operator class and added to the Belos problem.
368+
369+
2. **Changing graph structure:**
370+
- Graph is fixed after `fillComplete()`.
371+
- To modify: destroy graph (`K_graph = Teuchos::null`), rebuild in `trilinos_lhs_create(...)`.
372+
373+
3. **Debugging convergence issues:**
374+
- Enable Belos verbosity: comment out `#define NOOUTPUT` in `trilinos_impl.cpp`.
375+
- Print matrix/RHS: call `printMatrixToFile()`, `printRHSToFile()`.
376+
- Check conditioning: ensure diagonal has no zeros (handled by `checkDiagonalIsZero()`).
377+
- To check the sparse pattern of the matrix, consider using hdf5 library. This will allow to save large files that can be read by using a simple Python script
378+
379+
4. **Performance profiling:**
380+
- Use Teuchos::Time for timing sections (example: `Belos Solve Timer` in code).
381+
- Trilinos has built-in profiling via `Teuchos::TimeMonitor` (enable with CMake flag).
382+
383+
---
384+
385+
## References
386+
387+
- [Trilinos Documentation](https://trilinos.github.io/)
388+
- [Tpetra User Guide](https://docs.trilinos.org/dev/packages/tpetra/doc/html/index.html)
389+
- [Belos User Guide](https://docs.trilinos.org/dev/packages/belos/doc/html/index.html)
390+
- [MueLu User Guide](https://trilinos.github.io/pdfs/mueluguide.pdf)
391+
- [Ifpack2 Documentation](https://docs.trilinos.org/dev/packages/ifpack2/doc/html/index.html)
392+
393+
---
394+
395+
**Last updated:** November 2025

0 commit comments

Comments
 (0)