# Copyright (C) 2020-2024 Sebastian Blauth
#
# This file is part of cashocs.
#
# cashocs is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# cashocs is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with cashocs. If not, see <https://www.gnu.org/licenses/>.
"""Utilities for UFL forms."""
from __future__ import annotations
from typing import Any, List, Optional, Tuple, TypeVar, Union
import fenics
try:
import ufl_legacy as ufl
except ImportError:
import ufl
from cashocs import _exceptions
from cashocs import _loggers
T = TypeVar("T")
def summation(x: List[T]) -> Union[T, fenics.Constant]:
"""Sums elements of a list in a UFL friendly fashion.
This can be used to sum, e.g., UFL forms, or UFL expressions that can be used in UFL
forms.
Args:
x: The list of entries that shall be summed.
Returns:
Sum of input (same type as entries of input).
Notes:
For "usual" summation of integers or floats, the built-in sum function
of python or the numpy variant are recommended. Still, they are
incompatible with FEniCS objects, so this function should be used for
the latter.
"""
if len(x) == 0:
y = fenics.Constant(0.0)
_loggers.warning("Empty list handed to summation, returning 0.")
else:
y = x[0]
for item in x[1:]:
y += item
return y
def multiplication(x: List[T]) -> Union[T, fenics.Constant]:
"""Multiplies the elements of a list in a UFL friendly fashion.
Used to build the product of certain UFL expressions to construct a UFL form.
Args:
x: The list whose entries shall be multiplied.
Returns:
The result of the multiplication.
"""
if len(x) == 0:
y = fenics.Constant(1.0)
_loggers.warning("Empty list handed to multiplication, returning 1.")
else:
y = x[0]
for item in x[1:]:
y *= item
return y
def max_(
a: Union[float, fenics.Function], b: Union[float, fenics.Function]
) -> ufl.core.expr.Expr:
"""Computes the maximum of a and b.
Args:
a: The first parameter.
b: The second parameter.
Returns:
The maximum of a and b.
"""
return (a + b + abs(a - b)) / fenics.Constant(2.0)
def min_(
a: Union[float, fenics.Function], b: Union[float, fenics.Function]
) -> ufl.core.expr.Expr:
"""Computes the minimum of a and b.
Args:
a: The first parameter.
b: The second parameter.
Returns:
The minimum of a and b.
"""
return (a + b - abs(a - b)) / fenics.Constant(2.0)
def moreau_yosida_regularization(
term: ufl.core.expr.Expr,
gamma: float,
measure: fenics.Measure,
lower_threshold: Optional[Union[float, fenics.Function]] = None,
upper_threshold: Optional[Union[float, fenics.Function]] = None,
shift_lower: Optional[Union[float, fenics.Function]] = None,
shift_upper: Optional[Union[float, fenics.Function]] = None,
) -> ufl.Form:
r"""Implements a Moreau-Yosida regularization of an inequality constraint.
The general form of the inequality is of the form ::
lower_threshold <= term <= upper_threshold
which is defined over the region specified in ``measure``.
In case ``lower_threshold`` or ``upper_threshold`` are ``None``, they are set to
:math:`-\infty` and :math:`\infty`, respectively.
Args:
term: The term inside the inequality constraint.
gamma: The weighting factor of the regularization.
measure: The measure over which the inequality constraint is defined.
lower_threshold: The lower threshold for the inequality constraint. In case this
is ``None``, the lower bound is set to :math:`-\infty`. The default is
``None``
upper_threshold: The upper threshold for the inequality constraint. In case this
is ``None``, the upper bound is set to :math:`\infty`. The default is
``None``
shift_lower: A shift function for the lower bound of the Moreau-Yosida
regularization. Should be non-positive. In case this is ``None``, it is set
to 0. Default is ``None``.
shift_upper: A shift function for the upper bound of the Moreau-Yosida
regularization. Should be non-negative. In case this is ``None``, it is set
to 0. Default is ``None``.
Returns:
The ufl form of the Moreau-Yosida regularization, to be used in the cost
functional.
"""
reg_lower = None
reg_upper = None
if lower_threshold is None and upper_threshold is None:
raise _exceptions.InputError(
"cashocs._utils.moreau_yosida_regularization",
"upper_threshold, lower_threshold",
"At least one of the threshold parameters has to be defined.",
)
if shift_lower is None:
shift_lower = fenics.Constant(0.0)
if shift_upper is None:
shift_upper = fenics.Constant(0.0)
reg = []
if lower_threshold is not None:
reg_lower = (
fenics.Constant(1 / (2 * gamma))
* pow(
min_(
shift_lower + fenics.Constant(gamma) * (term - lower_threshold),
fenics.Constant(0.0),
),
2,
)
* measure
)
reg.append(reg_lower)
if upper_threshold is not None:
reg_upper = (
fenics.Constant(1 / (2 * gamma))
* pow(
max_(
shift_upper + fenics.Constant(gamma) * (term - upper_threshold),
fenics.Constant(0.0),
),
2,
)
* measure
)
reg.append(reg_upper)
return summation(reg)
[docs]
def create_dirichlet_bcs(
function_space: fenics.FunctionSpace,
value: Union[
fenics.Constant, fenics.Expression, fenics.Function, float, Tuple[float]
],
boundaries: fenics.MeshFunction,
idcs: Union[List[Union[int, str]], int, str],
**kwargs: Any,
) -> List[fenics.DirichletBC]:
"""Create several Dirichlet boundary conditions at once.
Wraps multiple Dirichlet boundary conditions into a list, in case
they have the same value but are to be defined for multiple boundaries
with different markers. Particularly useful for defining homogeneous
boundary conditions.
Args:
function_space: The function space onto which the BCs should be imposed on.
value: The value of the boundary condition. Has to be compatible with the
function_space, so that it could also be used as
``fenics.DirichletBC(function_space, value, ...)``.
boundaries: The :py:class:`fenics.MeshFunction` object representing the
boundaries.
idcs: A list of indices / boundary markers that determine the boundaries
onto which the Dirichlet boundary conditions should be applied to.
Can also be a single entry for a single boundary. If your mesh file
is named, then you can also use the names of the boundaries to define the
boundary conditions.
**kwargs: Keyword arguments for fenics.DirichletBC
Returns:
A list of DirichletBC objects that represent the boundary conditions.
Examples:
Generate homogeneous Dirichlet boundary conditions for all 4 sides of
the unit square ::
import fenics
import cashocs
mesh, _, _, _, _, _ = cashocs.regular_mesh(25)
V = fenics.FunctionSpace(mesh, 'CG', 1)
bcs = cashocs.create_dirichlet_bcs(V, fenics.Constant(0), boundaries,
[1,2,3,4])
"""
mesh = function_space.mesh()
if not isinstance(idcs, list):
idcs = [idcs]
bcs_list = []
for entry in idcs:
if isinstance(entry, int):
bcs_list.append(
fenics.DirichletBC(function_space, value, boundaries, entry, **kwargs)
)
elif isinstance(entry, str):
physical_groups = mesh.physical_groups
if entry in physical_groups["ds"].keys():
bcs_list.append(
fenics.DirichletBC(
function_space,
value,
boundaries,
physical_groups["ds"][entry],
**kwargs,
)
)
else:
raise _exceptions.InputError(
"cashocs.create_dirichlet_bcs",
"idcs",
"The string you have supplied is not associated with a boundary.",
)
return bcs_list
def bilinear_boundary_form_modification(forms: List[ufl.Form]) -> List[ufl.Form]:
"""Modifies a bilinear form for the case it is given on the boundary only.
This avoids a bug in fenics.SystemAssembler where the matrices' sparsity pattern
is not initialized correctly.
"""
mod_forms = []
for form in forms:
trial, test = form.arguments()
mesh = trial.function_space().mesh()
dx = fenics.Measure("dx", domain=mesh)
mod_forms.append(form + fenics.Constant(0.0) * fenics.dot(trial, test) * dx)
return mod_forms