Skip to content

Commit 3ac1338

Browse files
authored
Merge pull request #1348 from mathics/predicate-processing
Advanced predicate processing
2 parents 2283335 + 69b6dbe commit 3ac1338

File tree

2 files changed

+402
-15
lines changed

2 files changed

+402
-15
lines changed

mathics/builtin/inference.py

Lines changed: 344 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1+
# -*- coding: utf-8 -*-
2+
13
from mathics.version import __version__ # noqa used in loading to check consistency.
4+
25
from mathics.core.expression import (
36
Expression,
47
Symbol,
@@ -14,8 +17,103 @@
1417
# TODO: Extend these rules?
1518

1619

20+
def debug_logical_expr(pref, expr, evaluation):
21+
pass
22+
# return
23+
# print(pref , expr) #expr.format(evaluation,"OutputForm").boxes_to_text(evaluation=evaluation))
24+
25+
26+
logical_algebraic_rules_spec = {
27+
# Inequality rules
28+
"Unequal[a_, b_]": "Not[Equal[a, b]]",
29+
"Greater[a_, b_]": "Less[b, a]",
30+
"GreaterEqual[a_, b_]": "Not[Less[a, b]]",
31+
"LessEqual[a_, b_]": "Not[Less[b, a]]",
32+
"PositiveQ[a_]": "Less[0, a]",
33+
"NegativeQ[a_]": "Less[a, 0]",
34+
# Logical basic reductions
35+
"Or[q_, Not[q_]]": "True",
36+
"Or[q_,]": "q",
37+
"Or[q_, q_]": "q",
38+
"Or[pred1___, q_, pred2___, q_, pred3___]": "Or[pred1, q, pred2, pred3]",
39+
# TODO: Logical operations should sort the leaves...
40+
"Or[Not[q_], q_]": "True",
41+
"Or[pred1___, q_, pred2___, Not[q_], pred3___]": "Or[pred1, pred2, pred3]",
42+
"Or[pred1___, Not[q_], pred2___, q_, pred3___]": "Or[pred1, pred2, pred3]",
43+
"And[q_,q_]": "q",
44+
"And[q_, Not[q_]]": "False",
45+
"And[Not[q_],q_]": "False",
46+
"And[pred1___, q_, pred2___, Not[q_], pred3___]": "False",
47+
"And[pred1___, Not[q_], pred2___, q_, pred3___]": "False",
48+
# Logical reductions involving equalities
49+
"Or[pred1___, a_==b_, pred2___ , b_==a_, pred3___]": "Or[pred1, a==b, pred2, pred3]",
50+
"And[pred1___, a_==b_, pred2___ , b_==a_, pred3___]": "And[pred1, a==b, pred2, pred3]",
51+
"Or[pred1___, a_==b_, pred2___ , Not[b_==a_], pred3___]": "Or[pred1, pred2, pred3]",
52+
"And[pred1___, a_==b_, pred2___ , Not[b_==a_], pred3___]": "False",
53+
"Xor[q_, Not[q_]]": "True",
54+
"Xor[a_==b_, Not[b_==a_]]": "True",
55+
# Logical reductions involving inequalities
56+
"Or[a_<b_, b_<a_]": "! (a==b)",
57+
"And[a_<b_, b_<a_]": "False",
58+
"Or[a_<b_, b_==a_]": "! (a>b)",
59+
"Or[b_==a_, a_<b_]": "! (a>b)",
60+
"And[a_<b_, b_==a_]": "False",
61+
"And[b_==a_, a_<b_]": "False",
62+
"And[pred1___, a_<b_, pred2___, Not[b_<a_], pred3___ ]": "And[pred1, a==b, pred2, pred3]",
63+
"And[pred1___, a_<b_, pred2___, b_<a_, pred3___ ]": "False",
64+
"And[pred1___, a_<b_, pred2___, b_==a_, pred3___ ]": "False",
65+
"Or[pred1___, a_<b_, pred2___, b_<a_, pred3___ ]": "And[pred1,Not[a==b],pred2, pred3]",
66+
"Or[pred1___, a_<b_, pred2___, b_==a_, pred3___ ]": "And[pred1,Not[b<a],pred2, pred3]",
67+
"Or[pred1___, q_, pred2___, Not[q_], pred3___ ]": "Or[pred1, pred2, pred3]",
68+
# Let's assume that variables are finite
69+
"-Infinity< Infinity": "True",
70+
"Infinity< -Infinity ": "False",
71+
"Infinity == -Infinity ": "False",
72+
"_Symbol < Infinity ": "True",
73+
"-Infinity <_Symbol": "True",
74+
}
75+
76+
remove_not_rules_spec = {
77+
"Not[a_<b_]": "a>=b",
78+
"Not[a_==b_]": "a!=b",
79+
}
80+
1781

18-
def get_assumptions_list(evaluation):
82+
logical_algebraic_rules = None
83+
remove_not_rules = None
84+
85+
86+
def ensure_logical_algebraic_rules():
87+
global logical_algebraic_rules
88+
global remove_not_rules
89+
if logical_algebraic_rules is None:
90+
logical_algebraic_rules = []
91+
for pattern, replace in logical_algebraic_rules_spec.items():
92+
pattern = parse_builtin_rule(pattern, SystemDefinitions())
93+
logical_algebraic_rules.append(
94+
Rule(pattern, parse_builtin_rule(replace), system=True)
95+
)
96+
remove_not_rules = []
97+
for pattern, replace in remove_not_rules_spec.items():
98+
pattern = parse_builtin_rule(pattern, SystemDefinitions())
99+
remove_not_rules.append(
100+
Rule(pattern, parse_builtin_rule(replace), system=True)
101+
)
102+
return
103+
104+
105+
def remove_nots_when_unnecesary(pred, evaluation):
106+
global remove_not_rules
107+
cc = True
108+
while cc:
109+
pred, cc = pred.apply_rules(remove_not_rules, evaluation)
110+
debug_logical_expr("-> ", pred, evaluation)
111+
if pred.is_true() or pred == SymbolFalse:
112+
return pred
113+
return pred
114+
115+
116+
def get_assumptions_list(evaluation):
19117
assumptions = None
20118
assumptions_def = evaluation.definitions.get_definition(
21119
"System`$Assumptions", only_if_exists=True
@@ -35,23 +133,254 @@ def get_assumptions_list(evaluation):
35133
return assumptions
36134

37135

136+
def remove_duplicated_assumptions(assumptions_list, evaluation):
137+
if len(assumptions_list) == 0:
138+
return assumptions_list
139+
assumptions_list = sorted(assumptions_list)
140+
unique_assumptions = [assumptions_list[0]]
141+
for i, assumption in enumerate(assumptions_list):
142+
if not (assumption == unique_assumptions[-1]):
143+
unique_assumptions.append(assumption)
144+
return unique_assumptions
38145

39-
def evaluate_predicate(pred, evaluation):
40-
if pred.has_form(("List", "Sequence"), None):
41-
return Expression(pred._head, *[evaluate_predicate(subp, evaluation) for subp in pred._leaves] )
42-
assumptions = get_assumptions_list(evaluation)
43146

147+
def logical_expand_assumptions(assumptions_list, evaluation):
148+
new_assumptions_list = []
149+
changed = False
150+
for assumption in assumptions_list:
151+
if assumption.is_symbol():
152+
if assumption.is_true():
153+
changed = True
154+
continue
155+
if assumption == SymbolFalse:
156+
evaluation.message("Assumption", "faas")
157+
changed = True
158+
continue
159+
if assumption.is_numeric():
160+
evaluation.message("Assumption", "baas")
161+
changed = True
162+
continue
163+
new_assumptions_list.append(assumption)
164+
continue
165+
if assumption.has_form("And", None):
166+
changed = True
167+
for leaf in assumption.leaves:
168+
new_assumptions_list.append(leaf)
169+
continue
170+
if assumption.has_form("Not", 1):
171+
sentence = assumption._leaves[0]
172+
if sentence.has_form("Or", None):
173+
changed = True
174+
for leaf in sentence._leaves:
175+
new_assumptions_list.append(Expression("Not", leaf))
176+
continue
177+
if sentence.has_form("And", None):
178+
leaves = (Expression("Not", leaf) for leaf in sentence._leaves)
179+
new_assumptions_list.append(Expression("Or", *leaves))
180+
continue
181+
if sentence.has_form("Implies", 2):
182+
changed = True
183+
new_assumptions_list.append(sentence._leaves[0])
184+
new_assumptions_list.append(Expression("Not", sentence._leaves[1]))
185+
if assumption.has_form("Nor", None):
186+
changed = True
187+
for leaf in assumption.leaves:
188+
new_assumptions_list.append(Expression("Not", leaf))
189+
continue
190+
else:
191+
new_assumptions_list.append(assumption)
192+
193+
if changed:
194+
new_assumptions_list = remove_duplicated_assumptions(
195+
new_assumptions_list, evaluation
196+
)
197+
198+
return new_assumptions_list, changed
199+
200+
201+
def algebraic_expand_assumptions(assumptions_list, evaluation):
202+
global logical_algebraic_rules
203+
ensure_logical_algebraic_rules()
204+
new_assumptions_list = []
205+
changed = False
206+
# First apply standard rules of reduction.
207+
# These rules are generated the first time that are used.
208+
for assumption in assumptions_list:
209+
assumption, applied = assumption.apply_rules(
210+
logical_algebraic_rules, evaluation
211+
)
212+
changed = changed or applied
213+
new_assumptions_list.append(assumption)
214+
if changed:
215+
return new_assumptions_list, changed
216+
# If not changed, let's try with the next set of rules
217+
for assumption in assumptions_list:
218+
if assumption.has_form("Not", 1):
219+
nas, local_change = algebraic_expand_assumptions(
220+
[assumption._leaves[0]], evaluation
221+
)
222+
if local_change:
223+
changed = local_change
224+
for na in nas:
225+
if na.has_form("Not", 1):
226+
new_assumptions_list.append(na._leaves[0])
227+
else:
228+
new_assumptions_list.append(Expression("Not", na))
229+
else:
230+
new_assumptions_list.append(assumption)
231+
elif assumption.has_form(("Equal", "Unequal", "Equivalent"), (3, None)):
232+
leaves = assumption.leaves()
233+
head = assumption.get_head()
234+
changed = True
235+
for i in range(len(leaves)):
236+
for j in range(i):
237+
new_assumptions_list.append(Expression(head, leaves[i], leaves[j]))
238+
new_assumptions_list.append(Expression(head, leaves[j], leaves[i]))
239+
elif assumption.has_form(
240+
("Less", "Greater", "LessEqual", "GreaterEqual"), (3, None)
241+
):
242+
leaves = assumption.leaves()
243+
head = assumption.get_head()
244+
changed = True
245+
for i in range(len(leaves)):
246+
for j in range(i):
247+
new_assumptions_list.append(Expression(head, leaves[i], leaves[j]))
248+
else:
249+
new_assumptions_list.append(assumption)
250+
251+
if changed:
252+
assumptions_list = remove_duplicated_assumptions(
253+
new_assumptions_list, evaluation
254+
)
255+
new_assumptions_list = []
256+
for assumption in assumptions_list:
257+
assumption, applied = assumption.apply_rules(
258+
logical_algebraic_rules, evaluation
259+
)
260+
new_assumptions_list.append(assumption)
261+
return new_assumptions_list, changed
262+
263+
264+
def get_assumption_rules_dispatch(evaluation):
265+
# TODO: cache the generated rules...
266+
assumptions_list = get_assumptions_list(evaluation)
267+
if assumptions_list is None:
268+
return None
269+
270+
# check for consistency:
271+
consistent_assumptions = Expression("And", *assumptions_list)
272+
val_consistent_assumptions = consistent_assumptions.evaluate(evaluation)
273+
if val_consistent_assumptions == SymbolFalse:
274+
evaluation.message("Inconsistent assumptions")
275+
276+
if assumptions_list is None:
277+
return remove_nots_when_unnecesary(pred, evaluation).evaluate(evaluation)
278+
279+
# Expands Logically
280+
assumptions_list, cont = logical_expand_assumptions(assumptions_list, evaluation)
281+
while cont:
282+
assumptions_list, cont = logical_expand_assumptions(
283+
assumptions_list, evaluation
284+
)
285+
286+
# Expands algebraically
287+
assumptions_list, cont = algebraic_expand_assumptions(assumptions_list, evaluation)
288+
while cont:
289+
assumptions_list, cont = algebraic_expand_assumptions(
290+
assumptions_list, evaluation
291+
)
44292
assumption_rules = []
45-
for assumption in assumptions:
46-
true_state = True
47-
while assumption.has_form("Not",1):
48-
true_state = False
49-
assumption = assumption._leaves[0]
50-
if true_state:
51-
assumption_rules.append(Rule(assumption, SymbolTrue))
293+
for pat in assumptions_list:
294+
value = True
295+
while pat.has_form("Not", 1):
296+
value = not value
297+
pat = pat._leaves[0]
298+
299+
if value:
300+
symbol_value = SymbolTrue
301+
symbol_negate_value = SymbolFalse
302+
else:
303+
symbol_value = SymbolFalse
304+
symbol_negate_value = SymbolTrue
305+
306+
if pat.has_form("Equal", 2):
307+
if value:
308+
lhs, rhs = pat._leaves
309+
if lhs.is_numeric():
310+
assumption_rules.append(Rule(rhs, lhs))
311+
else:
312+
assumption_rules.append(Rule(lhs, rhs))
313+
else:
314+
assumption_rules.append(Rule(pat, SymbolFalse))
315+
symm_pat = Expression(pat._head, pat._leaves[1], pat._leaves[0])
316+
assumption_rules.append(Rule(symm_pat, SymbolFalse))
317+
elif pat.has_form("Equivalent", 2):
318+
assumption_rules.append(Rule(pat, symbol_value))
319+
symm_pat = Expression(pat._head, pat._leaves[1], pat._leaves[0])
320+
assumption_rules.append(Rule(symm_pat, symbol_value))
321+
elif pat.has_form("Less", 2):
322+
if value:
323+
assumption_rules.append(Rule(pat, SymbolTrue))
324+
assumption_rules.append(
325+
Rule(
326+
Expression(pat._head, pat._leaves[1], pat._leaves[0]),
327+
SymbolFalse,
328+
)
329+
)
330+
for head in ("Equal", "Equivalent"):
331+
assumption_rules.append(
332+
Rule(
333+
Expression(head, pat._leaves[0], pat._leaves[1]),
334+
SymbolFalse,
335+
)
336+
)
337+
assumption_rules.append(
338+
Rule(
339+
Expression(head, pat._leaves[1], pat._leaves[0]),
340+
SymbolFalse,
341+
)
342+
)
343+
else:
344+
assumption_rules.append(Rule(pat, SymbolFalse))
52345
else:
53-
assumption_rules.append(Rule(assumption, SymbolFalse))
54-
55-
pred, changed = pred.apply_rules(assumption_rules, evaluation)
346+
assumption_rules.append(Rule(pat, symbol_value))
347+
# TODO: expand the pred and assumptions into an standard,
348+
# atomized form, and then apply the rules...
349+
if len(assumption_rules) == 0:
350+
return None
351+
return assumption_rules
352+
353+
354+
def evaluate_predicate(pred, evaluation):
355+
global logical_algebraic_rules
356+
global remove_not_rules
357+
358+
if pred.has_form(("List", "Sequence"), None):
359+
return Expression(
360+
pred._head, *[evaluate_predicate(subp, evaluation) for subp in pred._leaves]
361+
)
362+
363+
debug_logical_expr("reducing ", pred, evaluation)
364+
ensure_logical_algebraic_rules()
56365
pred = pred.evaluate(evaluation)
366+
debug_logical_expr("-> ", pred, evaluation)
367+
cc = True
368+
while cc:
369+
pred, cc = pred.apply_rules(logical_algebraic_rules, evaluation)
370+
debug_logical_expr("-> ", pred, evaluation)
371+
if pred.is_true() or pred == SymbolFalse:
372+
return pred
373+
374+
assumption_rules = get_assumption_rules_dispatch(evaluation)
375+
if assumption_rules is None:
376+
return remove_nots_when_unnecesary(pred, evaluation).evaluate(evaluation)
377+
378+
if assumption_rules is not None:
379+
debug_logical_expr(" Now, using the assumptions over ", pred, evaluation)
380+
changed = True
381+
while changed:
382+
pred, changed = pred.apply_rules(assumption_rules, evaluation)
383+
debug_logical_expr(" -> ", pred, evaluation)
384+
385+
pred = remove_nots_when_unnecesary(pred, evaluation).evaluate(evaluation)
57386
return pred

0 commit comments

Comments
 (0)