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

FEniCS / ufl / 17557791029

08 Sep 2025 04:38PM UTC coverage: 76.363% (+0.4%) from 75.917%
17557791029

Pull #401

github

web-flow
Merge branch 'main' into schnellerhase/remove-type-system
Pull Request #401: Removal of custom type system

495 of 534 new or added lines in 42 files covered. (92.7%)

6 existing lines in 2 files now uncovered.

9133 of 11960 relevant lines covered (76.36%)

0.76 hits per line

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

65.16
/ufl/core/expr.py
1
"""This module defines the ``Expr`` class, the superclass for all expression tree node types in UFL.
2

3
NB: A note about other operators not implemented here:
4

5
More operators (special functions) on ``Expr`` instances are defined in
6
``exproperators.py``, as well as the transpose ``A.T`` and spatial derivative
7
``a.dx(i)``.
8
This is to avoid circular dependencies between ``Expr`` and its subclasses.
9
"""
10
# Copyright (C) 2008-2016 Martin Sandve Alnæs
11
#
12
# This file is part of UFL (https://www.fenicsproject.org)
13
#
14
# SPDX-License-Identifier:    LGPL-3.0-or-later
15
#
16
# Modified by Anders Logg, 2008
17
# Modified by Massimiliano Leoni, 2016
18

19
import typing
1✔
20
import warnings
1✔
21

22
if typing.TYPE_CHECKING:
23
    from ufl.core.terminal import FormArgument
24

25
from ufl.core.compute_expr_hash import compute_expr_hash
1✔
26
from ufl.core.ufl_type import UFLRegistry, UFLType, ufl_type
1✔
27

28

29
@ufl_type()
1✔
30
class Expr(UFLType):
1✔
31
    """Base class for all UFL expression types.
32

33
    *Instance properties*
34
        Every ``Expr`` instance will have certain properties.
35
        The most important ones are ``ufl_operands``, ``ufl_shape``,
36
        ``ufl_free_indices``, and ``ufl_index_dimensions`` properties.
37
        Expressions are immutable and hashable.
38

39
    *Type traits*
40
        The ``Expr`` API defines a number of type traits that each subclass
41
        needs to provide. Most of these are specified indirectly via
42
        the arguments to the ``ufl_type`` class decorator, allowing UFL
43
        to do some consistency checks and automate most of the traits
44
        for most types. Type traits are accessed via a class or
45
        instance object of the form ``obj._ufl_traitname_``. See the source
46
        code for description of each type trait.
47

48
    *Operators*
49
        Some Python special functions are implemented in this class,
50
        some are implemented in subclasses, and some are attached to
51
        this class in the ``ufl_type`` class decorator.
52

53
    *Defining subclasses*
54
        To define a new expression class, inherit from either
55
        ``Terminal`` or ``Operator``, and apply the ``ufl_type`` class
56
        decorator with suitable arguments.  See the docstring of
57
        ``ufl_type`` for details on its arguments.  Looking at existing
58
        classes similar to the one you wish to add is a good
59
        idea. Looking through the comments in the ``Expr`` class and
60
        ``ufl_type`` to understand all the properties that may need to
61
        be specified is also a good idea. Note that many algorithms in
62
        UFL and form compilers will need handlers implemented for each
63
        new type::.
64

65
        .. code-block:: python
66

67
            @ufl_type()
68
            class MyOperator(Operator):
69
                pass
70

71
    *Type collections*
72
        All ``Expr`` subclasses are collected by ``ufl_type`` in global
73
        variables available via ``Expr``.
74

75
    *Profiling*
76
        Object creation statistics can be collected by doing
77

78
        .. code-block:: python
79

80
            Expr.ufl_enable_profiling()
81
            # ... run some code
82
            initstats, delstats = Expr.ufl_disable_profiling()
83

84
        Giving a list of creation and deletion counts for each typecode.
85
    """
86

87
    # --- Each Expr subclass must define __slots__ or _ufl_noslots_ at
88
    # --- the top ---
89
    # This is to freeze member variables for objects of this class and
90
    # save memory by skipping the per-instance dict.
91
    ufl_operands: tuple["FormArgument", ...]
1✔
92
    ufl_shape: tuple[int, ...]
1✔
93
    ufl_free_indices: tuple[int, ...]
1✔
94
    ufl_index_dimensions: tuple
1✔
95

96
    _ufl_is_terminal_modifier_: bool = False
1✔
97
    _ufl_is_in_reference_frame_: bool = False
1✔
98
    # _ufl_noslots_ = True
99

100
    __slots__ = ("__weakref__", "_hash")
1✔
101

102
    # --- Basic object behaviour ---
103

104
    def __getnewargs__(self):
1✔
105
        """Get newargs tuple.
106

107
        The tuple returned here is passed to as args to cls.__new__(cls, *args).
108

109
        This implementation passes the operands, which is () for terminals.
110

111
        May be necessary to override if __new__ is implemented in a subclass.
112
        """
113
        return self.ufl_operands
1✔
114

115
    def __init__(self):
1✔
116
        """Initialise."""
117
        self._hash = None
1✔
118

119
    # List of all terminal modifier types
120

121
    # --- Mechanism for profiling object creation and deletion ---
122

123
    # Backup of default init and del
124
    _ufl_regular__init__ = __init__
1✔
125

126
    def _ufl_profiling__init__(self):
1✔
127
        """Replacement constructor with object counting."""
128
        Expr._ufl_regular__init__(self)
1✔
129
        UFLRegistry().register_object_creation(type(self))
1✔
130

131
    def _ufl_profiling__del__(self):
1✔
132
        """Replacement destructor with object counting."""
133
        UFLRegistry().register_object_destruction(type(self))
1✔
134

135
    @staticmethod
1✔
136
    def ufl_enable_profiling():
1✔
137
        """Turn on the object counting mechanism and reset counts to zero."""
138
        Expr.__init__ = Expr._ufl_profiling__init__
1✔
139
        setattr(Expr, "__del__", Expr._ufl_profiling__del__)
1✔
140
        UFLRegistry().reset_object_tracking()
1✔
141

142
    @staticmethod
1✔
143
    def ufl_disable_profiling():
1✔
144
        """Turn off the object counting mechanism. Return object init and del counts."""
145
        Expr.__init__ = Expr._ufl_regular__init__
1✔
146
        delattr(Expr, "__del__")
1✔
147

148
    # === Abstract functions that must be implemented by subclasses ===
149

150
    # --- Functions for reconstructing expression ---
151

152
    def _ufl_expr_reconstruct_(self, *operands):
1✔
153
        """Return a new object of the same type with new operands."""
154
        raise NotImplementedError(self.__class__._ufl_expr_reconstruct_)
×
155

156
    # --- Functions for geometric properties of expression ---
157

158
    def ufl_domains(self):
1✔
159
        """Return all domains this expression is defined on."""
160
        warnings.warn(
×
161
            "Expr.ufl_domains() is deprecated, please use extract_domains(expr) instead.",
162
            DeprecationWarning,
163
        )
164
        from ufl.domain import extract_domains
×
165

166
        return extract_domains(self)
×
167

168
    def ufl_domain(self):
1✔
169
        """Return the single unique domain this expression is defined on, or throw an error."""
170
        warnings.warn(
×
171
            "Expr.ufl_domain() is deprecated, please use extract_unique_domain(expr) instead.",
172
            DeprecationWarning,
173
        )
174
        from ufl.domain import extract_unique_domain
×
175

176
        return extract_unique_domain(self)
×
177

178
    # --- Functions for float evaluation ---
179

180
    def evaluate(self, x, mapping, component, index_values):
1✔
181
        """Evaluate expression at given coordinate with given values for terminals."""
NEW
182
        raise ValueError(f"Symbolic evaluation of {type(self).__name__} not available.")
×
183

184
    def _ufl_evaluate_scalar_(self):
1✔
185
        if self.ufl_shape or self.ufl_free_indices:
1✔
186
            raise TypeError("Cannot evaluate a nonscalar expression to a scalar value.")
×
187
        return self(())  # No known x
1✔
188

189
    def __float__(self):
1✔
190
        """Try to evaluate as scalar and cast to float."""
191
        try:
1✔
192
            v = float(self._ufl_evaluate_scalar_())
1✔
193
        except Exception:
×
194
            v = NotImplemented
×
195
        return v
1✔
196

197
    def __complex__(self):
1✔
198
        """Try to evaluate as scalar and cast to complex."""
199
        try:
×
200
            v = complex(self._ufl_evaluate_scalar_())
×
201
        except TypeError:
×
202
            v = NotImplemented
×
203
        return v
×
204

205
    def __bool__(self):
1✔
206
        """By default, all Expr are nonzero/False."""
207
        return True
1✔
208

209
    def __nonzero__(self):
1✔
210
        """By default, all Expr are nonzero/False."""
211
        return self.__bool__()
×
212

213
    @staticmethod
1✔
214
    def _ufl_coerce_(value):
1✔
215
        """Convert any value to a UFL type."""
216
        # Quick skip for most types
217
        if isinstance(value, Expr):
×
218
            return value
×
219

220
        # Conversion from non-ufl types
221
        # (the _ufl_from_*_ functions are attached to Expr by ufl_type)
222
        ufl_from_type = f"_ufl_from_{value.__class__.__name__}_"
×
223
        return getattr(Expr, ufl_from_type)(value)
×
224

225
        # if hasattr(Expr, ufl_from_type):
226
        #     return getattr(Expr, ufl_from_type)(value)
227
        # Fail gracefully if no valid type conversion found
228
        # raise TypeError("Cannot convert a {0.__class__.__name__} to UFL type.".format(value))
229

230
    # --- Special functions for string representations ---
231

232
    # All subclasses must implement _ufl_signature_data_
233
    def _ufl_signature_data_(self, renumbering):
1✔
234
        """Return data that uniquely identifies form compiler relevant aspects of this object."""
235
        raise NotImplementedError(self.__class__._ufl_signature_data_)
×
236

237
    # All subclasses must implement __repr__
238
    def __repr__(self):
1✔
239
        """Return string representation this object can be reconstructed from."""
240
        raise NotImplementedError(self.__class__.__repr__)
×
241

242
    # All subclasses must implement __str__
243
    def __str__(self):
1✔
244
        """Return pretty print string representation of this object."""
245
        raise NotImplementedError(self.__class__.__str__)
×
246

247
    def _ufl_err_str_(self):
1✔
248
        """Return a short string to represent this Expr in an error message."""
249
        return f"<{type(self).__name__} id={id(self)}>"
1✔
250

251
    def _simplify_indexed(self, multiindex):
1✔
252
        """Return a simplified Expr used in the constructor of Indexed(self, multiindex)."""
253
        raise NotImplementedError(self.__class__._simplify_indexed)
1✔
254

255
    # --- Special functions used for processing expressions ---
256

257
    def __eq__(self, other):
1✔
258
        """Checks whether the two expressions are represented the exact same way.
259

260
        This does not check if the expressions are
261
        mathematically equal or equivalent! Used by sets and dicts.
262
        """
263
        raise NotImplementedError(self.__class__.__eq__)
×
264

265
    def __hash__(self):
1✔
266
        """Return hash."""
267
        return compute_expr_hash(self)
1✔
268

269
    def __len__(self):
1✔
270
        """Length of expression. Used for iteration over vector expressions."""
271
        s = self.ufl_shape
1✔
272
        if len(s) == 1:
1✔
273
            return s[0]
1✔
274
        raise NotImplementedError("Cannot take length of non-vector expression.")
1✔
275

276
    def __iter__(self):
1✔
277
        """Iteration over vector expressions."""
278
        for i in range(len(self)):
1✔
279
            yield self[i]
1✔
280

281
    def __floordiv__(self, other):
1✔
282
        """UFL does not support integer division."""
283
        raise NotImplementedError(self.__class__.__floordiv__)
1✔
284

285
    def __pos__(self):
1✔
286
        """Unary + is a no-op."""
287
        return self
×
288

289
    def __round__(self, n=None):
1✔
290
        """Round to nearest integer or to nearest nth decimal."""
291
        try:
1✔
292
            val = float(self._ufl_evaluate_scalar_())
1✔
293
            val = round(val, n)
1✔
294
        except TypeError:
×
295
            val = complex(self._ufl_evaluate_scalar_())
×
296
            val = round(val.real, n) + round(val.imag, n) * 1j
×
297
        except TypeError:
×
298
            val = NotImplemented
×
299
        return val
1✔
300

301
    # For definitions see exproperators.py
302
    T: property
1✔
303

304
    def dx(self, *ii):
1✔
305
        """Integral."""
306
        pass
×
307

308
    def __call__(self, other):
1✔
309
        """Evaluate."""
310
        pass
×
311

312
    def __ne__(self, other):
1✔
313
        """Negate."""
314
        pass
×
315

316
    def __lt__(self, other):
1✔
317
        """A boolean expresion (left < right) for use with conditional."""
318
        pass
×
319

320
    def __gt__(self, other):
1✔
321
        """A boolean expresion (left > right) for use with conditional."""
322
        pass
×
323

324
    def __le__(self, other):
1✔
325
        """A boolean expresion (left <= right) for use with conditional."""
326
        pass
×
327

328
    def __ge__(self, other):
1✔
329
        """A boolean expresion (left >= right) for use with conditional."""
330
        pass
×
331

332
    def __xor__(self, other):
1✔
333
        """A^indices := as_tensor(A, indices)."""
334
        pass
×
335

336
    def __mul__(self, other):
1✔
337
        """Multiply."""
338
        pass
×
339

340
    def __rmul__(self, other):
1✔
341
        """Multiply."""
342
        pass
×
343

344
    def __truediv__(self, other):
1✔
345
        """Divide."""
346
        pass
×
347

348
    def __rtruediv__(self, other):
1✔
349
        """Divide."""
350
        pass
×
351

352
    def __add__(self, other):
1✔
353
        """Add."""
354
        pass
×
355

356
    def __radd__(self, other):
1✔
357
        """Add."""
358
        pass
×
359

360
    def __sub__(self, other):
1✔
361
        """Subtract."""
362
        pass
×
363

364
    def __rsub__(self, other):
1✔
365
        """Subtract."""
366
        pass
×
367

368
    def __div__(self, other):
1✔
369
        """Divide."""
370
        pass
×
371

372
    def __rdiv__(self, other):
1✔
373
        """Divide."""
374
        pass
×
375

376
    def __pow__(self, other):
1✔
377
        """Raise to a power."""
378
        pass
×
379

380
    def __rpow__(self, other):
1✔
381
        """Raise to a power."""
382
        pass
×
383

384
    def __neg__(self):
1✔
385
        """Negate."""
386
        pass
×
387

388
    def __getitem__(self, index):
1✔
389
        """Get item."""
390
        pass
×
391

392

393
def ufl_err_str(expr):
1✔
394
    """Return a UFL error string."""
395
    if hasattr(expr, "_ufl_err_str_"):
1✔
396
        return expr._ufl_err_str_()
1✔
397
    else:
398
        return repr(expr)
×
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

© 2025 Coveralls, Inc