@@ -294,3 +294,98 @@ LinearAlgebra.ldiv!(fact::JacobiPreconditioner, v) = ldiv!(fact.factorization, v
294294
295295allow_views (:: JacobiPreconditioner ) = true
296296allow_views (:: Type{JacobiPreconditioner} ) = true
297+
298+
299+
300+ """
301+ SchurComplementPreconditioner{AT, ST}
302+
303+ Preconditioner for saddle-point problems of the form:
304+ ```
305+ [ A B ]
306+ [ Bᵀ 0 ]
307+ ```
308+
309+ # Fields
310+ - `partitions`: Vector of two ranges defining matrix partitioning
311+ - `A_fac::AT`: Factorization of the A-block (top-left)
312+ - `S_fac::ST`: Factorization of the Schur complement (approximation of -BᵀA⁻¹B)
313+ """
314+ struct SchurComplementPreconditioner{AT, ST}
315+ partitions
316+ A_fac:: AT
317+ S_fac:: ST
318+ end
319+
320+
321+ """
322+ SchurComplementPreconBuilder(dofs_first_block, A_factorization, S_factorization = lu)
323+
324+ Factory function creating preconditioners for saddle-point problems.
325+
326+ # Arguments
327+ - `dofs_first_block`: Number of degrees of freedom in the first block (size of A matrix)
328+ - `A_factorization`: Factorization method for the A-block (e.g., `ilu0, Diagonal, lu`)
329+ - `S_factorization`: Factorization method for the Schur complement (defaults to `lu`)
330+
331+ Returns a function `prec(M, p)` that creates a `SchurComplementPreconditioner`.
332+ """
333+ function SchurComplementPreconBuilder (dofs_first_block, A_factorization, S_factorization = lu)
334+
335+ # this is the resulting preconditioner
336+ function prec (M, p)
337+
338+ # We have
339+ # M = [ A B ] → n1 dofs
340+ # [ Bᵀ 0 ] → n2 dofs
341+
342+ n1 = dofs_first_block
343+ n2 = size (M, 1 ) - n1
344+
345+ # I see no other way than creating this expensive copy
346+ A = M[1 : n1, 1 : n1]
347+ B = M[1 : n1, (n1 + 1 ): end ]
348+
349+ # first factorization
350+ A_fac = A_factorization (A)
351+
352+ # compute the (dense!) Schur Matrix
353+ # S ≈ - Bᵀ A⁻¹ B
354+ S = zeros (n2, n2)
355+
356+ @info " Computing the Schur complement..."
357+ ldiv_buffer = zeros (n1)
358+ for col in 1 : n2
359+ # TODO : the following is not thread parallel?
360+ @views ldiv! (ldiv_buffer, A_fac, B[:, col])
361+ @views mul! (S[:, col], B' , ldiv_buffer)
362+ end
363+ S .*= - 1.0
364+ @info " ...done"
365+
366+ S_fac = S_factorization (sparse (S))
367+
368+ return SchurComplementPreconditioner ([1 : n1, (n1 + 1 ): (n1 + n2)], A_fac, S_fac)
369+ end
370+
371+ return prec
372+ end
373+
374+
375+ function LinearAlgebra. ldiv! (u, p:: SchurComplementPreconditioner , v)
376+
377+ (part1, part2) = p. partitions
378+ @views ldiv! (u[part1], p. A_fac, v[part1])
379+ @views ldiv! (u[part2], p. S_fac, v[part2])
380+
381+ return u
382+ end
383+
384+ function LinearAlgebra. ldiv! (p:: SchurComplementPreconditioner , v)
385+
386+ (part1, part2) = p. partitions
387+ @views ldiv! (p. A_fac, v[part1])
388+ @views ldiv! (p. S_fac, v[part2])
389+
390+ return v
391+ end
0 commit comments