assignment_collection.py 20.5 KB
Newer Older
1
import itertools
2
from copy import copy
Martin Bauer's avatar
Martin Bauer committed
3
4
5
6
from typing import Any, Dict, Iterable, Iterator, List, Optional, Sequence, Set, Union

import sympy as sp

7
import pystencils
8
from pystencils.assignment import Assignment
9
from pystencils.simp.simplifications import (sort_assignments_topologically, transform_lhs_and_rhs, transform_rhs)
10
11
12
from pystencils.sympyextensions import count_operations, fast_subs


Martin Bauer's avatar
Martin Bauer committed
13
class AssignmentCollection:
14
    """
Martin Bauer's avatar
Martin Bauer committed
15
    A collection of equations with subexpression definitions, also represented as assignments,
16
    that are used in the main equations. AssignmentCollection can be passed to simplification methods.
17
18
19
    These simplification methods can change the subexpressions, but the number and
    left hand side of the main equations themselves is not altered.
    Additionally a dictionary of simplification hints is stored, which are set by the functions that create
20
    assignment collections to transport information to the simplification system.
21

Martin Bauer's avatar
Martin Bauer committed
22
23
24
    Attributes:
        main_assignments: list of assignments
        subexpressions: list of assignments defining subexpressions used in main equations
25
        simplification_hints: dict that is used to annotate the assignment collection with hints that are
Martin Bauer's avatar
Martin Bauer committed
26
27
28
29
30
                              used by the simplification system. See documentation of the simplification rules for
                              potentially required hints and their meaning.
        subexpression_symbol_generator: generator for new symbols that are used when new subexpressions are added
                                        used to get new symbols that are unique for this AssignmentCollection

31
32
    """

Martin Bauer's avatar
Martin Bauer committed
33
    # ------------------------------- Creation & Inplace Manipulation --------------------------------------------------
34

35
    def __init__(self, main_assignments: Union[List[Assignment], Dict[sp.Expr, sp.Expr]],
36
                 subexpressions: Union[List[Assignment], Dict[sp.Expr, sp.Expr]] = {},
Martin Bauer's avatar
Martin Bauer committed
37
38
                 simplification_hints: Optional[Dict[str, Any]] = None,
                 subexpression_symbol_generator: Iterator[sp.Symbol] = None) -> None:
39
40
41
42
43
44
45
        if isinstance(main_assignments, Dict):
            main_assignments = [Assignment(k, v)
                                for k, v in main_assignments.items()]
        if isinstance(subexpressions, Dict):
            subexpressions = [Assignment(k, v)
                              for k, v in subexpressions.items()]

46
47
48
49
50
        main_assignments = list(itertools.chain.from_iterable(
            [(a if isinstance(a, Iterable) else [a]) for a in main_assignments]))
        subexpressions = list(itertools.chain.from_iterable(
            [(a if isinstance(a, Iterable) else [a]) for a in subexpressions]))

Martin Bauer's avatar
Martin Bauer committed
51
52
        self.main_assignments = main_assignments
        self.subexpressions = subexpressions
53

Martin Bauer's avatar
Martin Bauer committed
54
55
        if simplification_hints is None:
            simplification_hints = {}
56

Martin Bauer's avatar
Martin Bauer committed
57
        self.simplification_hints = simplification_hints
58

Martin Bauer's avatar
Martin Bauer committed
59
60
        if subexpression_symbol_generator is None:
            self.subexpression_symbol_generator = SymbolGen()
61
        else:
Martin Bauer's avatar
Martin Bauer committed
62
            self.subexpression_symbol_generator = subexpression_symbol_generator
63

Martin Bauer's avatar
Martin Bauer committed
64
65
66
67
    def add_simplification_hint(self, key: str, value: Any) -> None:
        """Adds an entry to the simplification_hints dictionary and checks that is does not exist yet."""
        assert key not in self.simplification_hints, "This hint already exists"
        self.simplification_hints[key] = value
Martin Bauer's avatar
Martin Bauer committed
68

Martin Bauer's avatar
Martin Bauer committed
69
70
    def add_subexpression(self, rhs: sp.Expr, lhs: Optional[sp.Symbol] = None, topological_sort=True) -> sp.Symbol:
        """Adds a subexpression to current collection.
71

Martin Bauer's avatar
Martin Bauer committed
72
73
74
75
76
        Args:
            rhs: right hand side of new subexpression
            lhs: optional left hand side of new subexpression. If None a new unique symbol is generated.
            topological_sort: sort the subexpressions topologically after insertion, to make sure that
                              definition of a symbol comes before its usage. If False, subexpression is appended.
77

Martin Bauer's avatar
Martin Bauer committed
78
79
        Returns:
            left hand side symbol (which could have been generated)
80
        """
Martin Bauer's avatar
Martin Bauer committed
81
        if lhs is None:
Martin Bauer's avatar
Martin Bauer committed
82
            lhs = next(self.subexpression_symbol_generator)
Martin Bauer's avatar
Martin Bauer committed
83
84
85
        eq = Assignment(lhs, rhs)
        self.subexpressions.append(eq)
        if topological_sort:
86
87
            self.topological_sort(sort_subexpressions=True,
                                  sort_main_assignments=False)
Martin Bauer's avatar
Martin Bauer committed
88
        return lhs
89

Martin Bauer's avatar
Martin Bauer committed
90
91
92
    def topological_sort(self, sort_subexpressions: bool = True, sort_main_assignments: bool = True) -> None:
        """Sorts subexpressions and/or main_equations topologically to make sure symbol usage comes after definition."""
        if sort_subexpressions:
93
            self.subexpressions = sort_assignments_topologically(self.subexpressions)
Martin Bauer's avatar
Martin Bauer committed
94
        if sort_main_assignments:
95
            self.main_assignments = sort_assignments_topologically(self.main_assignments)
96
97
98
99

    # ---------------------------------------------- Properties  -------------------------------------------------------

    @property
Martin Bauer's avatar
Martin Bauer committed
100
101
102
    def all_assignments(self) -> List[Assignment]:
        """Subexpression and main equations as a single list."""
        return self.subexpressions + self.main_assignments
103
104

    @property
Martin Bauer's avatar
Martin Bauer committed
105
106
107
108
    def free_symbols(self) -> Set[sp.Symbol]:
        """All symbols used in the assignment collection, which do not occur as left hand sides in any assignment."""
        free_symbols = set()
        for eq in self.all_assignments:
109
110
111
112
113
            if isinstance(eq, Assignment):
                free_symbols.update(eq.rhs.atoms(sp.Symbol))
            elif isinstance(eq, pystencils.astnodes.Node):
                free_symbols.update(eq.undefined_symbols)

Martin Bauer's avatar
Martin Bauer committed
114
        return free_symbols - self.bound_symbols
115
116

    @property
Martin Bauer's avatar
Martin Bauer committed
117
118
    def bound_symbols(self) -> Set[sp.Symbol]:
        """All symbols which occur on the left hand side of a main assignment or a subexpression."""
119
120
121
122
123
        bound_symbols_set = set(
            [assignment.lhs for assignment in self.all_assignments if isinstance(assignment, Assignment)]
        )

        assert len(bound_symbols_set) == len(list(a for a in self.all_assignments if isinstance(a, Assignment))), \
124
            "Not in SSA form - same symbol assigned multiple times"
125
126
127
128
129
130
131

        bound_symbols_set = bound_symbols_set.union(*[
            assignment.symbols_defined for assignment in self.all_assignments
            if isinstance(assignment, pystencils.astnodes.Node)
        ]
        )

Martin Bauer's avatar
Martin Bauer committed
132
        return bound_symbols_set
133

134
    @property
135
136
137
138
139
140
141
142
143
    def free_fields(self):
        """All fields accessed in the assignment collection, which do not occur as left hand sides in any assignment."""
        return {s.field for s in self.free_symbols if hasattr(s, 'field')}

    @property
    def bound_fields(self):
        """All field accessed on the left hand side of a main assignment or a subexpression."""
        return {s.field for s in self.bound_symbols if hasattr(s, 'field')}

144
    @property
Martin Bauer's avatar
Martin Bauer committed
145
146
    def defined_symbols(self) -> Set[sp.Symbol]:
        """All symbols which occur as left-hand-sides of one of the main equations"""
147
148
149
150
151
        return (set(
            [assignment.lhs for assignment in self.main_assignments if isinstance(assignment, Assignment)]
        ).union(*[assignment.symbols_defined for assignment in self.main_assignments if isinstance(
                assignment, pystencils.astnodes.Node)]
                ))
152

153
    @property
Martin Bauer's avatar
Martin Bauer committed
154
155
156
157
    def operation_count(self):
        """See :func:`count_operations` """
        return count_operations(self.all_assignments, only_type=None)

Stephan Seitz's avatar
Stephan Seitz committed
158
159
160
    def atoms(self, *args):
        return set().union(*[a.atoms(*args) for a in self.all_assignments])

Martin Bauer's avatar
Martin Bauer committed
161
162
    def dependent_symbols(self, symbols: Iterable[sp.Symbol]) -> Set[sp.Symbol]:
        """Returns all symbols that depend on one of the passed symbols.
163

Martin Bauer's avatar
Martin Bauer committed
164
        A symbol 'a' depends on a symbol 'b', if there is an assignment 'a <- some_expression(b)' i.e. when
Martin Bauer's avatar
Martin Bauer committed
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
        'b' is required to compute 'a'.
        """

        queue = list(symbols)

        def add_symbols_from_expr(expr):
            dependent_symbols = expr.atoms(sp.Symbol)
            for ds in dependent_symbols:
                queue.append(ds)

        handled_symbols = set()
        assignment_dict = {e.lhs: e.rhs for e in self.all_assignments}

        while len(queue) > 0:
            e = queue.pop(0)
            if e in handled_symbols:
                continue
            if e in assignment_dict:
                add_symbols_from_expr(assignment_dict[e])
            handled_symbols.add(e)

        return handled_symbols

Martin Bauer's avatar
Martin Bauer committed
188
    def lambdify(self, symbols: Sequence[sp.Symbol], fixed_symbols: Optional[Dict[sp.Symbol, Any]] = None, module=None):
Martin Bauer's avatar
Martin Bauer committed
189
        """Returns a python function to evaluate this equation collection.
190

Martin Bauer's avatar
Martin Bauer committed
191
192
193
194
        Args:
            symbols: symbol(s) which are the parameter for the created function
            fixed_symbols: dictionary with substitutions, that are applied before sympy's lambdify
            module: same as sympy.lambdify parameter. Defines which module to use e.g. 'numpy'
195

Martin Bauer's avatar
Martin Bauer committed
196
197
198
199
200
201
202
203
204
205
206
        Examples:
              >>> a, b, c, d = sp.symbols("a b c d")
              >>> ac = AssignmentCollection([Assignment(c, a + b), Assignment(d, a**2 + b)],
              ...                           subexpressions=[Assignment(b, a + b / 2)])
              >>> python_function = ac.lambdify([a], fixed_symbols={b: 2})
              >>> python_function(4)
              {c: 6, d: 18}
        """
        assignments = self.new_with_substitutions(fixed_symbols, substitute_on_lhs=False) if fixed_symbols else self
        assignments = assignments.new_without_subexpressions().main_assignments
        lambdas = {assignment.lhs: sp.lambdify(symbols, assignment.rhs, module) for assignment in assignments}
207

Martin Bauer's avatar
Martin Bauer committed
208
209
        def f(*args, **kwargs):
            return {s: func(*args, **kwargs) for s, func in lambdas.items()}
210

Martin Bauer's avatar
Martin Bauer committed
211
212
        return f
    # ---------------------------- Creating new modified collections ---------------------------------------------------
213

Martin Bauer's avatar
Martin Bauer committed
214
215
216
217
    def copy(self,
             main_assignments: Optional[List[Assignment]] = None,
             subexpressions: Optional[List[Assignment]] = None) -> 'AssignmentCollection':
        """Returns a copy with optionally replaced main_assignments and/or subexpressions."""
218

Martin Bauer's avatar
Martin Bauer committed
219
220
221
        res = copy(self)
        res.simplification_hints = self.simplification_hints.copy()
        res.subexpression_symbol_generator = copy(self.subexpression_symbol_generator)
222

Martin Bauer's avatar
Martin Bauer committed
223
224
225
226
        if main_assignments is not None:
            res.main_assignments = main_assignments
        else:
            res.main_assignments = self.main_assignments.copy()
227

Martin Bauer's avatar
Martin Bauer committed
228
229
230
231
        if subexpressions is not None:
            res.subexpressions = subexpressions
        else:
            res.subexpressions = self.subexpressions.copy()
Martin Bauer's avatar
Martin Bauer committed
232

Martin Bauer's avatar
Martin Bauer committed
233
        return res
234

Martin Bauer's avatar
Martin Bauer committed
235
    def new_with_substitutions(self, substitutions: Dict, add_substitutions_as_subexpressions: bool = False,
236
237
                               substitute_on_lhs: bool = True,
                               sort_topologically: bool = True) -> 'AssignmentCollection':
Martin Bauer's avatar
Martin Bauer committed
238
        """Returns new object, where terms are substituted according to the passed substitution dict.
239

Martin Bauer's avatar
Martin Bauer committed
240
241
242
243
        Args:
            substitutions: dict that is passed to sympy subs, substitutions are done main assignments and subexpressions
            add_substitutions_as_subexpressions: if True, the substitutions are added as assignments to subexpressions
            substitute_on_lhs: if False, the substitutions are done only on the right hand side of assignments
244
245
            sort_topologically: if subexpressions are added as substitutions and this parameters is true,
                                the subexpressions are sorted topologically after insertion
Martin Bauer's avatar
Martin Bauer committed
246
247
248
        Returns:
            New AssignmentCollection where substitutions have been applied, self is not altered.
        """
249
250
251
        transform = transform_lhs_and_rhs if substitute_on_lhs else transform_rhs
        transformed_subexpressions = transform(self.subexpressions, fast_subs, substitutions)
        transformed_assignments = transform(self.main_assignments, fast_subs, substitutions)
252

Martin Bauer's avatar
Martin Bauer committed
253
        if add_substitutions_as_subexpressions:
254
255
            transformed_subexpressions = [Assignment(b, a) for a, b in
                                          substitutions.items()] + transformed_subexpressions
256
257
            if sort_topologically:
                transformed_subexpressions = sort_assignments_topologically(transformed_subexpressions)
258
        return self.copy(transformed_assignments, transformed_subexpressions)
259

Martin Bauer's avatar
Martin Bauer committed
260
261
262
263
264
    def new_merged(self, other: 'AssignmentCollection') -> 'AssignmentCollection':
        """Returns a new collection which contains self and other. Subexpressions are renamed if they clash."""
        own_definitions = set([e.lhs for e in self.main_assignments])
        other_definitions = set([e.lhs for e in other.main_assignments])
        assert len(own_definitions.intersection(other_definitions)) == 0, \
265
            "Cannot merge collections, since both define the same symbols"
266

Martin Bauer's avatar
Martin Bauer committed
267
268
        own_subexpression_symbols = {e.lhs: e.rhs for e in self.subexpressions}
        substitution_dict = {}
269

Martin Bauer's avatar
Martin Bauer committed
270
        processed_other_subexpression_equations = []
Martin Bauer's avatar
Martin Bauer committed
271
272
273
        for other_subexpression_eq in other.subexpressions:
            if other_subexpression_eq.lhs in own_subexpression_symbols:
                if other_subexpression_eq.rhs == own_subexpression_symbols[other_subexpression_eq.lhs]:
Martin Bauer's avatar
Martin Bauer committed
274
275
276
277
                    continue  # exact the same subexpression equation exists already
                else:
                    # different definition - a new name has to be introduced
                    new_lhs = next(self.subexpression_symbol_generator)
Martin Bauer's avatar
Martin Bauer committed
278
                    new_eq = Assignment(new_lhs, fast_subs(other_subexpression_eq.rhs, substitution_dict))
Martin Bauer's avatar
Martin Bauer committed
279
                    processed_other_subexpression_equations.append(new_eq)
Martin Bauer's avatar
Martin Bauer committed
280
                    substitution_dict[other_subexpression_eq.lhs] = new_lhs
Martin Bauer's avatar
Martin Bauer committed
281
            else:
Martin Bauer's avatar
Martin Bauer committed
282
                processed_other_subexpression_equations.append(fast_subs(other_subexpression_eq, substitution_dict))
Martin Bauer's avatar
Martin Bauer committed
283
284
285
286

        processed_other_main_assignments = [fast_subs(eq, substitution_dict) for eq in other.main_assignments]
        return self.copy(self.main_assignments + processed_other_main_assignments,
                         self.subexpressions + processed_other_subexpression_equations)
287

Martin Bauer's avatar
Martin Bauer committed
288
289
    def new_filtered(self, symbols_to_extract: Iterable[sp.Symbol]) -> 'AssignmentCollection':
        """Extracts equations that have symbols_to_extract as left hand side, together with necessary subexpressions.
290

Martin Bauer's avatar
Martin Bauer committed
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
        Returns:
            new AssignmentCollection, self is not altered
        """
        symbols_to_extract = set(symbols_to_extract)
        dependent_symbols = self.dependent_symbols(symbols_to_extract)
        new_assignments = []
        for eq in self.all_assignments:
            if eq.lhs in symbols_to_extract:
                new_assignments.append(eq)

        new_sub_expr = [eq for eq in self.subexpressions
                        if eq.lhs in dependent_symbols and eq.lhs not in symbols_to_extract]
        return AssignmentCollection(new_assignments, new_sub_expr)

    def new_without_unused_subexpressions(self) -> 'AssignmentCollection':
        """Returns new collection that only contains subexpressions required to compute the main assignments."""
        all_lhs = [eq.lhs for eq in self.main_assignments]
        return self.new_filtered(all_lhs)

    def new_with_inserted_subexpression(self, symbol: sp.Symbol) -> 'AssignmentCollection':
        """Eliminates the subexpression with the given symbol on its left hand side, by substituting it everywhere."""
        new_subexpressions = []
        subs_dict = None
Martin Bauer's avatar
Martin Bauer committed
314
315
        for se in self.subexpressions:
            if se.lhs == symbol:
Martin Bauer's avatar
Martin Bauer committed
316
                subs_dict = {se.lhs: se.rhs}
Martin Bauer's avatar
Martin Bauer committed
317
            else:
Martin Bauer's avatar
Martin Bauer committed
318
319
                new_subexpressions.append(se)
        if subs_dict is None:
Martin Bauer's avatar
Martin Bauer committed
320
321
            return self

Martin Bauer's avatar
Martin Bauer committed
322
323
324
        new_subexpressions = [Assignment(eq.lhs, fast_subs(eq.rhs, subs_dict)) for eq in new_subexpressions]
        new_eqs = [Assignment(eq.lhs, fast_subs(eq.rhs, subs_dict)) for eq in self.main_assignments]
        return self.copy(new_eqs, new_subexpressions)
Martin Bauer's avatar
Martin Bauer committed
325

Martin Bauer's avatar
Martin Bauer committed
326
327
    def new_without_subexpressions(self, subexpressions_to_keep: Set[sp.Symbol] = set()) -> 'AssignmentCollection':
        """Returns a new collection where all subexpressions have been inserted."""
328
        if len(self.subexpressions) == 0:
329
330
            return self.copy()

Martin Bauer's avatar
Martin Bauer committed
331
        subexpressions_to_keep = set(subexpressions_to_keep)
332

Martin Bauer's avatar
Martin Bauer committed
333
334
335
        kept_subexpressions = []
        if self.subexpressions[0].lhs in subexpressions_to_keep:
            substitution_dict = {}
336
            kept_subexpressions.append(self.subexpressions[0])
337
        else:
Martin Bauer's avatar
Martin Bauer committed
338
            substitution_dict = {self.subexpressions[0].lhs: self.subexpressions[0].rhs}
339

Martin Bauer's avatar
Martin Bauer committed
340
341
342
343
344
        subexpression = [e for e in self.subexpressions]
        for i in range(1, len(subexpression)):
            subexpression[i] = fast_subs(subexpression[i], substitution_dict)
            if subexpression[i].lhs in subexpressions_to_keep:
                kept_subexpressions.append(subexpression[i])
345
            else:
Martin Bauer's avatar
Martin Bauer committed
346
                substitution_dict[subexpression[i].lhs] = subexpression[i].rhs
347

Martin Bauer's avatar
Martin Bauer committed
348
349
        new_assignment = [fast_subs(eq, substitution_dict) for eq in self.main_assignments]
        return self.copy(new_assignment, kept_subexpressions)
350

Martin Bauer's avatar
Martin Bauer committed
351
    # ----------------------------------------- Display and Printing   -------------------------------------------------
352

Martin Bauer's avatar
Martin Bauer committed
353
354
355
356
357
358
359
360
361
362
363
364
    def _repr_html_(self):
        """Interface to Jupyter notebook, to display as a nicely formatted HTML table"""
        def make_html_equation_table(equations):
            no_border = 'style="border:none"'
            html_table = '<table style="border:none; width: 100%; ">'
            line = '<tr {nb}> <td {nb}>$${eq}$$</td>  </tr> '
            for eq in equations:
                format_dict = {'eq': sp.latex(eq),
                               'nb': no_border, }
                html_table += line.format(**format_dict)
            html_table += "</table>"
            return html_table
365

Martin Bauer's avatar
Martin Bauer committed
366
367
368
369
370
371
372
373
374
        result = ""
        if len(self.subexpressions) > 0:
            result += "<div>Subexpressions:</div>"
            result += make_html_equation_table(self.subexpressions)
        result += "<div>Main Assignments:</div>"
        result += make_html_equation_table(self.main_assignments)
        return result

    def __repr__(self):
375
        return f"AssignmentCollection: {str(tuple(self.defined_symbols))[1:-1]} <- f{tuple(self.free_symbols)}"
Martin Bauer's avatar
Martin Bauer committed
376
377

    def __str__(self):
Martin Bauer's avatar
Martin Bauer committed
378
        result = "Subexpressions:\n"
Martin Bauer's avatar
Martin Bauer committed
379
        for eq in self.subexpressions:
380
            result += f"\t{eq}\n"
Martin Bauer's avatar
Martin Bauer committed
381
        result += "Main Assignments:\n"
Martin Bauer's avatar
Martin Bauer committed
382
        for eq in self.main_assignments:
383
            result += f"\t{eq}\n"
Martin Bauer's avatar
Martin Bauer committed
384
        return result
385

386
    def __iter__(self):
387
        return self.all_assignments.__iter__()
388

389
390
391
392
393
394
395
396
397
398
399
400
401
    @property
    def main_assignments_dict(self):
        return {a.lhs: a.rhs for a in self.main_assignments}

    @property
    def subexpressions_dict(self):
        return {a.lhs: a.rhs for a in self.subexpressions}

    def set_main_assignments_from_dict(self, main_assignments_dict):
        self.main_assignments = [Assignment(k, v)
                                 for k, v in main_assignments_dict.items()]

    def set_sub_expressions_from_dict(self, sub_expressions_dict):
402
403
        self.subexpressions = [Assignment(k, v)
                               for k, v in sub_expressions_dict.items()]
404

405
    def find(self, *args, **kwargs):
406
407
408
        return set.union(
            *[a.find(*args, **kwargs) for a in self.all_assignments]
        )
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429

    def match(self, *args, **kwargs):
        rtn = {}
        for a in self.all_assignments:
            partial_result = a.match(*args, **kwargs)
            if partial_result:
                rtn.update(partial_result)
        return rtn

    def subs(self, *args, **kwargs):
        return AssignmentCollection(
            main_assignments=[a.subs(*args, **kwargs) for a in self.main_assignments],
            subexpressions=[a.subs(*args, **kwargs) for a in self.subexpressions]
        )

    def replace(self, *args, **kwargs):
        return AssignmentCollection(
            main_assignments=[a.replace(*args, **kwargs) for a in self.main_assignments],
            subexpressions=[a.replace(*args, **kwargs) for a in self.subexpressions]
        )

430
431
432
    def __eq__(self, other):
        return set(self.all_assignments) == set(other.all_assignments)

433
434
435
    def __bool__(self):
        return bool(self.all_assignments)

436
437

class SymbolGen:
Martin Bauer's avatar
Martin Bauer committed
438
    """Default symbol generator producing number symbols ζ_0, ζ_1, ..."""
439

Martin Bauer's avatar
Martin Bauer committed
440
    def __init__(self, symbol="xi"):
441
        self._ctr = 0
Martin Bauer's avatar
Martin Bauer committed
442
        self._symbol = symbol
443
444
445
446
447

    def __iter__(self):
        return self

    def __next__(self):
448
        name = f"{self._symbol}_{self._ctr}"
449
        self._ctr += 1
Martin Bauer's avatar
Martin Bauer committed
450
        return sp.Symbol(name)