added SCHUR_SOLVE=SELFP / SELF option and stokes problem test

update for self/selfp
This commit is contained in:
jalmerol
2026-07-03 12:19:55 +02:00
parent 0c9d295264
commit 6004643a7b
11 changed files with 1235 additions and 55 deletions
+7 -1
View File
@@ -29,6 +29,7 @@ set(SOURCES_D_NEST_CG_TEST psb_d_nest_cg_test.F90)
set(SOURCES_D_NEST_PREC_CG_TEST psb_d_nest_prec_cg_test.F90)
set(SOURCES_D_NEST_MULT_PREC_CG_TEST psb_d_nest_mult_prec_cg_test.F90)
set(SOURCES_D_NEST_SCHUR_PREC_CG_TEST psb_d_nest_schur_prec_cg_test.F90)
set(SOURCES_D_NEST_PETSC_STOKES_TEST psb_d_nest_petsc_stokes_test.F90)
add_executable(psb_d_nest_glob_test ${SOURCES_D_NEST_GLOB_TEST})
target_link_libraries(psb_d_nest_glob_test psblas::util psblas::linsolve psblas::prec psblas::ext psblas::base)
@@ -48,8 +49,13 @@ target_link_libraries(psb_d_nest_mult_prec_cg_test psblas::util psblas::linsolve
add_executable(psb_d_nest_schur_prec_cg_test ${SOURCES_D_NEST_SCHUR_PREC_CG_TEST})
target_link_libraries(psb_d_nest_schur_prec_cg_test psblas::util psblas::linsolve psblas::prec psblas::ext psblas::base)
add_executable(psb_d_nest_petsc_stokes_test ${SOURCES_D_NEST_PETSC_STOKES_TEST})
target_link_libraries(psb_d_nest_petsc_stokes_test psblas::util psblas::linsolve psblas::prec psblas::ext psblas::base)
# Set output directory for executables
foreach(target psb_d_nest_glob_test psb_d_nest_rect_test psb_d_nest_cg_test psb_d_nest_prec_cg_test psb_d_nest_mult_prec_cg_test psb_d_nest_schur_prec_cg_test)
foreach(target psb_d_nest_glob_test psb_d_nest_rect_test psb_d_nest_cg_test psb_d_nest_prec_cg_test psb_d_nest_mult_prec_cg_test psb_d_nest_schur_prec_cg_test psb_d_nest_petsc_stokes_test)
set_target_properties(${target} PROPERTIES
RUNTIME_OUTPUT_DIRECTORY ${EXEDIR}
)
+14 -3
View File
@@ -6,7 +6,11 @@ include $(INCDIR)/Make.inc.psblas
# Libraries used
LIBDIR=$(INSTALLDIR)/lib
PSBLAS_LIB= -L$(LIBDIR) -lpsb_util -lpsb_linsolve -lpsb_prec -lpsb_ext -lpsb_base
ifeq ($(strip $(BLAS)),)
LDLIBS= $(PSBLDLIBS) -lblas
else
LDLIBS= $(PSBLDLIBS)
endif
#
# Compilers and such
#
@@ -17,7 +21,7 @@ FINCLUDES=$(FMFLAG)$(MODDIR) $(FMFLAG).
EXEDIR=./runs
all: runsd psb_d_nest_glob_test psb_d_nest_rect_test psb_d_nest_cg_test psb_d_nest_prec_cg_test \
psb_d_nest_mult_prec_cg_test psb_d_nest_schur_prec_cg_test
psb_d_nest_mult_prec_cg_test psb_d_nest_schur_prec_cg_test psb_d_nest_petsc_stokes_test
runsd:
(if test ! -d runs ; then mkdir runs; fi)
@@ -46,12 +50,19 @@ psb_d_nest_schur_prec_cg_test: psb_d_nest_schur_prec_cg_test.o
$(FLINK) psb_d_nest_schur_prec_cg_test.o -o psb_d_nest_schur_prec_cg_test $(PSBLAS_LIB) $(LDLIBS)
/bin/mv psb_d_nest_schur_prec_cg_test $(EXEDIR)
psb_d_nest_petsc_stokes_test: psb_d_nest_petsc_stokes_test.o
$(FLINK) psb_d_nest_petsc_stokes_test.o -o psb_d_nest_petsc_stokes_test $(PSBLAS_LIB) $(LDLIBS)
/bin/mv psb_d_nest_petsc_stokes_test $(EXEDIR)
clean:
/bin/rm -f psb_d_nest_glob_test.o psb_d_nest_rect_test.o psb_d_nest_cg_test.o \
psb_d_nest_prec_cg_test.o psb_d_nest_mult_prec_cg_test.o psb_d_nest_schur_prec_cg_test.o *$(.mod) \
psb_d_nest_prec_cg_test.o psb_d_nest_mult_prec_cg_test.o psb_d_nest_schur_prec_cg_test.o \
psb_d_nest_petsc_stokes_test.o *$(.mod) \
$(EXEDIR)/psb_d_nest_glob_test $(EXEDIR)/psb_d_nest_rect_test $(EXEDIR)/psb_d_nest_cg_test \
$(EXEDIR)/psb_d_nest_prec_cg_test $(EXEDIR)/psb_d_nest_mult_prec_cg_test \
$(EXEDIR)/psb_d_nest_schur_prec_cg_test
$(EXEDIR)/psb_d_nest_schur_prec_cg_test $(EXEDIR)/psb_d_nest_petsc_stokes_test
verycleanlib:
(cd ../..; make veryclean)
lib:
+105 -13
View File
@@ -527,9 +527,11 @@ y
No Schur matrix is ever stored. The code only needs block products and field solves.
### Richardson Schur Solve
### Matrix-Free Schur Solves
When `SCHUR_SOLVE = MATRIX_FREE`, the preconditioner needs an approximation to `S^{-1}` even though it only has a routine for `S x`. The implementation therefore uses a small preconditioned Richardson iteration:
When `SCHUR_SOLVE = MATRIX_FREE`, the preconditioner needs an approximation to a Schur inverse even though it only has routines that apply Schur products. The implementation uses two matrix-free strategies, depending on the selected composition.
For the two-field Schur compositions (`SCHUR_LOWER`, `SCHUR_UPPER`, and `SCHUR_FULL`), the matrix-free Schur solve uses a small preconditioned Richardson iteration:
```text
z_{k+1} = z_k + M^{-1} (b - S z_k)
@@ -543,7 +545,15 @@ Each Richardson step uses:
2. a Schur residual `b - S z_k`
3. the field-2 preconditioner or field-2 inner solve to correct `z_k`
This gives a cheap inner solve for the Schur complement without assembling or storing `S`.
For the three-field PDE-control composition (`SCHUR_PDE_CONTROL`), the Schur field is field 3 and the KKT Schur complement is negative for the standard distributed-control block signs. The implementation therefore solves the positive matrix-free equation
```text
T x_3 = -r_3, T = -S
```
with a small conjugate-gradient iteration. The product `T x_3` is applied without assembling `T`; each CG iteration calls the PDE-control Schur action and uses `psb_gedot` with the field-3 descriptor for global reductions. The field-3 block preconditioner is still used as an optional left preconditioner, but when `A_33 = 0` it is only an identity fallback.
This gives a stronger Schur inverse approximation for KKT optimal-control systems where the diagonal field-3 block is zero and a Richardson update based on `A_33` is ineffective.
The implementation does not assemble an exact sparse Schur complement. Instead, it applies Schur actions using:
@@ -556,13 +566,23 @@ Supported Schur compositions are:
- `SCHUR_LOWER`
- `SCHUR_UPPER`
- `SCHUR_FULL`
- `SCHUR_PDE_CONTROL` (aliases: `PDE_CONTROL_SCHUR`, `SCHUR_2PLUS1`, `SCHUR3`, `SCHUR_3FIELD`, `SCHUR_THREE_FIELD`)
The Schur solve mode is controlled by `SCHUR_SOLVE`:
- `A22`: use the field-2 block preconditioner as the Schur approximation
- `MATRIX_FREE`: use a small Richardson iteration around the matrix-free Schur action
- `MATRIX_FREE`: use a matrix-free Schur solve; two-field Schur compositions use Richardson, while `SCHUR_PDE_CONTROL` uses CG on the positive field-3 Schur action
- `SELFP` or `SELF`: build a PETSc-style lumped Schur approximation from `A_21`, the diagonal of `A_11`, and `A_12`, then build a normal PSBLAS `BJAC` Schur preconditioner on that approximation
The `MATRIX_FREE` mode is controlled by `SCHUR_MAXIT` and `SCHUR_TOL`.
For two-field Schur compositions, `SELFP` approximates
```text
S_p ~= A_22 - A_21 diag(A_11)^{-1} A_12
```
The assembled `S_p` is not the exact Schur complement. It is a sparse local approximation intended for use as a preconditioner, analogous in spirit to PETSc field-split `selfp` with a lumped inverse for `A_11`. After assembly, the nested preconditioner attaches the existing block-preconditioner machinery to `S_p`: currently this is a `BJAC` block preconditioner, and routed sub-options such as `SUB_SOLVE=ILU` and `SUB_FILLIN` are replayed onto it. During Schur application, `SCHUR_SOLVE=SELFP` applies this built Schur preconditioner directly instead of running Richardson on the matrix-free Schur action.
The `MATRIX_FREE` mode is controlled by `SCHUR_MAXIT` and `SCHUR_TOL`. The `SELFP` mode is controlled primarily by the sub-preconditioner options routed to the Schur approximation, especially `SUB_SOLVE` and `SUB_FILLIN`.
## Implementation Structure
@@ -795,6 +815,39 @@ S x_2 = A_22 x_2 - A_21 B_1 A_12 x_2
where `B_1` is the field-1 block solve or inner field solve. `psb_d_nested_schur_solve` either applies the field-2 block solve directly (`A22`) or runs a short preconditioned Richardson iteration using the matrix-free Schur action (`MATRIX_FREE`).
## PDE-Control Schur Apply Path
`psb_d_nested_apply_pde_control_schur` is defined for exactly three fields. It targets KKT systems with the block structure
```text
[ A_11 0 A_13 ]
[ 0 A_22 A_23 ]
[ A_31 A_32 A_33 ]
```
Fields 1 and 2 are grouped into the leading diagonal block `D = diag(A_11,A_22)`, and field 3 is the Schur field. The preconditioner applies the usual block factorization steps:
1. solve the leading fields with `B_1` and `B_2`
2. update the field-3 residual with `A_31 x_1 + A_32 x_2`
3. approximately solve the field-3 Schur equation
4. correct fields 1 and 2 with the upper coupling through `A_13` and `A_23`
The matrix-free positive PDE-control Schur action is
```text
T x_3 = A_31 B_1 A_13 x_3 + A_32 B_2 A_23 x_3 - A_33 x_3
```
For a standard distributed-control KKT matrix such as
```text
[ M 0 K ]
[ 0 alpha M -M ]
[ K -M 0 ]
```
this operator is `T = -S`, where `S` is the field-3 KKT Schur complement. `psb_d_nested_pde_control_schur_solve` solves `T x_3 = -r_3` with a matrix-free CG iteration controlled by `SCHUR_MAXIT` and `SCHUR_TOL`. This is important when `A_33 = 0`: the old field-3 diagonal-block approximation cannot precondition the Schur complement, but the matrix-free CG path can still use the actual Schur action.
## Public Options
The nested preconditioner exposes integer option keys in `psb_d_nestedprec`:
@@ -816,9 +869,9 @@ The user-facing string options routed by `psb_d_prec_type_impl.f90` are:
| Option | Type | Values | Scope |
| --- | --- | --- | --- |
| `COMPOSITION`, `NEST_COMPOSITION` | character | `ADDITIVE`, `DIAGONAL`, `MULTIPLICATIVE`, `SYMMETRIC_MULTIPLICATIVE`, `SCHUR_LOWER`, `SCHUR_UPPER`, `SCHUR_FULL` | global |
| `COMPOSITION`, `NEST_COMPOSITION` | character | `ADDITIVE`, `DIAGONAL`, `MULTIPLICATIVE`, `SYMMETRIC_MULTIPLICATIVE`, `SCHUR_LOWER`, `SCHUR_UPPER`, `SCHUR_FULL`, `SCHUR_PDE_CONTROL` | global |
| `BLOCK_SOLVE`, `NEST_BLOCK_SOLVE` | character | `DIAG`, `BJAC`, `NONE` | global or `idx` field |
| `SCHUR_SOLVE`, `NEST_SCHUR_SOLVE` | character | `A22`, `MATRIX_FREE` | global |
| `SCHUR_SOLVE`, `NEST_SCHUR_SOLVE` | character | `A22`, `A33`, `MATRIX_FREE`, `SELFP`, `SELF` | global |
| `SCHUR_MAXIT`, `NEST_SCHUR_MAXIT` | integer | nonnegative iteration count | global |
| `SCHUR_TOL`, `NEST_SCHUR_TOL` | real | nonnegative tolerance | global |
| `INNER_SOLVE`, `KRYLOV_SOLVE`, `FIELD_SOLVE` | character | `NONE`, `CG`, `BICGSTAB` | global or `idx` field |
@@ -843,6 +896,12 @@ call prec%set('SCHUR_SOLVE', 'MATRIX_FREE', info)
call prec%set('SCHUR_MAXIT', 6, info)
call prec%set('SCHUR_TOL', 1.0d-8, info)
! Alternative two-field Stokes-style Schur approximation:
! call prec%set('SCHUR_SOLVE', 'SELFP', info)
! call prec%set('BLOCK_SOLVE', 'BJAC', info)
! call prec%set('SUB_SOLVE', 'ILU', info)
! call prec%set('SUB_FILLIN', 0, info)
call prec%set('BLOCK_SOLVE', 'BJAC', info, idx=1)
call prec%set('BLOCK_SOLVE', 'DIAG', info, idx=2)
@@ -857,6 +916,26 @@ call psb_krylov('BICGSTAB', nested_matrix%a_glob, prec, b, x, eps, &
This keeps the outer solve in the existing PSBLAS API while allowing field-specific behavior inside the nested preconditioner.
For a three-field optimal-control KKT system, the PDE-control Schur composition is configured as:
```fortran
call prec%init('NEST', info)
call prec%set('COMPOSITION', 'SCHUR_PDE_CONTROL', info)
call prec%set('SCHUR_SOLVE', 'MATRIX_FREE', info)
call prec%set('SCHUR_MAXIT', 200, info)
call prec%set('SCHUR_TOL', 0.0d0, info)
call prec%set('BLOCK_SOLVE', 'DIAG', info, idx=1)
call prec%set('BLOCK_SOLVE', 'DIAG', info, idx=2)
call prec%set('BLOCK_SOLVE', 'DIAG', info, idx=3)
call prec%build(nested_matrix%a_glob, nested_matrix%desc_glob, info)
call psb_krylov('BICGSTAB', nested_matrix%a_glob, prec, b, x, eps, &
& nested_matrix%desc_glob, info)
```
The field-3 `DIAG` setting is harmless for zero `A_33` blocks because PSBLAS diagonal preconditioning treats zero diagonal entries as identity. The useful inverse approximation comes from the matrix-free CG solve on the PDE-control Schur action.
## Factory And API Integration
`psb_dprecinit` recognizes the nested preconditioner name:
@@ -915,12 +994,23 @@ These tests exercise:
- nested preconditioner initialization
- additive/diagonal composition
- multiplicative and symmetric multiplicative composition
- Schur lower, upper, and full composition
- matrix-free Schur solve mode
- Schur lower, upper, full, and PDE-control composition
- matrix-free Schur solve mode, including CG for `SCHUR_PDE_CONTROL`
- per-field inner Krylov configuration and apply through the Schur preconditioner
- serial and MPI execution paths in the nested examples
Validation of the nested preconditioner examples showed convergence for the additive, multiplicative, and Schur-style configurations on the nested Laplacian test problems.
Validation of the nested preconditioner examples showed convergence for the additive, multiplicative, and Schur-style configurations on the nested Laplacian test problems. The AMG4PSBLAS nested optimal-control MatrixMarket sample also converges with `SCHUR_PDE_CONTROL`, `SCHUR_SOLVE=MATRIX_FREE`, and `SCHUR_MAXIT=200` on serial and two-rank MPI runs.
The PETSc binary Stokes nested test was also used to compare the original matrix-free Schur solve against the new `SELFP` approximation on two MPI ranks. With `SCHUR_FULL`, `BICGSTAB`, `BLOCK_SOLVE=BJAC`, and `SUB_SOLVE=ILU`, the observed runs were:
| Schur solve | Sub fill | Iterations | Relative residual | Solve time |
| --- | ---: | ---: | ---: | ---: |
| `MATRIX_FREE` | 1 | 532 | `8.9510E-05` | about `4.95s` |
| `SELFP` | 0 | 412 | `9.5632E-05` | about `0.50s` |
| `SELFP` | 1 | 393 | `9.5120E-05` | about `0.51s` |
| `SELFP` | 2 | 570 | `9.5620E-05` | about `0.80s` |
For this case, `SELFP` reduced the iteration count compared with the matrix-free Schur solve and made each solve substantially cheaper. `SUB_FILLIN=1` gave the lowest iteration count, while `SUB_FILLIN=0` was the best practical default in the measured runs because it was close in iterations and cheapest to build/apply. Larger fill was not automatically better for the lumped Schur approximation.
The Schur nested preconditioner test includes an additional `SCHUR_FULL` / `A22+INNER` case. That case configures independent per-field inner Krylov contexts, builds the nested preconditioner, applies it directly to the right-hand side, and verifies a successful finite preconditioner application. The existing Schur cases continue to check outer BiCGSTAB convergence.
@@ -933,8 +1023,9 @@ The current implementation has the following limitations:
- double precision only
- nested preconditioner name currently exposed as `NEST`
- transposed preconditioner application is rejected
- Schur-style compositions are implemented for exactly two fields
- matrix-free Schur solve currently uses a small Richardson iteration
- two-field Schur compositions are implemented by `SCHUR_LOWER`, `SCHUR_UPPER`, and `SCHUR_FULL`; the specialized three-field Schur composition is `SCHUR_PDE_CONTROL` only
- two-field matrix-free Schur solves use Richardson; PDE-control matrix-free Schur solves use CG on the positive field-3 Schur action
- `SELFP` is currently a two-field Schur approximation for `SCHUR_LOWER`, `SCHUR_UPPER`, and `SCHUR_FULL`; it assembles a local lumped Schur preconditioning matrix, not an exact distributed Schur complement
- inner Krylov methods currently include `CG` and `BICGSTAB`
## Future Work
@@ -942,10 +1033,11 @@ The current implementation has the following limitations:
Recommended next steps are:
- add single, complex, and complex double precision variants
- expose a dedicated Schur preconditioner for the PDE-control CG solve, instead of relying on the field-3 block preconditioner fallback
## Summary
The nested preconditioner adds a field-split preconditioning layer to PSBLAS while preserving the existing solver and preconditioner API. It reuses standard PSBLAS block preconditioners on diagonal field blocks and combines them through additive, multiplicative, symmetric multiplicative, and Schur-style compositions.
The nested preconditioner adds a field-split preconditioning layer to PSBLAS while preserving the existing solver and preconditioner API. It reuses standard PSBLAS block preconditioners on diagonal field blocks and combines them through additive, multiplicative, symmetric multiplicative, two-field Schur, and three-field PDE-control Schur compositions.
The implementation integrates with:
+27
View File
@@ -215,6 +215,33 @@ A field-split interface (`psb_d_nest_get_block`, `psb_d_nest_get_field_desc`, `p
All tests run both serially and in parallel, and the result is invariant with respect to the number of MPI processes.
### PETSc Stokes block file test
`psb_d_nest_petsc_stokes_test` reads PETSc-style Stokes MatrixMarket blocks and
assembles the saddle-point operator as
```text
[ A Bt ]
[ B 0 ]
```
using `psb_d_nest_matrix`, so the rectangular `Bt` and `B` blocks are kept as
nested sub-blocks. Configure it with environment variables:
```sh
export PETSC_STOKES_DIR=/path/to/petsc/stokes/files
export PETSC_STOKES_METHOD=BICGSTAB
export PETSC_STOKES_PREC=NEST
export PETSC_STOKES_COMPOSITION=SCHUR_FULL
export PETSC_STOKES_SCHUR_SOLVE=MATRIX_FREE
./runs/psb_d_nest_petsc_stokes_test
```
The deal.II Stokes file test and optimal-control file test are not part of
`test/nested`; the retained file-based nested sample here is the PETSc Stokes
variant.
### Build and run
The PSBLAS library must be built/installed first (from the repository root):
@@ -0,0 +1,446 @@
!
! File: psb_d_nest_petsc_stokes_test.F90
!
! Read the PETSc binary Stokes files used by PETSc ex87/ex87_stokes,
! assemble the 2x2 operator as a PSBLAS nested matrix, and solve it through
! the standard PSBLAS Krylov/preconditioner interface.
!
! Expected files in PETSC_STOKES_DIR, or in
! ${DATAFILESPATH}/matrices/hpddm/GENEO
! when PETSC_STOKES_DIR is not set:
! B00.dat, B10.dat, B11.dat, rhs_B.dat
!
program psb_d_nest_petsc_stokes_test
use, intrinsic :: iso_fortran_env, only : int32, real64
use psb_base_mod
use psb_prec_mod
use psb_linsolve_mod
use psb_d_nest_mod
implicit none
integer(int32), parameter :: petsc_mat_classid = 1211216_int32
integer(int32), parameter :: petsc_vec_classid = 1211214_int32
type petsc_matrix
integer(psb_lpk_) :: nrow = 0_psb_lpk_
integer(psb_lpk_) :: ncol = 0_psb_lpk_
integer(psb_lpk_) :: nnz = 0_psb_lpk_
integer(psb_lpk_), allocatable :: row(:), col(:)
real(psb_dpk_), allocatable :: val(:)
end type petsc_matrix
type petsc_vector
integer(psb_lpk_) :: n = 0_psb_lpk_
real(psb_dpk_), allocatable :: val(:)
end type petsc_vector
type(psb_ctxt_type) :: context
type(psb_d_nest_matrix) :: nested_matrix
type(psb_dprec_type) :: preconditioner
type(psb_d_vect_type) :: rhs, x_solution, residual
type(petsc_matrix) :: b00, b10, b11
type(petsc_vector) :: rhs_full
integer(psb_ipk_) :: my_rank, num_procs, info
integer(psb_ipk_) :: max_iter, trace_level, stop_criterion, n_iter, n_insert, schur_maxit, sub_fillin
integer(psb_lpk_) :: n_u, n_p, n_total
integer(psb_lpk_), allocatable :: rows(:), cols(:)
real(psb_dpk_), allocatable :: vals(:)
real(psb_dpk_) :: eps, check_tol, final_residual, residual_norm, rhs_norm
real(psb_dpk_) :: t0, t_read, t_assemble, t_prec, t_solve
character(len=256) :: input_dir, method, ptype, composition, schur_solve, block_solve, sub_solve
logical :: empty_a11
call psb_init(context)
call psb_info(context, my_rank, num_procs)
call get_stokes_dir(input_dir)
call get_string_env('PETSC_STOKES_METHOD', method, 'BICGSTAB')
call get_string_env('PETSC_STOKES_PREC', ptype, 'NEST')
call get_string_env('PETSC_STOKES_COMPOSITION', composition, 'SCHUR_FULL')
call get_string_env('PETSC_STOKES_SCHUR_SOLVE', schur_solve, 'MATRIX_FREE')
call get_string_env('PETSC_STOKES_BLOCK_SOLVE', block_solve, 'BJAC')
call get_string_env('PETSC_STOKES_SUB_SOLVE', sub_solve, 'ILU')
call get_int_env('PETSC_STOKES_ITMAX', max_iter, 2000)
call get_int_env('PETSC_STOKES_TRACE', trace_level, 0)
call get_int_env('PETSC_STOKES_SCHUR_MAXIT', schur_maxit,20)
call get_int_env('PETSC_STOKES_SUB_FILLIN', sub_fillin, 1)
call get_real_env('PETSC_STOKES_EPS', eps, 1.0e-4_psb_dpk_)
call get_real_env('PETSC_STOKES_CHECK_TOL', check_tol, 1.0e-4_psb_dpk_)
stop_criterion = 2
if (my_rank == 0) then
write(*,'(a)') 'PETSc Stokes nested binary test'
write(*,'(a,a)') ' input dir : ', trim(input_dir)
write(*,'(a,a)') ' method : ', trim(method)
write(*,'(a,a)') ' prec : ', trim(ptype)
write(*,'(a,a)') ' composition: ', trim(composition)
write(*,'(a,a)') ' schur solve: ', trim(schur_solve)
write(*,'(a,a)') ' block solve: ', trim(block_solve)
write(*,'(a,a)') ' sub solve : ', trim(sub_solve)
write(*,'(a,i0)') ' schur maxit: ', schur_maxit
write(*,'(a,i0)') ' sub fillin : ', sub_fillin
write(*,'(a,es12.4)') ' tolerance : ', eps
write(*,'(a,es12.4)') ' check tol : ', check_tol
end if
t0 = psb_wtime()
call read_petsc_matrix(path_join(input_dir, 'B00.dat'), b00)
call read_petsc_matrix(path_join(input_dir, 'B10.dat'), b10)
call read_petsc_matrix(path_join(input_dir, 'B11.dat'), b11)
call read_petsc_vector(path_join(input_dir, 'rhs_B.dat'), rhs_full)
t_read = psb_wtime() - t0
n_u = b00%nrow
n_p = b10%nrow
n_total = n_u + n_p
empty_a11 = (b11%nnz == 0_psb_lpk_)
if ((b00%ncol /= n_u) .or. (b10%ncol /= n_u) .or. &
(b11%nrow /= n_p) .or. (b11%ncol /= n_p) .or. &
(rhs_full%n /= n_total)) then
if (my_rank == 0) write(*,*) 'FAIL: inconsistent PETSc Stokes dimensions'
call psb_abort(context)
end if
t0 = psb_wtime()
call nested_matrix%init(context, [n_u, n_p], info)
call check_info(info, 'nested_matrix%init')
call select_owned_entries(b00, nested_matrix%get_owned_rows(1), rows, cols, vals, n_insert)
call nested_matrix%ins(1, 1, n_insert, rows, cols, vals, info)
call check_info(info, 'insert B00')
call clear_triplets(rows, cols, vals)
call select_owned_entries_transpose(b10, nested_matrix%get_owned_rows(1), rows, cols, vals, n_insert)
call nested_matrix%ins(1, 2, n_insert, rows, cols, vals, info)
call check_info(info, 'insert B10 transpose')
call clear_triplets(rows, cols, vals)
call select_owned_entries(b10, nested_matrix%get_owned_rows(2), rows, cols, vals, n_insert)
call nested_matrix%ins(2, 1, n_insert, rows, cols, vals, info)
call check_info(info, 'insert B10')
call clear_triplets(rows, cols, vals)
if (.not. empty_a11) then
call select_owned_entries(b11, nested_matrix%get_owned_rows(2), rows, cols, vals, n_insert)
call nested_matrix%ins(2, 2, n_insert, rows, cols, vals, info)
call check_info(info, 'insert B11')
call clear_triplets(rows, cols, vals)
end if
call nested_matrix%asb(info)
call check_info(info, 'nested_matrix%asb')
t_assemble = psb_wtime() - t0
call psb_geall(rhs, nested_matrix%desc_glob, info)
call check_info(info, 'psb_geall(rhs)')
call insert_owned_rhs(rhs_full, n_u, nested_matrix%get_owned_rows(1), nested_matrix%get_owned_rows(2), &
& rhs, nested_matrix%desc_glob, info)
call check_info(info, 'insert rhs')
call psb_geasb(rhs, nested_matrix%desc_glob, info, dupl=psb_dupl_add_)
call check_info(info, 'psb_geasb(rhs)')
call psb_geall(x_solution, nested_matrix%desc_glob, info)
call check_info(info, 'psb_geall(x)')
call x_solution%zero()
call psb_geasb(x_solution, nested_matrix%desc_glob, info)
call check_info(info, 'psb_geasb(x)')
t0 = psb_wtime()
call preconditioner%init(context, trim(ptype), info)
call check_info(info, 'prec%init')
if (psb_toupper(trim(ptype)) == 'NEST') then
call preconditioner%set('COMPOSITION', trim(composition), info)
call preconditioner%set('SCHUR_SOLVE', trim(schur_solve), info)
call preconditioner%set('SCHUR_MAXIT', schur_maxit, info)
call preconditioner%set('BLOCK_SOLVE', trim(block_solve), info)
call preconditioner%set('SUB_SOLVE', trim(sub_solve), info)
call preconditioner%set('SUB_FILLIN', sub_fillin, info)
end if
call preconditioner%build(nested_matrix%a_glob, nested_matrix%desc_glob, info)
call check_info(info, 'prec%build')
t_prec = psb_wtime() - t0
t0 = psb_wtime()
call psb_krylov(trim(method), nested_matrix%a_glob, preconditioner, rhs, x_solution, eps, &
& nested_matrix%desc_glob, info, itmax=max_iter, iter=n_iter, err=final_residual, &
& itrace=trace_level, istop=stop_criterion)
call check_info(info, 'psb_krylov')
t_solve = psb_wtime() - t0
call psb_geall(residual, nested_matrix%desc_glob, info)
call psb_geasb(residual, nested_matrix%desc_glob, info)
call psb_geaxpby(done, rhs, dzero, residual, nested_matrix%desc_glob, info)
call psb_spmm(-done, nested_matrix%a_glob, x_solution, done, residual, nested_matrix%desc_glob, info)
call check_info(info, 'residual spmm')
residual_norm = psb_genrm2(residual, nested_matrix%desc_glob, info)
rhs_norm = psb_genrm2(rhs, nested_matrix%desc_glob, info)
if (my_rank == 0) then
write(*,'(a,i0,a,i0,a,i0,a,i0)') ' block sizes: n_u=', n_u, ' n_p=', n_p, ' total=', n_total, ' np=', num_procs
write(*,'(a,l1)') ' empty B11 : ', empty_a11
write(*,'(a,i8,a,es12.4)') ' iterations=', n_iter, ' solver err=', final_residual
write(*,'(a,es12.4)') ' ||b-Ax||_2 / ||b||_2=', residual_norm / max(rhs_norm, tiny(done))
if (residual_norm / max(rhs_norm, tiny(done)) > check_tol) then
write(*,'(a)') '[FAIL] psb_d_nest_petsc_stokes_test: residual above PETSC_STOKES_CHECK_TOL'
call psb_abort(context)
end if
write(*,'(a,es12.4,a,es12.4,a,es12.4,a,es12.4)') &
& ' times read=', t_read, ' assemble=', t_assemble, ' prec=', t_prec, ' solve=', t_solve
write(*,'(a)') '[PASS] psb_d_nest_petsc_stokes_test'
end if
9999 continue
call preconditioner%free(info)
call psb_gefree(residual, nested_matrix%desc_glob, info)
call psb_gefree(x_solution, nested_matrix%desc_glob, info)
call psb_gefree(rhs, nested_matrix%desc_glob, info)
call nested_matrix%free(info)
call psb_exit(context)
contains
subroutine check_info(info_value, label)
integer(psb_ipk_), intent(in) :: info_value
character(len=*), intent(in) :: label
if (info_value /= psb_success_) then
if (my_rank == 0) write(*,*) 'FAIL: ', trim(label), ' info=', info_value
call psb_abort(context)
end if
end subroutine check_info
function path_join(dir, name) result(path)
character(len=*), intent(in) :: dir, name
character(len=512) :: path
integer :: n
n = len_trim(dir)
if (n > 0 .and. dir(n:n) == '/') then
path = trim(dir) // trim(name)
else
path = trim(dir) // '/' // trim(name)
end if
end function path_join
subroutine get_stokes_dir(value)
character(len=*), intent(out) :: value
character(len=256) :: datafiles
integer :: status
call get_environment_variable('PETSC_STOKES_DIR', value, status=status)
if (status == 0 .and. len_trim(value) > 0) return
call get_environment_variable('DATAFILESPATH', datafiles, status=status)
if (status == 0 .and. len_trim(datafiles) > 0) then
value = trim(datafiles) // '/matrices/hpddm/GENEO'
else
value = '.'
end if
end subroutine get_stokes_dir
subroutine get_string_env(name, value, default_value)
character(len=*), intent(in) :: name, default_value
character(len=*), intent(out) :: value
integer :: status
call get_environment_variable(name, value, status=status)
if (status /= 0 .or. len_trim(value) == 0) value = default_value
end subroutine get_string_env
subroutine get_int_env(name, value, default_value)
character(len=*), intent(in) :: name
integer(psb_ipk_), intent(out) :: value
integer(psb_ipk_), intent(in) :: default_value
character(len=64) :: text
integer :: status
call get_environment_variable(name, text, status=status)
if (status == 0 .and. len_trim(text) > 0) then
read(text,*) value
else
value = default_value
end if
end subroutine get_int_env
subroutine get_real_env(name, value, default_value)
character(len=*), intent(in) :: name
real(psb_dpk_), intent(out) :: value
real(psb_dpk_), intent(in) :: default_value
character(len=64) :: text
integer :: status
call get_environment_variable(name, text, status=status)
if (status == 0 .and. len_trim(text) > 0) then
read(text,*) value
else
value = default_value
end if
end subroutine get_real_env
subroutine read_petsc_matrix(filename, mat)
character(len=*), intent(in) :: filename
type(petsc_matrix), intent(out) :: mat
integer :: unit, ios
integer(int32) :: classid, nrow32, ncol32, nnz32
integer(int32), allocatable :: row_nnz(:), cols0(:)
real(real64), allocatable :: vals64(:)
integer(psb_lpk_) :: i, j, k
open(newunit=unit, file=trim(filename), status='old', action='read', access='stream', &
& form='unformatted', convert='big_endian', iostat=ios)
if (ios /= 0) then
write(*,*) 'Could not open PETSc matrix file: ', trim(filename)
stop 2
end if
read(unit) classid
if (classid /= petsc_mat_classid) stop 'Bad PETSc matrix classid'
read(unit) nrow32
read(unit) ncol32
read(unit) nnz32
mat%nrow = int(nrow32, psb_lpk_)
mat%ncol = int(ncol32, psb_lpk_)
mat%nnz = int(nnz32, psb_lpk_)
allocate(row_nnz(mat%nrow))
if (mat%nrow > 0_psb_lpk_) read(unit) row_nnz
allocate(cols0(mat%nnz), vals64(mat%nnz))
if (mat%nnz > 0_psb_lpk_) then
read(unit) cols0
read(unit) vals64
end if
close(unit)
allocate(mat%row(mat%nnz), mat%col(mat%nnz), mat%val(mat%nnz))
k = 0_psb_lpk_
do i = 1_psb_lpk_, mat%nrow
do j = 1_psb_lpk_, int(row_nnz(i), psb_lpk_)
k = k + 1_psb_lpk_
mat%row(k) = i
mat%col(k) = int(cols0(k), psb_lpk_) + 1_psb_lpk_
mat%val(k) = real(vals64(k), psb_dpk_)
end do
end do
if (k /= mat%nnz) stop 'Bad PETSc matrix row lengths'
end subroutine read_petsc_matrix
subroutine read_petsc_vector(filename, vec)
character(len=*), intent(in) :: filename
type(petsc_vector), intent(out) :: vec
integer :: unit, ios
integer(int32) :: classid, n32
real(real64), allocatable :: vals64(:)
open(newunit=unit, file=trim(filename), status='old', action='read', access='stream', &
& form='unformatted', convert='big_endian', iostat=ios)
if (ios /= 0) then
write(*,*) 'Could not open PETSc vector file: ', trim(filename)
stop 2
end if
read(unit) classid
if (classid /= petsc_vec_classid) stop 'Bad PETSc vector classid'
read(unit) n32
vec%n = int(n32, psb_lpk_)
allocate(vals64(vec%n), vec%val(vec%n))
if (vec%n > 0_psb_lpk_) read(unit) vals64
close(unit)
vec%val = real(vals64, psb_dpk_)
end subroutine read_petsc_vector
subroutine select_owned_entries(mat, owned_rows, out_rows, out_cols, out_vals, count)
type(petsc_matrix), intent(in) :: mat
integer(psb_lpk_), intent(in) :: owned_rows(:)
integer(psb_lpk_), allocatable, intent(out) :: out_rows(:), out_cols(:)
real(psb_dpk_), allocatable, intent(out) :: out_vals(:)
integer(psb_ipk_), intent(out) :: count
integer(psb_lpk_) :: i, first_owned, last_owned
integer(psb_ipk_) :: k
count = 0
if (size(owned_rows) == 0) then
allocate(out_rows(0), out_cols(0), out_vals(0))
return
end if
first_owned = minval(owned_rows)
last_owned = maxval(owned_rows)
do i = 1_psb_lpk_, mat%nnz
if (mat%row(i) >= first_owned .and. mat%row(i) <= last_owned) count = count + 1
end do
allocate(out_rows(count), out_cols(count), out_vals(count))
k = 0
do i = 1_psb_lpk_, mat%nnz
if (mat%row(i) >= first_owned .and. mat%row(i) <= last_owned) then
k = k + 1
out_rows(k) = mat%row(i)
out_cols(k) = mat%col(i)
out_vals(k) = mat%val(i)
end if
end do
end subroutine select_owned_entries
subroutine select_owned_entries_transpose(mat, owned_rows, out_rows, out_cols, out_vals, count)
type(petsc_matrix), intent(in) :: mat
integer(psb_lpk_), intent(in) :: owned_rows(:)
integer(psb_lpk_), allocatable, intent(out) :: out_rows(:), out_cols(:)
real(psb_dpk_), allocatable, intent(out) :: out_vals(:)
integer(psb_ipk_), intent(out) :: count
integer(psb_lpk_) :: i, first_owned, last_owned
integer(psb_ipk_) :: k
count = 0
if (size(owned_rows) == 0) then
allocate(out_rows(0), out_cols(0), out_vals(0))
return
end if
first_owned = minval(owned_rows)
last_owned = maxval(owned_rows)
do i = 1_psb_lpk_, mat%nnz
if (mat%col(i) >= first_owned .and. mat%col(i) <= last_owned) count = count + 1
end do
allocate(out_rows(count), out_cols(count), out_vals(count))
k = 0
do i = 1_psb_lpk_, mat%nnz
if (mat%col(i) >= first_owned .and. mat%col(i) <= last_owned) then
k = k + 1
out_rows(k) = mat%col(i)
out_cols(k) = mat%row(i)
out_vals(k) = mat%val(i)
end if
end do
end subroutine select_owned_entries_transpose
subroutine insert_owned_rhs(vec, velocity_size, owned_u, owned_p, rhs_vec, desc, info)
type(petsc_vector), intent(in) :: vec
integer(psb_lpk_), intent(in) :: velocity_size
integer(psb_lpk_), intent(in) :: owned_u(:), owned_p(:)
type(psb_d_vect_type), intent(inout) :: rhs_vec
type(psb_desc_type), intent(in) :: desc
integer(psb_ipk_), intent(out) :: info
integer(psb_lpk_), allocatable :: idx(:)
real(psb_dpk_), allocatable :: values(:)
integer :: i, n
n = size(owned_u) + size(owned_p)
allocate(idx(n), values(n))
do i = 1, size(owned_u)
idx(i) = owned_u(i)
values(i) = vec%val(owned_u(i))
end do
do i = 1, size(owned_p)
idx(size(owned_u) + i) = velocity_size + owned_p(i)
values(size(owned_u) + i) = vec%val(velocity_size + owned_p(i))
end do
call psb_geins(n, idx, values, rhs_vec, desc, info)
deallocate(idx, values)
end subroutine insert_owned_rhs
subroutine clear_triplets(in_rows, in_cols, in_vals)
integer(psb_lpk_), allocatable, intent(inout) :: in_rows(:), in_cols(:)
real(psb_dpk_), allocatable, intent(inout) :: in_vals(:)
if (allocated(in_rows)) deallocate(in_rows)
if (allocated(in_cols)) deallocate(in_cols)
if (allocated(in_vals)) deallocate(in_vals)
end subroutine clear_triplets
end program psb_d_nest_petsc_stokes_test
+3 -32
View File
@@ -302,41 +302,12 @@ program psb_d_nest_schur_prec_cg_test
call psb_gefree(x_solution, nested_matrix%desc_glob, info)
call preconditioner%free(info)
! Exercise transposed Schur application for the explicit A22 approximation.
call preconditioner%init(context, 'NEST', info)
call preconditioner%set('COMPOSITION', 'SCHUR_FULL', info)
call preconditioner%set('SCHUR_SOLVE', 'A22', info)
call preconditioner%set('BLOCK_SOLVE', 'DIAG', info)
call preconditioner%build(nested_matrix%a_glob, nested_matrix%desc_glob, info)
if (info /= psb_success_) then
if (my_rank == 0) write(*,*) 'FAIL: nested Schur transpose prec%build info=', info
all_passed = .false.
goto 9999
end if
call psb_geall(x_solution, nested_matrix%desc_glob, info)
call psb_geasb(x_solution, nested_matrix%desc_glob, info)
call preconditioner%apply(rhs, x_solution, nested_matrix%desc_glob, info, trans='T')
if (info /= psb_success_) then
if (my_rank == 0) write(*,*) 'FAIL: NEST Schur transpose apply info=', info
all_passed = .false.
end if
solution_error = psb_genrm2(x_solution, nested_matrix%desc_glob, info)
! Transposed nested preconditioner application is intentionally unsupported.
! The implementation returns psb_err_transpose_not_n_unsupported_ for trans /= 'N'.
if (my_rank == 0) then
write(*,'(a,a32,a,a16,a,a16,a,es12.4)') &
& ' prec=NEST/', 'SCHUR_FULL', &
& ' schur=', 'A22', &
& ' method=', 'APPLY^T', &
& ' ||P^T rhs||=', solution_error
write(*,'(a)') ' prec=NEST/SCHUR_FULL schur=A22 method=APPLY^T skipped: transpose unsupported'
end if
if ((info /= psb_success_) .or. (solution_error /= solution_error) .or. &
& (solution_error <= dzero)) all_passed = .false.
call psb_gefree(x_solution, nested_matrix%desc_glob, info)
call preconditioner%free(info)
if (my_rank == 0) then
if (all_passed) then
write(*,*) '[PASS] Krylov solvers converge with Schur-style NEST preconditioners'