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.
- {mathai-2.4.7 → mathai-2.4.9}/PKG-INFO +9 -3
- {mathai-2.4.7 → mathai-2.4.9}/README.md +8 -2
- {mathai-2.4.7 → mathai-2.4.9}/mathai/__init__.py +2 -2
- {mathai-2.4.7 → mathai-2.4.9}/mathai/benchmark.py +12 -129
- {mathai-2.4.7 → mathai-2.4.9}/mathai/decision.py +9 -7
- mathai-2.4.9/mathai/square_root.py +617 -0
- {mathai-2.4.7 → mathai-2.4.9}/mathai/univariate_inequality.py +1 -0
- {mathai-2.4.7 → mathai-2.4.9}/mathai.egg-info/PKG-INFO +9 -3
- {mathai-2.4.7 → mathai-2.4.9}/mathai.egg-info/SOURCES.txt +1 -0
- {mathai-2.4.7 → mathai-2.4.9}/setup.py +1 -1
- {mathai-2.4.7 → mathai-2.4.9}/mathai/apart.py +0 -0
- {mathai-2.4.7 → mathai-2.4.9}/mathai/base.py +0 -0
- {mathai-2.4.7 → mathai-2.4.9}/mathai/diff.py +0 -0
- {mathai-2.4.7 → mathai-2.4.9}/mathai/electronic.py +0 -0
- {mathai-2.4.7 → mathai-2.4.9}/mathai/expand.py +0 -0
- {mathai-2.4.7 → mathai-2.4.9}/mathai/factor.py +0 -0
- {mathai-2.4.7 → mathai-2.4.9}/mathai/formula_applier.py +0 -0
- {mathai-2.4.7 → mathai-2.4.9}/mathai/formula_compiler.py +0 -0
- {mathai-2.4.7 → mathai-2.4.9}/mathai/fraction.py +0 -0
- {mathai-2.4.7 → mathai-2.4.9}/mathai/graphics.py +0 -0
- {mathai-2.4.7 → mathai-2.4.9}/mathai/integrate.py +0 -0
- {mathai-2.4.7 → mathai-2.4.9}/mathai/inverse.py +0 -0
- {mathai-2.4.7 → mathai-2.4.9}/mathai/lambda_calculus.py +0 -0
- {mathai-2.4.7 → mathai-2.4.9}/mathai/limit.py +0 -0
- {mathai-2.4.7 → mathai-2.4.9}/mathai/linear.py +0 -0
- {mathai-2.4.7 → mathai-2.4.9}/mathai/logic.py +0 -0
- {mathai-2.4.7 → mathai-2.4.9}/mathai/matrix.py +0 -0
- {mathai-2.4.7 → mathai-2.4.9}/mathai/ml.py +0 -0
- {mathai-2.4.7 → mathai-2.4.9}/mathai/mlscalar.py +0 -0
- {mathai-2.4.7 → mathai-2.4.9}/mathai/ncert_data.txt +0 -0
- {mathai-2.4.7 → mathai-2.4.9}/mathai/ode.py +0 -0
- {mathai-2.4.7 → mathai-2.4.9}/mathai/parser.py +0 -0
- {mathai-2.4.7 → mathai-2.4.9}/mathai/pde.py +0 -0
- {mathai-2.4.7 → mathai-2.4.9}/mathai/printeq.py +0 -0
- {mathai-2.4.7 → mathai-2.4.9}/mathai/qm.py +0 -0
- {mathai-2.4.7 → mathai-2.4.9}/mathai/simplify.py +0 -0
- {mathai-2.4.7 → mathai-2.4.9}/mathai/statistics.py +0 -0
- {mathai-2.4.7 → mathai-2.4.9}/mathai/test.py +0 -0
- {mathai-2.4.7 → mathai-2.4.9}/mathai/tool.py +0 -0
- {mathai-2.4.7 → mathai-2.4.9}/mathai/trig.py +0 -0
- {mathai-2.4.7 → mathai-2.4.9}/mathai.egg-info/dependency_links.txt +0 -0
- {mathai-2.4.7 → mathai-2.4.9}/mathai.egg-info/top_level.txt +0 -0
- {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.
|
|
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
|
|
@@ -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
|
-
#
|
|
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
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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), [],
|
|
187
|
+
results = child.execute(copy.deepcopy(equation), [], terminal_condition)
|
|
243
188
|
except Exception:
|
|
244
|
-
continue
|
|
189
|
+
continue
|
|
245
190
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
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,
|
|
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
|
|
67
|
-
out =
|
|
68
|
-
"ncert_data"
|
|
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)
|
|
@@ -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
|