mathai 2.3.6__tar.gz → 2.3.8__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 (40) hide show
  1. {mathai-2.3.6 → mathai-2.3.8}/PKG-INFO +1 -1
  2. {mathai-2.3.6 → mathai-2.3.8}/mathai/__init__.py +2 -2
  3. mathai-2.3.8/mathai/decision.py +134 -0
  4. {mathai-2.3.6 → mathai-2.3.8}/mathai/formula_applier.py +96 -53
  5. {mathai-2.3.6 → mathai-2.3.8}/mathai/integrate.py +18 -8
  6. {mathai-2.3.6 → mathai-2.3.8}/mathai/linear.py +36 -0
  7. {mathai-2.3.6 → mathai-2.3.8}/mathai/matrix.py +202 -124
  8. {mathai-2.3.6 → mathai-2.3.8}/mathai.egg-info/PKG-INFO +1 -1
  9. {mathai-2.3.6 → mathai-2.3.8}/setup.py +1 -1
  10. mathai-2.3.6/mathai/decision.py +0 -358
  11. {mathai-2.3.6 → mathai-2.3.8}/README.md +0 -0
  12. {mathai-2.3.6 → mathai-2.3.8}/mathai/apart.py +0 -0
  13. {mathai-2.3.6 → mathai-2.3.8}/mathai/base.py +0 -0
  14. {mathai-2.3.6 → mathai-2.3.8}/mathai/diff.py +0 -0
  15. {mathai-2.3.6 → mathai-2.3.8}/mathai/electronic.py +0 -0
  16. {mathai-2.3.6 → mathai-2.3.8}/mathai/expand.py +0 -0
  17. {mathai-2.3.6 → mathai-2.3.8}/mathai/factor.py +0 -0
  18. {mathai-2.3.6 → mathai-2.3.8}/mathai/fraction.py +0 -0
  19. {mathai-2.3.6 → mathai-2.3.8}/mathai/graphics.py +0 -0
  20. {mathai-2.3.6 → mathai-2.3.8}/mathai/inverse.py +0 -0
  21. {mathai-2.3.6 → mathai-2.3.8}/mathai/lambda_calculus.py +0 -0
  22. {mathai-2.3.6 → mathai-2.3.8}/mathai/limit.py +0 -0
  23. {mathai-2.3.6 → mathai-2.3.8}/mathai/logic.py +0 -0
  24. {mathai-2.3.6 → mathai-2.3.8}/mathai/ml.py +0 -0
  25. {mathai-2.3.6 → mathai-2.3.8}/mathai/mlscalar.py +0 -0
  26. {mathai-2.3.6 → mathai-2.3.8}/mathai/ode.py +0 -0
  27. {mathai-2.3.6 → mathai-2.3.8}/mathai/parser.py +0 -0
  28. {mathai-2.3.6 → mathai-2.3.8}/mathai/pde.py +0 -0
  29. {mathai-2.3.6 → mathai-2.3.8}/mathai/printeq.py +0 -0
  30. {mathai-2.3.6 → mathai-2.3.8}/mathai/qm.py +0 -0
  31. {mathai-2.3.6 → mathai-2.3.8}/mathai/simplify.py +0 -0
  32. {mathai-2.3.6 → mathai-2.3.8}/mathai/statistics.py +0 -0
  33. {mathai-2.3.6 → mathai-2.3.8}/mathai/test.py +0 -0
  34. {mathai-2.3.6 → mathai-2.3.8}/mathai/tool.py +0 -0
  35. {mathai-2.3.6 → mathai-2.3.8}/mathai/trig.py +0 -0
  36. {mathai-2.3.6 → mathai-2.3.8}/mathai/univariate_inequality.py +0 -0
  37. {mathai-2.3.6 → mathai-2.3.8}/mathai.egg-info/SOURCES.txt +0 -0
  38. {mathai-2.3.6 → mathai-2.3.8}/mathai.egg-info/dependency_links.txt +0 -0
  39. {mathai-2.3.6 → mathai-2.3.8}/mathai.egg-info/top_level.txt +0 -0
  40. {mathai-2.3.6 → mathai-2.3.8}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mathai
3
- Version: 2.3.6
3
+ Version: 2.3.8
4
4
  Summary: Mathematics solving Ai tailored to NCERT
5
5
  Home-page: https://github.com/infinity390/mathai
6
6
  Requires-Python: >=3.7
@@ -15,7 +15,7 @@ from .linear import linear_solve
15
15
 
16
16
  from .test import test
17
17
 
18
- from .decision import god, trig7
18
+ from .decision import god
19
19
 
20
20
  from .expand import expand
21
21
 
@@ -54,7 +54,7 @@ from .limit import limit1, limit2, limit0, limit3, limit4, solve_inf, limit5
54
54
 
55
55
  from .univariate_inequality import wavycurvy, absolute, domain, handle_sqrt, prepare, eq2range, range2eq2, simple_wavycurvy
56
56
 
57
- from .matrix import matrix_solve
57
+ from .matrix import matrix_solve, matrix_formula
58
58
 
59
59
  from .base import *
60
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
@@ -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
@@ -254,9 +272,9 @@ def _match_struct_bind_core(eq, f, binding, const_1_vars, ignore_set, resolved_i
254
272
  out = extract_polynomial_coefficients(eq_plain, item, degree)
255
273
  if out is not None:
256
274
  out = out[::-1]
257
- while out[0] == 0:
275
+ while len(out) != 0 and out[0] == 0:
258
276
  out.pop(0)
259
- if len(out) == degree:
277
+ if len(out) == degree:
260
278
  local_binding = binding.copy()
261
279
  for i in range(1, len(f.children)):
262
280
  local_binding[f.children[i].name] = out[i - 1]
@@ -293,6 +311,37 @@ def _match_struct_bind_core(eq, f, binding, const_1_vars, ignore_set, resolved_i
293
311
  local_binding[f.children[1 - index].name] = operation(op_name, non_const_term)
294
312
  binding.update(local_binding)
295
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
296
345
  if fname.startswith("v_") and not f.children and fname in free_vars:
297
346
  f_str = str_form(f)
298
347
  if f_str in binding:
@@ -391,11 +440,13 @@ def _match_struct_bind(eq, f, binding, const_1_vars=None, ignore_set=None,
391
440
 
392
441
 
393
442
  class _Matcher:
394
- 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):
395
445
  self.orig_formula = formula
396
446
  self.formula_out = formula_out
397
447
  self.formula_w = en_w_addmul(formula)
398
- 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 ""
399
450
  f_sum_vars = set()
400
451
  _extract_f_sum_vars(self.formula_w, f_sum_vars)
401
452
  self.free_vars = frozenset(f_sum_vars)
@@ -404,15 +455,15 @@ class _Matcher:
404
455
  self.ignore_list_exprs = _as_exprs(ignore_list)
405
456
  self.ignore_set = {get_name(item) for item in self.ignore_list_exprs}
406
457
  self.ignore_names = {item.name for item in self.ignore_list_exprs}
407
- self.forbidden_list_exprs = _as_exprs(forbidden_list)
408
-
409
- 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()
410
461
  self.vl = sorted(names, key=lambda n: (0 if n in self.ignore_set else 1, n))
411
462
  self.must_be_var = {n: (n in self.ignore_set and n not in f_sum_vars) for n in self.vl}
412
463
  self.const_1_vars = {get_name(item) for item in _as_exprs(const_1)}
413
464
  self.must_be_const = {n: (n in self.const_1_vars) for n in self.vl}
414
465
  self.root_is_free = (
415
- not self.formula_w
466
+ not has_formula
416
467
  or (self.root_name.startswith("v_") and not self.formula_w.children)
417
468
  or self.root_name in _SPECIAL_PATTERN
418
469
  )
@@ -423,19 +474,15 @@ class _Matcher:
423
474
  self._same_count = [count_same_nodes(self.orig_formula, TreeNode(x)) for x in self.vl]
424
475
 
425
476
  def _is_forbidden(self, mapping):
426
- if not self.forbidden_list_exprs:
427
- return False
428
- for forb_pair in self.forbidden_list_exprs:
429
- if isinstance(forb_pair, list) and len(forb_pair) == 2:
430
- expr_part, forb_val = forb_pair[0], forb_pair[1]
431
- subbed_expr = _subst(expr_part, mapping)
432
- subbed_val = _subst(forb_val, mapping)
433
- if subbed_expr is None or subbed_val is None:
434
- return True
435
- simp_expr = simplify(subbed_expr)
436
- simp_val = simplify(subbed_val)
437
- if simp_expr is None or simp_val is None or simp_expr == simp_val:
438
- 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
439
486
  return False
440
487
 
441
488
  def conditions_hold(self, mapping):
@@ -486,11 +533,11 @@ class _Matcher:
486
533
  eq_cand_w = en_w_addmul(equation)
487
534
  binding = {}
488
535
  if not _match_struct_bind(eq_w, eq_cand_w, binding, self.const_1_vars,
489
- self.ignore_names, assigned_mapping, self.free_vars):
536
+ self.ignore_names, assigned_mapping, self.free_vars):
490
537
  return None
491
538
  full_mapping = dict(assigned_mapping)
492
539
  full_mapping.update({k: de_w_addmul(v) for k, v in binding.items()})
493
-
540
+
494
541
  if self._is_forbidden(full_mapping):
495
542
  return None
496
543
 
@@ -549,8 +596,10 @@ class _Matcher:
549
596
  return dfs({}, 0, 0, frozenset())
550
597
 
551
598
 
552
- def structure(equation, formula, formula_out, ignore_list=None, const_1=None, positive=None, negative=None, forbidden_list=None):
553
- 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)
554
603
 
555
604
 
556
605
  def convert_lst(eq):
@@ -594,16 +643,15 @@ def _compile_rule(s):
594
643
  if parsed_subs:
595
644
  for key, value in parsed_subs:
596
645
  subs.setdefault(key, [None]).append(value)
597
-
646
+
598
647
  lst = [convert_string(x) for x in item[3:]]
599
648
  lst += [None] * (5 - len(lst))
600
649
  ignore_list, const_1, positive, negative, forbidden_list = lst[:5]
601
-
650
+
602
651
  keys = tuple(subs)
603
652
  matchers = []
604
653
  for combination in itertools.product(*subs.values()):
605
654
  x, y, pos, neg, forb = a, b, positive, negative, forbidden_list
606
- valid_comb = True
607
655
  for key, value in zip(keys, combination):
608
656
  if value is not None:
609
657
  x = replace(x, key, value)
@@ -611,22 +659,17 @@ def _compile_rule(s):
611
659
  pos = _replace_in(pos, key, value)
612
660
  neg = _replace_in(neg, key, value)
613
661
  forb = _replace_in(forb, key, value)
614
-
615
- if forbidden_list is not None:
616
- comb_map = {k: v for k, v in zip(keys, combination) if v is not None}
617
- for forb_pair in _as_exprs(forbidden_list):
618
- if isinstance(forb_pair, list) and len(forb_pair) == 2:
619
- sub_expr = _subst(forb_pair[0], comb_map)
620
- sub_val = _subst(forb_pair[1], comb_map)
621
- if sub_expr is None or sub_val is None:
622
- valid_comb = False
623
- break
624
- simp_expr = simplify(sub_expr)
625
- simp_val = simplify(sub_val)
626
- if simp_expr is None or simp_val is None or simp_expr == simp_val:
627
- valid_comb = False
628
- break
629
-
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
+
630
673
  if valid_comb:
631
674
  simp_x = simplify(x)
632
675
  simp_y = simplify(y)
@@ -8,7 +8,7 @@ from .fraction import fraction
8
8
  from .simplify import simplify
9
9
  from .expand import expand
10
10
  from .base import *
11
- from .formula_applier import formula_list_applier
11
+ from .formula_applier import formula_list_applier, formula_applier
12
12
  from .inverse import inverse
13
13
  from .tool import poly
14
14
  from fractions import Fraction
@@ -36,6 +36,8 @@ def subs_heuristic(eq, var):
36
36
  if eq.name == "f_pow" and frac(eq.children[1]) is not None and frac(eq.children[1]) == Fraction(1,2):
37
37
  if eq.children[0] == var:
38
38
  output.append(str_form(eq))
39
+ if eq.name == "f_log":
40
+ output.append(str_form(eq))
39
41
  if eq.name in ["f_pow"] and var.name in str_form(eq):
40
42
  if eq.children[1].name[:2] != "v_":
41
43
  output.append(str_form(eq))
@@ -66,20 +68,22 @@ def subs_heuristic(eq, var):
66
68
  tmp = list(set([simplify(tree_form(x)) for x in output]))
67
69
  tmp = sorted(tmp, key=lambda x: len(str_form(x)))
68
70
  poly_term = None
69
-
71
+ poly_size = -1
70
72
  fx = is_integrate_subs
71
73
  output = []
72
74
  if set(all_branch(eq)) <= set("f_add f_mul f_pow".split(" ")):
73
75
  for item in tmp:
74
76
  if fx(item).name == "s_true":
75
- poly_term = item
77
+ if len(item) > poly_size:
78
+ poly_term = item
79
+ poly_size = len(item)
76
80
  else:
77
81
  output.append(item)
78
82
  p = None
79
83
  if poly_term is None:
80
84
  p = tmp
81
85
  else:
82
- p = output+[poly_term]
86
+ p = [poly_term]+output
83
87
  last = list(set(last))
84
88
  if len(p)>3:
85
89
  p = p[:3]
@@ -176,12 +180,14 @@ def integrate_subs(equation, term, v1, v2, extra):
176
180
  equation = simplify(equation)
177
181
  eq = equation
178
182
  termeq = term
179
- t = inverse(copy.deepcopy(termeq), v1)
180
- g = inverse(copy.deepcopy(termeq), v2)
183
+ t = inverse(termeq, v1)
184
+ g = inverse(termeq, v2)
181
185
  if g is None:
182
186
  return none
183
187
  if t is None:
184
- return none
188
+ equation = simplify(factor2(factor0(simplify(replace(eq, g, tree_form(v2))/diff(g, v1)))))
189
+ if v1 in str_form(equation):
190
+ return none
185
191
  else:
186
192
  t = expand(t)
187
193
  eq3 = diff(g, v1)
@@ -193,6 +199,9 @@ def integrate_subs(equation, term, v1, v2, extra):
193
199
  eq = replace(eq, tree_form(v1), t)
194
200
  equation = eq/eq3
195
201
  equation = simplify(equation)
202
+ if v1 in str_form(equation):
203
+ equation = replace(equation, tree_form(v1), t)
204
+ equation = formula_applier(equation, "abs(x) x [] _ _ _ _")
196
205
  if v1 in str_form(equation):
197
206
  return none
198
207
  return dowhile(TreeNode("f_subs", [TreeNode("f_integrate", [simplify(equation), tree_form(origv2)]),tree_form(origv2) ,g]+extra), lambda x: simplify(trig4(trig0(x))))
@@ -321,13 +330,14 @@ def integrate_full(root):
321
330
  return "f_integrate" not in str_form(x)
322
331
  result = None
323
332
  root = normalize(root)
333
+ root = formula_applier(root, "(a^b)^c a^(b*c) [] b c _ _")
324
334
  normalize2 = lambda x: normalize(x, False)
325
335
  log = []
326
336
  orig = copy.deepcopy(root)
327
337
  eq = root
328
338
  for item in [[lambda x: x, simplify, expand, normalize], [factor2, apart, normalize2, normalize], [trig1, normalize2],\
329
339
  [factor1, normalize, trig6, normalize, expand, normalize, integrate_subs_main, normalize, factor2, simplify, integrate_trig_formula, trig1, apart, normalize2],\
330
- [normalize, integrate_subs_main, normalize2, expand, normalize, byparts, normalize], [simplify, integrate_trig_formula, trig1, integrate_formula]]:
340
+ [normalize2, integrate_subs_main, expand, normalize, byparts, normalize], [simplify, integrate_trig_formula, trig1, integrate_formula]]:
331
341
  for item2 in item:
332
342
  eq = item2(eq)
333
343
  if eq not in log:
@@ -100,6 +100,42 @@ def linear_solve(eq, lst=None):
100
100
  else:
101
101
  return eq
102
102
 
103
+ fxconst = (lambda x: False) if lst is None else (lambda x: any(contain(x, item) for item in lst))
104
+
105
+ # 1. Collect all non-constant variables (vl) across all equations
106
+ vl = []
107
+ def varlist(node, fxconst):
108
+ nonlocal vl
109
+ if node.name[:2] == "v_" and not fxconst(node):
110
+ vl.append(node.name)
111
+ for child in node.children:
112
+ varlist(child, fxconst)
113
+
114
+ for item in eqlist:
115
+ varlist(item, fxconst)
116
+ vl = sorted(list(set(vl)))
117
+
118
+ # 2. Check linearity by iterating through all equations and variables:
119
+ # If diff w.r.t a variable still contains any variable from vl, it's non-linear.
120
+ for eq_item in eqlist:
121
+ for v in vl:
122
+ d = simplify(diff(eq_item, v))
123
+
124
+ # Helper to check if derivative 'd' contains any variable from vl
125
+ contains_var = False
126
+ def check_vars(node):
127
+ nonlocal contains_var
128
+ if node.name[:2] == "v_" and node.name in vl:
129
+ contains_var = True
130
+ return
131
+ for child in node.children:
132
+ check_vars(child)
133
+
134
+ check_vars(d)
135
+ if contains_var:
136
+ return None # Non-linear expression detected
137
+
138
+ # 3. Proceed with linear solver and rref if check passes
103
139
  out = None
104
140
  if lst is None:
105
141
  out = linear(copy.deepcopy(eqlist), lambda x: False)