Source code for cashocs.geometry.measure

# Copyright (C) 2020-2026 Fraunhofer ITWM and 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/>.

"""Extension of integration measures."""

from __future__ import annotations

from typing import Any, Literal

import fenics

try:
    import ufl_legacy as ufl
except ImportError:
    import ufl

from cashocs import _utils


class _EmptyMeasure(ufl.Measure):
    """Implements an empty measure (e.g. of a null set).

    This is used for automatic measure generation, e.g., if the fixed boundary is empty
    for a shape optimization problem, and is used to avoid case distinctions.

    Examples:
        The code ::

            dm = _EmptyMeasure(dx)
            u*dm

        is equivalent to ::

            Constant(0)*u*dm

        so that ``fenics.assemble(u*dm)`` generates zeros.

    """

    def __init__(self, measure: ufl.Measure) -> None:
        """Initializes self.

        Args:
            measure: The underlying UFL measure.

        """
        super().__init__(measure.integral_type())
        self.measure = measure

    def __rmul__(self, other: ufl.core.expr.Expr) -> ufl.Form:
        """Multiplies the empty measure to the right.

        Args:
            other: A UFL expression to be integrated over an empty measure.

        Returns:
            The resulting UFL form.

        """
        return fenics.Constant(0) * other * self.measure


[docs] def generate_measure( idx: list[int | str], measure: ufl.Measure ) -> ufl.Measure | _EmptyMeasure: """Generates a measure based on indices. Generates a :py:class:`ufl.MeasureSum` or :py:class:`_EmptyMeasure <cashocs.geometry._EmptyMeasure>` object corresponding to ``measure`` and the subdomains / boundaries specified in idx. This is a convenient shortcut to writing ``dx(1) + dx(2) + dx(3)`` in case many measures are involved. Args: idx: A list of indices for the boundary / volume markers that define the (new) measure. measure: The corresponding UFL measure. Returns: The corresponding sum of the measures or an empty measure. Examples: Here, we create a wrapper for the surface measure on the top and bottom of the unit square:: import fenics import cashocs mesh, _, boundaries, dx, ds, _ = cashocs.regular_mesh(25) top_bottom_measure = cashocs.geometry.generate_measure([3,4], ds) fenics.assemble(1*top_bottom_measure) """ if len(idx) == 0: out_measure = _EmptyMeasure(measure) else: out_measure = measure(idx[0]) for i in idx[1:]: out_measure += measure(i) return out_measure
[docs] class NamedMeasure(ufl.Measure): """A named integration measure, which can use strings for defining subdomains.""" def __init__( self, integral_type: Literal["dx", "ds", "dS"], domain: fenics.Mesh | None = None, subdomain_id: str = "everywhere", metadata: dict | None = None, subdomain_data: fenics.MeshFunction | None = None, physical_groups: dict[str, dict[str, int]] | None = None, ) -> None: """See base class.""" super().__init__( integral_type, domain=domain, subdomain_id=subdomain_id, metadata=metadata, subdomain_data=subdomain_data, ) self.physical_groups = physical_groups def __call__( self, subdomain_id: Any = None, metadata: Any = None, domain: Any = None, subdomain_data: Any = None, degree: Any = None, scheme: Any = None, rule: Any = None, ) -> ufl.Measure | None: """See base class. This implementation also allows strings for subdomain id. """ if self._integral_type == "cell": tag_type = "dx" elif self._integral_type in ("exterior_facet", "interior_facet"): tag_type = "ds" if isinstance(subdomain_id, int | str): tag_int = _utils.tag_to_int(self.physical_groups, subdomain_id, tag_type) if tag_int is None: return _EmptyMeasure(self) else: return super().__call__( subdomain_id=tag_int, metadata=metadata, domain=domain, subdomain_data=subdomain_data, degree=degree, scheme=scheme, rule=rule, ) elif isinstance(subdomain_id, list | tuple): tag_list: list[int | str] = [] for tag in subdomain_id: tag_int = _utils.tag_to_int(self.physical_groups, tag, tag_type) if tag_int is not None: tag_list.append(tag_int) return generate_measure(tag_list, self) return None