Skip to content

Commit bcb1e5f

Browse files
committed
* using is_uniform property in matching SequenceBlank and SequenceNullBlank patterns
* `get_result` now accepts an optional attribute `is_uniform` to set this property in its result. * `Table`, `Sum` and `Product` now set the element_property `is_uniform` to provide a faster evaluation of large lists. * convert_expression_elements now handles is_uniform
1 parent e51a26d commit bcb1e5f

File tree

6 files changed

+71
-38
lines changed

6 files changed

+71
-38
lines changed

mathics/builtin/arithmetic.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
Test,
4343
)
4444
from mathics.core.convert.sympy import SympyExpression, from_sympy, sympy_symbol_prefix
45-
from mathics.core.element import BaseElement
45+
from mathics.core.element import BaseElement, ElementsProperties
4646
from mathics.core.evaluation import Evaluation
4747
from mathics.core.expression import Expression
4848
from mathics.core.expression_predefined import (
@@ -783,8 +783,12 @@ class Product(IterationFunction, SympyFunction, PrefixOperator):
783783
sympy_name = "Product"
784784
throw_iterb = False
785785

786-
def get_result(self, elements):
787-
return Expression(SymbolTimes, *elements)
786+
def get_result(self, elements, is_uniform=False):
787+
return Expression(
788+
SymbolTimes,
789+
*elements,
790+
elements_properties=ElementsProperties(is_uniform=is_uniform),
791+
)
788792

789793
def to_sympy(self, expr, **kwargs):
790794
if expr.has_form("Product", 2) and expr.elements[1].has_form("List", 3):
@@ -1020,8 +1024,12 @@ class Sum(IterationFunction, SympyFunction, PrefixOperator):
10201024
# Do not throw warning message for symbolic iteration bounds
10211025
throw_iterb = False
10221026

1023-
def get_result(self, elements) -> Expression:
1024-
return Expression(SymbolPlus, *elements)
1027+
def get_result(self, elements, is_uniform=False) -> Expression:
1028+
return Expression(
1029+
SymbolPlus,
1030+
*elements,
1031+
elements_properties=ElementsProperties(is_uniform=is_uniform),
1032+
)
10251033

10261034
def to_sympy(self, expr, **kwargs) -> Optional[SympyExpression]:
10271035
"""

mathics/builtin/list/constructing.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -566,10 +566,12 @@ class Table(IterationFunction):
566566

567567
summary_text = "make a table of values of an expression"
568568

569-
def get_result(self, elements) -> ListExpression:
569+
def get_result(self, elements, is_uniform=False) -> ListExpression:
570570
return ListExpression(
571571
*elements,
572-
elements_properties=ElementsProperties(elements_fully_evaluated=True),
572+
elements_properties=ElementsProperties(
573+
elements_fully_evaluated=True, is_uniform=is_uniform
574+
),
573575
)
574576

575577

mathics/builtin/patterns/basic.py

Lines changed: 43 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,15 @@
1919
BLANKSEQUENCE_GENERAL_PATTERN_SORT_KEY,
2020
BLANKSEQUENCE_WITH_PATTERN_PATTERN_SORT_KEY,
2121
)
22-
from mathics.core.symbols import BaseElement
22+
from mathics.core.symbols import BaseElement, Symbol
2323

2424
# This tells documentation how to sort this module
2525
sort_order = "mathics.builtin.rules-and-patterns.basic"
2626

2727

2828
class _Blank(PatternObject, ABC):
2929
arg_counts = [0, 1]
30-
30+
target_head: OptionalType[Symbol]
3131
_instance = None
3232

3333
def __new__(cls, *args, **kwargs):
@@ -49,12 +49,14 @@ def init(
4949
) -> None:
5050
super().init(expr, evaluation=evaluation)
5151
if expr.elements:
52-
self.head = expr.elements[0]
52+
target_head = expr.elements[0]
53+
assert isinstance(target_head, Symbol)
54+
self.target_head = target_head
5355
else:
5456
# FIXME: elswhere, some code wants to
5557
# get the attributes of head.
5658
# So is this really the best thing to do here?
57-
self.head = None
59+
self.target_head = None
5860

5961

6062
class Blank(_Blank):
@@ -104,8 +106,9 @@ def match(self, expression: BaseElement, pattern_context: dict):
104106
yield_func = pattern_context["yield_func"]
105107

106108
if not expression.has_form("Sequence", 0):
107-
if self.head is not None:
108-
if expression.get_head().sameQ(self.head):
109+
target_head = self.target_head
110+
if target_head is not None:
111+
if expression.get_head() is target_head:
109112
yield_func(vars_dict, None)
110113
else:
111114
yield_func(vars_dict, None)
@@ -157,19 +160,25 @@ class BlankNullSequence(_Blank):
157160

158161
def match(self, expression: Expression, pattern_context: dict):
159162
"""Match with a BlankNullSequence"""
160-
vars_dict = pattern_context["vars_dict"]
161-
yield_func = pattern_context["yield_func"]
162-
elements = expression.get_sequence()
163-
if self.head:
164-
ok = True
163+
164+
target_head = self.target_head
165+
if target_head:
166+
elements = expression.get_sequence()
167+
is_uniform = False
168+
if isinstance(expression, Expression):
169+
element_properties = expression.elements_properties
170+
if element_properties is not None:
171+
is_uniform = element_properties.is_uniform
165172
for element in elements:
166-
if element.get_head() != self.head:
167-
ok = False
173+
if target_head is not element.get_head():
174+
return
175+
# If the expression is uniform, further checks are not necesary.
176+
if is_uniform:
168177
break
169-
if ok:
170-
yield_func(vars_dict, None)
171-
else:
172-
yield_func(vars_dict, None)
178+
179+
vars_dict = pattern_context["vars_dict"]
180+
yield_func = pattern_context["yield_func"]
181+
yield_func(vars_dict, None)
173182

174183
@property
175184
def element_order(self) -> tuple:
@@ -240,21 +249,28 @@ class BlankSequence(_Blank):
240249
summary_text = "match to a non-empty sequence of elements"
241250

242251
def match(self, expression: Expression, pattern_context: dict):
243-
vars_dict = pattern_context["vars_dict"]
244-
yield_func = pattern_context["yield_func"]
245252
elements = expression.get_sequence()
253+
246254
if not elements:
247255
return
248-
if self.head:
249-
ok = True
256+
257+
target_head = self.target_head
258+
if target_head:
259+
is_uniform = False
260+
if isinstance(expression, Expression):
261+
element_properties = expression.elements_properties
262+
if element_properties is not None:
263+
is_uniform = element_properties.is_uniform
250264
for element in elements:
251-
if element.get_head() != self.head:
252-
ok = False
265+
if target_head is not element.get_head():
266+
return
267+
# If the expression is uniform, further checks are not necesary.
268+
if is_uniform:
253269
break
254-
if ok:
255-
yield_func(vars_dict, None)
256-
else:
257-
yield_func(vars_dict, None)
270+
271+
vars_dict = pattern_context["vars_dict"]
272+
yield_func = pattern_context["yield_func"]
273+
yield_func(vars_dict, None)
258274

259275
def get_match_count(self, vars_dict: OptionalType[dict] = None) -> tuple:
260276
return (1, None)

mathics/builtin/procedural.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,7 @@ class Do(IterationFunction):
309309
allow_loopcontrol = True
310310
summary_text = "evaluate an expression looping over a variable"
311311

312-
def get_result(self, _items):
312+
def get_result(self, _items, is_uniform=False):
313313
return SymbolNull
314314

315315

mathics/core/builtin.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1023,7 +1023,7 @@ class IterationFunction(Builtin, ABC):
10231023
allow_loopcontrol = False
10241024
throw_iterb = True
10251025

1026-
def get_result(self, elements) -> Expression:
1026+
def get_result(self, elements, is_uniform=False) -> Expression:
10271027
raise NotImplementedError
10281028

10291029
def eval_symbol(self, expr, iterator, evaluation):
@@ -1159,6 +1159,8 @@ def eval_iter(self, expr, i, imin, imax, di, evaluation):
11591159
).evaluate(evaluation)
11601160

11611161
result = []
1162+
last_head = None
1163+
is_uniform = True
11621164
while True:
11631165
cont = Expression(SymbolLessEqual, index, normalised_range).evaluate(
11641166
evaluation
@@ -1186,6 +1188,10 @@ def eval_iter(self, expr, i, imin, imax, di, evaluation):
11861188
evaluation,
11871189
)
11881190
result.append(item)
1191+
if last_head is None:
1192+
last_head = item.get_head()
1193+
elif is_uniform and last_head is not item.get_head():
1194+
is_uniform = False
11891195
except ContinueInterrupt:
11901196
if self.allow_loopcontrol:
11911197
pass
@@ -1202,7 +1208,7 @@ def eval_iter(self, expr, i, imin, imax, di, evaluation):
12021208
else:
12031209
raise
12041210
index = Expression(SymbolPlus, index, Integer1).evaluate(evaluation)
1205-
return self.get_result(result)
1211+
return self.get_result(result, is_uniform=is_uniform)
12061212

12071213
def eval_list(self, expr, i, items, evaluation):
12081214
"%(name)s[expr_, {i_Symbol, {items___}}]"

mathics/core/expression.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1981,7 +1981,8 @@ def convert_expression_elements(
19811981

19821982
# All of the properties start out optimistic (True) and are reset when that
19831983
# proves wrong.
1984-
elements_properties = ElementsProperties(True, True, True)
1984+
# Also, expressions from this function are expected to be uniform.
1985+
elements_properties = ElementsProperties(True, True, True, True)
19851986

19861987
is_literal = True
19871988
values = [] # If is_literal, "values" contains the (Python) literal values

0 commit comments

Comments
 (0)