mathai 2.4.7__tar.gz → 2.4.9__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 (43) hide show
  1. {mathai-2.4.7 → mathai-2.4.9}/PKG-INFO +9 -3
  2. {mathai-2.4.7 → mathai-2.4.9}/README.md +8 -2
  3. {mathai-2.4.7 → mathai-2.4.9}/mathai/__init__.py +2 -2
  4. {mathai-2.4.7 → mathai-2.4.9}/mathai/benchmark.py +12 -129
  5. {mathai-2.4.7 → mathai-2.4.9}/mathai/decision.py +9 -7
  6. mathai-2.4.9/mathai/square_root.py +617 -0
  7. {mathai-2.4.7 → mathai-2.4.9}/mathai/univariate_inequality.py +1 -0
  8. {mathai-2.4.7 → mathai-2.4.9}/mathai.egg-info/PKG-INFO +9 -3
  9. {mathai-2.4.7 → mathai-2.4.9}/mathai.egg-info/SOURCES.txt +1 -0
  10. {mathai-2.4.7 → mathai-2.4.9}/setup.py +1 -1
  11. {mathai-2.4.7 → mathai-2.4.9}/mathai/apart.py +0 -0
  12. {mathai-2.4.7 → mathai-2.4.9}/mathai/base.py +0 -0
  13. {mathai-2.4.7 → mathai-2.4.9}/mathai/diff.py +0 -0
  14. {mathai-2.4.7 → mathai-2.4.9}/mathai/electronic.py +0 -0
  15. {mathai-2.4.7 → mathai-2.4.9}/mathai/expand.py +0 -0
  16. {mathai-2.4.7 → mathai-2.4.9}/mathai/factor.py +0 -0
  17. {mathai-2.4.7 → mathai-2.4.9}/mathai/formula_applier.py +0 -0
  18. {mathai-2.4.7 → mathai-2.4.9}/mathai/formula_compiler.py +0 -0
  19. {mathai-2.4.7 → mathai-2.4.9}/mathai/fraction.py +0 -0
  20. {mathai-2.4.7 → mathai-2.4.9}/mathai/graphics.py +0 -0
  21. {mathai-2.4.7 → mathai-2.4.9}/mathai/integrate.py +0 -0
  22. {mathai-2.4.7 → mathai-2.4.9}/mathai/inverse.py +0 -0
  23. {mathai-2.4.7 → mathai-2.4.9}/mathai/lambda_calculus.py +0 -0
  24. {mathai-2.4.7 → mathai-2.4.9}/mathai/limit.py +0 -0
  25. {mathai-2.4.7 → mathai-2.4.9}/mathai/linear.py +0 -0
  26. {mathai-2.4.7 → mathai-2.4.9}/mathai/logic.py +0 -0
  27. {mathai-2.4.7 → mathai-2.4.9}/mathai/matrix.py +0 -0
  28. {mathai-2.4.7 → mathai-2.4.9}/mathai/ml.py +0 -0
  29. {mathai-2.4.7 → mathai-2.4.9}/mathai/mlscalar.py +0 -0
  30. {mathai-2.4.7 → mathai-2.4.9}/mathai/ncert_data.txt +0 -0
  31. {mathai-2.4.7 → mathai-2.4.9}/mathai/ode.py +0 -0
  32. {mathai-2.4.7 → mathai-2.4.9}/mathai/parser.py +0 -0
  33. {mathai-2.4.7 → mathai-2.4.9}/mathai/pde.py +0 -0
  34. {mathai-2.4.7 → mathai-2.4.9}/mathai/printeq.py +0 -0
  35. {mathai-2.4.7 → mathai-2.4.9}/mathai/qm.py +0 -0
  36. {mathai-2.4.7 → mathai-2.4.9}/mathai/simplify.py +0 -0
  37. {mathai-2.4.7 → mathai-2.4.9}/mathai/statistics.py +0 -0
  38. {mathai-2.4.7 → mathai-2.4.9}/mathai/test.py +0 -0
  39. {mathai-2.4.7 → mathai-2.4.9}/mathai/tool.py +0 -0
  40. {mathai-2.4.7 → mathai-2.4.9}/mathai/trig.py +0 -0
  41. {mathai-2.4.7 → mathai-2.4.9}/mathai.egg-info/dependency_links.txt +0 -0
  42. {mathai-2.4.7 → mathai-2.4.9}/mathai.egg-info/top_level.txt +0 -0
  43. {mathai-2.4.7 → mathai-2.4.9}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mathai
3
- Version: 2.4.7
3
+ Version: 2.4.9
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
@@ -1,5 +1,3 @@
1
- from .benchmark import load_sequences, strategy_tree, execute_parallel
2
-
3
1
  from .ode import diffsolve as ode_solve
4
2
  from .ode import diffsolve_sep as ode_shift_term
5
3
 
@@ -9,6 +7,8 @@ from .qm import hydrogen_gse, helium_gse, power_rule
9
7
 
10
8
  from .graphics import draw_graphics_from_solution, solve_graphics, solve_graphics_string
11
9
 
10
+ from .square_root import solve_sqrt
11
+
12
12
  from .benchmark import benchmark
13
13
 
14
14
  from .formula_compiler import formula_compiler
@@ -1,6 +1,4 @@
1
- import atexit
2
1
  import copy
3
- import multiprocessing
4
2
  from pathlib import Path
5
3
 
6
4
  from .simplify import simplify
@@ -175,138 +173,23 @@ def print_tree(node, depth=0):
175
173
 
176
174
 
177
175
  # ---------------------------------------------------------------------------
178
- # Parallel execution
179
- #
180
- # Design: nothing heavy is pickled per call.
181
- # - Each worker builds the tree once, from the dataset name, at pool start.
182
- # - Terminal conditions are looked up by name in TERMINAL_CONDITIONS, so no
183
- # function object is ever sent to a worker.
184
- # - Pools persist across calls, so spawn/import cost is paid once.
185
- # - Per task, only (branch index, equation, condition name, generation)
186
- # is sent.
176
+ # Serial Execution Entry Point
187
177
  # ---------------------------------------------------------------------------
188
178
 
189
- # Register terminal conditions here. Workers re-import this module and find
190
- # them by name.
191
- TERMINAL_CONDITIONS = {
192
- "no_integrate": lambda eq: not contain2(eq, "f_integrate"),
193
- }
194
179
 
195
- _TREES = {} # data_name -> root Node (cached per process)
196
- _POOLS = {} # (data_name, n_cores) -> (pool, generation counter)
197
-
198
- # Worker-process globals, set once by _init_worker
199
- _W_BRANCHES = None
200
- _W_GEN = None
201
-
202
-
203
- def _get_tree(data_name):
204
- if data_name not in _TREES:
205
- _TREES[data_name] = strategy_tree(load_sequences(data_name))
206
- return _TREES[data_name]
207
-
208
-
209
- def _init_worker(data_name, gen):
210
- global _W_BRANCHES, _W_GEN
211
- _W_BRANCHES = list(_get_tree(data_name).children.values())
212
- _W_GEN = gen
213
-
214
-
215
- def _worker(task):
216
- idx, equation, cond_name, my_gen = task
217
-
218
- # Stale task left over from a call that already finished: skip it.
219
- if _W_GEN.value != my_gen:
220
- return []
221
-
222
- cond = TERMINAL_CONDITIONS[cond_name] if cond_name else None
223
- try:
224
- return _W_BRANCHES[idx].execute(equation, [], cond)
225
- except Exception:
226
- # A failing branch must not discard results from other branches.
227
- return []
228
-
229
-
230
- def _first_match(results, cond):
231
- for eq, seq in results:
232
- if cond is None or cond(eq):
233
- return [(eq, seq)]
234
- return None
235
-
236
-
237
- def _execute_sequential(data_name, equation, cond_name):
238
- cond = TERMINAL_CONDITIONS[cond_name] if cond_name else None
239
-
240
- for child in _get_tree(data_name).children.values():
180
+ def execute_serial(tree, equation, terminal_condition=None):
181
+ """
182
+ Executes the strategy tree serially and returns at most one
183
+ matching path as a list of tuples: [(equation, sequence)].
184
+ """
185
+ for child in tree.children.values():
241
186
  try:
242
- results = child.execute(copy.deepcopy(equation), [], cond)
187
+ results = child.execute(copy.deepcopy(equation), [], terminal_condition)
243
188
  except Exception:
244
- continue # same semantics as the workers
189
+ continue
245
190
 
246
- match = _first_match(results, cond)
247
- if match is not None:
248
- return match
191
+ for eq, seq in results:
192
+ if terminal_condition is None or terminal_condition(eq):
193
+ return [(eq, seq)]
249
194
 
250
195
  return []
251
-
252
-
253
- def _get_pool(data_name, n_cores):
254
- key = (data_name, n_cores)
255
-
256
- if key not in _POOLS:
257
- gen = multiprocessing.Value("i", 0, lock=False)
258
- pool = multiprocessing.Pool(
259
- n_cores, initializer=_init_worker, initargs=(data_name, gen)
260
- )
261
- _POOLS[key] = (pool, gen)
262
-
263
- return _POOLS[key]
264
-
265
-
266
- def shutdown_pools():
267
- for pool, _ in _POOLS.values():
268
- pool.terminate()
269
- pool.join()
270
- _POOLS.clear()
271
-
272
-
273
- atexit.register(shutdown_pools)
274
-
275
-
276
- def execute_parallel(data_name, equation, n_cores=None, condition_name=None):
277
- """
278
- Run the strategy tree for `data_name` against `equation` and return at
279
- most one result as a one-item list [(equation, sequence)].
280
-
281
- condition_name: key into TERMINAL_CONDITIONS, or None.
282
-
283
- On Windows/macOS (spawn start method) callers using n_cores > 1 must be
284
- guarded by ``if __name__ == "__main__":``.
285
- """
286
- n_branches = len(_get_tree(data_name).children)
287
- if n_branches == 0:
288
- return []
289
-
290
- if n_cores is None:
291
- n_cores = multiprocessing.cpu_count()
292
- # Never more processes than branches, never fewer than one
293
- n_cores = max(1, min(n_cores, n_branches))
294
-
295
- if n_cores == 1:
296
- return _execute_sequential(data_name, equation, condition_name)
297
-
298
- cond = TERMINAL_CONDITIONS[condition_name] if condition_name else None
299
- pool, gen = _get_pool(data_name, n_cores)
300
-
301
- my_gen = gen.value + 1
302
- gen.value = my_gen
303
- tasks = [(i, equation, condition_name, my_gen) for i in range(n_branches)]
304
-
305
- try:
306
- for branch_results in pool.imap_unordered(_worker, tasks):
307
- match = _first_match(branch_results, cond)
308
- if match is not None:
309
- return match
310
- return []
311
- finally:
312
- gen.value = my_gen + 1 # cancels any still-queued tasks
@@ -15,7 +15,7 @@ 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_parallel
18
+ from .benchmark import load_sequences, strategy_tree, execute_serial
19
19
 
20
20
  def vlist_2(eq):
21
21
  out = []
@@ -61,19 +61,21 @@ def solve_subs(q):
61
61
  if len(solution.keys()) == 0:
62
62
  return orig
63
63
  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(" "))
64
66
 
65
67
 
66
- def god2(string, core=1):
67
- out = execute_parallel(
68
- "ncert_data", simplify(parse(string)), core, "no_integrate"
68
+ def god2(string):
69
+ out = execute_serial(
70
+ strategy_tree(load_sequences("ncert_data")),
71
+ simplify(parse(string)),
72
+ _no_integrate_left,
69
73
  )
70
-
71
74
  if len(out) >= 1:
72
75
  print(string)
73
76
  print("=>", out[0][0])
74
77
  print(["parse", "simplify"] + out[0][1])
75
-
76
- return out
78
+ return None
77
79
 
78
80
  def god(string, debug=True):
79
81
  if debug:
@@ -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)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mathai
3
- Version: 2.4.7
3
+ Version: 2.4.9
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
@@ -30,6 +30,7 @@ mathai/pde.py
30
30
  mathai/printeq.py
31
31
  mathai/qm.py
32
32
  mathai/simplify.py
33
+ mathai/square_root.py
33
34
  mathai/statistics.py
34
35
  mathai/test.py
35
36
  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.7",
6
+ version="2.4.9",
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",
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
File without changes