-
-
Notifications
You must be signed in to change notification settings - Fork 66
Expand file tree
/
Copy pathexpression.py
More file actions
1941 lines (1668 loc) · 73.5 KB
/
expression.py
File metadata and controls
1941 lines (1668 loc) · 73.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# cython: language_level=3
# -*- coding: utf-8 -*-
import math
import time
from bisect import bisect_left
from itertools import chain
from typing import Any, Callable, Iterable, List, Optional, Tuple, Type
import sympy
from mathics.core.atoms import Integer, String
# FIXME: adjust mathics.core.attributes to uppercase attribute names
from mathics.core.attributes import (
A_FLAT,
A_HOLD_ALL,
A_HOLD_ALL_COMPLETE,
A_HOLD_FIRST,
A_HOLD_REST,
A_LISTABLE,
A_NO_ATTRIBUTES,
A_NUMERIC_FUNCTION,
A_ORDERLESS,
A_SEQUENCE_HOLD,
attribute_string_to_number,
)
from mathics.core.convert.python import from_python
from mathics.core.convert.sympy import SympyExpression, sympy_symbol_prefix
from mathics.core.element import ElementsProperties, EvalMixin, ensure_context
from mathics.core.evaluation import Evaluation
from mathics.core.interrupt import ReturnInterrupt
from mathics.core.structure import LinkedStructure
from mathics.core.symbols import (
Atom,
BaseElement,
Monomial,
NumericOperators,
Symbol,
SymbolAbs,
SymbolDivide,
SymbolList,
SymbolN,
SymbolPlus,
SymbolTimes,
SymbolTrue,
symbol_set,
)
from mathics.core.systemsymbols import (
SymbolAborted,
SymbolAlternatives,
SymbolBlank,
SymbolCondition,
SymbolDirectedInfinity,
SymbolFunction,
SymbolMinus,
SymbolPattern,
SymbolPower,
SymbolSequence,
SymbolSin,
SymbolSlot,
SymbolSqrt,
SymbolSubtract,
SymbolUnevaluated,
)
# from mathics.timing import timeit
SymbolBlankSequence = Symbol("System`BlankSequence")
SymbolBlankNullSequence = Symbol("System`BlankNullSequence")
SymbolCompiledFunction = Symbol("System`CompiledFunction")
SymbolDefault = Symbol("System`Default")
SymbolEvaluate = Symbol("System`Evaluate")
SymbolOptional = Symbol("Optional")
SymbolOptionsPattern = Symbol("OptionsPattern")
SymbolPatternTest = Symbol("PatternTest")
SymbolSlotSequence = Symbol("SlotSequence")
SymbolVerbatim = Symbol("Verbatim")
symbols_arithmetic_operations = symbol_set(
SymbolAbs,
SymbolDivide,
SymbolMinus,
SymbolPlus,
SymbolPower,
SymbolSin,
SymbolSqrt,
SymbolSubtract,
SymbolTimes,
)
class BoxError(Exception):
def __init__(self, box, form) -> None:
super().__init__("Box %s cannot be formatted as %s" % (box, form))
self.box = box
self.form = form
# ExpressionCache keeps track of the following attributes for one Expression instance:
# time: (1) the last time (in terms of Definitions.now) this expression was evaluated
# or (2) None, if the current expression has not yet been evaluated (i.e. is new or
# changed).
# symbols: (1) a set of symbols occuring in this expression's head, its elements'
# heads, any of its sub expressions' heads or as Symbol elements somewhere (maybe deep
# down) in the expression tree start by this expressions' elements, or (2) None, if no
# information on which symbols are contained in this expression is available
# sequences: (1) a list of element indices that indicate the position of all Sequence
# heads that are either in the element's head or any of the indicated elements's sub
# expressions' heads, or (2) None, if no information is available.
class ExpressionCache:
def __init__(self, time=None, symbols=None, sequences=None, copy=None):
if copy is not None:
time = time or copy.time
symbols = symbols or copy.symbols
sequences = sequences or copy.sequences
self.time = time
self.symbols = symbols
self.sequences = sequences
def copy(self):
return ExpressionCache(self.time, self.symbols, self.sequences)
def sliced(self, lower, upper):
# indicates that the Expression's elements have been slices with
# the given indices.
seq = self.sequences
if seq:
a = bisect_left(seq, lower) # all(val >= i for val in seq[a:])
b = bisect_left(seq, upper) # all(val >= j for val in seq[b:])
new_sequences = tuple(x - lower for x in seq[a:b])
elif seq is not None:
new_sequences = tuple()
else:
new_sequences = None
return ExpressionCache(self.time, self.symbols, new_sequences)
def reordered(self):
# indicates that the Expression's elements have been reordered
# or reduced (i.e. that the elements have changed, but that
# no new element instances were added).
sequences = self.sequences
# note that we keep sequences == [], since they are fine even
# after having reordered elements.
if sequences:
sequences = None
return ExpressionCache(None, self.symbols, sequences)
@staticmethod
def union(expressions, evaluation) -> Optional["ExpressionCache"]:
definitions = evaluation.definitions
for expr in expressions:
if not hasattr(expr, "_cache") or expr.is_uncertain_final_definitions(
definitions
):
return None
# FIXME: this is workaround the current situtation that some
# Atoms, like String, have a cache even though they don't need
# it, by virtue of this getting set up in
# BaseElement.__init__. Removing the self._cache in there the
# causes Boxing to mess up. Untangle this mess.
if expr._cache is None:
return None
symbols = set.union(*[expr._cache.symbols for expr in expressions])
return ExpressionCache(
definitions.now, symbols, None if "System`Sequence" in symbols else tuple()
)
class Expression(BaseElement, NumericOperators, EvalMixin):
"""
A Mathics M-Expression.
A Mathics M-Expression is a list where the head is a function designator.
(In the more common S-Expression the head is an a Symbol. In Mathics this can be
an expression that acts as a function.
positional Arguments:
- head -- The head of the M-Expression
- *elements - optional: the remaining elements
- *literal_values - optional: if this is not None, then all elements
are (Python) literal values and literal_values
contains these literals.
Keyword Arguments:
- elements_properties -- properties of the collection of elements
"""
_head: BaseElement
_elements: List[BaseElement]
_sequences: Any
_cache: Optional[ExpressionCache]
elements_properties: Optional[ElementsProperties]
options: Optional[tuple]
pattern_sequence: bool
def __init__(
self,
head: BaseElement,
*elements: Tuple[BaseElement],
elements_properties: Optional[ElementsProperties] = None,
literal_values: Optional[tuple] = None,
):
self.options = None
self.pattern_sequence = False
# # Uncomment to check for errors:
# assert isinstance(head, BaseElement)
# assert isinstance(elements, tuple)
# assert all(isinstance(e, BaseElement) for e in elements)
# assert head is BaseElement
self._head = head
self._elements = elements
self.elements_properties = elements_properties
self.value = literal_values
self._is_literal = None if literal_values is None else True
self._sequences = None
self._cache = None
def __getnewargs__(self):
return (self._head, self._elements)
def __hash__(self):
return hash(("Expression", self._head) + tuple(self._elements))
def __repr__(self) -> str:
return "<Expression: %s>" % self
def __str__(self) -> str:
return "%s[%s]" % (
self._head,
", ".join([element.__str__() for element in self._elements]),
)
def _as_sympy_function(self, **kwargs) -> sympy.Function:
sym_args = [element.to_sympy(**kwargs) for element in self._elements]
if None in sym_args:
return None
f = sympy.Function(str(sympy_symbol_prefix + self.get_head_name()))
return f(*sym_args)
# Note: this function is called a *lot* so it needs to be fast.
def _build_elements_properties(self):
"""
Compute ElementsProperties and store in self.elements_properties
"""
# All of the properties start out optimistic (True) and are reset when that proves wrong.
self.elements_properties = ElementsProperties(True, True, True)
last_element = None
values = []
for element in self._elements:
# Test for the literalness, and the three properties mentioned above
if element.is_literal:
values.append(element.value)
else:
self.elements_properties.elements_fully_evaluated = False
if isinstance(element, Expression):
# "self" can't be flat.
self.elements_properties.is_flat = False
# "elements_properties" only exists for Expression types
# If we haven't set element.elements properties, compute that...
if element.elements_properties is None:
if hasattr(self, "_is_literal"):
self._is_literal = False
element._build_elements_properties()
# and now possibly adjust self.elements_properties.elements_fully_evaluted
if self.elements_properties.elements_fully_evaluated:
self._elements_fully_evaluated = (
element.elements_properties.elements_fully_evaluated
)
if element.is_literal:
values.append(element.value)
else:
# FIXME: uncommenting this out messes up formatting.
# File "mathics-core/mathics/core/formatter.py", line 135, in ret_fn
# return boxes_to_method(elements, **opts)
# TypeError: boxes_to_text() takes 1 positional argument but 2 were given
# Why?
self.elements_properties.elements_fully_evaluated = False
# Test for ordered property
if self.elements_properties.is_ordered and last_element is not None:
try:
self.elements_properties.is_ordered = last_element <= element
except Exception:
self.elements_properties.is_ordered = False
last_element = element
# self.is_literal should only be True for ListExpression.
# However we have still some Expression(ListSymbol, ...) around?
if self.is_literal:
assert self.elements_properties.elements_fully_evaluated
self.value = tuple(values)
def _flatten_sequence(self, sequence, evaluation) -> "Expression":
indices = self.sequences()
if not indices:
return self
elements = self._elements
flattened = []
extend = flattened.extend
k = 0
for i in indices:
extend(elements[k:i])
extend(sequence(elements[i]))
k = i + 1
extend(elements[k:])
return self.restructure(self._head, flattened, evaluation)
def _does_not_contain_symbol(self, symbol_name: str) -> bool:
"""
Return True if all of elements (at any level) under self cannot contain
a ``symbol_name``. Otherwise return False if there might be ``symbol``
name under the elements of ``self``.
"""
cache = self._cache
if cache is None:
return False
symbols = cache.symbols
if symbols is not None and symbol_name not in symbols:
return True
else:
return False
def _rebuild_cache(self):
cache = self._cache
if cache is None:
time = None
elif cache.symbols is None:
time = cache.time
elif cache.sequences is None:
time = cache.time
else:
return cache
sym = set((self.get_head_name(),))
seq = []
for i, element in enumerate(self._elements):
if isinstance(element, Expression):
element_symbols = element._rebuild_cache().symbols
sym.update(element_symbols)
if "System`Sequence" in element_symbols:
seq.append(i)
elif isinstance(element, Symbol):
sym.add(element.get_name())
cache = ExpressionCache(time, sym, seq)
self._cache = cache
return cache
def _timestamp_cache(self, evaluation):
self._cache = ExpressionCache(evaluation.definitions.now, copy=self._cache)
def clear_cache(self):
self._cache = None
def copy(self, reevaluate=False) -> "Expression":
expr = Expression(self._head.copy(reevaluate))
expr.elements = tuple(element.copy(reevaluate) for element in self._elements)
if not reevaluate:
# rebuilding the cache in self speeds up large operations, e.g.
# First[Timing[Fold[#1+#2&, Range[750]]]]
expr._cache = self._rebuild_cache()
expr.options = self.options
expr.original = self
expr._sequences = self._sequences
return expr
def default_format(self, evaluation, form) -> str:
return "%s[%s]" % (
self._head.default_format(evaluation, form),
", ".join(
[element.default_format(evaluation, form) for element in self._elements]
),
)
@property
def elements(self):
return self._elements
@elements.setter
def elements(self, values: Iterable):
self._elements = tuple(values)
# Set to build self.elements_properties on next evaluation()
self.elements_properties = None
def equal2(self, rhs: Any) -> Optional[bool]:
"""Mathics two-argument Equal (==)
returns True if self and rhs are identical.
"""
if self.sameQ(rhs):
return True
# if rhs is an Atom, return None
elif isinstance(rhs, Atom):
return None
head = self._head
# Here we only need to deal with Expressions.
equal_heads = head.equal2(rhs._head)
if not equal_heads:
return equal_heads
# From here, we can assume that both heads are the same
if head in (SymbolList, SymbolSequence):
if len(self.elements) != len(rhs.elements):
return False
for item1, item2 in zip(self.elements, rhs.elements):
result = item1.equal2(item2)
if not result:
return result
return True
elif head in (SymbolDirectedInfinity,):
return self.elements[0].equal2(rhs.elements[0])
return None
# Note that the return type is some subclass of BaseElement, it could be
# a Real, an Expression, etc. It probably will *not* be a BaseElement since
# the point of evaluation when there is not an error is to produce a concrete result.
def evaluate(
self,
evaluation: Evaluation,
) -> Optional[Type["BaseElement"]]:
"""
Apply transformation rules and expression evaluation to ``evaluation`` via
``rewrite_apply_eval_step()`` until that method tells us to stop,
or unti we hit an $IterationLimit or TimeConstrained limit.
Evaluation is a recusive:``rewrite_apply_eval_step()`` may call us.
"""
if evaluation.timeout:
return
expr = self
reevaluate = True
limit = None
iteration = 1
names = set()
definitions = evaluation.definitions
old_options = evaluation.options
evaluation.inc_recursion_depth()
if evaluation.definitions.trace_evaluation:
if evaluation.definitions.timing_trace_evaluation:
evaluation.print_out(time.time() - evaluation.start_time)
evaluation.print_out(
" " * evaluation.recursion_depth + "Evaluating: %s" % expr
)
try:
# Evaluation loop:
while reevaluate:
# If definitions have not changed in the last evaluation,
# then evaluating again will produce the same result
if not expr.is_uncertain_final_definitions(definitions):
break
# Here the names of the lookupname of the expression
# are stored. This is necesary for the implementation
# of the builtin `Return[]`
names.add(expr.get_lookup_name())
# This loads the default options associated
# to the expression
if hasattr(expr, "options") and expr.options:
evaluation.options = expr.options
# ``rewrite_apply_eval_step()`` makes a pass at
# evaluating the expression. If we know that a further
# evaluation will not be needed, ``reevaluate`` is set
# False. Note that ``rewrite_apply_eval_step()`` can
# perform further ``evaluate`` and we will recurse
# back into this routine.
expr, reevaluate = expr.rewrite_apply_eval_step(evaluation)
if not reevaluate:
break
# TraceEvaluation[] logging.
if evaluation.definitions.trace_evaluation:
evaluation.print_out(
" " * evaluation.recursion_depth + "-> %s" % expr
)
iteration += 1
# Check whether we have hit $Iterationlimit: is the number of times
# ``reevaluate`` came back False in this loop.
if limit is None:
limit = definitions.get_config_value("$IterationLimit")
if limit is None:
limit = "inf"
if limit != "inf" and iteration > limit:
evaluation.error("$IterationLimit", "itlim", limit)
return SymbolAborted
# "Return gets discarded only if it was called from within the r.h.s.
# of a user-defined rule."
# http://mathematica.stackexchange.com/questions/29353/how-does-return-work
# Otherwise it propogates up.
#
except ReturnInterrupt as ret:
if names.intersection(definitions.user.keys()):
return ret.expr
else:
raise ret
finally:
# Restores the state
evaluation.options = old_options
evaluation.dec_recursion_depth()
return expr
def evaluate_elements(self, evaluation) -> "Expression":
"""
return a new expression with the same head, and the
evaluable elements evaluated.
"""
elements = [
element.evaluate(evaluation) if isinstance(element, EvalMixin) else element
for element in self._elements
]
head = self._head.evaluate_elements(evaluation)
return Expression(head, *elements)
def filter(self, head, cond, evaluation):
# faster equivalent to: Expression(head, [element in self.elements if cond(element)])
return structure(head, self, evaluation).filter(self, cond)
# FIXME: go over and preserve elements_properties.
def flatten_pattern_sequence(self, evaluation):
def sequence(element):
flattened = element.flatten_pattern_sequence(evaluation)
if element.get_head() is SymbolSequence and element.pattern_sequence:
return flattened._elements
else:
return [flattened]
expr = self._flatten_sequence(sequence, evaluation)
if hasattr(self, "options"):
expr.options = self.options
if expr.elements_properties is None:
expr._build_elements_properties()
else:
expr.elements_properties.is_flat = True
return expr
def flatten_sequence(self, evaluation):
def sequence(element):
if element.get_head_name() == "System`Sequence":
return element._elements
else:
return [element]
return self._flatten_sequence(sequence, evaluation)
def flatten_with_respect_to_head(
self, head, pattern_only=False, callback=None, level=100
) -> "Expression":
"""
Flatten elements in self which have `head` in them.
The idea is that in an expression like:
Expression(Plus, 1, Expression(Plus, 2, 3), 4)
when "Plus" is specified as the head, this expression should get changed to:
Expression(Plus, 1, 2, 3, 4)
In other words, all of the Plus operands are collected to together into one operation.
This is more efficiently evaluated. Note that we only flatten Plus functions, not other functions,
whether or not they contain Plus.
So in:
Expression(Plus, Times(1, 2, Plus(3, 4)))
the expression is unchanged.
head: head element to be consdier flattening on. Only expressions with this will be flattened.
This is always the head element or the next head element of the expression that the
elements are drawn from
callback: a callback function called each time a element is flattened.
level: maximum depth to flatten. This often isn't used and seems to have been put in
as a potential safety measure possibly for the future. If you don't want a limit
on flattening pass a negative number.
pattern_only: if True, just apply to elements that are pattern_sequence (see ExpressionPattern.get_wrappings)
"""
from mathics.core.convert.expression import to_expression_with_specialization
if level == 0:
return self
if self._does_not_contain_symbol(head.get_name()):
return self
sub_level = level - 1
do_flatten = False
for element in self._elements:
if element.get_head().sameQ(head) and (
not pattern_only or element.pattern_sequence
):
do_flatten = True
break
if do_flatten:
new_elements = []
for element in self._elements:
if element.get_head().sameQ(head) and (
not pattern_only or element.pattern_sequence
):
new_element = element.flatten_with_respect_to_head(
head, pattern_only, callback, level=sub_level
)
if callback is not None:
callback(new_element._elements, element)
new_elements.extend(new_element._elements)
else:
new_elements.append(element)
return to_expression_with_specialization(self._head, *new_elements)
else:
return self
def get_atoms(self, include_heads=True):
"""Returns a list of atoms involved in the expression."""
# Comment @mmatera: maybe, what we really want here are the Symbol's
# involved in the expression, not the atoms.
if include_heads:
atoms = self._head.get_atoms()
else:
atoms = []
for element in self._elements:
atoms.extend(element.get_atoms())
return atoms
def get_attributes(self, definitions):
result = A_NO_ATTRIBUTES
# Maybe this deserves to specialize Function
if self._head is SymbolFunction and len(self._elements) == 3:
res = self._elements[2]
if res.has_form("List", None):
attributes = res._elements
else:
attributes = (res,)
for attrib in attributes:
if not isinstance(attrib, Symbol):
# if we had here an evaluation object, instead of
# a definition
# evaluation.message("Attributes","attnf", a)
continue
result = result | attribute_string_to_number.get(attrib.name, 0)
return result
def get_elements(self):
# print("Use of get_elements is deprecated. Use elements instead.")
return self._elements
def get_head(self):
return self._head
def get_head_name(self):
return self._head.name if isinstance(self._head, Symbol) else ""
def get_lookup_name(self) -> str:
lookup_symbol = self._head
while True:
if isinstance(lookup_symbol, Symbol):
return lookup_symbol.name
if isinstance(lookup_symbol, Atom):
return lookup_symbol.get_head().name
lookup_symbol = lookup_symbol._head
def get_mutable_elements(self) -> list:
"""
Return a shallow mutable copy of the elements
"""
return list(self._elements)
def get_option_values(
self, evaluation, allow_symbols=False, stop_on_error=True
) -> Optional[dict]:
"""
Build a dictionary of options from an expression.
For example Symbol("Integrate").get_option_values(evaluation, allow_symbols=True)
will return a list of options associated to the definition of the symbol "Integrate".
"""
options = self
if options.has_form("List", None):
options = options.flatten_with_respect_to_head(SymbolList)
values = options.elements
else:
values = [options]
option_values = {}
for option in values:
symbol_name = option.get_name()
if allow_symbols and symbol_name:
options = evaluation.definitions.get_options(symbol_name)
option_values.update(options)
else:
if not option.has_form(("Rule", "RuleDelayed"), 2):
if stop_on_error:
return None
else:
continue
name = option.elements[0].get_name()
if not name and isinstance(option.elements[0], String):
name = ensure_context(option.elements[0].get_string_value())
if not name:
if stop_on_error:
return None
else:
continue
option_values[name] = option.elements[1]
return option_values
# FIXME: return type should be a specific kind of Tuple, not a tuple.
def get_sort_key(self, pattern_sort=False) -> tuple:
if pattern_sort:
"""
Pattern sort key structure:
0: 0/2: Atom / Expression
1: pattern: 0 / 11-31 for blanks / 1 for empty Alternatives /
40 for OptionsPattern
2: 0/1: 0 for PatternTest
3: 0/1: 0 for Pattern
4: 0/1: 1 for Optional
5: head / 0 for atoms
6: elements / 0 for atoms
7: 0/1: 0 for Condition
"""
head = self._head
pattern = 0
if head is SymbolBlank:
pattern = 1
elif head is SymbolBlankSequence:
pattern = 2
elif head is SymbolBlankNullSequence:
pattern = 3
if pattern > 0:
if self._elements:
pattern += 10
else:
pattern += 20
if pattern > 0:
return (
2,
pattern,
1,
1,
0,
head.get_sort_key(True),
tuple(element.get_sort_key(True) for element in self._elements),
1,
)
if head is SymbolPatternTest:
if len(self._elements) != 2:
return (3, 0, 0, 0, 0, head, self._elements, 1)
sub = list(self._elements[0].get_sort_key(True))
sub[2] = 0
return tuple(sub)
elif head is SymbolCondition:
if len(self._elements) != 2:
return (3, 0, 0, 0, 0, head, self._elements, 1)
sub = list(self._elements[0].get_sort_key(True))
sub[7] = 0
return tuple(sub)
elif head is SymbolPattern:
if len(self._elements) != 2:
return (3, 0, 0, 0, 0, head, self._elements, 1)
sub = list(self._elements[1].get_sort_key(True))
sub[3] = 0
return tuple(sub)
elif head is SymbolOptional:
if len(self._elements) not in (1, 2):
return (3, 0, 0, 0, 0, head, self._elements, 1)
sub = list(self._elements[0].get_sort_key(True))
sub[4] = 1
return tuple(sub)
elif head is SymbolAlternatives:
min_key = (4,)
min = None
for element in self._elements:
key = element.get_sort_key(True)
if key < min_key:
min = element
min_key = key
if min is None:
# empty alternatives -> very restrictive pattern
return (2, 1)
return min_key
elif head is SymbolVerbatim:
if len(self._elements) != 1:
return (3, 0, 0, 0, 0, head, self._elements, 1)
return self._elements[0].get_sort_key(True)
elif head is SymbolOptionsPattern:
return (2, 40, 0, 1, 1, 0, head, self._elements, 1)
else:
# Append (4,) to elements so that longer expressions have higher
# precedence
return (
2,
0,
1,
1,
0,
head.get_sort_key(True),
tuple(
chain(
(element.get_sort_key(True) for element in self._elements),
((4,),),
)
),
1,
)
else:
"""
General sort key structure:
0: 1/2: Numeric / General Expression
1: 2/3 Special arithmetic (Times / Power) / General Expression
2: Element: Head
3: tuple: list of Elements
4: 1: No clue...
"""
exps = {}
head = self._head
if head is SymbolTimes:
for element in self._elements:
name = element.get_name()
if element.has_form("Power", 2):
var = element._elements[0].get_name()
exp = element._elements[1].round_to_float()
if var and exp is not None:
exps[var] = exps.get(var, 0) + exp
elif name:
exps[name] = exps.get(name, 0) + 1
elif self.has_form("Power", 2):
var = self._elements[0].get_name()
exp = self._elements[1].round_to_float()
if var and exp is not None:
exps[var] = exps.get(var, 0) + exp
if exps:
return (
1 if self.is_numeric() else 2,
2,
Monomial(exps),
1,
head,
self._elements,
1,
)
else:
return (
1 if self.is_numeric() else 2,
3,
head,
len(self._elements),
self._elements,
1,
)
@property
def head(self):
return self._head
@head.setter
def head(self, value):
raise ValueError("Expression.head is write protected.")
@property
def is_literal(self) -> bool:
"""
True if the value doesn't change after evaluation, i.e. a
value is set and it does not depend on definition
bindings. That is why, in contrast to
`is_uncertain_final_definitions()` we don't need a
`definitions` parameter.
"""
# Right now we are pessimisitic. We might consider changing this for
# Lists. Lists definitions can't be changed right?
return False
# If we have a List we may do something like:
# return self._elements_fully_evaluated
def is_uncertain_final_definitions(self, definitions) -> bool:
"""
Used in Expression.evaluate() to determine if we need to reevaluate
an expression.
"""
# Some Atoms just don't have a cache.
if not hasattr(self, "_cache"):
return False
cache = self._cache
# FIXME: why do we return True when no cache is found? Explain.
if cache is None:
return True
time = cache.time
if time is None:
return True
if cache.symbols is None:
cache = self._rebuild_cache()
return definitions.is_uncertain_final_value(time, cache.symbols)
def has_form(self, heads, *element_counts):
"""
element_counts:
(,): no elements allowed
(None,): no constraint on number of elements
(n, None): element count >= n
(n1, n2, ...): element count in {n1, n2, ...}
"""
head_name = self._head.get_name()
if isinstance(heads, (tuple, list, set)):
if head_name not in [ensure_context(h) for h in heads]:
return False
else:
if head_name != ensure_context(heads):
return False
if not element_counts:
return False
if element_counts and element_counts[0] is not None:
count = len(self._elements)
if count not in element_counts:
if (
len(element_counts) == 2
and element_counts[1] is None # noqa
and count >= element_counts[0]
):
return True
else:
return False
return True
def has_symbol(self, symbol_name: str) -> bool:
"""
Return True if the expression contains ``symbol_name`` in its head or
any of its elements.
One place where we want to check for symbols is in to
determine whether an Expression cache needs to be
invalidated. Another place is in Series expansion.
Note that head in an M-Expression can be an expression.
Derivative and Series are like this.
"""
if self._does_not_contain_symbol(symbol_name) or not hasattr(
self._head, "has_symbol"
):
return False
return self._head.has_symbol(symbol_name) or any(
element.has_symbol(symbol_name)
for element in self._elements
if hasattr(element, "has_symbol")
)
def restructure(self, head, elements, evaluation, structure_cache=None, deps=None):
"""Faster equivalent of: Expression(head, *elements)
The caller guarantees that _all_ elements are either from
self.elements (or its subtrees) or from one of the expression given
in the tuple "deps" (or its subtrees).
If this method is called repeatedly, and the caller guarantees
that no definitions change between subsequent calls, then heads_cache
may be passed an initially empty dict to speed up calls.