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

FEniCS / ufl / 18629405325

19 Oct 2025 10:56AM UTC coverage: 77.06% (+0.4%) from 76.622%
18629405325

Pull #401

github

schnellerhase
Ruff
Pull Request #401: Removal of custom type system

494 of 533 new or added lines in 41 files covered. (92.68%)

6 existing lines in 2 files now uncovered.

9325 of 12101 relevant lines covered (77.06%)

0.77 hits per line

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

68.27
/ufl/algorithms/transformer.py
1
"""Transformer.
2

3
This module defines the Transformer base class and some
4
basic specializations to further base other algorithms upon,
5
as well as some utilities for easier application of such
6
algorithms.
7
"""
8
# Copyright (C) 2008-2016 Martin Sandve Alnæs and Anders Logg
9
#
10
# This file is part of UFL (https://www.fenicsproject.org)
11
#
12
# SPDX-License-Identifier:    LGPL-3.0-or-later
13
#
14
# Modified by Anders Logg, 2009-2010
15

16
import inspect
1✔
17

18
from ufl.algorithms.map_integrands import map_integrands
1✔
19
from ufl.classes import Variable, all_ufl_classes
1✔
20
from ufl.core.ufl_type import UFLType
1✔
21

22

23
def is_post_handler(function):
1✔
24
    """Check if function is a handler that expects transformed children as input."""
25
    insp = inspect.getfullargspec(function)
1✔
26
    num_args = len(insp[0]) + int(insp[1] is not None)
1✔
27
    visit_children_first = num_args > 2
1✔
28
    return visit_children_first
1✔
29

30

31
class Transformer:
1✔
32
    """Transformer.
33

34
    Base class for a visitor-like algorithm design pattern used to
35
    transform expression trees from one representation to another.
36
    """
37

38
    _handlers_cache: dict[type, tuple[str, bool]] = {}
1✔
39

40
    def __init__(self, variable_cache=None):
1✔
41
        """Initialise."""
42
        if variable_cache is None:
1✔
43
            variable_cache = {}
1✔
44
        self._variable_cache = variable_cache
1✔
45

46
        # Analyse class properties and cache handler data the
47
        # first time this is run for a particular class
48
        cache_data = Transformer._handlers_cache.get(type(self))
1✔
49
        if not cache_data:
1✔
50
            cache_data = [None] * len(all_ufl_classes)
1✔
51
            # For all UFL classes
52
            for classobject in all_ufl_classes:
1✔
53
                # Iterate over the inheritance chain
54
                # (NB! This assumes that all UFL classes inherits a single
55
                # Expr subclass and that this is the first superclass!)
56
                for c in classobject.mro():
1✔
57
                    # Register classobject with handler for the first
58
                    # encountered superclass
59
                    try:
1✔
60
                        handler_name = c._ufl_handler_name_
1✔
UNCOV
61
                    except AttributeError as attribute_error:
×
UNCOV
62
                        if type(classobject) is not UFLType:
×
63
                            raise attribute_error
×
64
                        # Default handler name for UFL types
UNCOV
65
                        handler_name = UFLType._ufl_handler_name_
×
66
                    function = getattr(self, handler_name, None)
1✔
67
                    if function:
1✔
68
                        cache_data[classobject._ufl_typecode_] = (
1✔
69
                            handler_name,
70
                            is_post_handler(function),
71
                        )
72
                        break
1✔
73
            Transformer._handlers_cache[type(self)] = cache_data
1✔
74

75
        # Build handler list for this particular class (get functions
76
        # bound to self)
77
        self._handlers = [(getattr(self, name), post) for (name, post) in cache_data]
1✔
78
        # Keep a stack of objects visit is called on, to ease
79
        # backtracking
80
        self._visit_stack = []
1✔
81

82
    def print_visit_stack(self):
1✔
83
        """Print visit stack."""
84
        print("/" * 80)
×
85
        print("Visit stack in Transformer:")
×
86

87
        def sstr(s):
×
88
            """Format."""
89
            ss = str(type(s)) + " ; "
×
90
            n = 160 - len(ss)
×
91
            return ss + str(s)[:n]
×
92

93
        print("\n".join(map(sstr, self._visit_stack)))
×
94
        print("\\" * 80)
×
95

96
    def visit(self, o):
1✔
97
        """Visit."""
98
        # Update stack
99
        self._visit_stack.append(o)
1✔
100

101
        # Get handler for the UFL class of o (type(o) may be an
102
        # external subclass of the actual UFL class)
103
        h, visit_children_first = self._handlers[o._ufl_typecode_]
1✔
104

105
        # Is this a handler that expects transformed children as
106
        # input?
107
        if visit_children_first:
1✔
108
            # Yes, visit all children first and then call h.
109
            r = h(o, *map(self.visit, o.ufl_operands))
1✔
110
        else:
111
            # No, this is a handler that handles its own children
112
            # (arguments self and o, where self is already bound)
113
            r = h(o)
1✔
114

115
        # Update stack and return
116
        self._visit_stack.pop()
1✔
117
        return r
1✔
118

119
    def undefined(self, o):
1✔
120
        """Trigger error."""
NEW
121
        raise ValueError(f"No handler defined for {type(o).__name__}.")
×
122

123
    def reuse(self, o):
1✔
124
        """Reuse Expr (ignore children)."""
125
        return o
1✔
126

127
    def reuse_if_untouched(self, o, *ops):
1✔
128
        """Reuse object if operands are the same objects.
129

130
        Use in your own subclass by setting e.g. `expr = MultiFunction.reuse_if_untouched`
131
        as a default rule.
132
        """
133
        if all(a is b for a, b in zip(o.ufl_operands, ops)):
1✔
134
            return o
1✔
135
        else:
136
            return o._ufl_expr_reconstruct_(*ops)
1✔
137

138
    # It's just so slow to compare all operands, avoiding it now
139
    reuse_if_possible = reuse_if_untouched
1✔
140

141
    def always_reconstruct(self, o, *operands):
1✔
142
        """Reconstruct expr."""
143
        return o._ufl_expr_reconstruct_(*operands)
×
144

145
    # Set default behaviour for any UFLType
146
    ufl_type = undefined
1✔
147

148
    # Set default behaviour for any Terminal
149
    terminal = reuse
1✔
150

151
    def reuse_variable(self, o):
1✔
152
        """Reuse variable."""
153
        # Check variable cache to reuse previously transformed
154
        # variable if possible
155
        e, l = o.ufl_operands  # noqa: E741
×
156
        v = self._variable_cache.get(l)
×
157
        if v is not None:
×
158
            return v
×
159

160
        # Visit the expression our variable represents
161
        e2 = self.visit(e)
×
162

163
        # If the expression is the same, reuse Variable object
164
        if e == e2:
×
165
            v = o
×
166
        else:
167
            # Recreate Variable (with same label)
168
            v = Variable(e2, l)
×
169

170
        # Cache variable
171
        self._variable_cache[l] = v
×
172
        return v
×
173

174
    def reconstruct_variable(self, o):
1✔
175
        """Reconstruct variable."""
176
        # Check variable cache to reuse previously transformed
177
        # variable if possible
178
        e, l = o.ufl_operands  # noqa: E741
×
179
        v = self._variable_cache.get(l)
×
180
        if v is not None:
×
181
            return v
×
182

183
        # Visit the expression our variable represents
184
        e2 = self.visit(e)
×
185

186
        # Always reconstruct Variable (with same label)
187
        v = Variable(e2, l)
×
188
        self._variable_cache[l] = v
×
189
        return v
×
190

191

192
class ReuseTransformer(Transformer):
1✔
193
    """Reuse transformer."""
194

195
    def __init__(self, variable_cache=None):
1✔
196
        """Initialise."""
197
        Transformer.__init__(self, variable_cache)
1✔
198

199
    # Set default behaviour for any Expr
200
    expr = Transformer.reuse_if_untouched
1✔
201

202
    # Set default behaviour for any Terminal
203
    terminal = Transformer.reuse
1✔
204

205
    # Set default behaviour for Variable
206
    variable = Transformer.reuse_variable
1✔
207

208

209
class CopyTransformer(Transformer):
1✔
210
    """Copy transformer."""
211

212
    def __init__(self, variable_cache=None):
1✔
213
        """Initialise."""
214
        Transformer.__init__(self, variable_cache)
×
215

216
    # Set default behaviour for any Expr
217
    expr = Transformer.always_reconstruct
1✔
218

219
    # Set default behaviour for any Terminal
220
    terminal = Transformer.reuse
1✔
221

222
    # Set default behaviour for Variable
223
    variable = Transformer.reconstruct_variable
1✔
224

225

226
class VariableStripper(ReuseTransformer):
1✔
227
    """Variable stripper."""
228

229
    def __init__(self):
1✔
230
        """Initialise."""
231
        ReuseTransformer.__init__(self)
1✔
232

233
    def variable(self, o):
1✔
234
        """Visit a variable."""
235
        return self.visit(o.ufl_operands[0])
1✔
236

237

238
def apply_transformer(e, transformer, integral_type=None):
1✔
239
    """Apply transforms.
240

241
    Apply transformer.visit(expression) to each integrand expression in
242
    form, or to form if it is an Expr.
243
    """
244
    return map_integrands(transformer.visit, e, integral_type)
1✔
245

246

247
def strip_variables(e):
1✔
248
    """Replace all Variable instances with the expression they represent."""
249
    return apply_transformer(e, VariableStripper())
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

© 2025 Coveralls, Inc