mathai 2.3.5__tar.gz → 2.3.7__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.
Files changed (41) hide show
  1. {mathai-2.3.5 → mathai-2.3.7}/PKG-INFO +1 -1
  2. {mathai-2.3.5 → mathai-2.3.7}/mathai/__init__.py +6 -10
  3. mathai-2.3.7/mathai/decision.py +134 -0
  4. mathai-2.3.7/mathai/diff.py +179 -0
  5. {mathai-2.3.5 → mathai-2.3.7}/mathai/formula_applier.py +98 -53
  6. {mathai-2.3.5 → mathai-2.3.7}/mathai/integrate.py +19 -274
  7. {mathai-2.3.5 → mathai-2.3.7}/mathai/linear.py +36 -0
  8. {mathai-2.3.5 → mathai-2.3.7}/mathai/matrix.py +202 -124
  9. {mathai-2.3.5 → mathai-2.3.7}/mathai/qm.py +2 -3
  10. {mathai-2.3.5 → mathai-2.3.7}/mathai/simplify.py +8 -0
  11. {mathai-2.3.5 → mathai-2.3.7}/mathai.egg-info/PKG-INFO +1 -1
  12. {mathai-2.3.5 → mathai-2.3.7}/setup.py +1 -1
  13. mathai-2.3.5/mathai/decision.py +0 -355
  14. mathai-2.3.5/mathai/diff.py +0 -24
  15. {mathai-2.3.5 → mathai-2.3.7}/README.md +0 -0
  16. {mathai-2.3.5 → mathai-2.3.7}/mathai/apart.py +0 -0
  17. {mathai-2.3.5 → mathai-2.3.7}/mathai/base.py +0 -0
  18. {mathai-2.3.5 → mathai-2.3.7}/mathai/electronic.py +0 -0
  19. {mathai-2.3.5 → mathai-2.3.7}/mathai/expand.py +0 -0
  20. {mathai-2.3.5 → mathai-2.3.7}/mathai/factor.py +0 -0
  21. {mathai-2.3.5 → mathai-2.3.7}/mathai/fraction.py +0 -0
  22. {mathai-2.3.5 → mathai-2.3.7}/mathai/graphics.py +0 -0
  23. {mathai-2.3.5 → mathai-2.3.7}/mathai/inverse.py +0 -0
  24. {mathai-2.3.5 → mathai-2.3.7}/mathai/lambda_calculus.py +0 -0
  25. {mathai-2.3.5 → mathai-2.3.7}/mathai/limit.py +0 -0
  26. {mathai-2.3.5 → mathai-2.3.7}/mathai/logic.py +0 -0
  27. {mathai-2.3.5 → mathai-2.3.7}/mathai/ml.py +0 -0
  28. {mathai-2.3.5 → mathai-2.3.7}/mathai/mlscalar.py +0 -0
  29. {mathai-2.3.5 → mathai-2.3.7}/mathai/ode.py +0 -0
  30. {mathai-2.3.5 → mathai-2.3.7}/mathai/parser.py +0 -0
  31. {mathai-2.3.5 → mathai-2.3.7}/mathai/pde.py +0 -0
  32. {mathai-2.3.5 → mathai-2.3.7}/mathai/printeq.py +0 -0
  33. {mathai-2.3.5 → mathai-2.3.7}/mathai/statistics.py +0 -0
  34. {mathai-2.3.5 → mathai-2.3.7}/mathai/test.py +0 -0
  35. {mathai-2.3.5 → mathai-2.3.7}/mathai/tool.py +0 -0
  36. {mathai-2.3.5 → mathai-2.3.7}/mathai/trig.py +0 -0
  37. {mathai-2.3.5 → mathai-2.3.7}/mathai/univariate_inequality.py +0 -0
  38. {mathai-2.3.5 → mathai-2.3.7}/mathai.egg-info/SOURCES.txt +0 -0
  39. {mathai-2.3.5 → mathai-2.3.7}/mathai.egg-info/dependency_links.txt +0 -0
  40. {mathai-2.3.5 → mathai-2.3.7}/mathai.egg-info/top_level.txt +0 -0
  41. {mathai-2.3.5 → mathai-2.3.7}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mathai
3
- Version: 2.3.5
3
+ Version: 2.3.7
4
4
  Summary: Mathematics solving Ai tailored to NCERT
5
5
  Home-page: https://github.com/infinity390/mathai
6
6
  Requires-Python: >=3.7
@@ -5,7 +5,7 @@ from .pde import solve_pde
5
5
 
6
6
  from .qm import hydrogen_gse, helium_gse, power_rule
7
7
 
8
- from .graphics import draw_graphics_from_solution
8
+ from .graphics import draw_graphics_from_solution, solve_graphics, solve_graphics_string
9
9
 
10
10
  from .lambda_calculus import lambda_expand, lambda_compress, lambda_reduce
11
11
 
@@ -15,9 +15,7 @@ from .linear import linear_solve
15
15
 
16
16
  from .test import test
17
17
 
18
- from .decision import god, trig7
19
-
20
- from .graphics import solve_graphics, solve_graphics_string
18
+ from .decision import god
21
19
 
22
20
  from .expand import expand
23
21
 
@@ -32,12 +30,10 @@ from .formula_applier import formula_applier, formula_list_applier
32
30
 
33
31
  from .integrate import integrate_subs_main as integrate_subs
34
32
  from .integrate import byparts as integrate_byparts
35
- from .integrate import sqint as integrate_fraction
36
- from .integrate import conv_int
33
+ from .integrate import integrate_fraction
37
34
  from .integrate import solve_integrate as integrate_clean
38
- from .integrate import integrate_formula, integrate_trig_formula, integrate_qm_formula, integrate_full, integrate_definite
35
+ from .integrate import integrate_formula, integrate_trig_formula, integrate_full, def_integrate_formula
39
36
  from .integrate import normalize as integration_basic
40
- from .integrate import normalize_qm as integration_qm_basic
41
37
 
42
38
  from .diff import diff, diff2
43
39
 
@@ -54,11 +50,11 @@ from .inverse import inverse, inverse2
54
50
  from .logic import logic0, set_sub, truth_gen, solve_logically
55
51
  from .apart import apart, apart2
56
52
 
57
- from .limit import limit1, limit2, limit0, limit3, limit4, solve_inf
53
+ from .limit import limit1, limit2, limit0, limit3, limit4, solve_inf, limit5
58
54
 
59
55
  from .univariate_inequality import wavycurvy, absolute, domain, handle_sqrt, prepare, eq2range, range2eq2, simple_wavycurvy
60
56
 
61
- from .matrix import matrix_solve
57
+ from .matrix import matrix_solve, matrix_formula
62
58
 
63
59
  from .base import *
64
60
  from .printeq import printeq_obj, print_raw
@@ -0,0 +1,134 @@
1
+ import math
2
+ from .inverse import inverse, inverse2
3
+ from .integrate import integrate_full, def_integrate_formula
4
+ from .ode import diffsolve as ode_solve
5
+ from .parser import parse
6
+ from .simplify import simplify, log0
7
+ from .base import *
8
+ from .diff import diff, diff2
9
+ from .trig import trig0, trig1, zu_simplify
10
+ from .univariate_inequality import simple_wavycurvy, wavycurvy, prepare, absolute, handle_sqrt, eq2range, domain, Range
11
+ from .fraction import fraction
12
+ from .expand import expand
13
+ from .logic import logic0, set_sub, truth_gen, solve_logically
14
+ from .factor import factor2, factor
15
+ from .limit import limit1, limit5
16
+ from .linear import linear_solve
17
+ from .matrix import matrix_solve, matrix_formula
18
+
19
+ def vlist_2(eq):
20
+ out = []
21
+ if eq.name[2:] in "det adj dim".split(" ") and eq.children[0].name[:2] == "v_":
22
+ return [eq]
23
+ if eq.name[:2] == "v_":
24
+ return [eq]
25
+ for child in eq.children:
26
+ out += vlist_2(child)
27
+ return list(set(out))
28
+ def convert_single_solution(eq):
29
+ if eq.name == "f_range":
30
+ out = eq2range(eq)
31
+ if out.r == [False] and len(out.p) == 1:
32
+ return TreeNode("f_eq", [out.variable - out.p[0], TreeNode("d_0")])
33
+ return eq
34
+ def solve_subs(q):
35
+ solution = {}
36
+ orig = copy.deepcopy(q)
37
+ if q.name != "f_and":
38
+ q = TreeNode("f_and", [q])
39
+ lst = q.children
40
+ if not all(item.name == "f_eq" for item in lst):
41
+ return orig
42
+ q = simplify(q)
43
+ done = True
44
+ while done:
45
+ done = False
46
+ for i in range(len(q.children)):
47
+ item = q.children[i]
48
+ for key, item2 in solution.items():
49
+ item = replace(item, tree_form(key), item2)
50
+ item = fraction(expand(trig0(simplify(item))))
51
+ q.children[i] = item
52
+ vars_in_item = vlist_2(item)
53
+ if len(vars_in_item) == 1:
54
+ target_var = vars_in_item[0]
55
+ tmp = inverse(item.children[0], str_form(target_var))
56
+ if tmp is not None:
57
+ solution[str_form(target_var)] = tmp
58
+ done = True
59
+ break
60
+ if len(solution.keys()) == 0:
61
+ return orig
62
+ return operation("f_and", [item for item in lst if all(not contain(item, tree_form(key)) for key in solution.keys())] + [TreeNode("f_eq", [tree_form(key), item]) for key, item in solution.items()])
63
+ def god(string):
64
+ print(f"? {string}")
65
+ print("thinking...")
66
+ eq = None
67
+ eq = parse(string)
68
+ log = [eq]
69
+ if "f_pdif" in str_form(eq):
70
+ eq = diff2(eq)
71
+ if "f_limit" in str_form(eq):
72
+ eq = limit1(limit5(eq))
73
+ if any(contain2(eq, "f_"+item) for item in "adj det list transpose".split(" ")):
74
+ eq = matrix_formula(eq)
75
+ print(eq)
76
+ eq = matrix_solve(eq)
77
+ print(eq)
78
+ eq = solve_subs(eq)
79
+ print(f"=> {eq}")
80
+ print()
81
+ return eq
82
+ elif all(not contain2(eq, "f_"+item) for item in "dif add mul abs pow dif integrate arcsin sin cos log limit eq lt le ge gt".split(" ")) and\
83
+ any(contain2(eq, "f_"+item) for item in "and or not imply equiv".split(" ")):
84
+ eq = solve_logically(truth_gen(simplify(set_sub(eq))))
85
+ print(f"=> {eq}")
86
+ print()
87
+ return eq
88
+ elif not (eq.name == "f_and" and len(vlist(eq)) > 1) and any(contain2(eq, "f_"+item) for item in "eq lt le ge gt".split(" ")) and all(not contain2(eq, "f_"+item) for item in "limit dif integrate".split(" ")):
89
+ lst = [simplify, log0, simplify, lambda x: dowhile(x, absolute), lambda x: dowhile(x, lambda y: simplify(expand(y))), lambda x: dowhile(x, lambda y: simplify(fraction(y))), logic0, simple_wavycurvy, simple_wavycurvy]
90
+ fx2 = lambda x: dowhile(x, lambda y: simplify(fraction(y)))
91
+ fx3 = lambda x: dowhile(x, trig1)
92
+ lst2 = [simplify, trig0, lambda x: dowhile(x, lambda y: fx2(fx3(y))), solve_subs, logic0]
93
+ sel = lst.copy()
94
+ if any(contain2(eq, "f_"+item) for item in "sin cos tan cosec sec cot".split(" ")) or\
95
+ len(vlist(eq)) > 1:
96
+ sel = lst2
97
+ for item in sel:
98
+ eq = item(eq)
99
+ if eq not in log:
100
+ log.append(eq)
101
+ print(eq)
102
+ # eq = trig7(eq)
103
+ print(f"=> {eq}")
104
+ print()
105
+ return eq
106
+ elif "f_dif" in str_form(eq) or "f_integrate" in str_form(eq):
107
+ if "f_dif" in str_form(eq) and "f_integrate" not in str_form(eq):
108
+ eq = simplify(ode_solve(simplify(eq)))
109
+ log.append(eq)
110
+ if "f_integrate" in str_form(eq):
111
+ eq = def_integrate_formula(eq)
112
+ eq = integrate_full(eq)
113
+ eq = limit5(eq)
114
+ eq = limit1(eq)
115
+ eq = simplify(expand(simplify(eq)))
116
+ print(f"=> {eq}")
117
+ print()
118
+ return eq
119
+ if any("f_"+item in str_form(eq) for item in "eq lt le ge gt".split(" ")):
120
+ eq = simple_wavycurvy(eq)
121
+ eq = transform_dfs(eq, convert_single_solution)
122
+ out = linear_solve(eq)
123
+ if out is not None:
124
+ eq = out
125
+ print(eq)
126
+ eq = solve_subs(eq)
127
+ out = linear_solve(eq)
128
+ if out is not None:
129
+ eq = out
130
+ eq = simple_wavycurvy(eq)
131
+ eq = simplify(expand(simplify(fraction(simplify(eq)))))
132
+ print(f"=> {eq}")
133
+ print()
134
+ return eq
@@ -0,0 +1,179 @@
1
+ from .simplify import simplify
2
+ from .base import *
3
+ from .parser import parse
4
+ from .formula_applier import formula_list_applier
5
+
6
+ diff2_s = """pdif(e^a,x) e^a*pdif(a,x) [] x _ _ _
7
+ pdif(a^b,x) b*(a^(b-1))*pdif(a,x)+(a^b)*log(a)*pdif(b,x) [] x _ _ _
8
+ pdif(sin(a),x) cos(a)*pdif(a,x) [] x _ _ _
9
+ pdif(cos(a),x) -sin(a)*pdif(a,x) [] x _ _ _
10
+ pdif(arcsin(a),x) (1/sqrt(1-a^2))*pdif(a,x) [] x _ _ _
11
+ pdif(arccos(a),x) (-1/sqrt(1-a^2))*pdif(a,x) [] x _ _ _
12
+ pdif(arctan(a),x) (1/(1+a^2))*pdif(a,x) [] x _ _ _
13
+ pdif(k,x) 0 [] x k _ _
14
+ pdif(x,x) 1 [] x _ _ _
15
+ pdif(product(a,b),x) pdif(a,x)*b+a*pdif(b,x) [] x _ _ _
16
+ pdif(sum(a,b),x) pdif(a,x)+pdif(b,x) [] x _ _ _
17
+ pdif(product(k,a),x) k*pdif(a,x) [] x k _ _"""
18
+
19
+ diff2 = lambda x: formula_list_applier(x, diff2_s)
20
+ '''
21
+ def diff(equation, var=None):
22
+ var = var if var else "v_0"
23
+ out = diff2(simplify(TreeNode("f_pdif", [equation, tree_form(var)])))
24
+ return out
25
+ '''
26
+ def diff(equation, var="v_0"):
27
+ def diffeq_iterative(root_eq):
28
+ stack = [(root_eq, False)]
29
+ results = {}
30
+ while stack:
31
+ eq, processed = stack.pop()
32
+ if eq in results:
33
+ continue
34
+ if not processed:
35
+ eq_simplified = simplify(eq)
36
+ if "v_" not in str_form(eq_simplified):
37
+ results[eq] = tree_form("d_0")
38
+ continue
39
+ stack.append((eq, True))
40
+ if eq.name in ("f_add", "f_mul"):
41
+ for child in eq.children:
42
+ if child not in results:
43
+ stack.append((child, False))
44
+ elif eq.name in ("f_abs", "f_tan", "f_log", "f_arcsin", "f_arccos", "f_arctan", "f_sin", "f_cos"):
45
+ child = eq.children[0]
46
+ if child not in results:
47
+ stack.append((child, False))
48
+ elif eq.name == "f_pow":
49
+ for child in eq.children:
50
+ if child not in results:
51
+ stack.append((child, False))
52
+ else:
53
+ if "v_" not in str_form(eq):
54
+ results[eq] = tree_form("d_0")
55
+ continue
56
+ if eq.name == "f_add":
57
+ add_res = tree_form("d_0")
58
+ for child in eq.children:
59
+ add_res = add_res + results.get(child, tree_form("d_0"))
60
+ results[eq] = add_res
61
+ elif eq.name == "f_abs":
62
+ u = eq.children[0]
63
+ du = results.get(u, tree_form("d_0"))
64
+ results[eq] = du * u / eq
65
+ elif eq.name == "f_pow" and eq.children[0].name == "s_e":
66
+ u = eq.children[1]
67
+ du = results.get(u, tree_form("d_0"))
68
+ results[eq] = du * eq
69
+ elif eq.name == "f_tan":
70
+ u = eq.children[0]
71
+ du = results.get(u, tree_form("d_0"))
72
+ cos_u = u.fx("cos")
73
+ results[eq] = du / (cos_u * cos_u)
74
+ elif eq.name == "f_log":
75
+ u = eq.children[0]
76
+ du = results.get(u, tree_form("d_0"))
77
+ results[eq] = du * (tree_form("d_1") / u)
78
+ elif eq.name == "f_arcsin":
79
+ u = eq.children[0]
80
+ du = results.get(u, tree_form("d_0"))
81
+ one = tree_form("d_1")
82
+ half = tree_form("d_2") ** tree_form("d_-1")
83
+ results[eq] = du / ((one - u * u) ** half)
84
+ elif eq.name == "f_arccos":
85
+ u = eq.children[0]
86
+ du = results.get(u, tree_form("d_0"))
87
+ one = tree_form("d_1")
88
+ half = tree_form("d_2") ** tree_form("d_-1")
89
+ results[eq] = tree_form("d_-1") * du / ((one - u * u) ** half)
90
+ elif eq.name == "f_arctan":
91
+ u = eq.children[0]
92
+ du = results.get(u, tree_form("d_0"))
93
+ results[eq] = du / (tree_form("d_1") + u * u)
94
+ elif eq.name == "f_pow" and "v_" in str_form(eq.children[1]):
95
+ u, v = eq.children
96
+ du = results.get(u, tree_form("d_0"))
97
+ dv = results.get(v, tree_form("d_0"))
98
+ term1 = (v / u) * du
99
+ term2 = u.fx("log") * dv
100
+ results[eq] = (u ** v) * (term1 + term2)
101
+ elif eq.name == "f_mul":
102
+ add_res = tree_form("d_0")
103
+ n_children = len(eq.children)
104
+ for i in range(n_children):
105
+ target_child = eq.children[i]
106
+ d_target = results.get(target_child, tree_form("d_0"))
107
+ other_terms = [eq.children[j] for j in range(n_children) if j != i]
108
+ if len(other_terms) == 1:
109
+ other_prod = other_terms[0]
110
+ else:
111
+ other_prod = TreeNode("f_mul", other_terms)
112
+ add_res = add_res + (d_target * other_prod)
113
+ results[eq] = add_res
114
+ elif eq.name == "f_sin":
115
+ u = eq.children[0]
116
+ du = results.get(u, tree_form("d_0"))
117
+ results[eq] = du * u.fx("cos")
118
+ elif eq.name == "f_cos":
119
+ u = eq.children[0]
120
+ du = results.get(u, tree_form("d_0"))
121
+ results[eq] = tree_form("d_-1") * du * u.fx("sin")
122
+ elif eq.name[:2] == "v_":
123
+ results[eq] = TreeNode("f_dif", [eq])
124
+ elif eq.name == "f_pow" and "v_" not in str_form(eq.children[1]):
125
+ base, power = eq.children
126
+ dbase = results.get(base, tree_form("d_0"))
127
+ power_minus_1 = power - tree_form("d_1")
128
+ results[eq] = power * (base ** power_minus_1) * dbase
129
+ else:
130
+ results[eq] = TreeNode("f_dif", [eq, tree_form(var)])
131
+ return results.get(root_eq, tree_form("d_0"))
132
+ def helper2_iterative(root_eq, target_var):
133
+ stack = [(root_eq, False)]
134
+ results = {}
135
+ while stack:
136
+ eq, processed = stack.pop()
137
+ if eq in results:
138
+ continue
139
+ if not processed:
140
+ stack.append((eq, True))
141
+ for child in eq.children:
142
+ if child not in results:
143
+ stack.append((child, False))
144
+ else:
145
+ if eq.name == "f_dif":
146
+ if eq.children[0].name == target_var:
147
+ results[eq] = tree_form("d_1")
148
+ elif not contain(eq.children[0], target_var):
149
+ results[eq] = tree_form("d_0")
150
+ else:
151
+ results[eq] = eq
152
+ else:
153
+ new_children = [results.get(child, child) for child in eq.children]
154
+ results[eq] = TreeNode(eq.name, new_children)
155
+ return results.get(root_eq, root_eq)
156
+ def calc_iterative(root_eq):
157
+ stack = [(root_eq, False)]
158
+ results = {}
159
+ while stack:
160
+ eq, processed = stack.pop()
161
+ if eq in results:
162
+ continue
163
+ if not processed:
164
+ stack.append((eq, True))
165
+ for child in eq.children:
166
+ if child not in results:
167
+ stack.append((child, False))
168
+ else:
169
+ if eq.name == "f_dif":
170
+ results[eq] = diffeq_iterative(eq.children[0])
171
+ else:
172
+ new_children = [results.get(child, child) for child in eq.children]
173
+ results[eq] = TreeNode(eq.name, new_children)
174
+ return results.get(root_eq, root_eq)
175
+ if var is None:
176
+ return simplify(calc_iterative(equation))
177
+ equation = diffeq_iterative(equation)
178
+ equation = helper2_iterative(equation, var)
179
+ return simplify(equation)
@@ -16,15 +16,16 @@ _W_NAMES = {
16
16
  _COMMUTATIVE = frozenset(("f_addw", "f_mulw", "f_hadamardw"))
17
17
  _ORDERED_ASSOC = frozenset(("f_wmulw",))
18
18
  _LABEL_PREFIXES = frozenset(("f_", "s_", "d_"))
19
- _SPECIAL_PATTERN = frozenset(("f_poly", "f_sum", "f_product", "f_hprod"))
19
+ _SPECIAL_PATTERN = frozenset(("f_poly", "f_sum", "f_product", "f_hprod", "f_mprod"))
20
20
  _PATTERN_OPS = {"f_sum": "f_addw", "f_product": "f_mulw", "f_hprod": "f_hadamardw"}
21
+ _ORDERED_PATTERN_OPS = {"f_mprod": "f_wmulw"}
21
22
  _AGG_NAMES = frozenset((
22
23
  "f_addw", "f_add", "f_mulw", "f_mul", "f_hadamardw", "f_hadamard",
23
- "f_product", "f_sum", "f_hprod", "f_poly",
24
+ "f_product", "f_sum", "f_hprod", "f_mprod", "f_poly", "f_wmulw", "f_wmul",
24
25
  ))
25
26
  _EXTRACT_NAMES = frozenset((
26
27
  "f_add", "f_wadd", "f_waddw", "f_wmul", "f_wmulw",
27
- "f_mul", "f_product", "f_sum", "f_hprod", "f_poly",
28
+ "f_mul", "f_product", "f_sum", "f_hprod", "f_mprod", "f_poly",
28
29
  ))
29
30
  _DE_W_NAMES = frozenset(("f_addw", "f_mulw", "f_waddw", "f_hadamardw", "f_wmulw"))
30
31
 
@@ -32,7 +33,7 @@ _DE_W_NAMES = frozenset(("f_addw", "f_mulw", "f_waddw", "f_hadamardw", "f_wmulw"
32
33
  def transform_dfs(node, fn, stable=None):
33
34
  if node is None:
34
35
  return None
35
- if stable is not None and node in stable:
36
+ if stable is not None and id(node) in stable:
36
37
  return node
37
38
  children = node.children
38
39
  if children is None:
@@ -50,15 +51,17 @@ def transform_dfs(node, fn, stable=None):
50
51
  if out is not None and out is not cur and out == cur:
51
52
  out = cur
52
53
  if stable is not None and unchanged and out is cur:
53
- stable[cur] = cur
54
+ stable[id(cur)] = cur # holding the reference keeps the id valid
54
55
  return out
55
56
 
56
57
 
57
58
  def dowhile(x, fn):
58
59
  while True:
59
60
  y = fn(x)
60
- if y is x or y == x or y is None:
61
- return x if y is None else y
61
+ if y is None:
62
+ return x
63
+ if y is x or y == x:
64
+ return y
62
65
  x = y
63
66
 
64
67
 
@@ -68,8 +71,10 @@ def _fixpoint(eq, fn):
68
71
  stable = {}
69
72
  while True:
70
73
  new = transform_dfs(eq, fn, stable)
71
- if new is eq or new == eq or new is None:
72
- return eq if new is None else new
74
+ if new is None:
75
+ return eq
76
+ if new is eq or new == eq:
77
+ return new
73
78
  eq = new
74
79
 
75
80
 
@@ -111,6 +116,8 @@ def _collect_labels(node, acc):
111
116
  acc.add("f_mulw")
112
117
  elif node.name == "f_hprod":
113
118
  acc.add("f_hadamardw")
119
+ elif node.name == "f_mprod":
120
+ acc.add("f_wmulw")
114
121
  elif node.name == "f_poly":
115
122
  acc.add("f_mulw")
116
123
  acc.add("f_addw")
@@ -190,6 +197,17 @@ def _as_exprs(x):
190
197
  return [x]
191
198
 
192
199
 
200
+ def _as_pairs(x):
201
+ """Like _as_exprs but keeps [expr, value] pairs intact (no flattening)."""
202
+ if x is None or not isinstance(x, list):
203
+ return []
204
+ if x and all(isinstance(p, list) for p in x):
205
+ return [p for p in x if len(p) == 2]
206
+ if len(x) == 2 and not any(isinstance(p, list) for p in x):
207
+ return [x] # a single bare pair
208
+ return []
209
+
210
+
193
211
  def _subst(node, mapping):
194
212
  if node is None:
195
213
  return None
@@ -253,8 +271,10 @@ def _match_struct_bind_core(eq, f, binding, const_1_vars, ignore_set, resolved_i
253
271
  for item in vlist(eq):
254
272
  out = extract_polynomial_coefficients(eq_plain, item, degree)
255
273
  if out is not None:
256
- out = out[::-1][1:]
257
- if out[0] != 0:
274
+ out = out[::-1]
275
+ while len(out) != 0 and out[0] == 0:
276
+ out.pop(0)
277
+ if len(out) == degree:
258
278
  local_binding = binding.copy()
259
279
  for i in range(1, len(f.children)):
260
280
  local_binding[f.children[i].name] = out[i - 1]
@@ -291,6 +311,37 @@ def _match_struct_bind_core(eq, f, binding, const_1_vars, ignore_set, resolved_i
291
311
  local_binding[f.children[1 - index].name] = operation(op_name, non_const_term)
292
312
  binding.update(local_binding)
293
313
  return True
314
+ if fname in _ORDERED_PATTERN_OPS and len(f.children) >= 2:
315
+ op_name = _ORDERED_PATTERN_OPS[fname]
316
+ if eq.name == op_name and len(eq.children) >= len(f.children):
317
+ fc, ec = f.children, eq.children
318
+ n = len(fc)
319
+ m = len(ec)
320
+ if m < n:
321
+ return False
322
+
323
+ def search_ordered_pattern(i, j, b):
324
+ if i == n:
325
+ return b if j == m else None
326
+ pat = fc[i]
327
+ free = (not pat.children) and pat.name in free_vars
328
+ remaining_pats = n - i - 1
329
+ max_len = (m - j - remaining_pats) if free else 1
330
+ for L in range(1, max_len + 1):
331
+ seg = ec[j] if L == 1 else TreeNode(op_name, list(ec[j:j + L]))
332
+ sb = b.copy()
333
+ if _match_struct_bind_core(seg, pat, sb, const_1_vars,
334
+ ignore_set, resolved_ignore, free_vars):
335
+ r = search_ordered_pattern(i + 1, j + L, sb)
336
+ if r is not None:
337
+ return r
338
+ return None
339
+
340
+ result = search_ordered_pattern(0, 0, binding)
341
+ if result is None:
342
+ return False
343
+ binding.update(result)
344
+ return True
294
345
  if fname.startswith("v_") and not f.children and fname in free_vars:
295
346
  f_str = str_form(f)
296
347
  if f_str in binding:
@@ -389,11 +440,13 @@ def _match_struct_bind(eq, f, binding, const_1_vars=None, ignore_set=None,
389
440
 
390
441
 
391
442
  class _Matcher:
392
- def __init__(self, formula, formula_out, positive=None, negative=None, ignore_list=None, const_1=None, forbidden_list=None):
443
+ def __init__(self, formula, formula_out, positive=None, negative=None,
444
+ ignore_list=None, const_1=None, forbidden_list=None):
393
445
  self.orig_formula = formula
394
446
  self.formula_out = formula_out
395
447
  self.formula_w = en_w_addmul(formula)
396
- self.root_name = self.formula_w.name if self.formula_w else ""
448
+ has_formula = self.formula_w is not None
449
+ self.root_name = self.formula_w.name if has_formula else ""
397
450
  f_sum_vars = set()
398
451
  _extract_f_sum_vars(self.formula_w, f_sum_vars)
399
452
  self.free_vars = frozenset(f_sum_vars)
@@ -402,15 +455,15 @@ class _Matcher:
402
455
  self.ignore_list_exprs = _as_exprs(ignore_list)
403
456
  self.ignore_set = {get_name(item) for item in self.ignore_list_exprs}
404
457
  self.ignore_names = {item.name for item in self.ignore_list_exprs}
405
- self.forbidden_list_exprs = _as_exprs(forbidden_list)
406
-
407
- names = set(vlist(self.formula_w)) - f_sum_vars if self.formula_w else set()
458
+ self.forbidden_pairs = _as_pairs(forbidden_list) # FIX: keep pairs intact
459
+
460
+ names = (set(vlist(self.formula_w)) - f_sum_vars) if has_formula else set()
408
461
  self.vl = sorted(names, key=lambda n: (0 if n in self.ignore_set else 1, n))
409
462
  self.must_be_var = {n: (n in self.ignore_set and n not in f_sum_vars) for n in self.vl}
410
463
  self.const_1_vars = {get_name(item) for item in _as_exprs(const_1)}
411
464
  self.must_be_const = {n: (n in self.const_1_vars) for n in self.vl}
412
465
  self.root_is_free = (
413
- not self.formula_w
466
+ not has_formula
414
467
  or (self.root_name.startswith("v_") and not self.formula_w.children)
415
468
  or self.root_name in _SPECIAL_PATTERN
416
469
  )
@@ -421,19 +474,15 @@ class _Matcher:
421
474
  self._same_count = [count_same_nodes(self.orig_formula, TreeNode(x)) for x in self.vl]
422
475
 
423
476
  def _is_forbidden(self, mapping):
424
- if not self.forbidden_list_exprs:
425
- return False
426
- for forb_pair in self.forbidden_list_exprs:
427
- if isinstance(forb_pair, list) and len(forb_pair) == 2:
428
- expr_part, forb_val = forb_pair[0], forb_pair[1]
429
- subbed_expr = _subst(expr_part, mapping)
430
- subbed_val = _subst(forb_val, mapping)
431
- if subbed_expr is None or subbed_val is None:
432
- return True
433
- simp_expr = simplify(subbed_expr)
434
- simp_val = simplify(subbed_val)
435
- if simp_expr is None or simp_val is None or simp_expr == simp_val:
436
- return True
477
+ for expr_part, forb_val in self.forbidden_pairs:
478
+ subbed_expr = _subst(expr_part, mapping)
479
+ subbed_val = _subst(forb_val, mapping)
480
+ if subbed_expr is None or subbed_val is None:
481
+ return True
482
+ simp_expr = simplify(subbed_expr)
483
+ simp_val = simplify(subbed_val)
484
+ if simp_expr is None or simp_val is None or simp_expr == simp_val:
485
+ return True
437
486
  return False
438
487
 
439
488
  def conditions_hold(self, mapping):
@@ -484,11 +533,11 @@ class _Matcher:
484
533
  eq_cand_w = en_w_addmul(equation)
485
534
  binding = {}
486
535
  if not _match_struct_bind(eq_w, eq_cand_w, binding, self.const_1_vars,
487
- self.ignore_names, assigned_mapping, self.free_vars):
536
+ self.ignore_names, assigned_mapping, self.free_vars):
488
537
  return None
489
538
  full_mapping = dict(assigned_mapping)
490
539
  full_mapping.update({k: de_w_addmul(v) for k, v in binding.items()})
491
-
540
+
492
541
  if self._is_forbidden(full_mapping):
493
542
  return None
494
543
 
@@ -547,8 +596,10 @@ class _Matcher:
547
596
  return dfs({}, 0, 0, frozenset())
548
597
 
549
598
 
550
- def structure(equation, formula, formula_out, ignore_list=None, const_1=None, positive=None, negative=None, forbidden_list=None):
551
- return _Matcher(formula, formula_out, positive, negative, ignore_list, const_1, forbidden_list)(equation)
599
+ def structure(equation, formula, formula_out, ignore_list=None, const_1=None,
600
+ positive=None, negative=None, forbidden_list=None):
601
+ return _Matcher(formula, formula_out, positive, negative, ignore_list,
602
+ const_1, forbidden_list)(equation)
552
603
 
553
604
 
554
605
  def convert_lst(eq):
@@ -592,16 +643,15 @@ def _compile_rule(s):
592
643
  if parsed_subs:
593
644
  for key, value in parsed_subs:
594
645
  subs.setdefault(key, [None]).append(value)
595
-
646
+
596
647
  lst = [convert_string(x) for x in item[3:]]
597
648
  lst += [None] * (5 - len(lst))
598
649
  ignore_list, const_1, positive, negative, forbidden_list = lst[:5]
599
-
650
+
600
651
  keys = tuple(subs)
601
652
  matchers = []
602
653
  for combination in itertools.product(*subs.values()):
603
654
  x, y, pos, neg, forb = a, b, positive, negative, forbidden_list
604
- valid_comb = True
605
655
  for key, value in zip(keys, combination):
606
656
  if value is not None:
607
657
  x = replace(x, key, value)
@@ -609,22 +659,17 @@ def _compile_rule(s):
609
659
  pos = _replace_in(pos, key, value)
610
660
  neg = _replace_in(neg, key, value)
611
661
  forb = _replace_in(forb, key, value)
612
-
613
- if forbidden_list is not None:
614
- comb_map = {k: v for k, v in zip(keys, combination) if v is not None}
615
- for forb_pair in _as_exprs(forbidden_list):
616
- if isinstance(forb_pair, list) and len(forb_pair) == 2:
617
- sub_expr = _subst(forb_pair[0], comb_map)
618
- sub_val = _subst(forb_pair[1], comb_map)
619
- if sub_expr is None or sub_val is None:
620
- valid_comb = False
621
- break
622
- simp_expr = simplify(sub_expr)
623
- simp_val = simplify(sub_val)
624
- if simp_expr is None or simp_val is None or simp_expr == simp_val:
625
- valid_comb = False
626
- break
627
-
662
+
663
+ # FIX: check the already-substituted pairs; skip combinations that are
664
+ # forbidden outright (e.g. a pair that simplifies to identical sides)
665
+ valid_comb = True
666
+ for expr_part, forb_val in _as_pairs(forb):
667
+ se = simplify(expr_part)
668
+ sv = simplify(forb_val)
669
+ if se is None or sv is None or se == sv:
670
+ valid_comb = False
671
+ break
672
+
628
673
  if valid_comb:
629
674
  simp_x = simplify(x)
630
675
  simp_y = simplify(y)