@@ -294,3 +294,112 @@ LinearAlgebra.ldiv!(fact::JacobiPreconditioner, v) = ldiv!(fact.factorization, v
294294
295295allow_views (:: JacobiPreconditioner ) = true
296296allow_views (:: Type{JacobiPreconditioner} ) = true
297+
298+
299+ """
300+ SchurComplementPreconditioner{AT, ST}
301+
302+ Preconditioner for saddle-point problems of the form:
303+ ```
304+ [ A B ]
305+ [ Bᵀ 0 ]
306+ ```
307+
308+ # Fields
309+ - `partitions`: Vector of two ranges defining matrix partitioning
310+ - `A_fac::AT`: Factorization of the A-block (top-left)
311+ - `S_fac::ST`: Factorization of the Schur complement (approximation of -BᵀA⁻¹B)
312+ """
313+ struct SchurComplementPreconditioner{AT, ST}
314+ partitions
315+ A_fac:: AT
316+ S_fac:: ST
317+ end
318+
319+
320+ """
321+ SchurComplementPreconBuilder(dofs_first_block, A_factorization, S_factorization = lu)
322+
323+ Factory function creating preconditioners for saddle-point problems.
324+
325+ Creates a preconditioner of the form
326+ ```
327+ [ ≈ A 0 ]
328+ [ 0 ≈ -α BᵀA⁻¹B ]
329+ ```
330+
331+ where α is 1 by default (can be adjusted by `S_factor`).
332+
333+ In the Schur factor, we use `Diagonal(A)` for efficiency.
334+
335+ # Arguments
336+ - `dofs_first_block`: Number of degrees of freedom in the first block (size of A matrix)
337+ - `A_factorization`: Factorization method for the A-block (e.g., `ilu0, Diagonal, lu`)
338+ - `S_factorization`: Factorization method for the Schur complement (defaults to full `lu`)
339+ - `S_factor`: An additional scalar factor for the Schur complement block. Can be used to obtain positive definite preconditioners
340+
341+ Note: All factorizations need to be able for operate on vector views, as
342+
343+ Returns a function `prec(M, p)` that creates a `SchurComplementPreconditioner`.
344+ """
345+ function SchurComplementPreconBuilder (dofs_first_block, A_factorization, S_factorization = lu; verbosity = 0 , S_factor = 1.0 )
346+
347+ # this is the resulting preconditioner
348+ function prec (M)
349+
350+ # We have
351+ # M = [ A B ] → n1 dofs
352+ # [ Bᵀ 0 ] → n2 dofs
353+
354+ n1 = dofs_first_block
355+ n2 = size (M, 1 ) - n1
356+
357+ # I see no other way than creating this expensive copy
358+ A = M[1 : n1, 1 : n1]
359+ B = M[1 : n1, (n1 + 1 ): end ]
360+
361+ # first factorization
362+ A_fac = A_factorization (A)
363+
364+ verbosity > 0 && @info " SchurComplementPreconBuilder: A ($n1 ×$n1 ) is factorized"
365+
366+ # compute the Schur Matrix
367+ # S ≈ -α BᵀA⁻¹B
368+ # we use the diagonal of A: this is _very_ performant and creates a sparse result
369+ S = - S_factor * B' * (Diagonal (A) \ B)
370+
371+ verbosity > 0 && @info " SchurComplementPreconBuilder: S ($n2 ×$n2 ) is computed"
372+
373+ # factorize S
374+ S_fac = S_factorization (S)
375+
376+ verbosity > 0 && @info " SchurComplementPreconBuilder: S ($n2 ×$n2 ) is factorized"
377+
378+ return SchurComplementPreconditioner ([1 : n1, (n1 + 1 ): (n1 + n2)], A_fac, S_fac)
379+ end
380+
381+ return prec
382+ end
383+
384+
385+ function LinearAlgebra. ldiv! (u, p:: SchurComplementPreconditioner , v)
386+
387+ (part1, part2) = p. partitions
388+ # do it in parallel
389+ t1 = Threads. @spawn @views ldiv! (u[part1], p. A_fac, v[part1])
390+ t2 = Threads. @spawn @views ldiv! (u[part2], p. S_fac, v[part2])
391+ fetch .([t1, t2])
392+
393+ return u
394+ end
395+
396+ function LinearAlgebra. ldiv! (p:: SchurComplementPreconditioner , v)
397+
398+ (part1, part2) = p. partitions
399+ # do it in parallel
400+ t1 = Threads. @spawn @views ldiv! (p. A_fac, v[part1])
401+ t2 = Threads. @spawn @views ldiv! (p. S_fac, v[part2])
402+ fetch .([t1, t2])
403+
404+ return v
405+ end
0 commit comments