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

263 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-04-23 11:05 +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 

9from collections.abc import Iterable, Sequence 

10from functools import partial 

11 

12from petsc4py import PETSc 

13 

14import dolfinx.fem.petsc 

15import ufl 

16from dolfinx import fem as _fem 

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

18 

19from dolfinx_mpc.cpp import mpc as _cpp_mpc 

20 

21from .assemble_matrix import assemble_matrix, assemble_matrix_nest, create_matrix_nest 

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

23from .multipointconstraint import MultiPointConstraint 

24 

25 

26def assemble_jacobian_mpc( 

27 u: Sequence[_fem.Function] | _fem.Function, 

28 jacobian: _fem.Form | Sequence[Sequence[_fem.Form]], 

29 preconditioner: _fem.Form | Sequence[Sequence[_fem.Form]] | None, 

30 bcs: Iterable[_fem.DirichletBC], 

31 mpc: MultiPointConstraint | Sequence[MultiPointConstraint], 

32 _snes: PETSc.SNES, # type: ignore 

33 x: PETSc.Vec, # type: ignore 

34 J: PETSc.Mat, # type: ignore 

35 P: PETSc.Mat, # type: ignore 

36): 

37 """Assemble the Jacobian matrix and preconditioner. 

38 

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

40 be created by fixing the first four arguments: 

41 

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

43 bcs) 

44 

45 Args: 

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

47 jacobian 

48 jacobian: Form of the Jacobian 

49 preconditioner: Form of the preconditioner 

50 bcs: List of Dirichlet boundary conditions 

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

52 _snes: The solver instance 

53 x: The vector containing the point to evaluate at 

54 J: Matrix to assemble the Jacobian into 

55 P: Matrix to assemble the preconditioner into 

56 """ 

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

58 # Jacobian 

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

60 _fem.petsc.assign(x, u) # type: ignore 

61 

62 # Assemble Jacobian 

63 J.zeroEntries() 

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

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

66 else: 

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

68 J.assemble() 

69 if preconditioner is not None: 

70 P.zeroEntries() 

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

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

73 else: 

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

75 

76 P.assemble() 

77 

78 

79def assemble_residual_mpc( 

80 u: _fem.Function | Sequence[_fem.Function], 

81 residual: _fem.Form | Sequence[_fem.Form], 

82 jacobian: _fem.Form | Sequence[Sequence[_fem.Form]], 

83 bcs: Sequence[_fem.DirichletBC], 

84 mpc: MultiPointConstraint | Sequence[MultiPointConstraint], 

85 _snes: PETSc.SNES, # type: ignore 

86 x: PETSc.Vec, # type: ignore 

87 F: PETSc.Vec, # type: ignore 

88): 

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

90 

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

92 be created by fixing the first four arguments: 

93 

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

95 bcs) 

96 

97 Args: 

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

99 Jacobian. 

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

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

102 forms. 

103 bcs: List of Dirichlet boundary conditions. 

104 _snes: The solver instance. 

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

106 F: Vector to assemble the residual into. 

107 """ 

108 # Update input vector before assigning 

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

110 # Assign the input vector to the unknowns 

111 _fem.petsc.assign(x, u) # type: ignore 

112 if isinstance(u, Sequence): 

113 assert isinstance(mpc, Sequence) 

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

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

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

117 else: 

118 assert isinstance(u, _fem.Function) 

119 assert isinstance(mpc, MultiPointConstraint) 

120 mpc.homogenize(u) 

121 mpc.backsubstitution(u) 

122 # Assemble the residual 

123 _zero_vector(F) 

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

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

126 else: 

127 assert isinstance(residual, _fem.Form) 

128 assert isinstance(mpc, MultiPointConstraint) 

129 assemble_vector(residual, mpc, F) 

130 

131 # Lift vector 

132 try: 

133 # Nest and blocked lifting 

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

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

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

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

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

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

140 except TypeError: 

141 # Single form lifting 

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

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

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

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

146 

147 

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

149 def __init__( 

150 self, 

151 F: ufl.form.Form | Sequence[ufl.form.Form], 

152 u: _fem.Function | Sequence[_fem.Function], 

153 mpc: MultiPointConstraint | Sequence[MultiPointConstraint], 

154 bcs: Sequence[_fem.DirichletBC] | None = None, 

155 J: ufl.form.Form | Sequence[Sequence[ufl.form.Form]] | None = None, 

156 P: ufl.form.Form | Sequence[Sequence[ufl.form.Form]] | None = None, 

157 kind: str | Sequence[Sequence[str]] | None = None, 

158 form_compiler_options: dict | None = None, 

159 jit_options: dict | None = None, 

160 petsc_options: dict | None = None, 

161 entity_maps: Sequence[dolfinx.mesh.EntityMap] | None = None, 

162 ): 

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

164 

165 Solves problems of the form 

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

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

168 non-linear solver. 

169 

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

171 NewtonSolver has been renamed NewtonSolverNonlinearProblem. 

172 

173 Args: 

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

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

176 bcs: Dirichlet boundary conditions. 

177 J: UFL form(s) representing the Jacobian 

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

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

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

181 preconditioner (``MatType``). 

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

183 information. 

184 form_compiler_options: Options used in FFCx compilation of all 

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

186 available options. 

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

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

189 available options. Takes priority over all other option 

190 values. 

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

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

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

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

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

196 integration domain mesh) a map should be provided relating 

197 the entities in the integration domain mesh to the entities 

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

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

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

201 mesh. 

202 """ 

203 # Compile residual and Jacobian forms 

204 self._F = _fem.form( 

205 F, 

206 form_compiler_options=form_compiler_options, 

207 jit_options=jit_options, 

208 entity_maps=entity_maps, 

209 ) 

210 

211 if J is None: 

212 if isinstance(F, ufl.form.Form): 

213 du = ufl.TrialFunction(self._F.arguments()[0].ufl_function_space()) 

214 J = ufl.derivative(F, du) 

215 else: 

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

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

218 

219 self._J = _fem.form( 

220 J, 

221 form_compiler_options=form_compiler_options, 

222 jit_options=jit_options, 

223 entity_maps=entity_maps, 

224 ) 

225 

226 if P is not None: 

227 self._preconditioner = _fem.form( 

228 P, 

229 form_compiler_options=form_compiler_options, 

230 jit_options=jit_options, 

231 entity_maps=entity_maps, 

232 ) 

233 else: 

234 self._preconditioner = None 

235 

236 self._u = u 

237 # Set default values if not supplied 

238 bcs = [] if bcs is None else bcs 

239 self.mpc = mpc 

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

241 # vector 

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

243 assert isinstance(mpc, Sequence) 

244 assert isinstance(self._J, Sequence) 

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

246 elif kind is None: 

247 assert isinstance(mpc, MultiPointConstraint) 

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

249 else: 

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

251 

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

253 if kind == "nest": 

254 assert isinstance(mpc, Sequence) 

255 assert isinstance(self._F, Sequence) 

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

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

258 else: 

259 assert isinstance(mpc, MultiPointConstraint) 

260 self._b = create_vector([(mpc.function_space.dofmap.index_map, mpc.function_space.dofmap.index_map_bs)]) 

261 self._x = create_vector([(mpc.function_space.dofmap.index_map, mpc.function_space.dofmap.index_map_bs)]) 

262 

263 # Create PETSc structure for preconditioner if provided 

264 if self.preconditioner is not None: # type: ignore 

265 if kind == "nest": 

266 assert isinstance(self.preconditioner, Sequence) 

267 assert isinstance(self.mpc, Sequence) 

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

269 else: 

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

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

272 else: 

273 self._P_mat = None # type: ignore 

274 

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

276 # residual computation functions 

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

278 self.solver.setJacobian( 

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

280 ) 

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

282 

283 # Set PETSc options 

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

285 self.solver.setOptionsPrefix(problem_prefix) 

286 opts = PETSc.Options() 

287 opts.prefixPush(problem_prefix) 

288 

289 if petsc_options is not None: 

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

291 opts.setValue(k, v) 

292 self.solver.setFromOptions() 

293 

294 for k in petsc_options.keys(): 

295 opts.delValue(k) 

296 

297 opts.prefixPop() 

298 

299 def solve(self) -> tuple[_fem.Function | Sequence[_fem.Function], PETSc.ConvergedReason, int]: # type: ignore 

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

301 instance. 

302 

303 Returns: 

304 The solution, convergence reason and number of iterations. 

305 """ 

306 

307 # Move current iterate into the work array. 

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

309 

310 # Solve problem 

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

312 

313 # Move solution back to function 

314 dolfinx.fem.petsc.assign(self.x, self._u) # type: ignore 

315 if isinstance(self.mpc, Sequence): 

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

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

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

319 else: 

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

321 self.mpc.homogenize(self._u) 

322 self.mpc.backsubstitution(self._u) 

323 

324 converged_reason = self.solver.getConvergedReason() 

325 return self._u, converged_reason, self.solver.getIterationNumber() # type: ignore 

326 

327 

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

329 """ 

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

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

332 

333 Args: 

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

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

336 mpc: The multi point constraint. 

337 bcs: A list of Dirichlet boundary conditions. 

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

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

340 

341 .. highlight:: python 

342 .. code-block:: python 

343 

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

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

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

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

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

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

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

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

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

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

354 P: A preconditioner UFL form. 

355 Examples: 

356 Example usage: 

357 

358 .. highlight:: python 

359 .. code-block:: python 

360 

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

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

363 

364 """ 

365 

366 u: _fem.Function | list[_fem.Function] 

367 _a: _fem.Form | Sequence[Sequence[_fem.Form]] 

368 _L: _fem.Form | Sequence[_fem.Form] 

369 _preconditioner: _fem.Form | Sequence[Sequence[_fem.Form]] | None 

370 _mpc: MultiPointConstraint | Sequence[MultiPointConstraint] 

371 _A: PETSc.Mat 

372 _P: PETSc.Mat | None 

373 _b: PETSc.Vec 

374 _solver: PETSc.KSP 

375 _x: PETSc.Vec 

376 bcs: list[_fem.DirichletBC] 

377 __slots__ = tuple(__annotations__) 

378 

379 def __init__( 

380 self, 

381 a: ufl.Form | Sequence[Sequence[ufl.Form]], 

382 L: ufl.Form | Sequence[ufl.Form], 

383 mpc: MultiPointConstraint | Sequence[MultiPointConstraint], 

384 bcs: list[_fem.DirichletBC] | None = None, 

385 u: _fem.Function | Sequence[_fem.Function] | None = None, 

386 petsc_options_prefix: str = "dolfinx_mpc_linear_problem_", 

387 petsc_options: dict | None = None, 

388 form_compiler_options: dict | None = None, 

389 jit_options: dict | None = None, 

390 P: ufl.Form | Sequence[Sequence[ufl.Form]] | None = None, 

391 ): 

392 # Compile forms 

393 form_compiler_options = {} if form_compiler_options is None else form_compiler_options 

394 jit_options = {} if jit_options is None else jit_options 

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

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

397 

398 self._mpc = mpc 

399 # Nest assembly 

400 if isinstance(mpc, Sequence): 

401 is_nest = True 

402 # Sanity check 

403 for mpc_i in mpc: 

404 if not mpc_i.finalized: 

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

406 # Create function containing solution vector 

407 else: 

408 is_nest = False 

409 if not mpc.finalized: 

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

411 

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

413 if is_nest: 

414 if u is None: 

415 assert isinstance(self._mpc, Sequence) 

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

417 else: 

418 assert isinstance(self._mpc, Sequence) 

419 assert isinstance(u, Sequence) 

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

421 assert isinstance(u_i, _fem.Function) 

422 assert isinstance(mpc_i, MultiPointConstraint) 

423 if u_i.function_space is not mpc_i.function_space: 

424 raise ValueError( 

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

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

427 ) 

428 self.u = list(u) 

429 else: 

430 if u is None: 

431 assert isinstance(self._mpc, MultiPointConstraint) 

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

433 else: 

434 assert isinstance(u, _fem.Function) 

435 assert isinstance(self._mpc, MultiPointConstraint) 

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

437 self.u = u 

438 else: 

439 raise ValueError( 

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

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

442 ) 

443 

444 # Create MPC matrix and vector 

445 self._preconditioner = _fem.form(P, jit_options=jit_options, form_compiler_options=form_compiler_options) # type: ignore 

446 

447 if is_nest: 

448 assert isinstance(mpc, Sequence) 

449 assert isinstance(self._L, Sequence) 

450 assert isinstance(self._a, Sequence) 

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

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

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

454 if self._preconditioner is None: 

455 self._P_mat = None 

456 else: 

457 assert isinstance(self._preconditioner, Sequence) 

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

459 else: 

460 assert isinstance(mpc, MultiPointConstraint) 

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

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

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

464 self._b = create_vector([(mpc.function_space.dofmap.index_map, mpc.function_space.dofmap.index_map_bs)]) 

465 self._x = create_vector([(mpc.function_space.dofmap.index_map, mpc.function_space.dofmap.index_map_bs)]) 

466 if self._preconditioner is None: 

467 self._P_mat = None 

468 else: 

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

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

471 

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

473 

474 if is_nest: 

475 assert isinstance(self.u, Sequence) 

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

477 else: 

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

479 comm = self.u.function_space.mesh.comm 

480 

481 self._solver = PETSc.KSP().create(comm) 

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

483 

484 self._petsc_options_prefix = petsc_options_prefix 

485 self.solver.setOptionsPrefix(petsc_options_prefix) 

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

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

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

489 if self.P_mat is not None: 

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

491 

492 # Set options on KSP only 

493 if petsc_options is not None: 

494 opts = PETSc.Options() 

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

496 

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

498 opts.setValue(k, v) 

499 

500 self.solver.setFromOptions() 

501 

502 # Tidy up global options 

503 for k in petsc_options.keys(): 

504 opts.delValue(k) 

505 opts.prefixPop() 

506 

507 def solve(self) -> _fem.Function | list[_fem.Function]: 

508 """Solve the problem. 

509 

510 Returns: 

511 Function containing the solution""" 

512 

513 # Assemble lhs 

514 self._A.zeroEntries() 

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

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

517 else: 

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

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

520 

521 self._A.assemble() 

522 assert self._A.assembled 

523 

524 # Assemble the preconditioner if provided 

525 if self._P_mat is not None: 

526 self._P_mat.zeroEntries() 

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

528 assert isinstance(self._preconditioner, Sequence) 

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

530 else: 

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

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

533 self._P_mat.assemble() 

534 

535 # Assemble the residual 

536 _zero_vector(self._b) 

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

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

539 else: 

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

541 assert isinstance(self._mpc, MultiPointConstraint) 

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

543 

544 # Lift vector 

545 try: 

546 # Nest and blocked lifting 

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

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

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

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

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

552 except TypeError: 

553 # Single form lifting 

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

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

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

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

558 

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

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

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

562 _fem.petsc.assign(self._x, self.u) # type: ignore 

563 

564 if isinstance(self.u, Sequence): 

565 assert isinstance(self._mpc, Sequence) 

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

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

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

569 else: 

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

571 assert isinstance(self._mpc, MultiPointConstraint) 

572 self._mpc.homogenize(self.u) 

573 self._mpc.backsubstitution(self.u) 

574 

575 return self.u