mathai 2.4.8__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.
- {mathai-2.4.8 → mathai-2.4.9}/PKG-INFO +9 -3
- {mathai-2.4.8 → mathai-2.4.9}/README.md +8 -2
- {mathai-2.4.8 → mathai-2.4.9}/mathai/__init__.py +2 -0
- mathai-2.4.9/mathai/square_root.py +617 -0
- {mathai-2.4.8 → mathai-2.4.9}/mathai/univariate_inequality.py +1 -0
- {mathai-2.4.8 → mathai-2.4.9}/mathai.egg-info/PKG-INFO +9 -3
- {mathai-2.4.8 → mathai-2.4.9}/mathai.egg-info/SOURCES.txt +1 -0
- {mathai-2.4.8 → mathai-2.4.9}/setup.py +1 -1
- {mathai-2.4.8 → mathai-2.4.9}/mathai/apart.py +0 -0
- {mathai-2.4.8 → mathai-2.4.9}/mathai/base.py +0 -0
- {mathai-2.4.8 → mathai-2.4.9}/mathai/benchmark.py +0 -0
- {mathai-2.4.8 → mathai-2.4.9}/mathai/decision.py +0 -0
- {mathai-2.4.8 → mathai-2.4.9}/mathai/diff.py +0 -0
- {mathai-2.4.8 → mathai-2.4.9}/mathai/electronic.py +0 -0
- {mathai-2.4.8 → mathai-2.4.9}/mathai/expand.py +0 -0
- {mathai-2.4.8 → mathai-2.4.9}/mathai/factor.py +0 -0
- {mathai-2.4.8 → mathai-2.4.9}/mathai/formula_applier.py +0 -0
- {mathai-2.4.8 → mathai-2.4.9}/mathai/formula_compiler.py +0 -0
- {mathai-2.4.8 → mathai-2.4.9}/mathai/fraction.py +0 -0
- {mathai-2.4.8 → mathai-2.4.9}/mathai/graphics.py +0 -0
- {mathai-2.4.8 → mathai-2.4.9}/mathai/integrate.py +0 -0
- {mathai-2.4.8 → mathai-2.4.9}/mathai/inverse.py +0 -0
- {mathai-2.4.8 → mathai-2.4.9}/mathai/lambda_calculus.py +0 -0
- {mathai-2.4.8 → mathai-2.4.9}/mathai/limit.py +0 -0
- {mathai-2.4.8 → mathai-2.4.9}/mathai/linear.py +0 -0
- {mathai-2.4.8 → mathai-2.4.9}/mathai/logic.py +0 -0
- {mathai-2.4.8 → mathai-2.4.9}/mathai/matrix.py +0 -0
- {mathai-2.4.8 → mathai-2.4.9}/mathai/ml.py +0 -0
- {mathai-2.4.8 → mathai-2.4.9}/mathai/mlscalar.py +0 -0
- {mathai-2.4.8 → mathai-2.4.9}/mathai/ncert_data.txt +0 -0
- {mathai-2.4.8 → mathai-2.4.9}/mathai/ode.py +0 -0
- {mathai-2.4.8 → mathai-2.4.9}/mathai/parser.py +0 -0
- {mathai-2.4.8 → mathai-2.4.9}/mathai/pde.py +0 -0
- {mathai-2.4.8 → mathai-2.4.9}/mathai/printeq.py +0 -0
- {mathai-2.4.8 → mathai-2.4.9}/mathai/qm.py +0 -0
- {mathai-2.4.8 → mathai-2.4.9}/mathai/simplify.py +0 -0
- {mathai-2.4.8 → mathai-2.4.9}/mathai/statistics.py +0 -0
- {mathai-2.4.8 → mathai-2.4.9}/mathai/test.py +0 -0
- {mathai-2.4.8 → mathai-2.4.9}/mathai/tool.py +0 -0
- {mathai-2.4.8 → mathai-2.4.9}/mathai/trig.py +0 -0
- {mathai-2.4.8 → mathai-2.4.9}/mathai.egg-info/dependency_links.txt +0 -0
- {mathai-2.4.8 → mathai-2.4.9}/mathai.egg-info/top_level.txt +0 -0
- {mathai-2.4.8 → 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.
|
|
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(
|
|
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(
|
|
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,6 +7,8 @@ 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 .square_root import solve_sqrt
|
|
11
|
+
|
|
10
12
|
from .benchmark import benchmark
|
|
11
13
|
|
|
12
14
|
from .formula_compiler import formula_compiler
|
|
@@ -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)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mathai
|
|
3
|
-
Version: 2.4.
|
|
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(
|
|
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
|
|
@@ -3,7 +3,7 @@ from setuptools import setup, find_packages
|
|
|
3
3
|
|
|
4
4
|
setup(
|
|
5
5
|
name="mathai",
|
|
6
|
-
version="2.4.
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|