mathai 2.4.8__tar.gz → 2.5.0__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 (44) hide show
  1. {mathai-2.4.8 → mathai-2.5.0}/PKG-INFO +9 -3
  2. {mathai-2.4.8 → mathai-2.5.0}/README.md +8 -2
  3. {mathai-2.4.8 → mathai-2.5.0}/mathai/__init__.py +4 -2
  4. {mathai-2.4.8 → mathai-2.5.0}/mathai/benchmark.py +143 -41
  5. {mathai-2.4.8 → mathai-2.5.0}/mathai/decision.py +0 -16
  6. {mathai-2.4.8 → mathai-2.5.0}/mathai/ncert_data.txt +6 -2
  7. mathai-2.5.0/mathai/ncert_data_optimized.txt +22 -0
  8. mathai-2.5.0/mathai/square_root.py +617 -0
  9. {mathai-2.4.8 → mathai-2.5.0}/mathai/univariate_inequality.py +2 -0
  10. {mathai-2.4.8 → mathai-2.5.0}/mathai.egg-info/PKG-INFO +9 -3
  11. {mathai-2.4.8 → mathai-2.5.0}/mathai.egg-info/SOURCES.txt +2 -0
  12. {mathai-2.4.8 → mathai-2.5.0}/setup.py +2 -2
  13. {mathai-2.4.8 → mathai-2.5.0}/mathai/apart.py +0 -0
  14. {mathai-2.4.8 → mathai-2.5.0}/mathai/base.py +0 -0
  15. {mathai-2.4.8 → mathai-2.5.0}/mathai/diff.py +0 -0
  16. {mathai-2.4.8 → mathai-2.5.0}/mathai/electronic.py +0 -0
  17. {mathai-2.4.8 → mathai-2.5.0}/mathai/expand.py +0 -0
  18. {mathai-2.4.8 → mathai-2.5.0}/mathai/factor.py +0 -0
  19. {mathai-2.4.8 → mathai-2.5.0}/mathai/formula_applier.py +0 -0
  20. {mathai-2.4.8 → mathai-2.5.0}/mathai/formula_compiler.py +0 -0
  21. {mathai-2.4.8 → mathai-2.5.0}/mathai/fraction.py +0 -0
  22. {mathai-2.4.8 → mathai-2.5.0}/mathai/graphics.py +0 -0
  23. {mathai-2.4.8 → mathai-2.5.0}/mathai/integrate.py +0 -0
  24. {mathai-2.4.8 → mathai-2.5.0}/mathai/inverse.py +0 -0
  25. {mathai-2.4.8 → mathai-2.5.0}/mathai/lambda_calculus.py +0 -0
  26. {mathai-2.4.8 → mathai-2.5.0}/mathai/limit.py +0 -0
  27. {mathai-2.4.8 → mathai-2.5.0}/mathai/linear.py +0 -0
  28. {mathai-2.4.8 → mathai-2.5.0}/mathai/logic.py +0 -0
  29. {mathai-2.4.8 → mathai-2.5.0}/mathai/matrix.py +0 -0
  30. {mathai-2.4.8 → mathai-2.5.0}/mathai/ml.py +0 -0
  31. {mathai-2.4.8 → mathai-2.5.0}/mathai/mlscalar.py +0 -0
  32. {mathai-2.4.8 → mathai-2.5.0}/mathai/ode.py +0 -0
  33. {mathai-2.4.8 → mathai-2.5.0}/mathai/parser.py +0 -0
  34. {mathai-2.4.8 → mathai-2.5.0}/mathai/pde.py +0 -0
  35. {mathai-2.4.8 → mathai-2.5.0}/mathai/printeq.py +0 -0
  36. {mathai-2.4.8 → mathai-2.5.0}/mathai/qm.py +0 -0
  37. {mathai-2.4.8 → mathai-2.5.0}/mathai/simplify.py +0 -0
  38. {mathai-2.4.8 → mathai-2.5.0}/mathai/statistics.py +0 -0
  39. {mathai-2.4.8 → mathai-2.5.0}/mathai/test.py +0 -0
  40. {mathai-2.4.8 → mathai-2.5.0}/mathai/tool.py +0 -0
  41. {mathai-2.4.8 → mathai-2.5.0}/mathai/trig.py +0 -0
  42. {mathai-2.4.8 → mathai-2.5.0}/mathai.egg-info/dependency_links.txt +0 -0
  43. {mathai-2.4.8 → mathai-2.5.0}/mathai.egg-info/top_level.txt +0 -0
  44. {mathai-2.4.8 → mathai-2.5.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mathai
3
- Version: 2.4.8
3
+ Version: 2.5.0
4
4
  Summary: Mathematics solving Ai tailored to NCERT
5
5
  Home-page: https://github.com/infinity390/mathai
6
6
  Requires-Python: >=3.7
@@ -602,18 +602,24 @@ top_view_length : 80
602
602
  line_length : 80
603
603
  ```
604
604
 
605
- #### Example Demonstration 13 (formula applier)
605
+ #### Example Demonstration 13 (formula compiler/applier)
606
606
  ```python
607
607
  from mathai import *
608
608
  formula = "a*x^2+b*x+c a*(x-(-b+sqrt(b^2-4*a*c))/(2*a))*(x-(-b-sqrt(b^2-4*a*c))/(2*a))"
609
609
  formula += " [[a,1],[b,0],[b,1]] _ _ _ _"
610
610
  question = simplify(parse("sin(x)^2-4*sin(x)+4"))
611
- print(formula_applier(question, formula))
611
+ print(formula_list_compiler(formula)(question))
612
+
613
+ formula = "sin(x)^2+cos(x)^2 1"
614
+ formula += " [] _ _ _ _"
615
+ question = simplify(parse("sin(pi/3)^2+cos(pi/3)^2+1"))
616
+ print(formula_list_compiler(formula)(question))
612
617
  ```
613
618
 
614
619
  #### Output
615
620
  ```
616
621
  (-2+sin(x))^2
622
+ 2
617
623
  ```
618
624
 
619
625
  ### Questions solved using god() function
@@ -589,18 +589,24 @@ top_view_length : 80
589
589
  line_length : 80
590
590
  ```
591
591
 
592
- #### Example Demonstration 13 (formula applier)
592
+ #### Example Demonstration 13 (formula compiler/applier)
593
593
  ```python
594
594
  from mathai import *
595
595
  formula = "a*x^2+b*x+c a*(x-(-b+sqrt(b^2-4*a*c))/(2*a))*(x-(-b-sqrt(b^2-4*a*c))/(2*a))"
596
596
  formula += " [[a,1],[b,0],[b,1]] _ _ _ _"
597
597
  question = simplify(parse("sin(x)^2-4*sin(x)+4"))
598
- print(formula_applier(question, formula))
598
+ print(formula_list_compiler(formula)(question))
599
+
600
+ formula = "sin(x)^2+cos(x)^2 1"
601
+ formula += " [] _ _ _ _"
602
+ question = simplify(parse("sin(pi/3)^2+cos(pi/3)^2+1"))
603
+ print(formula_list_compiler(formula)(question))
599
604
  ```
600
605
 
601
606
  #### Output
602
607
  ```
603
608
  (-2+sin(x))^2
609
+ 2
604
610
  ```
605
611
 
606
612
  ### Questions solved using god() function
@@ -7,7 +7,9 @@ from .qm import hydrogen_gse, helium_gse, power_rule
7
7
 
8
8
  from .graphics import draw_graphics_from_solution, solve_graphics, solve_graphics_string
9
9
 
10
- from .benchmark import benchmark
10
+ from .square_root import solve_sqrt
11
+
12
+ from .benchmark import benchmark, god2, benchmark_test
11
13
 
12
14
  from .formula_compiler import formula_compiler
13
15
  from .formula_compiler import formula_list_compiler
@@ -20,7 +22,7 @@ from .linear import linear_solve
20
22
 
21
23
  from .test import test
22
24
 
23
- from .decision import god, god2
25
+ from .decision import god
24
26
 
25
27
  from .expand import expand
26
28
 
@@ -1,5 +1,6 @@
1
1
  import copy
2
2
  from pathlib import Path
3
+ import concurrent.futures
3
4
 
4
5
  from .simplify import simplify
5
6
  from .expand import expand
@@ -20,9 +21,16 @@ from .limit import limit1
20
21
  from .logic import logic0
21
22
  from .base import *
22
23
 
24
+ def _no_integrate_left(eq):
25
+ return eq.name == "f_range" or eq.name == "s_true" or all(not contain2(eq, "f_"+item) for item in "eq integrate subs limit limitpinf limitninf".split(" "))
26
+
27
+ # Top-level helper function for picklability in multiprocessing
28
+ def run_absolute(x):
29
+ return dowhile(x, absolute)
30
+
23
31
 
24
32
  fx_list = [
25
- lambda x: dowhile(x, absolute),
33
+ run_absolute,
26
34
  logic0,
27
35
  limit1,
28
36
  simple_wavycurvy,
@@ -52,45 +60,6 @@ integrate_clean apart trig0 trig1 trig4 integrate_fraction factor2 trig6
52
60
  FUNCS = dict(zip(name_list, fx_list))
53
61
 
54
62
 
55
- # ---------------------------------------------------------------------------
56
- # Benchmark
57
- # ---------------------------------------------------------------------------
58
-
59
-
60
- def benchmark(s="ncert_data"):
61
- data_path = Path(__file__).parent / f"{s}.txt"
62
- output_path = Path(__file__).parent / f"{s}_optimized.txt"
63
-
64
- optimized_lines = []
65
-
66
- with open(data_path, "r", encoding="utf-8") as file:
67
- for count, line in enumerate(file, 1):
68
- parts = line.split()
69
-
70
- if not parts:
71
- continue
72
-
73
- expr_str, *function_names = parts
74
- eq = simplify(parse(expr_str))
75
- effective_steps = [expr_str]
76
-
77
- for func_name in function_names:
78
- func = FUNCS[func_name]
79
- prev_eq = copy.deepcopy(eq)
80
- eq = func(eq)
81
-
82
- if eq != prev_eq:
83
- effective_steps.append(func_name)
84
-
85
- print(f"{count}. {expr_str} => {eq}")
86
- optimized_lines.append(" ".join(effective_steps))
87
-
88
- with open(output_path, "w", encoding="utf-8") as file:
89
- file.write("\n".join(optimized_lines) + "\n")
90
-
91
- print(f"\nOptimized dataset successfully written to: {output_path}")
92
-
93
-
94
63
  # ---------------------------------------------------------------------------
95
64
  # Strategy tree
96
65
  # ---------------------------------------------------------------------------
@@ -173,7 +142,7 @@ def print_tree(node, depth=0):
173
142
 
174
143
 
175
144
  # ---------------------------------------------------------------------------
176
- # Serial Execution Entry Point
145
+ # Serial & Parallel Execution Entry Points
177
146
  # ---------------------------------------------------------------------------
178
147
 
179
148
 
@@ -193,3 +162,136 @@ def execute_serial(tree, equation, terminal_condition=None):
193
162
  return [(eq, seq)]
194
163
 
195
164
  return []
165
+
166
+
167
+ def _execute_child_worker(args):
168
+ """Worker helper function for parallel execution across cores."""
169
+ child, equation, terminal_condition = args
170
+ try:
171
+ return child.execute(copy.deepcopy(equation), [], terminal_condition)
172
+ except Exception:
173
+ return []
174
+
175
+
176
+ def execute_parallel(tree, equation, terminal_condition=None):
177
+ """
178
+ Splits the strategy tree root node's children and distributes
179
+ them across multiple CPU cores using ProcessPoolExecutor.
180
+ """
181
+ children = list(tree.children.values())
182
+ if not children:
183
+ return []
184
+
185
+ tasks = [(child, equation, terminal_condition) for child in children]
186
+
187
+ with concurrent.futures.ProcessPoolExecutor() as executor:
188
+ futures = [executor.submit(_execute_child_worker, task) for task in tasks]
189
+
190
+ for future in concurrent.futures.as_completed(futures):
191
+ try:
192
+ results = future.result()
193
+ for eq, seq in results:
194
+ if terminal_condition is None or terminal_condition(eq):
195
+ return [(eq, seq)]
196
+ except Exception:
197
+ continue
198
+
199
+ return []
200
+
201
+
202
+ def god2(string, parallel=False):
203
+ tree = strategy_tree(load_sequences("ncert_data"))
204
+ if parallel:
205
+ out = execute_parallel(tree, simplify(parse(string)), _no_integrate_left)
206
+ else:
207
+ out = execute_serial(tree, simplify(parse(string)), _no_integrate_left)
208
+ if len(out) >= 1:
209
+ print(string)
210
+ print("=>", out[0][0])
211
+ print(["parse", "simplify"] + out[0][1])
212
+ return out
213
+
214
+
215
+ # ---------------------------------------------------------------------------
216
+ # Benchmark Test (Pure Execution, No Writing, No Strategy Trees)
217
+ # ---------------------------------------------------------------------------
218
+
219
+
220
+ def benchmark_test(s="ncert_data"):
221
+ data_path = Path(__file__).parent / f"{s}.txt"
222
+
223
+ with open(data_path, "r", encoding="utf-8") as file:
224
+ for count, line in enumerate(file, 1):
225
+ parts = line.split()
226
+
227
+ if not parts:
228
+ continue
229
+
230
+ expr_str, *function_names = parts
231
+ eq = simplify(parse(expr_str))
232
+
233
+ for func_name in function_names:
234
+ func = FUNCS[func_name]
235
+ prev_eq = copy.deepcopy(eq)
236
+ eq = func(eq)
237
+
238
+ print(f"{count}. {expr_str} => {eq}")
239
+
240
+ print(f"\nBenchmark test execution completed for: {s}.txt")
241
+
242
+
243
+ # ---------------------------------------------------------------------------
244
+ # Benchmark
245
+ # ---------------------------------------------------------------------------
246
+
247
+
248
+ def benchmark(s="ncert_data"):
249
+ data_path = Path(__file__).parent / f"{s}.txt"
250
+ output_path = Path(__file__).parent / f"{s}_optimized.txt"
251
+
252
+ optimized_lines = []
253
+
254
+ # Initialize an empty strategy tree
255
+ tree = Node("ROOT")
256
+
257
+ with open(data_path, "r", encoding="utf-8") as file:
258
+ for count, line in enumerate(file, 1):
259
+ parts = line.split()
260
+
261
+ if not parts:
262
+ continue
263
+
264
+ expr_str, *function_names = parts
265
+ eq_parsed = simplify(parse(expr_str))
266
+
267
+ # Try solving with the strategy tree built so far
268
+ out = execute_serial(tree, eq_parsed, _no_integrate_left)
269
+
270
+ if len(out) >= 1:
271
+ # Solved successfully by the strategy tree: DO NOT update tree, exclude from optimized file
272
+ print(f"{count}.", expr_str, "=>", out[0][0])
273
+ continue
274
+
275
+ # Strategy tree failed: fall back to manual function sequence
276
+ eq = eq_parsed
277
+ effective_steps = [expr_str]
278
+
279
+ for func_name in function_names:
280
+ func = FUNCS[func_name]
281
+ prev_eq = copy.deepcopy(eq)
282
+ eq = func(eq)
283
+ if eq != prev_eq:
284
+ effective_steps.append(func_name)
285
+
286
+ # Update the strategy tree ONLY because it failed
287
+ current_node = tree
288
+ for func_name in effective_steps[1:]:
289
+ current_node = current_node.add_child(func_name)
290
+
291
+ print(f"{count}. {expr_str} => {eq} **")
292
+ optimized_lines.append(" ".join(effective_steps))
293
+
294
+ with open(output_path, "w", encoding="utf-8") as file:
295
+ file.write("\n".join(optimized_lines) + "\n")
296
+
297
+ print(f"\nOptimized dataset successfully written to: {output_path}")
@@ -15,7 +15,6 @@ from .factor import factor2, factor
15
15
  from .limit import limit1, limit5
16
16
  from .linear import linear_solve
17
17
  from .matrix import matrix_solve, matrix_formula
18
- from .benchmark import load_sequences, strategy_tree, execute_serial
19
18
 
20
19
  def vlist_2(eq):
21
20
  out = []
@@ -61,21 +60,6 @@ def solve_subs(q):
61
60
  if len(solution.keys()) == 0:
62
61
  return orig
63
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()])
64
- def _no_integrate_left(eq):
65
- return eq.name == "f_range" or eq.name == "s_true" or all(not contain2(eq, "f_"+item) for item in "eq integrate subs limit limitpinf limitninf".split(" "))
66
-
67
-
68
- def god2(string):
69
- out = execute_serial(
70
- strategy_tree(load_sequences("ncert_data")),
71
- simplify(parse(string)),
72
- _no_integrate_left,
73
- )
74
- if len(out) >= 1:
75
- print(string)
76
- print("=>", out[0][0])
77
- print(["parse", "simplify"] + out[0][1])
78
- return None
79
63
 
80
64
  def god(string, debug=True):
81
65
  if debug:
@@ -1,5 +1,7 @@
1
- abs(3*x-5)+abs(8-x)=abs(3+2*x) absolute simplify simple_wavycurvy
2
- (sin(x)+cosec(x))^2+(cos(x)+sec(x))^2=7+tan(x)^2+cot(x)^2 trig0 fraction trig1 fraction simplify trig1 logic0
1
+ (x+1)*(x-1)=x^2-1 expand simplify logic0
2
+ (a+b)^2=a^2+b^2+2*a*b expand simplify logic0
3
+ 1+1=2 logic0
4
+ sin(x)^2+cos(x)^2=1 trig1 simplify logic0
3
5
  (1/(1-4*i)-2/(1+i))*((3-4*i)/(5+i))
4
6
  limit((1-cos(x))/x,x) limit1
5
7
  limit(sin(4*x)/sin(2*x),x) limit1
@@ -62,3 +64,5 @@ integrate(log(x)^2/x,x) integrate_subs integrate_formula integrate_clean
62
64
  integrate(sin(arctan(x))/(1+x^2),x) integrate_subs integrate_formula integrate_clean
63
65
  integrate(2*x*sin(x^2+1),x) integrate_formula integrate_subs integrate_formula integrate_clean
64
66
  integrate(sin(m*x),x) integrate_formula
67
+ abs(3*x-5)+abs(8-x)=abs(3+2*x) absolute simplify simple_wavycurvy
68
+ (sin(x)+cosec(x))^2+(cos(x)+sec(x))^2=7+tan(x)^2+cot(x)^2 trig0 fraction trig1 fraction simplify trig1 logic0
@@ -0,0 +1,22 @@
1
+ (x+1)*(x-1)=x^2-1 expand simplify logic0
2
+ 1+1=2 logic0
3
+ sin(x)^2+cos(x)^2=1 trig1 logic0
4
+ (1/(1-4*i)-2/(1+i))*((3-4*i)/(5+i))
5
+ limit((1-cos(x))/x,x) limit1
6
+ 4*x+3<6*x+7 simple_wavycurvy
7
+ integrate(x/((x+1)*(x+2)),x) apart integrate_formula
8
+ integrate(sin(2*x+5)^2,x) trig1 integrate_formula
9
+ integrate((1-cos(x))/(1+cos(x)),x) trig6 expand integrate_formula integrate_trig_formula simplify integrate_trig_formula integrate_formula
10
+ integrate(sin(x)/(1+cos(x)),x) trig6 expand integrate_formula simplify integrate_trig_formula
11
+ integrate(cos(2*x),x) integrate_formula
12
+ integrate(cosec(x)*(cosec(x)+cot(x)),x) trig0 fraction expand integrate_formula simplify integrate_trig_formula
13
+ integrate((1-sin(x))/cos(x)^2,x) expand integrate_formula integrate_trig_formula
14
+ integrate(x^2*(1-1/x^2),x) expand simplify integrate_formula
15
+ integrate(sec(x)^2/cosec(x)^2,x) trig0 simplify integrate_trig_formula integrate_formula trig1
16
+ integrate(sin(x)/sin(x+a),x) integrate_subs expand simplify integrate_formula integrate_trig_formula integrate_clean
17
+ integrate((e^(2*x)-1)/(e^(2*x)+1),x) integrate_subs integrate_formula expand simplify apart integrate_formula integrate_clean
18
+ integrate((e^(2*x)-e^(-2*x))/(e^(2*x)+e^(-2*x)),x) integrate_subs integrate_formula fraction simplify apart integrate_formula integrate_fraction integrate_clean
19
+ integrate(tan(2*x-3)^2,x) trig0 integrate_trig_formula integrate_formula
20
+ integrate((4*x+2)*sqrt(x^2+x+1),x) integrate_subs integrate_formula integrate_clean
21
+ abs(3*x-5)+abs(8-x)=abs(3+2*x) absolute simplify simple_wavycurvy
22
+ (sin(x)+cosec(x))^2+(cos(x)+sec(x))^2=7+tan(x)^2+cot(x)^2 trig0 fraction trig1 fraction simplify trig1 logic0
@@ -0,0 +1,617 @@
1
+ """Exact arithmetic with (nested) square roots.
2
+
3
+ G : an exact value const + sum(coef * sqrt(key)) where coef is a Fraction
4
+ and key is a Fraction or, for nested radicals, another G.
5
+ F : a number that is either an exact Fraction or a symbolic TreeNode.
6
+ """
7
+ import itertools
8
+ import math
9
+ from fractions import Fraction
10
+
11
+ from .base import *
12
+ from .simplify import simplify
13
+
14
+ MAX_INT_POWER = 64 # largest |integer exponent| expanded exactly
15
+ MAX_CONJUGATE_RADICALS = 8 # rationalising uses 2**n conjugates; cap n
16
+
17
+
18
+ class NotRadical(Exception):
19
+ """The tree is not const + sum(rational * sqrt(...))."""
20
+
21
+
22
+ # --------------------------------------------------------------------------
23
+ # small number helpers
24
+ # --------------------------------------------------------------------------
25
+ def frac_to_tree_2(eq):
26
+ if isinstance(eq, int):
27
+ eq = Fraction(eq)
28
+ if eq.numerator == 0:
29
+ return tree_form("d_0")
30
+ if eq.denominator == 1:
31
+ return tree_form(f"d_{eq.numerator}")
32
+ return TreeNode("f_div", [tree_form(f"d_{eq.numerator}"),
33
+ tree_form(f"d_{eq.denominator}")])
34
+
35
+
36
+ def perfect_sqrt(n):
37
+ """Exact rational square root of n, or None."""
38
+ n = Fraction(n)
39
+ if n < 0:
40
+ return None
41
+ p, q = math.isqrt(n.numerator), math.isqrt(n.denominator)
42
+ if p * p == n.numerator and q * q == n.denominator:
43
+ return Fraction(p, q)
44
+ return None
45
+
46
+
47
+ def simplify_sqrt(a):
48
+ """sqrt(a) = b * sqrt(c) with c squarefree (integer-valued Fraction)."""
49
+ a = Fraction(a)
50
+ if a <= 0:
51
+ return Fraction(1), a
52
+ p, q = a.numerator, a.denominator
53
+ c, b = p * q, 1 # sqrt(p/q) = sqrt(p*q) / q
54
+ while c % 4 == 0:
55
+ c //= 4
56
+ b *= 2
57
+ i = 3
58
+ while i * i <= c:
59
+ while c % (i * i) == 0:
60
+ c //= i * i
61
+ b *= i
62
+ i += 2
63
+ return Fraction(b, q), Fraction(c)
64
+
65
+
66
+ def coeff(eq):
67
+ """Split eq into (rational k, rest) with eq = k * rest; rest is None if eq is rational."""
68
+ out = frac(eq)
69
+ if out is not None:
70
+ return out, None
71
+ k, rest = Fraction(1), []
72
+ for factor in factor_generation(eq):
73
+ v = frac(factor)
74
+ if v is None:
75
+ rest.append(factor)
76
+ else:
77
+ k *= v
78
+ return k, product(rest)
79
+
80
+
81
+ def _coerce(x):
82
+ if isinstance(x, G):
83
+ return x
84
+ if isinstance(x, (int, Fraction)) and not isinstance(x, bool):
85
+ return G({}, Fraction(x))
86
+ raise TypeError(f"cannot use {type(x).__name__} as a G")
87
+
88
+
89
+ # --------------------------------------------------------------------------
90
+ # tree rewriting helpers
91
+ # --------------------------------------------------------------------------
92
+ def helper_2_h(eq):
93
+ if eq.name == "f_sqrt":
94
+ return eq.children[0] ** (tree_form("d_2") ** tree_form("d_-1"))
95
+ if eq.name == "f_div":
96
+ return eq.children[0] * eq.children[1] ** tree_form("d_-1")
97
+ return eq
98
+
99
+
100
+ def helper_3_h(eq):
101
+ if eq.name == "f_sub":
102
+ return eq.children[0] - eq.children[1]
103
+ if eq.name == "f_neg":
104
+ return -eq.children[0]
105
+ return eq
106
+
107
+
108
+ def helper_2(eq):
109
+ return transform_dfs(eq, helper_2_h)
110
+
111
+
112
+ def helper_3(eq):
113
+ return transform_dfs(eq, helper_3_h)
114
+
115
+
116
+ def _tree_eq(self):
117
+ return TreeNode(self.name, [child.eq() for child in self.children])
118
+
119
+
120
+ TreeNode.eq = _tree_eq
121
+
122
+
123
+ # --------------------------------------------------------------------------
124
+ # G : const + sum(coef * sqrt(key))
125
+ # --------------------------------------------------------------------------
126
+ class G:
127
+ def __init__(self, dic=None, const=Fraction(0)):
128
+ self.const = Fraction(const)
129
+ self.dic = {}
130
+ if dic is None:
131
+ return
132
+ if isinstance(dic, TreeNode):
133
+ self._from_tree(dic)
134
+ return
135
+ for key, item in dic.items():
136
+ if isinstance(key, int):
137
+ key = Fraction(key)
138
+ self.dic[key] = self.dic.get(key, Fraction(0)) + Fraction(item)
139
+
140
+ def _from_tree(self, tree):
141
+ value = frac(tree)
142
+ if value is not None:
143
+ self.const += value
144
+ return
145
+ terms = tree.children if tree.name == "f_add" else [tree]
146
+ for term in terms:
147
+ k, rest = coeff(helper_2(term))
148
+ if rest is None:
149
+ self.const += k
150
+ continue
151
+ if rest.name != "f_pow" or frac(rest.children[1]) != Fraction(1, 2):
152
+ raise NotRadical(str(term))
153
+ radicand = G(rest.children[0])
154
+ if not radicand.dic and radicand.const < 0:
155
+ raise NotRadical("negative radicand")
156
+ key = radicand if radicand.dic else radicand.const
157
+ self.dic[key] = self.dic.get(key, Fraction(0)) + k
158
+ self.simplify()
159
+
160
+ # ---- normal form -----------------------------------------------------
161
+ def copy(self):
162
+ return G(self.dic, self.const)
163
+
164
+ def is_zero(self):
165
+ return not self.dic and self.const == 0
166
+
167
+ def simplify(self):
168
+ """In place: pull squares out of radicands, merge equal radicals,
169
+ drop zero terms, move sqrt(1) terms into the constant."""
170
+ dic, const = {}, self.const
171
+ for key, coef in self.dic.items():
172
+ if isinstance(key, G):
173
+ key = key.copy().simplify()
174
+ if not key.dic:
175
+ key = key.const
176
+ if isinstance(key, Fraction):
177
+ b, key = simplify_sqrt(key)
178
+ coef = coef * b
179
+ if key == 1:
180
+ const += coef
181
+ continue
182
+ dic[key] = dic.get(key, Fraction(0)) + coef
183
+ self.dic = {k: v for k, v in dic.items()
184
+ if v != 0 and not (isinstance(k, Fraction) and k == 0)}
185
+ self.const = const
186
+ return self
187
+
188
+ # ---- arithmetic ------------------------------------------------------
189
+ def _terms(self):
190
+ return list(self.dic.items()) + [(Fraction(1), self.const)]
191
+
192
+ def __add__(self, other):
193
+ try:
194
+ other = _coerce(other)
195
+ except TypeError:
196
+ return NotImplemented
197
+ out = dict(self.dic)
198
+ for key, coef in other.dic.items():
199
+ out[key] = out.get(key, Fraction(0)) + coef
200
+ return G(out, self.const + other.const).simplify()
201
+
202
+ __radd__ = __add__
203
+
204
+ def __neg__(self):
205
+ return G({k: -v for k, v in self.dic.items()}, -self.const)
206
+
207
+ def __sub__(self, other):
208
+ try:
209
+ return self + (-_coerce(other))
210
+ except TypeError:
211
+ return NotImplemented
212
+
213
+ def __rsub__(self, other):
214
+ try:
215
+ return _coerce(other) + (-self)
216
+ except TypeError:
217
+ return NotImplemented
218
+
219
+ def __mul__(self, other):
220
+ try:
221
+ other = _coerce(other)
222
+ except TypeError:
223
+ return NotImplemented
224
+ out = {}
225
+ for a, ca in self._terms():
226
+ for b, cb in other._terms():
227
+ if isinstance(a, Fraction) and isinstance(b, Fraction):
228
+ c = a * b
229
+ else:
230
+ ga = G({}, a) if isinstance(a, Fraction) else a
231
+ gb = G({}, b) if isinstance(b, Fraction) else b
232
+ c = (ga * gb).simplify()
233
+ if not c.dic:
234
+ c = c.const
235
+ out[c] = out.get(c, Fraction(0)) + ca * cb
236
+ return G(out).simplify()
237
+
238
+ __rmul__ = __mul__
239
+
240
+ def __pow__(self, n):
241
+ """Integer powers; a negative power may return a TreeNode (see __truediv__)."""
242
+ if not isinstance(n, int):
243
+ return NotImplemented
244
+ if n < 0:
245
+ return G({}, 1) / (self ** -n)
246
+ result, base = G({}, 1), self.copy()
247
+ while n:
248
+ if n & 1:
249
+ result = result * base
250
+ n >>= 1
251
+ if n:
252
+ base = base * base
253
+ return result
254
+
255
+ def __truediv__(self, other):
256
+ """Returns a G, or a TreeNode when the quotient can't be rationalised."""
257
+ try:
258
+ other = _coerce(other)
259
+ except TypeError:
260
+ return NotImplemented
261
+ num, den = nested_radical(self), nested_radical(other)
262
+ if den.is_zero():
263
+ raise ZeroDivisionError("division by zero in G")
264
+ if num.is_zero():
265
+ return num
266
+ fallback = TreeNode("f_div", [num.eq(), den.eq()])
267
+ if any(isinstance(k, G) for k in den.dic):
268
+ return fallback
269
+ keys = list(den.dic)
270
+ if keys:
271
+ if len(keys) > MAX_CONJUGATE_RADICALS:
272
+ return fallback
273
+ conj_prod = G({}, 1)
274
+ for signs in itertools.product((1, -1), repeat=len(keys)):
275
+ if all(s == 1 for s in signs):
276
+ continue # that one is `den` itself
277
+ conj = G({k: den.dic[k] * s for k, s in zip(keys, signs)}, den.const)
278
+ conj_prod = conj_prod * conj
279
+ norm = nested_radical(conj_prod * den)
280
+ if norm.dic or norm.const == 0: # radicals not independent -> give up
281
+ return fallback
282
+ ans = nested_radical(conj_prod * num)
283
+ else:
284
+ norm, ans = G({}, den.const), num
285
+ return G({k: v / norm.const for k, v in ans.dic.items()},
286
+ ans.const / norm.const).simplify()
287
+
288
+ def __rtruediv__(self, other):
289
+ try:
290
+ return _coerce(other) / self
291
+ except TypeError:
292
+ return NotImplemented
293
+
294
+ # ---- comparison / display -------------------------------------------
295
+ def __eq__(self, other):
296
+ if isinstance(other, (int, Fraction)) and not isinstance(other, bool):
297
+ return not self.dic and self.const == other
298
+ if not isinstance(other, G):
299
+ return NotImplemented
300
+ return str_form(self.eq()) == str_form(other.eq())
301
+
302
+ def __hash__(self):
303
+ return hash(str_form(self.eq()))
304
+
305
+ def __repr__(self):
306
+ return str(self.eq())
307
+
308
+ def eq(self):
309
+ terms = []
310
+ for key, coef in self.dic.items():
311
+ if isinstance(key, Fraction):
312
+ root = frac_to_tree_2(key).fx("sqrt")
313
+ else:
314
+ root = key.eq().fx("sqrt")
315
+ if coef == 1:
316
+ terms.append(root)
317
+ elif coef != 0:
318
+ terms.append(frac_to_tree_2(coef) * root)
319
+ if self.const != 0:
320
+ terms.append(frac_to_tree_2(self.const))
321
+ return summation(terms)
322
+
323
+
324
+ # --------------------------------------------------------------------------
325
+ # denesting: sqrt(a + b*sqrt(c)) = sqrt(x) +/- sqrt(y)
326
+ # --------------------------------------------------------------------------
327
+ def denest(g):
328
+ """(sqrt(g) as a G, True) if g denests, otherwise (g normalised, False)."""
329
+ g = nested_radical(g)
330
+ if len(g.dic) != 1:
331
+ return g, False
332
+ (c, b), = g.dic.items()
333
+ a = g.const
334
+ if isinstance(c, G) or c <= 0 or a <= 0:
335
+ return g, False
336
+ d = perfect_sqrt(a * a - b * b * c)
337
+ if d is None:
338
+ return g, False
339
+ x, y = (a + d) / 2, (a - d) / 2
340
+ sign = Fraction(1) if b > 0 else Fraction(-1)
341
+ return (G({x: Fraction(1)}) + G({y: sign})), True
342
+
343
+
344
+ def nested_radical(g):
345
+ """Denest every nested radical of g that can be denested; returns a G."""
346
+ out, extra = {}, []
347
+ for key, coef in g.dic.items():
348
+ if isinstance(key, G):
349
+ val, ok = denest(key)
350
+ if ok:
351
+ extra.append(val * coef)
352
+ continue
353
+ key = val if val.dic else val.const
354
+ out[key] = out.get(key, Fraction(0)) + coef
355
+ result = G(out, g.const).simplify()
356
+ for e in extra:
357
+ result = result + e
358
+ return result
359
+
360
+
361
+ # --------------------------------------------------------------------------
362
+ # tree -> G evaluation
363
+ # --------------------------------------------------------------------------
364
+ def valid(eq):
365
+ if eq.name == "f_div" and contain2(eq.children[1], "f_sqrt"):
366
+ return False
367
+ if contain2(eq, "f_mul"):
368
+ return False
369
+ return all(valid(child) for child in eq.children)
370
+
371
+
372
+ def _to_tree(x):
373
+ return x.eq() if isinstance(x, G) else x
374
+
375
+
376
+ def _sqrt_of(inner):
377
+ if not isinstance(inner, G):
378
+ return TreeNode("f_sqrt", [inner])
379
+ inner = inner.copy().simplify()
380
+ if not inner.dic and inner.const < 0:
381
+ return TreeNode("f_sqrt", [inner.eq()])
382
+ return G({inner: Fraction(1)}).simplify()
383
+
384
+
385
+ def _divide(a, b):
386
+ if isinstance(a, G) and isinstance(b, G):
387
+ if b.is_zero():
388
+ return TreeNode("f_div", [a.eq(), b.eq()])
389
+ return a / b
390
+ return TreeNode("f_div", [_to_tree(a), _to_tree(b)])
391
+
392
+
393
+ def calc(eq):
394
+ """Evaluate a tree to a G where possible; unknown parts stay TreeNodes.
395
+ Never returns None."""
396
+ if not isinstance(eq, TreeNode):
397
+ return eq
398
+ value = frac(eq)
399
+ if value is not None:
400
+ return G({}, value)
401
+ if valid(eq):
402
+ try:
403
+ return G(eq)
404
+ except NotRadical:
405
+ pass
406
+ if not eq.children:
407
+ return eq
408
+
409
+ name = eq.name
410
+ parts = [calc(child) for child in eq.children]
411
+
412
+ if name in ("f_add", "f_mul"):
413
+ is_add = name == "f_add"
414
+ acc = G({}, 0 if is_add else 1)
415
+ rest = []
416
+ for p in parts:
417
+ if isinstance(p, G):
418
+ acc = acc + p if is_add else acc * p
419
+ else:
420
+ rest.append(p)
421
+ if not rest:
422
+ return acc
423
+ if is_add:
424
+ return summation(rest + ([] if acc.is_zero() else [acc.eq()]))
425
+ if acc.is_zero():
426
+ return acc
427
+ return product(rest + ([] if acc == 1 else [acc.eq()]))
428
+
429
+ if name == "f_sqrt" and len(parts) == 1:
430
+ return _sqrt_of(parts[0])
431
+
432
+ if name == "f_div" and len(parts) == 2:
433
+ return _divide(parts[0], parts[1])
434
+
435
+ if name == "f_pow" and len(parts) == 2 and isinstance(parts[0], G):
436
+ base, e = parts[0], frac(eq.children[1])
437
+ if e == Fraction(1, 2):
438
+ return _sqrt_of(base)
439
+ if e is not None and e.denominator == 1 and abs(e) <= MAX_INT_POWER:
440
+ n = int(e)
441
+ if n > 0 or (n == 0 and not base.is_zero()):
442
+ return base ** n
443
+ if n < 0 and not base.is_zero():
444
+ return _divide(G({}, 1), base ** -n)
445
+
446
+ return TreeNode(name, [_to_tree(p) for p in parts])
447
+
448
+
449
+ def solve_sqrt_h(eq):
450
+ x = calc(flatten_tree(helper_3(eq)))
451
+ if isinstance(x, G):
452
+ x = nested_radical(x).eq()
453
+ return flatten_tree(x)
454
+
455
+
456
+ def solve_sqrt(eq):
457
+ return dowhile(eq, lambda x: simplify(solve_sqrt_h(x), False))
458
+
459
+
460
+ def print_raw(eq):
461
+ print(string_equation(str_form(eq)))
462
+
463
+
464
+ def frac_2(eq):
465
+ return frac(helper_2(eq))
466
+
467
+
468
+ def simp(eq):
469
+ if isinstance(eq, F):
470
+ eq = eq.value
471
+ if isinstance(eq, TreeNode):
472
+ out = solve_sqrt(eq)
473
+ if out is not None:
474
+ eq = out
475
+ value = frac_2(eq)
476
+ return eq if value is None else value
477
+ return eq
478
+
479
+
480
+ # --------------------------------------------------------------------------
481
+ # F : exact Fraction or symbolic TreeNode
482
+ # --------------------------------------------------------------------------
483
+ def get_value(x):
484
+ if isinstance(x, F):
485
+ return x.value
486
+ if isinstance(x, int):
487
+ return Fraction(x)
488
+ return x
489
+
490
+
491
+ def promote_obj(x):
492
+ if isinstance(x, F):
493
+ x = x.value
494
+ if isinstance(x, TreeNode):
495
+ return x
496
+ if isinstance(x, (int, Fraction)):
497
+ return frac_to_tree_2(Fraction(x))
498
+ raise TypeError(f"cannot promote {type(x).__name__}")
499
+
500
+
501
+ def _is_operand(x):
502
+ return isinstance(x, (int, Fraction, TreeNode, F)) and not isinstance(x, bool)
503
+
504
+
505
+ def compare(a, b):
506
+ """-1 / 0 / 1, or None when the two can't be compared numerically."""
507
+ a, b = get_value(a), get_value(b)
508
+ if isinstance(a, Fraction) and isinstance(b, Fraction):
509
+ return (a > b) - (a < b)
510
+ if isinstance(a, TreeNode) and isinstance(b, TreeNode) and str_form(a) == str_form(b):
511
+ return 0
512
+ vals = []
513
+ for item in (a, b):
514
+ if isinstance(item, TreeNode):
515
+ try:
516
+ item = compute(helper_2(item))
517
+ except OverflowError:
518
+ item = None
519
+ vals.append(item)
520
+ if any(v is None for v in vals):
521
+ return None
522
+ try:
523
+ x, y = float(vals[0]), float(vals[1])
524
+ except OverflowError:
525
+ return None
526
+ if math.isclose(x, y, rel_tol=1e-12, abs_tol=1e-12):
527
+ return 0
528
+ return 1 if x > y else -1
529
+
530
+
531
+ class F:
532
+ def __init__(self, value=0):
533
+ if isinstance(value, F):
534
+ value = value.value
535
+ elif isinstance(value, int):
536
+ value = Fraction(value)
537
+ self.value = value
538
+ if isinstance(self.value, TreeNode):
539
+ self.value = simp(self.value)
540
+
541
+ def promote(self, other, op):
542
+ return F(TreeNode(op, [promote_obj(self), promote_obj(other)]))
543
+
544
+ def fx(self, name):
545
+ return F(TreeNode("f_" + name, [promote_obj(self)]))
546
+
547
+ def _binary(self, other, op, exact, reverse=False, force_symbolic=None):
548
+ if not _is_operand(other):
549
+ return NotImplemented
550
+ a, b = (F(other), self) if reverse else (self, F(other))
551
+ x, y = get_value(a), get_value(b)
552
+ if (isinstance(x, TreeNode) or isinstance(y, TreeNode)
553
+ or (force_symbolic is not None and force_symbolic(x, y))):
554
+ return a.promote(b, op)
555
+ return F(exact(x, y))
556
+
557
+ def __add__(self, o): return self._binary(o, "f_add", lambda x, y: x + y)
558
+ def __radd__(self, o): return self._binary(o, "f_add", lambda x, y: x + y, True)
559
+ def __sub__(self, o): return self._binary(o, "f_sub", lambda x, y: x - y)
560
+ def __rsub__(self, o): return self._binary(o, "f_sub", lambda x, y: x - y, True)
561
+ def __mul__(self, o): return self._binary(o, "f_mul", lambda x, y: x * y)
562
+ def __rmul__(self, o): return self._binary(o, "f_mul", lambda x, y: x * y, True)
563
+ def __truediv__(self, o): return self._binary(o, "f_div", lambda x, y: x / y)
564
+ def __rtruediv__(self, o): return self._binary(o, "f_div", lambda x, y: x / y, True)
565
+
566
+ # a non-integer exponent stays symbolic so the result stays exact
567
+ @staticmethod
568
+ def _fractional_exp(x, y):
569
+ return isinstance(y, Fraction) and y.denominator != 1
570
+
571
+ def __pow__(self, o):
572
+ return self._binary(o, "f_pow", lambda x, y: x ** y,
573
+ force_symbolic=F._fractional_exp)
574
+
575
+ def __rpow__(self, o):
576
+ return self._binary(o, "f_pow", lambda x, y: x ** y, True,
577
+ force_symbolic=F._fractional_exp)
578
+
579
+ def __neg__(self):
580
+ if isinstance(self.value, TreeNode):
581
+ return F(TreeNode("f_neg", [promote_obj(self)]))
582
+ return F(-self.value)
583
+
584
+ def __hash__(self):
585
+ v = get_value(self)
586
+ if isinstance(v, Fraction):
587
+ return hash(v)
588
+ return hash(str_form(v))
589
+
590
+ def __lt__(self, o):
591
+ return _is_operand(o) and compare(self, o) == -1
592
+
593
+ def __le__(self, o):
594
+ return _is_operand(o) and compare(self, o) in (-1, 0)
595
+
596
+ def __gt__(self, o):
597
+ return _is_operand(o) and compare(self, o) == 1
598
+
599
+ def __ge__(self, o):
600
+ return _is_operand(o) and compare(self, o) in (1, 0)
601
+
602
+ def __eq__(self, o):
603
+ if not _is_operand(o):
604
+ return NotImplemented
605
+ return compare(self, o) == 0
606
+
607
+ def __float__(self):
608
+ value = get_value(self)
609
+ if isinstance(value, TreeNode):
610
+ out = compute(helper_2(value))
611
+ if out is None:
612
+ raise ValueError("expression has no numeric value")
613
+ return float(out)
614
+ return float(value)
615
+
616
+ def __repr__(self):
617
+ return str(self.value)
@@ -10,6 +10,7 @@ import copy
10
10
  from .diff import diff
11
11
  from .logic import logic0
12
12
  from .tool import poly, poly_simplify
13
+ from .square_root import solve_sqrt
13
14
 
14
15
  def intersection2(domain, lst):
15
16
  domain = copy.deepcopy(domain)
@@ -606,6 +607,7 @@ def simple_wavycurvy(eq, mode=False):
606
607
  eq = factor2(eq)
607
608
  eq = simplify(eq, True, True)
608
609
  eq = wavycurvy(eq, mode)
610
+ eq = solve_sqrt(eq)
609
611
  if eq.name == "f_range":
610
612
  tmp = eq2range(eq).truth()
611
613
  if tmp == 1:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mathai
3
- Version: 2.4.8
3
+ Version: 2.5.0
4
4
  Summary: Mathematics solving Ai tailored to NCERT
5
5
  Home-page: https://github.com/infinity390/mathai
6
6
  Requires-Python: >=3.7
@@ -602,18 +602,24 @@ top_view_length : 80
602
602
  line_length : 80
603
603
  ```
604
604
 
605
- #### Example Demonstration 13 (formula applier)
605
+ #### Example Demonstration 13 (formula compiler/applier)
606
606
  ```python
607
607
  from mathai import *
608
608
  formula = "a*x^2+b*x+c a*(x-(-b+sqrt(b^2-4*a*c))/(2*a))*(x-(-b-sqrt(b^2-4*a*c))/(2*a))"
609
609
  formula += " [[a,1],[b,0],[b,1]] _ _ _ _"
610
610
  question = simplify(parse("sin(x)^2-4*sin(x)+4"))
611
- print(formula_applier(question, formula))
611
+ print(formula_list_compiler(formula)(question))
612
+
613
+ formula = "sin(x)^2+cos(x)^2 1"
614
+ formula += " [] _ _ _ _"
615
+ question = simplify(parse("sin(pi/3)^2+cos(pi/3)^2+1"))
616
+ print(formula_list_compiler(formula)(question))
612
617
  ```
613
618
 
614
619
  #### Output
615
620
  ```
616
621
  (-2+sin(x))^2
622
+ 2
617
623
  ```
618
624
 
619
625
  ### Questions solved using god() function
@@ -24,12 +24,14 @@ mathai/matrix.py
24
24
  mathai/ml.py
25
25
  mathai/mlscalar.py
26
26
  mathai/ncert_data.txt
27
+ mathai/ncert_data_optimized.txt
27
28
  mathai/ode.py
28
29
  mathai/parser.py
29
30
  mathai/pde.py
30
31
  mathai/printeq.py
31
32
  mathai/qm.py
32
33
  mathai/simplify.py
34
+ mathai/square_root.py
33
35
  mathai/statistics.py
34
36
  mathai/test.py
35
37
  mathai/tool.py
@@ -3,7 +3,7 @@ from setuptools import setup, find_packages
3
3
 
4
4
  setup(
5
5
  name="mathai",
6
- version="2.4.8",
6
+ version="2.5.0",
7
7
  description="Mathematics solving Ai tailored to NCERT",
8
8
  long_description=Path("README.md").read_text(encoding="utf-8"),
9
9
  long_description_content_type="text/markdown",
@@ -11,7 +11,7 @@ setup(
11
11
  packages=find_packages(),
12
12
  include_package_data=True,
13
13
  package_data={
14
- "mathai": ["ncert_data.txt"],
14
+ "mathai": ["ncert_data.txt", "ncert_data_optimized.txt"],
15
15
  },
16
16
  python_requires=">=3.7",
17
17
  )
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes