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
« 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
9from collections.abc import Iterable, Sequence
10from functools import partial
12from petsc4py import PETSc
14import dolfinx.fem.petsc
15import ufl
16from dolfinx import fem as _fem
17from dolfinx.la.petsc import _ghost_update, _zero_vector, create_vector
19from dolfinx_mpc.cpp import mpc as _cpp_mpc
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
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.
39 A function conforming to the interface expected by SNES.setJacobian can
40 be created by fixing the first four arguments:
42 functools.partial(assemble_jacobian, u, jacobian, preconditioner,
43 bcs)
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
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
76 P.assemble()
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`.
91 A function conforming to the interface expected by SNES.setResidual can
92 be created by fixing the first four arguments:
94 functools.partial(assemble_residual, u, jacobian, preconditioner,
95 bcs)
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)
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
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.
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.
170 Note: The deprecated version of this class for use with
171 NewtonSolver has been renamed NewtonSolverNonlinearProblem.
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 )
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)
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 )
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
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))
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)])
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
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)
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)
289 if petsc_options is not None:
290 for k, v in petsc_options.items():
291 opts.setValue(k, v)
292 self.solver.setFromOptions()
294 for k in petsc_options.keys():
295 opts.delValue(k)
297 opts.prefixPop()
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.
303 Returns:
304 The solution, convergence reason and number of iterations.
305 """
307 # Move current iterate into the work array.
308 _fem.petsc.assign(self._u, self.x)
310 # Solve problem
311 self.solver.solve(None, self.x)
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)
324 converged_reason = self.solver.getConvergedReason()
325 return self._u, converged_reason, self.solver.getIterationNumber() # type: ignore
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.
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.
341 .. highlight:: python
342 .. code-block:: python
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:
358 .. highlight:: python
359 .. code-block:: python
361 problem = LinearProblem(a, L, mpc, [bc0, bc1],
362 petsc_options={"ksp_type": "preonly", "pc_type": "lu"})
364 """
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__)
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)
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")
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 )
444 # Create MPC matrix and vector
445 self._preconditioner = _fem.form(P, jit_options=jit_options, form_compiler_options=form_compiler_options) # type: ignore
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)
472 self.bcs = [] if bcs is None else bcs
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
481 self._solver = PETSc.KSP().create(comm)
482 self._solver.setOperators(self._A, self._P_mat)
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_")
492 # Set options on KSP only
493 if petsc_options is not None:
494 opts = PETSc.Options()
495 opts.prefixPush(self.solver.getOptionsPrefix())
497 for k, v in petsc_options.items():
498 opts.setValue(k, v)
500 self.solver.setFromOptions()
502 # Tidy up global options
503 for k in petsc_options.keys():
504 opts.delValue(k)
505 opts.prefixPop()
507 def solve(self) -> _fem.Function | list[_fem.Function]:
508 """Solve the problem.
510 Returns:
511 Function containing the solution"""
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)
521 self._A.assemble()
522 assert self._A.assembled
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()
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)
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
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
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)
575 return self.u