Source code for dolfinx_mpc.problem
# -*- coding: utf-8 -*-
# Copyright (C) 2021 Jørgen S. Dokken
#
# This file is part of DOLFINx MPC
#
# SPDX-License-Identifier: MIT
from __future__ import annotations
import typing
from petsc4py import PETSc
import dolfinx.fem.petsc
import ufl
from dolfinx import cpp as _cpp
from dolfinx import fem as _fem
from dolfinx import la as _la
from .assemble_matrix import assemble_matrix, create_sparsity_pattern
from .assemble_vector import apply_lifting, assemble_vector
from .multipointconstraint import MultiPointConstraint
[docs]
class LinearProblem(dolfinx.fem.petsc.LinearProblem):
"""
Class for solving a linear variational problem with multi point constraints of the form
a(u, v) = L(v) for all v using PETSc as a linear algebra backend.
Args:
a: A bilinear UFL form, the left hand side of the variational problem.
L: A linear UFL form, the right hand side of the variational problem.
mpc: The multi point constraint.
bcs: A list of Dirichlet boundary conditions.
u: The solution function. It will be created if not provided. The function has
to be based on the functionspace in the mpc, i.e.
.. highlight:: python
.. code-block:: python
u = dolfinx.fem.Function(mpc.function_space)
petsc_options: Parameters that is passed to the linear algebra backend PETSc. #type: ignore
For available choices for the 'petsc_options' kwarg, see the PETSc-documentation
https://www.mcs.anl.gov/petsc/documentation/index.html.
form_compiler_options: Parameters used in FFCx compilation of this form. Run `ffcx --help` at
the commandline to see all available options. Takes priority over all
other parameter values, except for `scalar_type` which is determined by DOLFINx.
jit_options: Parameters used in CFFI JIT compilation of C code generated by FFCx.
See https://github.com/FEniCS/dolfinx/blob/main/python/dolfinx/jit.py#L22-L37
for all available parameters. Takes priority over all other parameter values.
Examples:
Example usage:
.. highlight:: python
.. code-block:: python
problem = LinearProblem(a, L, mpc, [bc0, bc1],
petsc_options={"ksp_type": "preonly", "pc_type": "lu"})
"""
u: _fem.Function
_a: _fem.Form
_L: _fem.Form
_mpc: MultiPointConstraint
_A: PETSc.Mat # type: ignore
_b: PETSc.Vec # type: ignore
_solver: PETSc.KSP # type: ignore
_x: PETSc.Vec # type: ignore
bcs: typing.List[_fem.DirichletBC]
__slots__ = tuple(__annotations__)
def __init__(
self,
a: ufl.Form,
L: ufl.Form,
mpc: MultiPointConstraint,
bcs: typing.Optional[typing.List[_fem.DirichletBC]] = None,
u: typing.Optional[_fem.Function] = None,
petsc_options: typing.Optional[dict] = None,
form_compiler_options: typing.Optional[dict] = None,
jit_options: typing.Optional[dict] = None,
):
# Compile forms
form_compiler_options = {} if form_compiler_options is None else form_compiler_options
jit_options = {} if jit_options is None else jit_options
self._a = _fem.form(a, jit_options=jit_options, form_compiler_options=form_compiler_options)
self._L = _fem.form(L, jit_options=jit_options, form_compiler_options=form_compiler_options)
if not mpc.finalized:
raise RuntimeError("The multi point constraint has to be finalized before calling initializer")
self._mpc = mpc
# Create function containing solution vector
if u is None:
self.u = _fem.Function(self._mpc.function_space)
else:
if u.function_space is self._mpc.function_space:
self.u = u
else:
raise ValueError(
"The input function has to be in the function space in the multi-point constraint",
"i.e. u = dolfinx.fem.Function(mpc.function_space)",
)
self._x = self.u.x.petsc_vec
# Create MPC matrix
pattern = create_sparsity_pattern(self._a, self._mpc)
pattern.finalize()
self._A = _cpp.la.petsc.create_matrix(self._mpc.function_space.mesh.comm, pattern)
self._b = _la.create_petsc_vector(
self._mpc.function_space.dofmap.index_map, self._mpc.function_space.dofmap.index_map_bs
)
self.bcs = [] if bcs is None else bcs
self._solver = PETSc.KSP().create(self.u.function_space.mesh.comm) # type: ignore
self._solver.setOperators(self._A)
# Give PETSc solver options a unique prefix
solver_prefix = "dolfinx_mpc_solve_{}".format(id(self))
self._solver.setOptionsPrefix(solver_prefix)
# Set PETSc options
opts = PETSc.Options() # type: ignore
opts.prefixPush(solver_prefix)
if petsc_options is not None:
for k, v in petsc_options.items():
opts[k] = v
opts.prefixPop()
self._solver.setFromOptions()
[docs]
def solve(self) -> _fem.Function:
"""Solve the problem.
Returns:
Function containing the solution"""
# Assemble lhs
self._A.zeroEntries()
assemble_matrix(self._a, self._mpc, bcs=self.bcs, A=self._A)
self._A.assemble()
assert self._A.assembled
# Assemble rhs
with self._b.localForm() as b_loc:
b_loc.set(0)
assemble_vector(self._L, self._mpc, b=self._b)
# Apply boundary conditions to the rhs
apply_lifting(self._b, [self._a], [self.bcs], self._mpc)
self._b.ghostUpdate(addv=PETSc.InsertMode.ADD, mode=PETSc.ScatterMode.REVERSE) # type: ignore
_fem.petsc.set_bc(self._b, self.bcs)
# Solve linear system and update ghost values in the solution
self._solver.solve(self._b, self._x)
self.u.x.scatter_forward()
self._mpc.backsubstitution(self.u)
return self.u