Coverage for python/src/dolfinx_mpc/problem.py: 78%

263 statements  

« prev     ^ index     » next       coverage.py v7.9.2, created at 2025-07-11 21:02 +0000

1# -*- coding: utf-8 -*- 

2# Copyright (C) 2021-2025 Jørgen S. Dokken 

3# 

4# This file is part of DOLFINx MPC 

5# 

6# SPDX-License-Identifier: MIT 

7from __future__ import annotations 

8 

9import typing 

10from functools import partial 

11from typing import Iterable, Sequence 

12 

13from petsc4py import PETSc 

14 

15import dolfinx.fem.petsc 

16import numpy as np 

17import numpy.typing as npt 

18import ufl 

19from dolfinx import fem as _fem 

20from dolfinx.la.petsc import _ghost_update, _zero_vector, create_vector 

21 

22from dolfinx_mpc.cpp import mpc as _cpp_mpc 

23 

24from .assemble_matrix import assemble_matrix, assemble_matrix_nest, create_matrix_nest 

25from .assemble_vector import apply_lifting, assemble_vector, assemble_vector_nest, create_vector_nest 

26from .multipointconstraint import MultiPointConstraint 

27 

28 

29def assemble_jacobian_mpc( 

30 u: typing.Union[Sequence[_fem.Function], _fem.Function], 

31 jacobian: typing.Union[_fem.Form, typing.Iterable[typing.Iterable[_fem.Form]]], 

32 preconditioner: typing.Optional[typing.Union[_fem.Form, typing.Iterable[typing.Iterable[_fem.Form]]]], 

33 bcs: typing.Iterable[_fem.DirichletBC], 

34 mpc: typing.Union[MultiPointConstraint, Sequence[MultiPointConstraint]], 

35 _snes: PETSc.SNES, # type: ignore 

36 x: PETSc.Vec, # type: ignore 

37 J: PETSc.Mat, # type: ignore 

38 P: PETSc.Mat, # type: ignore 

39): 

40 """Assemble the Jacobian matrix and preconditioner. 

41 

42 A function conforming to the interface expected by SNES.setJacobian can 

43 be created by fixing the first four arguments: 

44 

45 functools.partial(assemble_jacobian, u, jacobian, preconditioner, 

46 bcs) 

47 

48 Args: 

49 u: Function tied to the solution vector within the residual and 

50 jacobian 

51 jacobian: Form of the Jacobian 

52 preconditioner: Form of the preconditioner 

53 bcs: List of Dirichlet boundary conditions 

54 mpc: The multi point constraint or a sequence of multi point 

55 _snes: The solver instance 

56 x: The vector containing the point to evaluate at 

57 J: Matrix to assemble the Jacobian into 

58 P: Matrix to assemble the preconditioner into 

59 """ 

60 # Copy existing soultion into the function used in the residual and 

61 # Jacobian 

62 _ghost_update(x, PETSc.InsertMode.INSERT, PETSc.ScatterMode.FORWARD) # type: ignore 

63 _fem.petsc.assign(x, u) 

64 

65 # Assemble Jacobian 

66 J.zeroEntries() 

67 if J.getType() == "nest": 

68 assemble_matrix_nest(J, jacobian, mpc, bcs, diagval=1.0) # type: ignore 

69 else: 

70 assemble_matrix(jacobian, mpc, bcs, diagval=1.0, A=J) # type: ignore 

71 J.assemble() 

72 if preconditioner is not None: 

73 P.zeroEntries() 

74 if P.getType() == "nest": 

75 assemble_matrix_nest(P, preconditioner, mpc, bcs, diagval=1.0) # type: ignore 

76 else: 

77 assemble_matrix(mpc, preconditioner, bcs, diagval=1.0, A=P) # type: ignore 

78 

79 P.assemble() 

80 

81 

82def assemble_residual_mpc( 

83 u: typing.Union[_fem.Function, Sequence[_fem.Function]], 

84 residual: typing.Union[_fem.Form, typing.Iterable[_fem.Form]], 

85 jacobian: typing.Union[_fem.Form, typing.Iterable[typing.Iterable[_fem.Form]]], 

86 bcs: typing.Iterable[_fem.DirichletBC], 

87 mpc: typing.Union[MultiPointConstraint, Sequence[MultiPointConstraint]], 

88 _snes: PETSc.SNES, # type: ignore 

89 x: PETSc.Vec, # type: ignore 

90 F: PETSc.Vec, # type: ignore 

91): 

92 """Assemble the residual into the vector `F`. 

93 

94 A function conforming to the interface expected by SNES.setResidual can 

95 be created by fixing the first four arguments: 

96 

97 functools.partial(assemble_residual, u, jacobian, preconditioner, 

98 bcs) 

99 

100 Args: 

101 u: Function(s) tied to the solution vector within the residual and 

102 Jacobian. 

103 residual: Form of the residual. It can be a sequence of forms. 

104 jacobian: Form of the Jacobian. It can be a nested sequence of 

105 forms. 

106 bcs: List of Dirichlet boundary conditions. 

107 _snes: The solver instance. 

108 x: The vector containing the point to evaluate the residual at. 

109 F: Vector to assemble the residual into. 

110 """ 

111 # Update input vector before assigning 

112 _ghost_update(x, PETSc.InsertMode.INSERT, PETSc.ScatterMode.FORWARD) # type: ignore 

113 # Assign the input vector to the unknowns 

114 _fem.petsc.assign(x, u) 

115 if isinstance(u, Sequence): 

116 assert isinstance(mpc, Sequence) 

117 for i in range(len(u)): 

118 mpc[i].homogenize(u[i]) 

119 mpc[i].backsubstitution(u[i]) 

120 else: 

121 assert isinstance(u, _fem.Function) 

122 assert isinstance(mpc, MultiPointConstraint) 

123 mpc.homogenize(u) 

124 mpc.backsubstitution(u) 

125 # Assemble the residual 

126 _zero_vector(F) 

127 if x.getType() == "nest": 

128 assemble_vector_nest(F, residual, mpc) # type: ignore 

129 else: 

130 assert isinstance(residual, _fem.Form) 

131 assert isinstance(mpc, MultiPointConstraint) 

132 assemble_vector(residual, mpc, F) 

133 

134 # Lift vector 

135 try: 

136 # Nest and blocked lifting 

137 bcs1 = _fem.bcs.bcs_by_block(_fem.forms.extract_function_spaces(jacobian, 1), bcs) # type: ignore 

138 _fem.petsc._assign_block_data(residual, x) # type: ignore 

139 apply_lifting(F, jacobian, bcs=bcs1, constraint=mpc, x0=x, scale=-1.0) # type: ignore 

140 _ghost_update(F, PETSc.InsertMode.ADD, PETSc.ScatterMode.REVERSE) # type: ignore 

141 bcs0 = _fem.bcs.bcs_by_block(_fem.forms.extract_function_spaces(residual), bcs) # type: ignore 

142 _fem.petsc.set_bc(F, bcs0, x0=x, alpha=-1.0) 

143 except RuntimeError: 

144 # Single form lifting 

145 apply_lifting(F, [jacobian], bcs=[bcs], constraint=mpc, x0=[x], scale=-1.0) # type: ignore 

146 _ghost_update(F, PETSc.InsertMode.ADD, PETSc.ScatterMode.REVERSE) # type: ignore 

147 _fem.petsc.set_bc(F, bcs, x0=x, alpha=-1.0) 

148 _ghost_update(F, PETSc.InsertMode.INSERT, PETSc.ScatterMode.FORWARD) # type: ignore 

149 

150 

151class NonlinearProblem(dolfinx.fem.petsc.NonlinearProblem): 

152 def __init__( 

153 self, 

154 F: typing.Union[ufl.form.Form, Sequence[ufl.form.Form]], 

155 u: typing.Union[_fem.Function, Sequence[_fem.Function]], 

156 mpc: typing.Union[MultiPointConstraint, Sequence[MultiPointConstraint]], 

157 bcs: typing.Optional[Sequence[_fem.DirichletBC]] = None, 

158 J: typing.Optional[typing.Union[ufl.form.Form, Iterable[Iterable[ufl.form.Form]]]] = None, 

159 P: typing.Optional[typing.Union[ufl.form.Form, Iterable[Iterable[ufl.form.Form]]]] = None, 

160 kind: typing.Optional[typing.Union[str, typing.Iterable[typing.Iterable[str]]]] = None, 

161 form_compiler_options: typing.Optional[dict] = None, 

162 jit_options: typing.Optional[dict] = None, 

163 petsc_options: typing.Optional[dict] = None, 

164 entity_maps: typing.Optional[dict[dolfinx.mesh.Mesh, npt.NDArray[np.int32]]] = None, 

165 ): 

166 """Class for solving nonlinear problems with SNES. 

167 

168 Solves problems of the form 

169 :math:`F_i(u, v) = 0, i=0,...N\\ \\forall v \\in V` where 

170 :math:`u=(u_0,...,u_N), v=(v_0,...,v_N)` using PETSc SNES as the 

171 non-linear solver. 

172 

173 Note: The deprecated version of this class for use with 

174 NewtonSolver has been renamed NewtonSolverNonlinearProblem. 

175 

176 Args: 

177 F: UFL form(s) of residual :math:`F_i`. 

178 u: Function used to define the residual and Jacobian. 

179 bcs: Dirichlet boundary conditions. 

180 J: UFL form(s) representing the Jacobian 

181 :math:`J_ij = dF_i/du_j`. 

182 P: UFL form(s) representing the preconditioner. 

183 kind: The PETSc matrix type(s) for the Jacobian and 

184 preconditioner (``MatType``). 

185 See :func:`dolfinx.fem.petsc.create_matrix` for more 

186 information. 

187 form_compiler_options: Options used in FFCx compilation of all 

188 forms. Run ``ffcx --help`` at the command line to see all 

189 available options. 

190 jit_options: Options used in CFFI JIT compilation of C code 

191 generated by FFCx. See ``python/dolfinx/jit.py`` for all 

192 available options. Takes priority over all other option 

193 values. 

194 petsc_options: Options to pass to the PETSc SNES object. 

195 entity_maps: If any trial functions, test functions, or 

196 coefficients in the form are not defined over the same mesh 

197 as the integration domain, ``entity_maps`` must be 

198 supplied. For each key (a mesh, different to the 

199 integration domain mesh) a map should be provided relating 

200 the entities in the integration domain mesh to the entities 

201 in the key mesh e.g. for a key-value pair ``(msh, emap)`` 

202 in ``entity_maps``, ``emap[i]`` is the entity in ``msh`` 

203 corresponding to entity ``i`` in the integration domain 

204 mesh. 

205 """ 

206 # Compile residual and Jacobian forms 

207 self._F = _fem.form( 

208 F, 

209 form_compiler_options=form_compiler_options, 

210 jit_options=jit_options, 

211 entity_maps=entity_maps, 

212 ) 

213 

214 if J is None: 

215 dus = [ufl.TrialFunction(Fi.arguments()[0].ufl_function_space()) for Fi in F] 

216 J = _fem.forms.derivative_block(F, u, dus) 

217 

218 self._J = _fem.form( 

219 J, 

220 form_compiler_options=form_compiler_options, 

221 jit_options=jit_options, 

222 entity_maps=entity_maps, 

223 ) 

224 

225 if P is not None: 

226 self._preconditioner = _fem.form( 

227 P, 

228 form_compiler_options=form_compiler_options, 

229 jit_options=jit_options, 

230 entity_maps=entity_maps, 

231 ) 

232 else: 

233 self._preconditioner = None 

234 

235 self._u = u 

236 # Set default values if not supplied 

237 bcs = [] if bcs is None else bcs 

238 self.mpc = mpc 

239 # Create PETSc structures for the residual, Jacobian and solution 

240 # vector 

241 if kind == "nest" or isinstance(kind, Sequence): 

242 assert isinstance(mpc, Sequence) 

243 assert isinstance(self._J, Sequence) 

244 self._A = create_matrix_nest(self._J, mpc) 

245 elif kind is None: 

246 assert isinstance(mpc, MultiPointConstraint) 

247 self._A = _cpp_mpc.create_matrix(self._J._cpp_object, mpc._cpp_object) 

248 else: 

249 raise ValueError("Unsupported kind for matrix: {}".format(kind)) 

250 

251 kind = "nest" if self._A.getType() == "nest" else kind 

252 if kind == "nest": 

253 assert isinstance(mpc, Sequence) 

254 assert isinstance(self._F, Sequence) 

255 self._b = create_vector_nest(self._F, mpc) 

256 self._x = create_vector_nest(self._F, mpc) 

257 else: 

258 assert isinstance(mpc, MultiPointConstraint) 

259 self._b = create_vector(mpc.function_space.dofmap.index_map, mpc.function_space.dofmap.index_map_bs) 

260 self._x = create_vector(mpc.function_space.dofmap.index_map, mpc.function_space.dofmap.index_map_bs) 

261 

262 # Create PETSc structure for preconditioner if provided 

263 if self.preconditioner is not None: 

264 if kind == "nest": 

265 assert isinstance(self.preconditioner, Sequence) 

266 assert isinstance(self.mpc, Sequence) 

267 self._P_mat = create_matrix_nest(self.preconditioner, self.mpc) 

268 else: 

269 assert isinstance(self._P, _fem.Form) 

270 self._P_mat = _cpp_mpc.create_matrix(self.preconditioner, kind=kind) 

271 else: 

272 self._P_mat = None 

273 

274 # Create the SNES solver and attach the corresponding Jacobian and 

275 # residual computation functions 

276 self._snes = PETSc.SNES().create(comm=self.A.comm) # type: ignore 

277 self.solver.setJacobian( 

278 partial(assemble_jacobian_mpc, u, self.J, self.preconditioner, bcs, mpc), self._A, self.P_mat 

279 ) 

280 self.solver.setFunction(partial(assemble_residual_mpc, u, self.F, self.J, bcs, mpc), self.b) 

281 

282 # Set PETSc options 

283 problem_prefix = f"dolfinx_nonlinearproblem_{id(self)}" 

284 self.solver.setOptionsPrefix(problem_prefix) 

285 opts = PETSc.Options() # type: ignore 

286 opts.prefixPush(problem_prefix) 

287 

288 if petsc_options is not None: 

289 for k, v in petsc_options.items(): 

290 opts[k] = v 

291 self.solver.setFromOptions() 

292 

293 for k in petsc_options.keys(): 

294 del opts[k] 

295 

296 opts.prefixPop() 

297 

298 def solve(self) -> tuple[PETSc.Vec, int, int]: # type: ignore 

299 """Solve the problem and update the solution in the problem 

300 instance. 

301 

302 Returns: 

303 The solution, convergence reason and number of iterations. 

304 """ 

305 

306 # Move current iterate into the work array. 

307 _fem.petsc.assign(self._u, self.x) 

308 

309 # Solve problem 

310 self.solver.solve(None, self.x) 

311 

312 # Move solution back to function 

313 dolfinx.fem.petsc.assign(self.x, self._u) 

314 if isinstance(self.mpc, Sequence): 

315 for i in range(len(self._u)): 

316 self.mpc[i].backsubstitution(self._u[i]) 

317 self.mpc[i].backsubstitution(self._u[i]) 

318 else: 

319 assert isinstance(self._u, _fem.Function) 

320 self.mpc.homogenize(self._u) 

321 self.mpc.backsubstitution(self._u) 

322 

323 converged_reason = self.solver.getConvergedReason() 

324 return self._u, converged_reason, self.solver.getIterationNumber() 

325 

326 

327class LinearProblem(dolfinx.fem.petsc.LinearProblem): 

328 """ 

329 Class for solving a linear variational problem with multi point constraints of the form 

330 a(u, v) = L(v) for all v using PETSc as a linear algebra backend. 

331 

332 Args: 

333 a: A bilinear UFL form, the left hand side of the variational problem. 

334 L: A linear UFL form, the right hand side of the variational problem. 

335 mpc: The multi point constraint. 

336 bcs: A list of Dirichlet boundary conditions. 

337 u: The solution function. It will be created if not provided. The function has 

338 to be based on the functionspace in the mpc, i.e. 

339 

340 .. highlight:: python 

341 .. code-block:: python 

342 

343 u = dolfinx.fem.Function(mpc.function_space) 

344 petsc_options: Parameters that is passed to the linear algebra backend PETSc. #type: ignore 

345 For available choices for the 'petsc_options' kwarg, see the PETSc-documentation 

346 https://www.mcs.anl.gov/petsc/documentation/index.html. 

347 form_compiler_options: Parameters used in FFCx compilation of this form. Run `ffcx --help` at 

348 the commandline to see all available options. Takes priority over all 

349 other parameter values, except for `scalar_type` which is determined by DOLFINx. 

350 jit_options: Parameters used in CFFI JIT compilation of C code generated by FFCx. 

351 See https://github.com/FEniCS/dolfinx/blob/main/python/dolfinx/jit.py#L22-L37 

352 for all available parameters. Takes priority over all other parameter values. 

353 P: A preconditioner UFL form. 

354 Examples: 

355 Example usage: 

356 

357 .. highlight:: python 

358 .. code-block:: python 

359 

360 problem = LinearProblem(a, L, mpc, [bc0, bc1], 

361 petsc_options={"ksp_type": "preonly", "pc_type": "lu"}) 

362 

363 """ 

364 

365 u: typing.Union[_fem.Function, list[_fem.Function]] 

366 _a: typing.Union[_fem.Form, typing.Sequence[typing.Sequence[_fem.Form]]] 

367 _L: typing.Union[_fem.Form, typing.Sequence[_fem.Form]] 

368 _preconditioner: typing.Optional[typing.Union[_fem.Form, typing.Sequence[typing.Sequence[_fem.Form]]]] 

369 _mpc: typing.Union[MultiPointConstraint, typing.Sequence[MultiPointConstraint]] 

370 _A: PETSc.Mat # type: ignore 

371 _P: typing.Optional[PETSc.Mat] # type: ignore 

372 _b: PETSc.Vec # type: ignore 

373 _solver: PETSc.KSP # type: ignore 

374 _x: PETSc.Vec # type: ignore 

375 bcs: typing.List[_fem.DirichletBC] 

376 __slots__ = tuple(__annotations__) 

377 

378 def __init__( 

379 self, 

380 a: typing.Union[ufl.Form, typing.Iterable[typing.Iterable[ufl.Form]]], 

381 L: typing.Union[ufl.Form, typing.Iterable[ufl.Form]], 

382 mpc: typing.Union[MultiPointConstraint, typing.Sequence[MultiPointConstraint]], 

383 bcs: typing.Optional[typing.List[_fem.DirichletBC]] = None, 

384 u: typing.Optional[typing.Union[_fem.Function, typing.Sequence[_fem.Function]]] = None, 

385 petsc_options_prefix: str = "dolfinx_mpc_linear_problem_", 

386 petsc_options: typing.Optional[dict] = None, 

387 form_compiler_options: typing.Optional[dict] = None, 

388 jit_options: typing.Optional[dict] = None, 

389 P: typing.Optional[typing.Union[ufl.Form, typing.Iterable[ufl.Form]]] = None, 

390 ): 

391 # Compile forms 

392 form_compiler_options = {} if form_compiler_options is None else form_compiler_options 

393 jit_options = {} if jit_options is None else jit_options 

394 self._a = _fem.form(a, jit_options=jit_options, form_compiler_options=form_compiler_options) 

395 self._L = _fem.form(L, jit_options=jit_options, form_compiler_options=form_compiler_options) 

396 

397 self._mpc = mpc 

398 # Nest assembly 

399 if isinstance(mpc, Sequence): 

400 is_nest = True 

401 # Sanity check 

402 for mpc_i in mpc: 

403 if not mpc_i.finalized: 

404 raise RuntimeError("The multi point constraint has to be finalized before calling initializer") 

405 # Create function containing solution vector 

406 else: 

407 is_nest = False 

408 if not mpc.finalized: 

409 raise RuntimeError("The multi point constraint has to be finalized before calling initializer") 

410 

411 # Create function(s) containing solution vector(s) 

412 if is_nest: 

413 if u is None: 

414 assert isinstance(self._mpc, Sequence) 

415 self.u = [_fem.Function(self._mpc[i].function_space) for i in range(len(self._mpc))] 

416 else: 

417 assert isinstance(self._mpc, Sequence) 

418 assert isinstance(u, Sequence) 

419 for i, (mpc_i, u_i) in enumerate(zip(self._mpc, u)): 

420 assert isinstance(u_i, _fem.Function) 

421 assert isinstance(mpc_i, MultiPointConstraint) 

422 if u_i.function_space is not mpc_i.function_space: 

423 raise ValueError( 

424 "The input function has to be in the function space in the multi-point constraint", 

425 "i.e. u = dolfinx.fem.Function(mpc.function_space)", 

426 ) 

427 self.u = list(u) 

428 else: 

429 if u is None: 

430 assert isinstance(self._mpc, MultiPointConstraint) 

431 self.u = _fem.Function(self._mpc.function_space) 

432 else: 

433 assert isinstance(u, _fem.Function) 

434 assert isinstance(self._mpc, MultiPointConstraint) 

435 if u.function_space is self._mpc.function_space: 

436 self.u = u 

437 else: 

438 raise ValueError( 

439 "The input function has to be in the function space in the multi-point constraint", 

440 "i.e. u = dolfinx.fem.Function(mpc.function_space)", 

441 ) 

442 

443 # Create MPC matrix and vector 

444 self._preconditioner = _fem.form(P, jit_options=jit_options, form_compiler_options=form_compiler_options) 

445 

446 if is_nest: 

447 assert isinstance(mpc, Sequence) 

448 assert isinstance(self._L, Sequence) 

449 assert isinstance(self._a, Sequence) 

450 self._A = create_matrix_nest(self._a, mpc) 

451 self._b = create_vector_nest(self._L, mpc) 

452 self._x = create_vector_nest(self._L, mpc) 

453 if self._preconditioner is None: 

454 self._P_mat = None 

455 else: 

456 assert isinstance(self._preconditioner, Sequence) 

457 self._P_mat = create_matrix_nest(self._preconditioner, mpc) 

458 else: 

459 assert isinstance(mpc, MultiPointConstraint) 

460 assert isinstance(self._L, _fem.Form) 

461 assert isinstance(self._a, _fem.Form) 

462 self._A = _cpp_mpc.create_matrix(self._a._cpp_object, mpc._cpp_object) 

463 self._b = create_vector(mpc.function_space.dofmap.index_map, mpc.function_space.dofmap.index_map_bs) 

464 self._x = create_vector(mpc.function_space.dofmap.index_map, mpc.function_space.dofmap.index_map_bs) 

465 if self._preconditioner is None: 

466 self._P_mat = None 

467 else: 

468 assert isinstance(self._preconditioner, _fem.Form) 

469 self._P_mat = _cpp_mpc.create_matrix(self._preconditioner._cpp_object, mpc._cpp_object) 

470 

471 self.bcs = [] if bcs is None else bcs 

472 

473 if is_nest: 

474 assert isinstance(self.u, Sequence) 

475 comm = self.u[0].function_space.mesh.comm 

476 else: 

477 assert isinstance(self.u, _fem.Function) 

478 comm = self.u.function_space.mesh.comm 

479 

480 self._solver = PETSc.KSP().create(comm) # type: ignore 

481 self._solver.setOperators(self._A, self._P_mat) 

482 

483 self._petsc_options_prefix = petsc_options_prefix 

484 self.solver.setOptionsPrefix(petsc_options_prefix) 

485 self.A.setOptionsPrefix(f"{petsc_options_prefix}A_") 

486 self.b.setOptionsPrefix(f"{petsc_options_prefix}b_") 

487 self.x.setOptionsPrefix(f"{petsc_options_prefix}x_") 

488 if self.P_mat is not None: 

489 self.P_mat.setOptionsPrefix(f"{petsc_options_prefix}P_mat_") 

490 

491 # Set options on KSP only 

492 if petsc_options is not None: 

493 opts = PETSc.Options() # type: ignore 

494 opts.prefixPush(self.solver.getOptionsPrefix()) 

495 

496 for k, v in petsc_options.items(): 

497 opts[k] = v 

498 

499 self.solver.setFromOptions() 

500 

501 # Tidy up global options 

502 for k in petsc_options.keys(): 

503 del opts[k] 

504 opts.prefixPop() 

505 

506 def solve(self) -> typing.Union[list[_fem.Function], _fem.Function]: 

507 """Solve the problem. 

508 

509 Returns: 

510 Function containing the solution""" 

511 

512 # Assemble lhs 

513 self._A.zeroEntries() 

514 if self._A.getType() == "nest": 

515 assemble_matrix_nest(self._A, self._a, self._mpc, self.bcs, diagval=1.0) # type: ignore 

516 else: 

517 assert isinstance(self._a, _fem.Form) 

518 assemble_matrix(self._a, self._mpc, bcs=self.bcs, A=self._A) 

519 

520 self._A.assemble() 

521 assert self._A.assembled 

522 

523 # Assemble the preconditioner if provided 

524 if self._P_mat is not None: 

525 self._P_mat.zeroEntries() 

526 if self._P_mat.getType() == "nest": 

527 assert isinstance(self._preconditioner, typing.Sequence) 

528 assemble_matrix_nest(self._P_mat, self._preconditioner, self._mpc, self.bcs) # type: ignore 

529 else: 

530 assert isinstance(self._preconditioner, _fem.Form) 

531 assemble_matrix(self._preconditioner, self._mpc, bcs=self.bcs, A=self._P_mat) 

532 self._P_mat.assemble() 

533 

534 # Assemble the residual 

535 _zero_vector(self._b) 

536 if self._x.getType() == "nest": 

537 assemble_vector_nest(self._b, self._L, self._mpc) # type: ignore 

538 else: 

539 assert isinstance(self._L, _fem.Form) 

540 assert isinstance(self._mpc, MultiPointConstraint) 

541 assemble_vector(self._L, self._mpc, self._b) 

542 

543 # Lift vector 

544 try: 

545 # Nest and blocked lifting 

546 bcs1 = _fem.bcs.bcs_by_block(_fem.forms.extract_function_spaces(self._a, 1), self.bcs) # type: ignore 

547 apply_lifting(self._b, self._a, bcs=bcs1, constraint=self._mpc) # type: ignore 

548 _ghost_update(self._b, PETSc.InsertMode.ADD, PETSc.ScatterMode.REVERSE) # type: ignore 

549 bcs0 = _fem.bcs.bcs_by_block(_fem.forms.extract_function_spaces(self._L), self.bcs) # type: ignore 

550 _fem.petsc.set_bc(self._b, bcs0) 

551 except RuntimeError: 

552 # Single form lifting 

553 apply_lifting(self._b, [self._a], bcs=[self.bcs], constraint=self._mpc) # type: ignore 

554 _ghost_update(self._b, PETSc.InsertMode.ADD, PETSc.ScatterMode.REVERSE) # type: ignore 

555 _fem.petsc.set_bc(self._b, self.bcs) 

556 _ghost_update(self._b, PETSc.InsertMode.INSERT, PETSc.ScatterMode.FORWARD) # type: ignore 

557 

558 # Solve linear system and update ghost values in the solution 

559 self._solver.solve(self._b, self._x) 

560 _ghost_update(self._x, PETSc.InsertMode.INSERT, PETSc.ScatterMode.FORWARD) # type: ignore 

561 _fem.petsc.assign(self._x, self.u) 

562 

563 if isinstance(self.u, Sequence): 

564 assert isinstance(self._mpc, Sequence) 

565 for i in range(len(self.u)): 

566 self._mpc[i].homogenize(self.u[i]) 

567 self._mpc[i].backsubstitution(self.u[i]) 

568 else: 

569 assert isinstance(self.u, _fem.Function) 

570 assert isinstance(self._mpc, MultiPointConstraint) 

571 self._mpc.homogenize(self.u) 

572 self._mpc.backsubstitution(self.u) 

573 

574 return self.u