[petsc-users] Implementing the Sherman Morisson formula (low rank update) in petsc4py and FEniCS?

Olek Niewiarowski aan2 at princeton.edu
Tue Feb 4 17:07:36 CST 2020


Hello,
I am a FEniCS user but new to petsc4py. I am trying to modify the KSP solver through the SNES object to implement the Sherman-Morrison formula (e.g.  http://fourier.eng.hmc.edu/e176/lectures/algebra/node6.html ). I am solving a nonlinear system of the form [K(u)−kaaT]Δu=−F(u). Here the jacobian matrix K is modified by the term kaaT, where k is a scalar.  Notably, K is sparse, while the term kaaT results in a full matrix. This problem can be solved efficiently using the Sherman-Morrison formula :

[K−kaaT]-1 = K-1  - (kK-1 aaTK-1)/(1+kaTK-1a)
I have managed to successfully implement this at the linear solve level (by modifying the KSP solver) inside a custom Newton solver in python by following an incomplete tutorial at https://www.firedrakeproject.org/petsc-interface.html#defining-a-preconditioner :
  *               while (norm(delU) > alpha):  # while not converged
  *
  *                   self.update_F()  # call to method to update r.h.s form
  *                   self.update_K()  # call to update the jacobian form
  *                   K = assemble(self.K)  # assemble the jacobian matrix
  *                   F = assemble(self.F)  # assemble the r.h.s vector
  *                   a = assemble(self.a_form)  # assemble the a_form (see Sherman Morrison formula)
  *
  *                   for bc in self.mem.bc:  # apply boundary conditions
  *                       bc.apply(K, F)
  *                       bc.apply(K, a)
  *
  *                   B = PETSc.Mat().create()
  *
  *                   # Assemble the bilinear form that defines A and get the concrete
  *                   # PETSc matrix
  *                   A = as_backend_type(K).mat()  # get the PETSc objects for K and a
  *                   u = as_backend_type(a).vec()
  *
  *                   # Build the matrix "context"  # see firedrake docs
  *                   Bctx = MatrixFreeB(A, u, u, self.k)
  *
  *                   # Set up B
  *                   # B is the same size as A
  *                   B.setSizes(*A.getSizes())
  *                   B.setType(B.Type.PYTHON)
  *                   B.setPythonContext(Bctx)
  *                   B.setUp()
  *
  *
  *                   ksp = PETSc.KSP().create()   # create the KSP linear solver object
  *                   ksp.setOperators(B)
  *                   ksp.setUp()
  *                   pc = ksp.pc
  *                   pc.setType(pc.Type.PYTHON)
  *                   pc.setPythonContext(MatrixFreePC())
  *                   ksp.setFromOptions()
  *
  *                   solution = delU    # the incremental displacement at this iteration
  *
  *                   b = as_backend_type(-F).vec()
  *                   delu = solution.vector().vec()
  *
  *                   ksp.solve(b, delu)
  *
  *                   self.mem.u.vector().axpy(0.25, self.delU.vector())  # poor man's linesearch
  *                   counter += 1
Here is the corresponding petsc4py code adapted from the firedrake docs:


  1.  class MatrixFreePC(object):
  2.
  3.      def setUp(self, pc):
  4.          B, P = pc.getOperators()
  5.          # extract the MatrixFreeB object from B
  6.          ctx = B.getPythonContext()
  7.          self.A = ctx.A
  8.          self.u = ctx.u
  9.          self.v = ctx.v
  10.         self.k = ctx.k
  11.         # Here we build the PC object that uses the concrete,
  12.         # assembled matrix A.  We will use this to apply the action
  13.         # of A^{-1}
  14.         self.pc = PETSc.PC().create()
  15.         self.pc.setOptionsPrefix("mf_")
  16.         self.pc.setOperators(self.A)
  17.         self.pc.setFromOptions()
  18.         # Since u and v do not change, we can build the denominator
  19.         # and the action of A^{-1} on u only once, in the setup
  20.         # phase.
  21.         tmp = self.A.createVecLeft()
  22.         self.pc.apply(self.u, tmp)
  23.         self._Ainvu = tmp
  24.         self._denom = 1 + self.k*self.v.dot(self._Ainvu)
  25.
  26.     def apply(self, pc, x, y):
  27.         # y <- A^{-1}x
  28.         self.pc.apply(x, y)
  29.         # alpha <- (v^T A^{-1} x) / (1 + v^T A^{-1} u)
  30.         alpha = (self.k*self.v.dot(y)) / self._denom
  31.         # y <- y - alpha * A^{-1}u
  32.         y.axpy(-alpha, self._Ainvu)
  33.
  34.
  35. class MatrixFreeB(object):
  36.
  37.     def __init__(self, A, u, v, k):
  38.         self.A = A
  39.         self.u = u
  40.         self.v = v
  41.         self.k = k
  42.
  43.     def mult(self, mat, x, y):
  44.         # y <- A x
  45.         self.A.mult(x, y)
  46.
  47.         # alpha <- v^T x
  48.         alpha = self.v.dot(x)
  49.
  50.         # y <- y + alpha*u
  51.         y.axpy(alpha, self.u)

However, this approach is not efficient as it requires many iterations due to the Newton step being fixed, so I would like to implement it using SNES and use line search. Unfortunately, I have not been able to find any documentation/tutorial on how to do so. Provided I have the FEniCS forms for F, K, and a, I'd like to do something along the lines of:
solver  = PETScSNESSolver() # the FEniCS SNES wrapper
snes = solver.snes()  # the petsc4py SNES object
## ??
ksp = snes.getKSP()
 # set ksp option similar to above
solver.solve()

I would be very grateful if anyone could could help or point me to a reference or demo that does something similar (or maybe a completely different way of solving the problem!).
Many thanks in advance!
Alex
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.mcs.anl.gov/pipermail/petsc-users/attachments/20200204/30cb6f80/attachment-0001.html>


More information about the petsc-users mailing list