mathai 2.4.0__tar.gz → 2.4.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {mathai-2.4.0 → mathai-2.4.2}/PKG-INFO +1 -1
- {mathai-2.4.0 → mathai-2.4.2}/mathai/base.py +50 -41
- mathai-2.4.2/mathai/benchmark.py +25 -0
- {mathai-2.4.0 → mathai-2.4.2}/mathai/diff.py +2 -1
- {mathai-2.4.0 → mathai-2.4.2}/mathai/formula_compiler.py +82 -120
- {mathai-2.4.0 → mathai-2.4.2}/mathai/integrate.py +72 -39
- {mathai-2.4.0 → mathai-2.4.2}/mathai/limit.py +3 -2
- mathai-2.4.2/mathai/ncert_data.txt +53 -0
- {mathai-2.4.0 → mathai-2.4.2}/mathai/qm.py +3 -3
- {mathai-2.4.0 → mathai-2.4.2}/mathai/univariate_inequality.py +58 -43
- {mathai-2.4.0 → mathai-2.4.2}/mathai.egg-info/PKG-INFO +1 -1
- {mathai-2.4.0 → mathai-2.4.2}/setup.py +1 -1
- mathai-2.4.0/mathai/benchmark.py +0 -18
- mathai-2.4.0/mathai/ncert_data.txt +0 -61
- {mathai-2.4.0 → mathai-2.4.2}/README.md +0 -0
- {mathai-2.4.0 → mathai-2.4.2}/mathai/__init__.py +0 -0
- {mathai-2.4.0 → mathai-2.4.2}/mathai/apart.py +0 -0
- {mathai-2.4.0 → mathai-2.4.2}/mathai/decision.py +0 -0
- {mathai-2.4.0 → mathai-2.4.2}/mathai/electronic.py +0 -0
- {mathai-2.4.0 → mathai-2.4.2}/mathai/expand.py +0 -0
- {mathai-2.4.0 → mathai-2.4.2}/mathai/factor.py +0 -0
- {mathai-2.4.0 → mathai-2.4.2}/mathai/formula_applier.py +0 -0
- {mathai-2.4.0 → mathai-2.4.2}/mathai/fraction.py +0 -0
- {mathai-2.4.0 → mathai-2.4.2}/mathai/graphics.py +0 -0
- {mathai-2.4.0 → mathai-2.4.2}/mathai/inverse.py +0 -0
- {mathai-2.4.0 → mathai-2.4.2}/mathai/lambda_calculus.py +0 -0
- {mathai-2.4.0 → mathai-2.4.2}/mathai/linear.py +0 -0
- {mathai-2.4.0 → mathai-2.4.2}/mathai/logic.py +0 -0
- {mathai-2.4.0 → mathai-2.4.2}/mathai/matrix.py +0 -0
- {mathai-2.4.0 → mathai-2.4.2}/mathai/ml.py +0 -0
- {mathai-2.4.0 → mathai-2.4.2}/mathai/mlscalar.py +0 -0
- {mathai-2.4.0 → mathai-2.4.2}/mathai/ode.py +0 -0
- {mathai-2.4.0 → mathai-2.4.2}/mathai/parser.py +0 -0
- {mathai-2.4.0 → mathai-2.4.2}/mathai/pde.py +0 -0
- {mathai-2.4.0 → mathai-2.4.2}/mathai/printeq.py +0 -0
- {mathai-2.4.0 → mathai-2.4.2}/mathai/simplify.py +0 -0
- {mathai-2.4.0 → mathai-2.4.2}/mathai/statistics.py +0 -0
- {mathai-2.4.0 → mathai-2.4.2}/mathai/test.py +0 -0
- {mathai-2.4.0 → mathai-2.4.2}/mathai/tool.py +0 -0
- {mathai-2.4.0 → mathai-2.4.2}/mathai/trig.py +0 -0
- {mathai-2.4.0 → mathai-2.4.2}/mathai.egg-info/SOURCES.txt +0 -0
- {mathai-2.4.0 → mathai-2.4.2}/mathai.egg-info/dependency_links.txt +0 -0
- {mathai-2.4.0 → mathai-2.4.2}/mathai.egg-info/top_level.txt +0 -0
- {mathai-2.4.0 → mathai-2.4.2}/setup.cfg +0 -0
|
@@ -379,6 +379,7 @@ def factor_generation(eq, prime=False):
|
|
|
379
379
|
else:
|
|
380
380
|
output.append(child)
|
|
381
381
|
return output
|
|
382
|
+
|
|
382
383
|
def compute(eq):
|
|
383
384
|
if not isinstance(eq, TreeNode):
|
|
384
385
|
return eq
|
|
@@ -388,52 +389,60 @@ def compute(eq):
|
|
|
388
389
|
elif eq.name == "s_pi":
|
|
389
390
|
return math.pi
|
|
390
391
|
elif eq.name.startswith("d_"):
|
|
391
|
-
|
|
392
|
+
try:
|
|
393
|
+
return float(eq.name[2:])
|
|
394
|
+
except ValueError:
|
|
395
|
+
return None
|
|
392
396
|
else:
|
|
393
397
|
return None
|
|
398
|
+
|
|
394
399
|
values = [compute(child) for child in eq.children]
|
|
395
400
|
if None in values:
|
|
396
401
|
return None
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
result
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
402
|
+
|
|
403
|
+
try:
|
|
404
|
+
if eq.name == "f_floor":
|
|
405
|
+
return math.floor(values[0])
|
|
406
|
+
elif eq.name == "f_ceil":
|
|
407
|
+
return math.ceil(values[0])
|
|
408
|
+
elif eq.name == "f_add":
|
|
409
|
+
return sum(values)
|
|
410
|
+
elif eq.name == "f_mod":
|
|
411
|
+
return values[0] % values[1] if values[1] != 0 else None
|
|
412
|
+
elif eq.name == "f_abs":
|
|
413
|
+
return math.fabs(values[0])
|
|
414
|
+
elif eq.name == "f_sub":
|
|
415
|
+
return values[0] - values[1]
|
|
416
|
+
elif eq.name == "f_rad":
|
|
417
|
+
return values[0] * math.pi / 180
|
|
418
|
+
elif eq.name in ["f_wmul", "f_mul"]:
|
|
419
|
+
result = 1.0
|
|
420
|
+
for v in values:
|
|
421
|
+
result *= v
|
|
422
|
+
return result
|
|
423
|
+
elif eq.name == "f_neg":
|
|
424
|
+
return -values[0]
|
|
425
|
+
elif eq.name == "f_div":
|
|
426
|
+
return values[0] / values[1] if values[1] != 0 else None
|
|
427
|
+
elif eq.name == "f_pow":
|
|
428
|
+
return math.pow(values[0], values[1])
|
|
429
|
+
elif eq.name == "f_sin":
|
|
430
|
+
return math.sin(values[0])
|
|
431
|
+
elif eq.name == "f_cos":
|
|
432
|
+
return math.cos(values[0])
|
|
433
|
+
elif eq.name == "f_tan":
|
|
434
|
+
return math.tan(values[0])
|
|
435
|
+
elif eq.name == "f_arcsin":
|
|
436
|
+
return math.asin(values[0])
|
|
437
|
+
elif eq.name == "f_arccos":
|
|
438
|
+
return math.acos(values[0])
|
|
439
|
+
elif eq.name == "f_arctan":
|
|
440
|
+
return math.atan(values[0])
|
|
441
|
+
elif eq.name == "f_log":
|
|
442
|
+
return math.log(values[0]) if values[0] > 0 else None
|
|
443
|
+
else:
|
|
444
|
+
return None
|
|
445
|
+
except (ValueError, ZeroDivisionError, IndexError):
|
|
437
446
|
return None
|
|
438
447
|
def num_dem(equation):
|
|
439
448
|
num = []
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import random
|
|
2
|
+
from .simplify import simplify
|
|
3
|
+
from .expand import expand
|
|
4
|
+
from .parser import parse
|
|
5
|
+
from .fraction import fraction
|
|
6
|
+
from .apart import apart
|
|
7
|
+
from .integrate import integrate_formula, integrate_trig_formula, integrate_fraction
|
|
8
|
+
from .integrate import integrate_subs_main as integrate_subs
|
|
9
|
+
from .integrate import solve_integrate as integrate_clean
|
|
10
|
+
from .factor import factor2
|
|
11
|
+
from .trig import trig0, trig1, trig4
|
|
12
|
+
fx_list = [simplify, expand, fraction, integrate_formula, integrate_subs, integrate_trig_formula, integrate_clean, apart, trig0, trig1, trig4, integrate_fraction, factor2]
|
|
13
|
+
name_list = "simplify, expand, fraction, integrate_formula, integrate_subs, integrate_trig_formula, integrate_clean, apart, trig0, trig1, trig4, integrate_fraction, factor2".split(", ")
|
|
14
|
+
def benchmark_ncert():
|
|
15
|
+
with open("mathai/ncert_data.txt", "r", encoding="utf-8") as file:
|
|
16
|
+
content = file.read()
|
|
17
|
+
content = content.strip().split("\n")
|
|
18
|
+
count = 0
|
|
19
|
+
for item in content:
|
|
20
|
+
count += 1
|
|
21
|
+
item = item.strip()
|
|
22
|
+
eq = simplify(parse(item.split(" ")[0]))
|
|
23
|
+
for item2 in item.split(" ")[1:]:
|
|
24
|
+
eq = fx_list[name_list.index(item2)](eq)
|
|
25
|
+
print(f"{count}. {item.split(" ")[0]} => {eq}")
|
|
@@ -10,6 +10,7 @@ pdif(cos(a),x) -sin(a)*pdif(a,x) [] x _ _ _
|
|
|
10
10
|
pdif(arcsin(a),x) (1/sqrt(1-a^2))*pdif(a,x) [] x _ _ _
|
|
11
11
|
pdif(arccos(a),x) (-1/sqrt(1-a^2))*pdif(a,x) [] x _ _ _
|
|
12
12
|
pdif(arctan(a),x) (1/(1+a^2))*pdif(a,x) [] x _ _ _
|
|
13
|
+
pdif(log(a),x) (1/a)*pdif(a,x) [] x _ _ _
|
|
13
14
|
pdif(k,x) 0 [] x k _ _
|
|
14
15
|
pdif(x,x) 1 [] x _ _ _
|
|
15
16
|
pdif(product(a,b),x) pdif(a,x)*b+a*pdif(b,x) [] x _ _ _
|
|
@@ -20,5 +21,5 @@ diff2 = formula_list_compiler(diff2_s)
|
|
|
20
21
|
|
|
21
22
|
def diff(equation, var=None):
|
|
22
23
|
var = var if var else "v_0"
|
|
23
|
-
out = diff2(simplify(TreeNode("f_pdif", [equation, tree_form(var)])))
|
|
24
|
+
out = simplify(diff2(simplify(TreeNode("f_pdif", [equation, tree_form(var)]))))
|
|
24
25
|
return out
|
|
@@ -1,14 +1,22 @@
|
|
|
1
1
|
import copy
|
|
2
2
|
import itertools
|
|
3
3
|
import textwrap
|
|
4
|
+
import warnings
|
|
4
5
|
from .base import *
|
|
5
6
|
from .simplify import simplify
|
|
6
7
|
from .parser import parse
|
|
7
8
|
from .formula_applier import (
|
|
8
9
|
extract_polynomial_coefficients, convert_string, _as_pairs, _replace_in,
|
|
9
|
-
_as_exprs, get_name, en_w_addmul, formula_applier
|
|
10
|
+
_as_exprs, get_name, en_w_addmul, formula_applier,
|
|
10
11
|
)
|
|
11
12
|
|
|
13
|
+
try:
|
|
14
|
+
from .formula_applier import _split_rules
|
|
15
|
+
except ImportError:
|
|
16
|
+
def _split_rules(s):
|
|
17
|
+
return tuple(line for line in s.strip().split("\n") if line.strip())
|
|
18
|
+
|
|
19
|
+
|
|
12
20
|
# --- CONSTANTS & CONFIGURATION ---
|
|
13
21
|
|
|
14
22
|
_W_NAMES = {
|
|
@@ -18,29 +26,29 @@ _W_NAMES = {
|
|
|
18
26
|
"f_wadd": "f_waddw",
|
|
19
27
|
"f_wmul": "f_wmulw",
|
|
20
28
|
}
|
|
21
|
-
_COMMUTATIVE = frozenset(("f_addw", "f_mulw", "f_hadamardw"))
|
|
29
|
+
_COMMUTATIVE = frozenset(("f_addw", "f_mulw", "f_hadamardw", "f_waddw"))
|
|
22
30
|
_ORDERED_ASSOC = frozenset(("f_wmulw",))
|
|
23
31
|
_LABEL_PREFIXES = frozenset(("f_", "s_", "d_"))
|
|
24
32
|
_SPECIAL_PATTERN = frozenset(("f_poly", "f_sum", "f_wsum", "f_product", "f_hprod", "f_mprod"))
|
|
25
|
-
_PATTERN_OPS = {
|
|
26
|
-
|
|
33
|
+
_PATTERN_OPS = {
|
|
34
|
+
"f_sum": "f_addw",
|
|
35
|
+
"f_product": "f_mulw",
|
|
36
|
+
"f_hprod": "f_hadamardw",
|
|
37
|
+
"f_wsum": "f_waddw",
|
|
38
|
+
}
|
|
27
39
|
_ORDERED_PATTERN_OPS = {"f_mprod": "f_wmulw"}
|
|
28
40
|
_AGG_NAMES = frozenset((
|
|
29
41
|
"f_addw", "f_add", "f_mulw", "f_mul", "f_hadamardw", "f_hadamard",
|
|
30
|
-
"f_product", "f_sum", "f_hprod", "f_mprod", "f_poly", "f_wmulw", "f_wmul",
|
|
42
|
+
"f_product", "f_sum", "f_wsum", "f_hprod", "f_mprod", "f_poly", "f_wmulw", "f_wmul",
|
|
31
43
|
))
|
|
32
44
|
_EXTRACT_NAMES = frozenset((
|
|
33
45
|
"f_add", "f_wadd", "f_waddw", "f_wmul", "f_wmulw",
|
|
34
|
-
"f_mul", "f_product", "f_sum", "
|
|
46
|
+
"f_mul", "f_product", "f_sum", "f_wsum", "f_hprod", "f_mprod", "f_poly",
|
|
35
47
|
))
|
|
36
|
-
_DE_W_NAMES = frozenset(("f_addw", "f_mulw", "f_waddw", "f_hadamardw", "f_wmulw"
|
|
48
|
+
_DE_W_NAMES = frozenset(("f_addw", "f_mulw", "f_waddw", "f_hadamardw", "f_wmulw"))
|
|
37
49
|
|
|
38
50
|
|
|
39
51
|
# --- TREE NODE CLASS ---
|
|
40
|
-
# FIX 1: `from .base import *` may already provide TreeNode (print_code2 emits
|
|
41
|
-
# `a * b`, `~a`, `.fx(...)`, which only work on that class). Redefining it here
|
|
42
|
-
# shadowed it, giving two incompatible TreeNode classes with no __eq__/__hash__.
|
|
43
|
-
# Keep base's class if it exists; otherwise use this complete fallback.
|
|
44
52
|
|
|
45
53
|
if "TreeNode" not in globals():
|
|
46
54
|
class TreeNode:
|
|
@@ -55,7 +63,6 @@ if "TreeNode" not in globals():
|
|
|
55
63
|
return repr(self.name)
|
|
56
64
|
return f"TreeNode({self.name!r}, {self.children!r})"
|
|
57
65
|
|
|
58
|
-
# structural equality (generated code relies on `==` / `!=`)
|
|
59
66
|
def __eq__(self, other):
|
|
60
67
|
if not (hasattr(other, "name") and hasattr(other, "children")):
|
|
61
68
|
return NotImplemented
|
|
@@ -69,7 +76,6 @@ if "TreeNode" not in globals():
|
|
|
69
76
|
op = "f_" + op
|
|
70
77
|
return TreeNode(op, [self])
|
|
71
78
|
|
|
72
|
-
# operators emitted by print_code2_with_varlist
|
|
73
79
|
def __add__(self, o): return TreeNode("f_add", [self, o])
|
|
74
80
|
def __radd__(self, o): return TreeNode("f_add", [o, self])
|
|
75
81
|
def __mul__(self, o): return TreeNode("f_mul", [self, o])
|
|
@@ -92,7 +98,6 @@ def generate_tree_permutations(node):
|
|
|
92
98
|
if node.name in _COMMUTATIVE:
|
|
93
99
|
seen = set()
|
|
94
100
|
for perm in itertools.permutations(combined_children):
|
|
95
|
-
# FIX 2: dedupe on a structural signature (identity hashing never matched)
|
|
96
101
|
sig = tuple(repr(c) for c in perm)
|
|
97
102
|
if sig not in seen:
|
|
98
103
|
seen.add(sig)
|
|
@@ -114,8 +119,6 @@ def de_w_addmul(eq):
|
|
|
114
119
|
|
|
115
120
|
|
|
116
121
|
def print_condition(eq, printer=None):
|
|
117
|
-
# FIX 3: take the printer as a parameter (print_code2 used to fall into
|
|
118
|
-
# print_code_h) and don't IndexError when there is no else-branch.
|
|
119
122
|
printer = printer or print_code_h
|
|
120
123
|
assert eq.name == "f_condition"
|
|
121
124
|
|
|
@@ -152,8 +155,6 @@ def print_code_h(eq):
|
|
|
152
155
|
if eq.name == "f_index":
|
|
153
156
|
return f"{print_code_h(eq.children[0])}[{eq.children[1]}]"
|
|
154
157
|
if eq.name == "f_any":
|
|
155
|
-
# NOTE: `any(A B)` is not valid Python; left as-is because the intended
|
|
156
|
-
# translation isn't clear from this file.
|
|
157
158
|
return f"any({print_code_h(eq.children[0])} {print_code_h(eq.children[1])})"
|
|
158
159
|
if eq.name == "f_list":
|
|
159
160
|
return "[" + ", ".join(print_code_h(c) for c in eq.children) + "]"
|
|
@@ -169,9 +170,6 @@ def print_code2_with_varlist(eq, varlist):
|
|
|
169
170
|
return "None"
|
|
170
171
|
if not eq.children and eq.name in varlist:
|
|
171
172
|
return varlist[eq.name]
|
|
172
|
-
# FIX 4: the unreachable s_true / s_false branches that followed this
|
|
173
|
-
# (shadowed by this check) were removed: in a tree-building context they
|
|
174
|
-
# must stay tree nodes, which is what this line produces.
|
|
175
173
|
if eq.name.startswith(("d_", "v_", "s_")):
|
|
176
174
|
return f"tree_form('{eq.name}')"
|
|
177
175
|
|
|
@@ -198,15 +196,12 @@ def print_code2_with_varlist(eq, varlist):
|
|
|
198
196
|
|
|
199
197
|
|
|
200
198
|
def print_code(eq):
|
|
201
|
-
# FIX 5: dropped the blind str.replace loop (de_w_addmul already did the
|
|
202
|
-
# renaming; the replace could corrupt longer identifiers).
|
|
203
199
|
return print_code_h(de_w_addmul(eq))
|
|
204
200
|
|
|
205
201
|
|
|
206
202
|
# --- SPECIAL PATTERN MATCHERS ---
|
|
207
203
|
|
|
208
204
|
def _split_const(children, resolved_ignore):
|
|
209
|
-
"""Split into terms free of the 'variables of interest' and terms that use them."""
|
|
210
205
|
const_t, non_t = [], []
|
|
211
206
|
for ch in children:
|
|
212
207
|
(non_t if set(vlist(ch)) & resolved_ignore else const_t).append(ch)
|
|
@@ -218,37 +213,45 @@ def _op(op_name, items):
|
|
|
218
213
|
|
|
219
214
|
|
|
220
215
|
def _is_zero(node):
|
|
221
|
-
# TreeNode == 0 is False; zero is represented as the node "d_0"
|
|
222
216
|
return node is not None and (getattr(node, "name", None) == "d_0" or node == 0)
|
|
223
217
|
|
|
224
218
|
|
|
225
219
|
def special_pattern_poly(eq, f, binding):
|
|
226
|
-
# FIX 18: poly(x, a, b) = a*x + b has 2 coefficients -> degree 1. Ask the
|
|
227
|
-
# extractor for exactly that degree (it returns degree+1 ascending
|
|
228
|
-
# coefficients, or None if eq has a higher degree) and require a nonzero
|
|
229
|
-
# leading coefficient. The old code asked for degree 2, then stripped leading
|
|
230
|
-
# zeros with `out[0] == 0`, which is False for TreeNodes.
|
|
231
220
|
n_coeffs = len(f.children) - 1
|
|
232
221
|
if n_coeffs < 1:
|
|
233
222
|
return False
|
|
223
|
+
var_name = f.children[0].name
|
|
224
|
+
|
|
225
|
+
if var_name in binding:
|
|
226
|
+
bound = binding[var_name]
|
|
227
|
+
if getattr(bound, "children", None) or not str(getattr(bound, "name", "")).startswith("v_"):
|
|
228
|
+
return False
|
|
229
|
+
candidates = [bound.name]
|
|
230
|
+
else:
|
|
231
|
+
candidates = vlist(eq)
|
|
232
|
+
|
|
234
233
|
eq_plain = de_w_addmul(eq)
|
|
235
|
-
for item in
|
|
234
|
+
for item in candidates:
|
|
236
235
|
out = extract_polynomial_coefficients(eq_plain, item, n_coeffs - 1)
|
|
237
236
|
if out is None or len(out) != n_coeffs or _is_zero(out[-1]):
|
|
238
237
|
continue
|
|
239
|
-
out = out[::-1]
|
|
238
|
+
out = out[::-1]
|
|
240
239
|
local_binding = binding.copy()
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
240
|
+
consistent = True
|
|
241
|
+
for c, val in zip(f.children[1:], out):
|
|
242
|
+
if c.name in binding and binding[c.name] != val:
|
|
243
|
+
consistent = False
|
|
244
|
+
break
|
|
245
|
+
local_binding[c.name] = val
|
|
246
|
+
if not consistent:
|
|
247
|
+
continue
|
|
248
|
+
local_binding[var_name] = TreeNode(item)
|
|
244
249
|
binding.update(local_binding)
|
|
245
250
|
return True
|
|
246
251
|
return False
|
|
247
252
|
|
|
248
253
|
|
|
249
254
|
def special_pattern(eq, f, binding, const_1_vars, resolved_ignore):
|
|
250
|
-
# FIX 6: the old code had two near-identical branches, accepted patterns with
|
|
251
|
-
# >2 children but only bound two of them, and used a strict-subset test.
|
|
252
255
|
if eq is None or f is None:
|
|
253
256
|
return False
|
|
254
257
|
if f.name == "f_poly":
|
|
@@ -272,8 +275,8 @@ def special_pattern(eq, f, binding, const_1_vars, resolved_ignore):
|
|
|
272
275
|
if not const_t:
|
|
273
276
|
return False
|
|
274
277
|
if not non_t:
|
|
275
|
-
binding[
|
|
276
|
-
binding[
|
|
278
|
+
binding[const_name] = const_t[0]
|
|
279
|
+
binding[other_name] = _op(op_name, const_t[1:])
|
|
277
280
|
else:
|
|
278
281
|
binding[const_name] = _op(op_name, const_t)
|
|
279
282
|
binding[other_name] = _op(op_name, non_t)
|
|
@@ -316,15 +319,17 @@ def special_pattern_router(fname, eq, f=None, binding=None, const_1_vars=None, r
|
|
|
316
319
|
return special_pattern_ordered(eq, f, binding, const_1_vars, resolved_ignore) if f else False
|
|
317
320
|
if fname in _PATTERN_OPS:
|
|
318
321
|
return special_pattern(eq, f, binding, const_1_vars, resolved_ignore) if f else False
|
|
322
|
+
if fname in _SPECIAL_PATTERN:
|
|
323
|
+
raise ValueError(
|
|
324
|
+
f"special pattern {fname!r} is not registered in _PATTERN_OPS / _ORDERED_PATTERN_OPS"
|
|
325
|
+
)
|
|
319
326
|
return True
|
|
320
327
|
|
|
321
328
|
|
|
322
329
|
# --- TWO-STEP COMPILER ---
|
|
323
330
|
|
|
324
331
|
def _consumed(form_node):
|
|
325
|
-
|
|
326
|
-
(an ordered-assoc child consumes two)."""
|
|
327
|
-
return sum(2 if c.name in _ORDERED_ASSOC else 1 for c in form_node.children)
|
|
332
|
+
return len(form_node.children)
|
|
328
333
|
|
|
329
334
|
|
|
330
335
|
def _node_src(node):
|
|
@@ -340,9 +345,6 @@ def compile_permutation_matcher_two_step(
|
|
|
340
345
|
negative=None,
|
|
341
346
|
forbidden_list=None,
|
|
342
347
|
):
|
|
343
|
-
"""Returns the *body* of a function of `eq`: it returns the rewritten tree
|
|
344
|
-
on a match and None otherwise. Conditions are built as plain Python
|
|
345
|
-
expression strings joined with short-circuiting `and`."""
|
|
346
348
|
ignore_list = ignore_list or []
|
|
347
349
|
const_1 = const_1 or []
|
|
348
350
|
positive = positive or []
|
|
@@ -350,24 +352,20 @@ def compile_permutation_matcher_two_step(
|
|
|
350
352
|
forbidden_list = forbidden_list or []
|
|
351
353
|
|
|
352
354
|
ignore_set = {get_name(i) for i in ignore_list}
|
|
353
|
-
# FIX 7: const_1 holds TreeNodes; the old `key in const_1` compared a str to
|
|
354
|
-
# TreeNodes and was never true.
|
|
355
355
|
const_1_set = {get_name(i) for i in const_1}
|
|
356
356
|
|
|
357
|
-
varlist = {}
|
|
358
|
-
specials = []
|
|
359
|
-
late_eq = []
|
|
357
|
+
varlist = {}
|
|
358
|
+
specials = []
|
|
359
|
+
late_eq = []
|
|
360
|
+
struct_expr = {}
|
|
360
361
|
|
|
361
362
|
def helper(expr, form_node):
|
|
362
363
|
name = form_node.name
|
|
363
364
|
|
|
364
365
|
if name in _SPECIAL_PATTERN:
|
|
365
|
-
# FIX 8: special patterns were emitted as router calls with no pattern,
|
|
366
|
-
# no binding dict -> always False and never bound anything.
|
|
367
366
|
specials.append((expr, form_node))
|
|
368
367
|
for c in form_node.children:
|
|
369
368
|
if c.name in varlist:
|
|
370
|
-
# FIX 16: var already bound structurally -> the router's binding must agree
|
|
371
369
|
late_eq.append(f"{varlist[c.name]} == _b[{c.name!r}]")
|
|
372
370
|
else:
|
|
373
371
|
varlist[c.name] = f"_b[{c.name!r}]"
|
|
@@ -376,37 +374,22 @@ def compile_permutation_matcher_two_step(
|
|
|
376
374
|
if name.startswith("v_"):
|
|
377
375
|
if name in varlist:
|
|
378
376
|
if varlist[name].startswith("_b["):
|
|
379
|
-
# FIX 17: `_b[...]` doesn't exist until the router has run, so this
|
|
380
|
-
# comparison must come after it (it used to be inline -> KeyError)
|
|
381
377
|
late_eq.append(f"{varlist[name]} == {expr}")
|
|
378
|
+
struct_expr.setdefault(name, expr)
|
|
382
379
|
return "True"
|
|
383
380
|
return f"{varlist[name]} == {expr}"
|
|
384
381
|
varlist[name] = expr
|
|
382
|
+
struct_expr[name] = expr
|
|
385
383
|
return "True"
|
|
386
384
|
|
|
387
385
|
n = _consumed(form_node)
|
|
388
|
-
access = [f"{expr}.children[{i}]" for i in range(n)]
|
|
389
|
-
child_exprs, pos = [], 0
|
|
390
|
-
for child in form_node.children:
|
|
391
|
-
if child.name in _ORDERED_ASSOC:
|
|
392
|
-
# FIX 9: wrap with the *child's* name (was the parent's name)
|
|
393
|
-
child_exprs.append(f"TreeNode({child.name!r}, [{access[pos]}, {access[pos + 1]}])")
|
|
394
|
-
pos += 2
|
|
395
|
-
else:
|
|
396
|
-
child_exprs.append(access[pos])
|
|
397
|
-
pos += 1
|
|
398
|
-
|
|
399
|
-
# FIX 10: length check uses the number of children actually consumed
|
|
400
386
|
checks = [f"{expr}.name == {name!r}", f"len({expr}.children) == {n}"]
|
|
401
|
-
for
|
|
402
|
-
sub = helper(
|
|
387
|
+
for i, child in enumerate(form_node.children):
|
|
388
|
+
sub = helper(f"{expr}.children[{i}]", child)
|
|
403
389
|
if sub != "True":
|
|
404
390
|
checks.append(sub)
|
|
405
391
|
return "(" + " and ".join(checks) + ")"
|
|
406
392
|
|
|
407
|
-
# FIX 11: the old `form_node.count` permutation hack enumerated n! orderings per
|
|
408
|
-
# node just to pick the last one. Every pattern ordering is already produced by
|
|
409
|
-
# generate_tree_permutations, so matching children positionally is enough.
|
|
410
393
|
match = helper("eq", tree_node)
|
|
411
394
|
|
|
412
395
|
special_bound = {c.name for _, f in specials for c in f.children}
|
|
@@ -415,15 +398,16 @@ def compile_permutation_matcher_two_step(
|
|
|
415
398
|
|
|
416
399
|
conds = [match]
|
|
417
400
|
|
|
418
|
-
# ignore-vars must be bare variables
|
|
419
401
|
for e in struct_ignore:
|
|
420
402
|
conds.append(f"({e}.name.startswith('v_') and not {e}.children)")
|
|
421
403
|
|
|
422
|
-
# FIX 12: `ignore` used to collect *every* v_ variable (and mid-traversal); it is
|
|
423
|
-
# now the ignore_list variables only, and special patterns get real variable names.
|
|
424
404
|
c1_src = "set([" + ", ".join(repr(k) for k in sorted(const_1_set)) + "])"
|
|
425
405
|
ig_src = "set([" + ", ".join(f"{e}.name" for e in struct_ignore) + "])"
|
|
426
406
|
for expr, fnode in specials:
|
|
407
|
+
seed = {c.name: struct_expr[c.name] for c in fnode.children if c.name in struct_expr}
|
|
408
|
+
if seed:
|
|
409
|
+
items = ", ".join(f"{k!r}: {v}" for k, v in seed.items())
|
|
410
|
+
conds.append(f"(_b.update({{{items}}}) or True)")
|
|
427
411
|
conds.append(
|
|
428
412
|
f"special_pattern_router({fnode.name!r}, {expr}, {_node_src(fnode)}, _b, {c1_src}, {ig_src})"
|
|
429
413
|
)
|
|
@@ -439,7 +423,6 @@ def compile_permutation_matcher_two_step(
|
|
|
439
423
|
if k in varlist:
|
|
440
424
|
conds.append(f"all(not contain({varlist[k]}, item) for item in {ig_list})")
|
|
441
425
|
|
|
442
|
-
# FIX 13: forbidden values were interpolated with repr() -> `x != 'd_0'`
|
|
443
426
|
for lhs, val in forbidden_list:
|
|
444
427
|
if lhs.name in varlist:
|
|
445
428
|
conds.append(f"{varlist[lhs.name]} != {print_code2_with_varlist(val, varlist)}")
|
|
@@ -468,7 +451,7 @@ def indent_block(code_str, spaces=4):
|
|
|
468
451
|
return textwrap.indent(code_str, " " * spaces)
|
|
469
452
|
|
|
470
453
|
|
|
471
|
-
# --- CONTAINER
|
|
454
|
+
# --- CONTAINER MATCH BLOCKS ---
|
|
472
455
|
|
|
473
456
|
def _commutative_block(op, k, fn):
|
|
474
457
|
plain = op[:-1]
|
|
@@ -503,7 +486,6 @@ def _ordered_block(op, k, fn):
|
|
|
503
486
|
|
|
504
487
|
|
|
505
488
|
def formula_compiler(s):
|
|
506
|
-
"""Compile a rule string into a transformation function."""
|
|
507
489
|
item = s.split(" ")
|
|
508
490
|
a, b = (simplify(parse(x)) for x in item[:2])
|
|
509
491
|
|
|
@@ -559,10 +541,6 @@ def formula_compiler(s):
|
|
|
559
541
|
negative=_as_exprs(neg),
|
|
560
542
|
forbidden_list=_as_pairs(forb),
|
|
561
543
|
)
|
|
562
|
-
# FIX 14: every matcher is its own top-level function with a parameter
|
|
563
|
-
# named `eq` (the generated code refers to `eq`, but the old wrapper
|
|
564
|
-
# called its parameter `sub_eq`), and indentation is done with
|
|
565
|
-
# textwrap instead of multi-line f-string interpolation.
|
|
566
544
|
fn = f"_m{len(fn_defs)}"
|
|
567
545
|
fn_defs.append(f"def {fn}(eq):\n" + indent_block(body, 4))
|
|
568
546
|
|
|
@@ -573,10 +551,12 @@ def formula_compiler(s):
|
|
|
573
551
|
else:
|
|
574
552
|
blocks.append(f"r = {fn}(eq)\nif r is not None:\n return r\n")
|
|
575
553
|
|
|
554
|
+
if not fn_defs:
|
|
555
|
+
warnings.warn(f"rule produced no matchers (simplify returned None?): {s!r}")
|
|
556
|
+
|
|
576
557
|
src = "\n\n".join(fn_defs) + "\n\n"
|
|
577
558
|
src += "def transform(eq_orig):\n eq = copy.deepcopy(eq_orig)\n"
|
|
578
559
|
for blk in blocks:
|
|
579
|
-
# FIX 15: blocks used to be appended at column 0, i.e. outside the def
|
|
580
560
|
src += indent_block(blk, 4) + "\n"
|
|
581
561
|
src += " return eq_orig\n"
|
|
582
562
|
|
|
@@ -604,38 +584,27 @@ def formula_compiler(s):
|
|
|
604
584
|
def apply(x):
|
|
605
585
|
w = en_w_addmul(x)
|
|
606
586
|
out = env["transform"](w)
|
|
607
|
-
if out is w:
|
|
587
|
+
if out is w:
|
|
608
588
|
return x
|
|
609
|
-
return None if out is None else de_w_addmul(out)
|
|
589
|
+
return None if out is None else de_w_addmul(out)
|
|
610
590
|
|
|
611
|
-
apply.source = src
|
|
591
|
+
apply.source = src
|
|
612
592
|
return apply
|
|
613
593
|
|
|
614
594
|
|
|
615
|
-
|
|
595
|
+
formula_compiler_from_rule_string = formula_compiler
|
|
616
596
|
|
|
617
|
-
try:
|
|
618
|
-
from .formula_applier import _split_rules
|
|
619
|
-
except ImportError:
|
|
620
|
-
def _split_rules(s):
|
|
621
|
-
return tuple(line for line in s.strip().split("\n") if line.strip())
|
|
622
597
|
|
|
598
|
+
# --- RULE LISTS ---
|
|
623
599
|
|
|
624
600
|
def transform_dfs_changed(root, func, arg=None, key=None):
|
|
625
|
-
"""Iterative post-order transform that
|
|
626
|
-
* only rebuilds a node if one of its children actually changed,
|
|
627
|
-
* re-normalises a node's replacement when func changes it (so no outer
|
|
628
|
-
dowhile is needed), and
|
|
629
|
-
* never processes the same subtree twice: every subtree that has reached a
|
|
630
|
-
fixed point is cached by structural key and reused.
|
|
631
|
-
`func(node, *arg)` may return None or the same node to mean 'unchanged'."""
|
|
632
601
|
if root is None:
|
|
633
602
|
return None
|
|
634
603
|
arg = () if arg is None else arg
|
|
635
604
|
key = key or str_form
|
|
636
|
-
stable = {}
|
|
637
|
-
in_progress = set()
|
|
638
|
-
values = []
|
|
605
|
+
stable = {}
|
|
606
|
+
in_progress = set()
|
|
607
|
+
values = []
|
|
639
608
|
stack = [("visit", root, None)]
|
|
640
609
|
|
|
641
610
|
def finish(sig, cur, out):
|
|
@@ -652,7 +621,7 @@ def transform_dfs_changed(root, func, arg=None, key=None):
|
|
|
652
621
|
if sig in stable:
|
|
653
622
|
values.append(stable[sig])
|
|
654
623
|
continue
|
|
655
|
-
if sig in in_progress:
|
|
624
|
+
if sig in in_progress:
|
|
656
625
|
values.append(node)
|
|
657
626
|
continue
|
|
658
627
|
in_progress.add(sig)
|
|
@@ -664,44 +633,37 @@ def transform_dfs_changed(root, func, arg=None, key=None):
|
|
|
664
633
|
kids = values[len(values) - n:]
|
|
665
634
|
del values[len(values) - n:]
|
|
666
635
|
if all(a is b for a, b in zip(kids, node.children)):
|
|
667
|
-
cur = node
|
|
636
|
+
cur = node
|
|
668
637
|
else:
|
|
669
638
|
cur = TreeNode(node.name, kids)
|
|
670
639
|
res = func(cur, *arg)
|
|
671
640
|
if res is None or res is cur or key(res) == key(cur):
|
|
672
|
-
finish(sig, cur, cur)
|
|
641
|
+
finish(sig, cur, cur)
|
|
673
642
|
else:
|
|
674
643
|
stack.append(("record", cur, sig))
|
|
675
|
-
stack.append(("visit", res, None))
|
|
676
|
-
else:
|
|
644
|
+
stack.append(("visit", res, None))
|
|
645
|
+
else:
|
|
677
646
|
finish(sig, node, values.pop())
|
|
678
647
|
return values[-1]
|
|
679
|
-
|
|
648
|
+
|
|
649
|
+
|
|
680
650
|
def formula_list_compiler(s, mode="fixpoint"):
|
|
681
|
-
"""Compile a multi-line rule string (one rule per line) into one function.
|
|
682
|
-
mode="fixpoint": transform_dfs_changed (skips unchanged / already-stable nodes)
|
|
683
|
-
mode="dowhile" : whole-tree dowhile(transform_dfs(...)) loop
|
|
684
|
-
A line starting with "@" is always handled by formula_applier (the "@" is stripped);
|
|
685
|
-
other lines go there only if _needs_applier() says so."""
|
|
686
|
-
|
|
687
651
|
rules = []
|
|
688
652
|
for line in _split_rules(s):
|
|
689
653
|
line = line.strip()
|
|
690
|
-
|
|
691
|
-
if forced:
|
|
654
|
+
if line.startswith("@"):
|
|
692
655
|
line = line[1:].strip()
|
|
693
|
-
if forced:
|
|
694
656
|
rules.append(lambda x, line=line: formula_applier(x, line))
|
|
695
657
|
else:
|
|
696
658
|
rules.append(formula_compiler(line))
|
|
697
|
-
|
|
659
|
+
|
|
698
660
|
def node_fn(node):
|
|
699
661
|
for rule in rules:
|
|
700
662
|
r = rule(node)
|
|
701
663
|
if r is not None:
|
|
702
664
|
node = r
|
|
703
665
|
return node
|
|
704
|
-
|
|
666
|
+
|
|
705
667
|
if mode == "dowhile":
|
|
706
668
|
def apply(eq):
|
|
707
669
|
if eq is None:
|
|
@@ -712,7 +674,7 @@ def formula_list_compiler(s, mode="fixpoint"):
|
|
|
712
674
|
return None if eq is None else transform_dfs_changed(eq, node_fn)
|
|
713
675
|
else:
|
|
714
676
|
raise ValueError("mode must be 'fixpoint' or 'dowhile'")
|
|
715
|
-
|
|
677
|
+
|
|
716
678
|
apply.rules = rules
|
|
717
679
|
apply.node_fn = node_fn
|
|
718
680
|
apply.sources = [getattr(r, "source", None) for r in rules]
|