Skip to content

Commit d1cf6a4

Browse files
1 / Fix issues with pattern matching for Rubi (#1176)
# Overview In this PR, fixes for pattern matching, esp. those that concern ``Rubi` ``, are collected. # Related issues - #1172 - #1173 # Failing patterns - Pattern `a_. + b_. x_` does not match `1 + 2 x` - Likely fixed by aravindh-krishnamoorthy@85ad0f5 - Pattern `x_.^m_.` does not match `x` - Likely fixed by aravindh-krishnamoorthy@c1999ce + aravindh-krishnamoorthy@e1dee54 - Pattern `a_. + b_.` does not match `x` - Crash: `mathics/core/pattern.py` -> `assert new_pattern is not None` - Likely fixed by aravindh-krishnamoorthy@0a89945 - ~~Pattern `x_Integer + y_Integer z_.` for `1 + 2 x`~~ (Not directly relevant for Rubi) - ~See #1176 (comment) - ~Pattern with conditional within `With`: `g[x_] := With[{}, x /; False] /; True` matches for `g[2]`~ - ~Must not match.~ (Fixes for items struck out above are moved to the next PR.) # Final review # Tests - [X] Tests for aravindh-krishnamoorthy@85ad0f5 - [X] Tests for aravindh-krishnamoorthy@c1999ce + aravindh-krishnamoorthy@e1dee54 - [X] Tests for aravindh-krishnamoorthy@0a89945 # Documentation N/A
1 parent 0d8a8f6 commit d1cf6a4

File tree

4 files changed

+158
-3
lines changed

4 files changed

+158
-3
lines changed

mathics/core/pattern.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -701,7 +701,12 @@ def match_expression_with_one_identity(
701701
isinstance(pat_elem, PatternObject)
702702
and pat_elem.get_head() == SymbolOptional
703703
):
704-
if len(pat_elem.elements) == 2:
704+
if optionals:
705+
# A default pattern already exists
706+
# Do not use the second one
707+
if new_pattern is None:
708+
new_pattern = pat_elem
709+
elif len(pat_elem.elements) == 2:
705710
pat, value = pat_elem.elements
706711
if isinstance(pat, Pattern):
707712
key = pat.elements[0].atom.name # type: ignore[attr-defined]
@@ -724,8 +729,12 @@ def match_expression_with_one_identity(
724729
result = defaultvalue_expr.evaluate(evaluation)
725730
assert result is not None
726731
if result.sameQ(defaultvalue_expr):
727-
return
728-
optionals[key] = result
732+
if new_pattern is None:
733+
# The optional pattern has no default value
734+
# for the given position
735+
new_pattern = pat_elem
736+
else:
737+
optionals[key] = result
729738
else:
730739
return
731740
elif new_pattern is not None:
@@ -757,6 +766,8 @@ def match_expression_with_one_identity(
757766
del parms["attributes"]
758767
assert new_pattern is not None
759768
new_pattern.match(expression=expression, pattern_context=parms)
769+
for optional in optionals:
770+
vars_dict.pop(optional)
760771

761772

762773
def basic_match_expression(

test/builtin/test_testing_expressions.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,3 +135,45 @@ def test_private_doctests_numerical_properties(str_expr, msgs, str_expected, fai
135135
failure_message=fail_msg,
136136
expected_messages=msgs,
137137
)
138+
139+
140+
@pytest.mark.parametrize(
141+
("str_expr", "msgs", "str_expected", "fail_msg"),
142+
[
143+
# Two default arguments (linear)
144+
("MatchQ[1, a_.+b_.*x_]", None, "True", None),
145+
("MatchQ[x, a_.+b_.*x_]", None, "True", None),
146+
("MatchQ[2*x, a_.+b_.*x_]", None, "True", None),
147+
("MatchQ[1+x, a_.+b_.*x_]", None, "True", None),
148+
("MatchQ[1+2*x, a_.+b_.*x_]", None, "True", None),
149+
# Default argument (power)
150+
("MatchQ[1, x_^m_.]", None, "True", None),
151+
("MatchQ[x, x_^m_.]", None, "True", None),
152+
("MatchQ[x^1, x_^m_.]", None, "True", None),
153+
("MatchQ[x^2, x_^m_.]", None, "True", None),
154+
# Two default arguments (power)
155+
("MatchQ[1, x_.^m_.]", None, "True", None),
156+
("MatchQ[x, x_.^m_.]", None, "True", None),
157+
("MatchQ[x^1, x_.^m_.]", None, "True", None),
158+
("MatchQ[x^2, x_.^m_.]", None, "True", None),
159+
# Two default arguments (no non-head)
160+
("MatchQ[1, a_.+b_.]", None, "True", None),
161+
("MatchQ[x, a_.+b_.]", None, "True", None),
162+
("MatchQ[1+x, a_.+b_.]", None, "True", None),
163+
("MatchQ[1+2*x, a_.+b_.]", None, "True", None),
164+
("MatchQ[1, a_.*b_.]", None, "True", None),
165+
("MatchQ[x, a_.*b_.]", None, "True", None),
166+
("MatchQ[2*x, a_.*b_.]", None, "True", None),
167+
],
168+
)
169+
def test_matchq(str_expr, msgs, str_expected, fail_msg):
170+
"""text_expressions.matchq"""
171+
check_evaluation(
172+
str_expr,
173+
str_expected,
174+
to_string_expr=True,
175+
to_string_expected=True,
176+
hold_expected=True,
177+
failure_message=fail_msg,
178+
expected_messages=msgs,
179+
)

test/core/test_rules.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,3 +184,48 @@ def test_flat_on_rules(str_expr, str_expected, msg):
184184
@pytest.mark.xfail
185185
def test_default_optional_on_rules(str_expr, str_expected, msg):
186186
check_evaluation(str_expr, str_expected, failure_message=msg)
187+
188+
189+
@pytest.mark.parametrize(
190+
("str_expr", "msgs", "str_expected", "fail_msg"),
191+
[
192+
# Two default arguments (linear)
193+
("rule=A[a_.+B[b_.*x_]]->{a,b,x};", None, "Null", None),
194+
("A[B[1]] /. rule", None, "{0, 1, 1}", None),
195+
("A[B[x]] /. rule", None, "{0, 1, x}", None),
196+
("A[B[2*x]] /. rule", None, "{0, x, 2}", None),
197+
("A[1+B[x]] /. rule", None, "{1, 1, x}", None),
198+
("A[1+B[2*x]] /. rule", None, "{1, x, 2}", None),
199+
# Default argument (power)
200+
("rule=A[x_^n_.]->{x,n};", None, "Null", None),
201+
("A[1] /. rule", None, "{1, 1}", None),
202+
("A[x] /. rule", None, "{x, 1}", None),
203+
("A[x^1] /. rule", None, "{x, 1}", None),
204+
("A[x^2] /. rule", None, "{x, 2}", None),
205+
# Two default arguments (power)
206+
("rule=A[x_.^n_.]->{x,n};", None, "Null", None),
207+
("A[] /. rule", None, "A[]", None),
208+
("A[1] /. rule", None, "{1, 1}", None),
209+
("A[x] /. rule", None, "{x, 1}", None),
210+
("A[x^1] /. rule", None, "{x, 1}", None),
211+
("A[x^2] /. rule", None, "{x, 2}", None),
212+
# Two default arguments (no non-head)
213+
("rule=A[a_. + B[b_.*x_.]]->{a,b,x};", None, "Null", None),
214+
("A[B[]] /. rule", None, "A[B[]]", None),
215+
("A[B[1]] /. rule", None, "{0, 1, 1}", None),
216+
("A[B[x]] /. rule", None, "{0, 1, x}", None),
217+
("A[1 + B[x]] /. rule", None, "{1, 1, x}", None),
218+
("A[1 + B[2*x]] /. rule", None, "{1, 2, x}", None),
219+
],
220+
)
221+
def test_pattern_rules(str_expr, msgs, str_expected, fail_msg):
222+
"""pattern_rules"""
223+
check_evaluation(
224+
str_expr,
225+
str_expected,
226+
to_string_expr=True,
227+
to_string_expected=True,
228+
hold_expected=True,
229+
failure_message=fail_msg,
230+
expected_messages=msgs,
231+
)

test/eval/test_patterns.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# -*- coding: utf-8 -*-
2+
"""
3+
Unit tests for mathics.eval.patterns
4+
"""
5+
6+
from test.helper import session
7+
8+
import pytest
9+
10+
from mathics.core.definitions import Definitions
11+
from mathics.core.parser import MathicsSingleLineFeeder, parse
12+
from mathics.core.pattern import ExpressionPattern
13+
from mathics.eval.patterns import Matcher
14+
15+
# Preload the Mathics definitions
16+
defintions = Definitions(True)
17+
18+
19+
def check_pattern(str_expr, str_pattern):
20+
expr = parse(defintions, MathicsSingleLineFeeder(str_expr))
21+
pattern = ExpressionPattern(parse(defintions, MathicsSingleLineFeeder(str_pattern)))
22+
ret = Matcher(pattern, session.evaluation).match(expr, session.evaluation)
23+
assert ret == True
24+
25+
26+
@pytest.mark.parametrize(
27+
("str_expr", "str_pattern"),
28+
[
29+
# Two default arguments (linear)
30+
("1", "a_.+b_.*x_"),
31+
("x", "a_.+b_.*x_"),
32+
("2*x", "a_.+b_.*x_"),
33+
("1+x", "a_.+b_.*x_"),
34+
("1+2*x", "a_.+b_.*x_"),
35+
# Default argument (power)
36+
("1", "x_^m_."),
37+
("x", "x_^m_."),
38+
("x^1", "x_^m_."),
39+
("x^2", "x_^m_."),
40+
# Two default arguments (power)
41+
("1", "x_.^m_."),
42+
("x", "x_.^m_."),
43+
("x^1", "x_.^m_."),
44+
("x^2", "x_.^m_."),
45+
# Two default arguments (no non-head)
46+
("1", "a_.+b_."),
47+
("x", "a_.+b_."),
48+
("1+x", "a_.+b_."),
49+
("1+2*x", "a_.+b_."),
50+
("1", "a_.*b_."),
51+
("x", "a_.*b_."),
52+
("2*x", "a_.*b_."),
53+
],
54+
)
55+
def test_eval_patterns(str_expr, str_pattern):
56+
"""eval_patterns"""
57+
check_pattern(str_expr, str_pattern)

0 commit comments

Comments
 (0)