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

286 statements  

« prev     ^ index     » next       coverage.py v7.16.1, created at 2026-09-16 10:47 +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 ( 

23 apply_lifting, 

24 apply_mpc_lifting, 

25 assemble_vector, 

26 assemble_vector_nest, 

27 create_vector_nest, 

28) 

29from .multipointconstraint import MultiPointConstraint 

30 

31 

32def assemble_jacobian_mpc( 

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

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

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

36 bcs: Iterable[_fem.DirichletBC], 

37 mpc: MultiPointConstraint | Sequence[MultiPointConstraint], 

38 _snes: PETSc.SNES, # type: ignore 

39 x: PETSc.Vec, # type: ignore 

40 J: PETSc.Mat, # type: ignore 

41 P: PETSc.Mat, # type: ignore 

42): 

43 """Assemble the Jacobian matrix and preconditioner. 

44 

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

46 be created by fixing the first four arguments: 

47 

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

49 bcs) 

50 

51 Args: 

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

53 jacobian 

54 jacobian: Form of the Jacobian 

55 preconditioner: Form of the preconditioner 

56 bcs: List of Dirichlet boundary conditions 

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

58 _snes: The solver instance 

59 x: The vector containing the point to evaluate at 

60 J: Matrix to assemble the Jacobian into 

61 P: Matrix to assemble the preconditioner into 

62 """ 

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

64 # Jacobian 

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

66 # Assign the input vector to the unknowns 

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

68 if isinstance(u, Sequence): 

69 assert isinstance(mpc, Sequence) 

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

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

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

73 else: 

74 assert isinstance(u, _fem.Function) 

75 assert isinstance(mpc, MultiPointConstraint) 

76 mpc.homogenize(u) 

77 mpc.backsubstitution(u) 

78 

79 # Assemble Jacobian 

80 J.zeroEntries() 

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

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

83 else: 

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

85 J.assemble() 

86 if preconditioner is not None: 

87 P.zeroEntries() 

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

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

90 else: 

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

92 

93 P.assemble() 

94 

95 

96def assemble_residual_mpc( 

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

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

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

100 bcs: Sequence[_fem.DirichletBC], 

101 mpc: MultiPointConstraint | Sequence[MultiPointConstraint], 

102 _snes: PETSc.SNES, # type: ignore 

103 x: PETSc.Vec, # type: ignore 

104 F: PETSc.Vec, # type: ignore 

105): 

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

107 

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

109 be created by fixing the first four arguments: 

110 

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

112 bcs) 

113 

114 Args: 

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

116 Jacobian. 

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

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

119 forms. 

120 bcs: List of Dirichlet boundary conditions. 

121 _snes: The solver instance. 

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

123 F: Vector to assemble the residual into. 

124 """ 

125 # Update input vector before assigning 

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

127 # Assign the input vector to the unknowns 

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

129 if isinstance(u, Sequence): 

130 assert isinstance(mpc, Sequence) 

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

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

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

134 else: 

135 assert isinstance(u, _fem.Function) 

136 assert isinstance(mpc, MultiPointConstraint) 

137 mpc.homogenize(u) 

138 mpc.backsubstitution(u) 

139 # Assemble the residual 

140 _zero_vector(F) 

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

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

143 else: 

144 assert isinstance(residual, _fem.Form) 

145 assert isinstance(mpc, MultiPointConstraint) 

146 assemble_vector(residual, mpc, F) 

147 

148 # Lift vector 

149 try: 

150 # Nest and blocked lifting 

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

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

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

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

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

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

157 except (TypeError, ValueError): 

158 # Single form lifting 

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

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

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

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

163 

164 

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

166 def __init__( 

167 self, 

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

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

170 mpc: MultiPointConstraint | Sequence[MultiPointConstraint], 

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

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

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

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

175 form_compiler_options: dict | None = None, 

176 jit_options: dict | None = None, 

177 petsc_options_prefix: str = "dolfinx_mpc_nonlinear_problem_", 

178 petsc_options: dict | None = None, 

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

180 ): 

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

182 

183 Solves problems of the form 

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

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

186 non-linear solver. 

187 

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

189 NewtonSolver has been renamed NewtonSolverNonlinearProblem. 

190 

191 Args: 

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

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

194 bcs: Dirichlet boundary conditions. 

195 J: UFL form(s) representing the Jacobian 

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

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

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

199 preconditioner (``MatType``). 

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

201 information. 

202 form_compiler_options: Options used in FFCx compilation of all 

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

204 available options. 

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

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

207 available options. Takes priority over all other option 

208 values. 

209 petsc_options_prefix: Options prefix used as the root prefix on 

210 all internally created PETSc objects (SNES, A, b, x and the 

211 preconditioner matrix). Typically ends with ``_``. Must be the 

212 same on all ranks and is usually unique within the 

213 programme. 

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

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

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

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

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

219 integration domain mesh) a map should be provided relating 

220 the entities in the integration domain mesh to the entities 

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

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

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

224 mesh. 

225 """ 

226 # Compile residual and Jacobian forms 

227 self._F = _fem.form( 

228 F, 

229 form_compiler_options=form_compiler_options, 

230 jit_options=jit_options, 

231 entity_maps=entity_maps, 

232 ) 

233 

234 if J is None: 

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

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

237 J = ufl.derivative(F, du) 

238 else: 

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

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

241 

242 self._J = _fem.form( 

243 J, 

244 form_compiler_options=form_compiler_options, 

245 jit_options=jit_options, 

246 entity_maps=entity_maps, 

247 ) 

248 

249 if P is not None: 

250 self._preconditioner = _fem.form( 

251 P, 

252 form_compiler_options=form_compiler_options, 

253 jit_options=jit_options, 

254 entity_maps=entity_maps, 

255 ) 

256 else: 

257 self._preconditioner = None 

258 

259 self._u = u 

260 # Set default values if not supplied 

261 bcs = [] if bcs is None else bcs 

262 self.mpc = mpc 

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

264 # vector 

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

266 assert isinstance(mpc, Sequence) 

267 assert isinstance(self._J, Sequence) 

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

269 elif kind is None: 

270 assert isinstance(mpc, MultiPointConstraint) 

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

272 else: 

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

274 

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

276 if kind == "nest": 

277 assert isinstance(mpc, Sequence) 

278 assert isinstance(self._F, Sequence) 

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

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

281 else: 

282 assert isinstance(mpc, MultiPointConstraint) 

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

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

285 

286 # Create PETSc structure for preconditioner if provided 

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

288 if kind == "nest": 

289 assert isinstance(self.preconditioner, Sequence) 

290 assert isinstance(self.mpc, Sequence) 

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

292 else: 

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

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

295 else: 

296 self._P_mat = None # type: ignore 

297 

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

299 # residual computation functions 

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

301 self.solver.setJacobian( 

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

303 ) 

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

305 

306 # Set PETSc options 

307 self._petsc_options_prefix = petsc_options_prefix 

308 self.solver.setOptionsPrefix(petsc_options_prefix) 

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

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

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

312 if self.P_mat is not None: 

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

314 

315 # Set options on SNES only 

316 if petsc_options is not None: 

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

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

319 

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

321 opts.setValue(k, v) 

322 

323 self.solver.setFromOptions() 

324 

325 # Tidy up global options 

326 for k in petsc_options.keys(): 

327 opts.delValue(k) 

328 opts.prefixPop() 

329 

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

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

332 instance. 

333 

334 Returns: 

335 The solution, convergence reason and number of iterations. 

336 """ 

337 

338 # Move current iterate into the work array. 

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

340 

341 # Solve problem 

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

343 

344 # Move solution back to function 

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

346 if isinstance(self.mpc, Sequence): 

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

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

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

350 else: 

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

352 self.mpc.homogenize(self._u) 

353 self.mpc.backsubstitution(self._u) 

354 

355 converged_reason = self.solver.getConvergedReason() 

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

357 

358 

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

360 """ 

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

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

363 

364 Args: 

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

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

367 mpc: The multi point constraint. 

368 bcs: A list of Dirichlet boundary conditions. 

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

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

371 

372 .. highlight:: python 

373 .. code-block:: python 

374 

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

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

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

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

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

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

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

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

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

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

385 P: A preconditioner UFL form. 

386 Examples: 

387 Example usage: 

388 

389 .. highlight:: python 

390 .. code-block:: python 

391 

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

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

394 

395 """ 

396 

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

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

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

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

401 _mpc: MultiPointConstraint | Sequence[MultiPointConstraint] 

402 _A: PETSc.Mat 

403 _P: PETSc.Mat | None 

404 _b: PETSc.Vec 

405 _solver: PETSc.KSP 

406 _x: PETSc.Vec 

407 bcs: list[_fem.DirichletBC] 

408 __slots__ = tuple(__annotations__) 

409 

410 def __init__( 

411 self, 

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

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

414 mpc: MultiPointConstraint | Sequence[MultiPointConstraint], 

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

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

417 petsc_options_prefix: str = "dolfinx_mpc_linear_problem_", 

418 petsc_options: dict | None = None, 

419 form_compiler_options: dict | None = None, 

420 jit_options: dict | None = None, 

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

422 ): 

423 # Compile forms 

424 form_compiler_options = {} if form_compiler_options is None else form_compiler_options 

425 jit_options = {} if jit_options is None else jit_options 

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

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

428 

429 self._mpc = mpc 

430 # Nest assembly 

431 if isinstance(mpc, Sequence): 

432 is_nest = True 

433 # Sanity check 

434 for mpc_i in mpc: 

435 if not mpc_i.finalized: 

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

437 # Create function containing solution vector 

438 else: 

439 is_nest = False 

440 if not mpc.finalized: 

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

442 

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

444 if is_nest: 

445 if u is None: 

446 assert isinstance(self._mpc, Sequence) 

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

448 else: 

449 assert isinstance(self._mpc, Sequence) 

450 assert isinstance(u, Sequence) 

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

452 assert isinstance(u_i, _fem.Function) 

453 assert isinstance(mpc_i, MultiPointConstraint) 

454 if u_i.function_space is not mpc_i.function_space: 

455 raise ValueError( 

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

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

458 ) 

459 self.u = list(u) 

460 else: 

461 if u is None: 

462 assert isinstance(self._mpc, MultiPointConstraint) 

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

464 else: 

465 assert isinstance(u, _fem.Function) 

466 assert isinstance(self._mpc, MultiPointConstraint) 

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

468 self.u = u 

469 else: 

470 raise ValueError( 

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

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

473 ) 

474 

475 # Create MPC matrix and vector 

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

477 

478 if is_nest: 

479 assert isinstance(mpc, Sequence) 

480 assert isinstance(self._L, Sequence) 

481 assert isinstance(self._a, Sequence) 

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

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

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

485 if self._preconditioner is None: 

486 self._P_mat = None 

487 else: 

488 assert isinstance(self._preconditioner, Sequence) 

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

490 else: 

491 assert isinstance(mpc, MultiPointConstraint) 

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

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

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

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

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

497 if self._preconditioner is None: 

498 self._P_mat = None 

499 else: 

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

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

502 

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

504 

505 if is_nest: 

506 assert isinstance(self.u, Sequence) 

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

508 else: 

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

510 comm = self.u.function_space.mesh.comm 

511 

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

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

514 

515 self._petsc_options_prefix = petsc_options_prefix 

516 self.solver.setOptionsPrefix(petsc_options_prefix) 

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

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

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

520 if self.P_mat is not None: 

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

522 

523 # Set options on KSP only 

524 if petsc_options is not None: 

525 opts = PETSc.Options() 

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

527 

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

529 opts.setValue(k, v) 

530 

531 self.solver.setFromOptions() 

532 

533 # Tidy up global options 

534 for k in petsc_options.keys(): 

535 opts.delValue(k) 

536 opts.prefixPop() 

537 

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

539 """Solve the problem. 

540 

541 Returns: 

542 Function containing the solution""" 

543 

544 # Refresh the constraint offsets, so that a change in the values of the 

545 # Dirichlet conditions held by the constraint is picked up 

546 if isinstance(self._mpc, Sequence): 

547 for mpc_i in self._mpc: 

548 mpc_i.update_constants() 

549 else: 

550 self._mpc.update_constants() 

551 

552 # Assemble lhs 

553 self._A.zeroEntries() 

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

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

556 else: 

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

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

559 

560 self._A.assemble() 

561 assert self._A.assembled 

562 

563 # Assemble the preconditioner if provided 

564 if self._P_mat is not None: 

565 self._P_mat.zeroEntries() 

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

567 assert isinstance(self._preconditioner, Sequence) 

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

569 else: 

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

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

572 self._P_mat.assemble() 

573 

574 # Assemble the residual 

575 _zero_vector(self._b) 

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

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

578 else: 

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

580 assert isinstance(self._mpc, MultiPointConstraint) 

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

582 

583 # Lift vector 

584 # Decide between nest/blocked and single form lifting up front, so that a 

585 # failure in one of the lifting calls cannot fall through to the other 

586 # branch and apply the lifting twice 

587 try: 

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

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

590 blocked = True 

591 except ValueError: 

592 blocked = False 

593 

594 if blocked: 

595 # Nest and blocked lifting 

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

597 apply_mpc_lifting(self._b, self._a, constraint=self._mpc) # type: ignore 

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

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

600 else: 

601 # Single form lifting 

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

603 apply_mpc_lifting(self._b, [self._a], constraint=self._mpc) # type: ignore 

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

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

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

607 

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

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

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

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

612 

613 if isinstance(self.u, Sequence): 

614 assert isinstance(self._mpc, Sequence) 

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

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

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

618 else: 

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

620 assert isinstance(self._mpc, MultiPointConstraint) 

621 self._mpc.homogenize(self.u) 

622 self._mpc.backsubstitution(self.u) 

623 

624 return self.u