• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

FEniCS / ufl / 18630567039

19 Oct 2025 12:42PM UTC coverage: 75.964% (-0.7%) from 76.622%
18630567039

Pull #431

github

jorgensd
Test ufl against release branch
Pull Request #431: Dokken/test release

8960 of 11795 relevant lines covered (75.96%)

0.76 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

79.17
/ufl/algorithms/apply_coefficient_split.py
1
"""Apply coefficient split.
2

3
This module contains the apply_coefficient_split function that
4
decomposes mixed coefficients in the given Expr into components.
5

6
"""
7

8
from functools import singledispatchmethod
1✔
9

10
import numpy as np
1✔
11

12
from ufl.classes import (
1✔
13
    Coefficient,
14
    ComponentTensor,
15
    Expr,
16
    MultiIndex,
17
    NegativeRestricted,
18
    PositiveRestricted,
19
    ReferenceGrad,
20
    ReferenceValue,
21
    Restricted,
22
    Terminal,
23
)
24
from ufl.core.multiindex import indices
1✔
25
from ufl.corealg.dag_traverser import DAGTraverser
1✔
26
from ufl.tensors import as_tensor
1✔
27

28

29
class CoefficientSplitter(DAGTraverser):
1✔
30
    """DAGTraverser to split mixed coefficients."""
31

32
    def __init__(
1✔
33
        self,
34
        coefficient_split: dict,
35
        compress: bool | None = True,
36
        visited_cache: dict[tuple, Expr] | None = None,
37
        result_cache: dict[Expr, Expr] | None = None,
38
    ) -> None:
39
        """Initialise.
40

41
        Args:
42
            coefficient_split: `dict` that maps mixed coefficients to their components.
43
            compress: If True, ``result_cache`` will be used.
44
            visited_cache: cache of intermediate results; expr -> r = self.process(expr, ...).
45
            result_cache: cache of result objects for memory reuse, r -> r.
46

47
        """
48
        super().__init__(compress=compress, visited_cache=visited_cache, result_cache=result_cache)
1✔
49
        self._coefficient_split = coefficient_split
1✔
50

51
    @singledispatchmethod
1✔
52
    def process(
1✔
53
        self,
54
        o: Expr,
55
        reference_value: bool | None = False,
56
        reference_grad: int | None = 0,
57
        restricted: str | None = None,
58
    ) -> Expr:
59
        """Split mixed coefficients.
60

61
        Args:
62
            o: `Expr` to be processed.
63
            reference_value: Whether `ReferenceValue` has been applied or not.
64
            reference_grad: Number of `ReferenceGrad`s that have been applied.
65
            restricted: '+', '-', or None.
66

67
        Returns:
68
            This ``o`` wrapped with `ReferenceValue` (if ``reference_value``),
69
            `ReferenceGrad` (``reference_grad`` times), and `Restricted` (if
70
            ``restricted`` is '+' or '-'). The underlying terminal will be
71
            decomposed into components according to ``self._coefficient_split``.
72

73
        """
74
        return super().process(o)
×
75

76
    @process.register(Expr)
1✔
77
    def _(
1✔
78
        self,
79
        o: Expr,
80
        reference_value: bool | None = False,
81
        reference_grad: int | None = 0,
82
        restricted: str | None = None,
83
    ) -> Expr:
84
        """Handle Expr."""
85
        return self.reuse_if_untouched(
×
86
            o,
87
            reference_value=reference_value,
88
            reference_grad=reference_grad,
89
            restricted=restricted,
90
        )
91

92
    @process.register(ReferenceValue)
1✔
93
    def _(
1✔
94
        self,
95
        o: Expr,
96
        reference_value: bool | None = False,
97
        reference_grad: int | None = 0,
98
        restricted: str | None = None,
99
    ) -> Expr:
100
        """Handle ReferenceValue."""
101
        if reference_value:
1✔
102
            raise RuntimeError(f"Can not apply ReferenceValue on a ReferenceValue: got {o}")
×
103
        (op,) = o.ufl_operands
1✔
104
        if not op._ufl_terminal_modifiers_:
1✔
105
            raise ValueError(f"Must be a terminal modifier: {op!r}.")
×
106
        return self(
1✔
107
            op,
108
            reference_value=True,
109
            reference_grad=reference_grad,
110
            restricted=restricted,
111
        )
112

113
    @process.register(ReferenceGrad)
1✔
114
    def _(
1✔
115
        self,
116
        o: Expr,
117
        reference_value: bool = False,
118
        reference_grad: int = 0,
119
        restricted: str | None = None,
120
    ) -> Expr:
121
        """Handle ReferenceGrad."""
122
        (op,) = o.ufl_operands
1✔
123
        if not op._ufl_terminal_modifiers_:
1✔
124
            raise ValueError(f"Must be a terminal modifier: {op!r}.")
×
125
        return self(
1✔
126
            op,
127
            reference_value=reference_value,
128
            reference_grad=reference_grad + 1,
129
            restricted=restricted,
130
        )
131

132
    @process.register(Restricted)
1✔
133
    def _(
1✔
134
        self,
135
        o: Restricted,
136
        reference_value: bool | None = False,
137
        reference_grad: int | None = 0,
138
        restricted: str | None = None,
139
    ) -> Expr:
140
        """Handle Restricted."""
141
        if restricted is not None:
1✔
142
            raise RuntimeError(f"Can not apply Restricted on a Restricted: got {o}")
×
143
        (op,) = o.ufl_operands
1✔
144
        if not op._ufl_terminal_modifiers_:
1✔
145
            raise ValueError(f"Must be a terminal modifier: {op!r}.")
×
146
        return self(
1✔
147
            op,
148
            reference_value=reference_value,
149
            reference_grad=reference_grad,
150
            restricted=o._side,
151
        )
152

153
    @process.register(Terminal)
1✔
154
    def _(
1✔
155
        self,
156
        o: Expr,
157
        reference_value: bool | None = False,
158
        reference_grad: int = 0,
159
        restricted: str | None = None,
160
    ) -> Expr:
161
        """Handle Terminal."""
162
        return self._handle_terminal(
×
163
            o,
164
            reference_value=reference_value,
165
            reference_grad=reference_grad,
166
            restricted=restricted,
167
        )
168

169
    @process.register(Coefficient)
1✔
170
    def _(
1✔
171
        self,
172
        o: Expr,
173
        reference_value: bool | None = False,
174
        reference_grad: int = 0,
175
        restricted: str | None = None,
176
    ) -> Expr:
177
        """Handle Coefficient."""
178
        if o not in self._coefficient_split:
1✔
179
            return self._handle_terminal(
×
180
                o,
181
                reference_value=reference_value,
182
                reference_grad=reference_grad,
183
                restricted=restricted,
184
            )
185
        if not reference_value:
1✔
186
            raise RuntimeError("ReferenceValue expected")
×
187
        beta = indices(reference_grad)
1✔
188
        components = []
1✔
189
        for coeff in self._coefficient_split[o]:
1✔
190
            c = self._handle_terminal(
1✔
191
                coeff,
192
                reference_value=reference_value,
193
                reference_grad=reference_grad,
194
                restricted=restricted,
195
            )
196
            for alpha in np.ndindex(coeff.ufl_element().reference_value_shape):
1✔
197
                components.append(c[alpha + beta])
1✔
198
        (i,) = indices(1)
1✔
199
        return ComponentTensor(as_tensor(components)[i], MultiIndex((i,) + beta))
1✔
200

201
    def _handle_terminal(
1✔
202
        self,
203
        o: Expr,
204
        reference_value: bool | None = False,
205
        reference_grad: int = 0,
206
        restricted: str | None = None,
207
    ) -> Expr:
208
        """Wrap terminal as needed."""
209
        c = o
1✔
210
        if reference_value:
1✔
211
            c = ReferenceValue(c)
1✔
212
        for _ in range(reference_grad):
1✔
213
            c = ReferenceGrad(c)
1✔
214
        if restricted == "+":
1✔
215
            c = PositiveRestricted(c)
1✔
216
        elif restricted == "-":
×
217
            c = NegativeRestricted(c)
×
218
        elif restricted is not None:
×
219
            raise RuntimeError(f"Got unknown restriction: {restricted}")
×
220
        return c
1✔
221

222

223
def apply_coefficient_split(expr: Expr, coefficient_split: dict) -> Expr:
1✔
224
    """Split mixed coefficients.
225

226
    Args:
227
        expr: UFL expression.
228
        coefficient_split: `dict` that maps mixed coefficients to their components.
229

230
    Returns:
231
        ``expr`` with uderlying mixed coefficients split according to ``coefficient_split``.
232

233
    """
234
    if not coefficient_split:
1✔
235
        return expr
×
236
    return CoefficientSplitter(coefficient_split)(expr)
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc