Skip to content

Commit

Permalink
Two postponed TODOs in recent PRs...
Browse files Browse the repository at this point in the history
* Move eval functions to mathics.eval.numbers.numbers
* Move "no_doc" after imports
  • Loading branch information
rocky committed Oct 27, 2024
1 parent f88d3c2 commit a35c843
Show file tree
Hide file tree
Showing 3 changed files with 101 additions and 116 deletions.
103 changes: 10 additions & 93 deletions mathics/builtin/atomic/numbers.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,7 @@
However, things like 'N[Pi, 100]' should work as expected.
"""

from functools import lru_cache

import mpmath
import sympy

from mathics.core.atoms import (
Integer,
Expand All @@ -33,6 +30,7 @@
from mathics.core.attributes import A_LISTABLE, A_PROTECTED, A_READ_PROTECTED
from mathics.core.builtin import Builtin, Predefined
from mathics.core.convert.python import from_python
from mathics.core.evaluation import Evaluation
from mathics.core.expression import Expression
from mathics.core.list import ListExpression
from mathics.core.number import (
Expand All @@ -52,100 +50,19 @@
SymbolRound,
)
from mathics.eval.nevaluator import eval_N
from mathics.eval.numbers.numbers import eval_Accuracy, eval_Precision
from mathics.eval.numbers.numbers import (
check_finite_decimal,
convert_float_base,
convert_repeating_decimal,
eval_Accuracy,
eval_Precision,
log_n_b,
)

SymbolIntegerDigits = Symbol("IntegerDigits")
SymbolIntegerExponent = Symbol("IntegerExponent")


@lru_cache()
def log_n_b(py_n, py_b) -> int:
return (
int(mpmath.floor(mpmath.log(py_n, py_b))) + 1 if py_n != 0 and py_n != 1 else 1
)


def check_finite_decimal(denominator):
# The rational number is finite decimal if the denominator has form 2^a * 5^b
while denominator % 5 == 0:
denominator = denominator / 5

while denominator % 2 == 0:
denominator = denominator / 2

return True if denominator == 1 else False


def convert_repeating_decimal(numerator, denominator, base):
head = [x for x in str(numerator // denominator)]
tails = []
subresults = [numerator % denominator]
numerator %= denominator

while numerator != 0: # only rational input can go to this case
numerator *= base
result_digit, numerator = divmod(numerator, denominator)
tails.append(str(result_digit))
if numerator not in subresults:
subresults.append(numerator)
else:
break

for i in range(len(head) - 1, -1, -1):
j = len(tails) - 1
if head[i] != tails[j]:
break
else:
del tails[j]
tails.insert(0, head[i])
del head[i]

# truncate all leading 0's
if all(elem == "0" for elem in head):
for i in range(0, len(tails)):
if tails[0] == "0":
tails = tails[1:] + [str(0)]
else:
break
return (head, tails)


def convert_float_base(x, base, precision=10):
length_of_int = 0 if x == 0 else int(mpmath.log(x, base))
# iexps = list(range(length_of_int, -1, -1))

def convert_int(x, base, exponents):
out = []
for e in range(0, exponents + 1):
d = x % base
out.append(d)
x = x / base
if x == 0:
break
out.reverse()
return out

def convert_float(x, base, exponents):
out = []
for e in range(0, exponents):
d = int(x * base)
out.append(d)
x = (x * base) - d
if x == 0:
break
return out

int_part = convert_int(int(x), base, length_of_int)
if isinstance(x, (float, sympy.Float)):
# fexps = list(range(-1, -int(precision + 1), -1))
real_part = convert_float(x - int(x), base, precision + 1)
return int_part + real_part
elif isinstance(x, int):
return int_part
else:
raise TypeError(x)


class Accuracy(Builtin):
"""
<url>
Expand Down Expand Up @@ -255,7 +172,7 @@ class IntegerExponent(Builtin):

summary_text = "number of trailing 0s in a given base"

def eval_two_arg_integers(self, n: Integer, b: Integer, evaluation):
def eval_two_arg_integers(self, n: Integer, b: Integer, evaluation: Evaluation):
"""IntegerExponent[n_Integer, b_Integer]"""

py_n, py_b = n.value, b.value
Expand Down
6 changes: 3 additions & 3 deletions mathics/builtin/box/expression.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
# This is never intended to go in Mathics3 docs
no_doc = True

from typing import Optional, Sequence, Union

from mathics.core.attributes import A_PROTECTED, A_READ_PROTECTED
Expand All @@ -10,6 +7,9 @@
from mathics.core.list import ListExpression
from mathics.core.symbols import Symbol, SymbolHoldForm, ensure_context

# This is never intended to go in Mathics3 docs
no_doc = True


def split_name(name: str) -> str:
"""
Expand Down
108 changes: 88 additions & 20 deletions mathics/eval/numbers/numbers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
Implementation of numbers handling functions.
"""

from functools import lru_cache
from typing import Optional

import mpmath
Expand All @@ -20,6 +21,87 @@
from mathics.core.symbols import SymbolPlus


def check_finite_decimal(denominator):
# The rational number is finite decimal if the denominator has form 2^a * 5^b
while denominator % 5 == 0:
denominator = denominator / 5

while denominator % 2 == 0:
denominator = denominator / 2

return True if denominator == 1 else False


def convert_repeating_decimal(numerator, denominator, base):
head = [x for x in str(numerator // denominator)]
tails = []
subresults = [numerator % denominator]
numerator %= denominator

while numerator != 0: # only rational input can go to this case
numerator *= base
result_digit, numerator = divmod(numerator, denominator)
tails.append(str(result_digit))
if numerator not in subresults:
subresults.append(numerator)
else:
break

for i in range(len(head) - 1, -1, -1):
j = len(tails) - 1
if head[i] != tails[j]:
break
else:
del tails[j]
tails.insert(0, head[i])
del head[i]

# truncate all leading 0's
if all(elem == "0" for elem in head):
for i in range(0, len(tails)):
if tails[0] == "0":
tails = tails[1:] + [str(0)]
else:
break
return (head, tails)


def convert_float_base(x, base, precision=10):
length_of_int = 0 if x == 0 else int(mpmath.log(x, base))
# iexps = list(range(length_of_int, -1, -1))

def convert_int(x, base, exponents):
out = []
for e in range(0, exponents + 1):
d = x % base
out.append(d)
x = x / base
if x == 0:
break
out.reverse()
return out

def convert_float(x, base, exponents):
out = []
for e in range(0, exponents):
d = int(x * base)
out.append(d)
x = (x * base) - d
if x == 0:
break
return out

int_part = convert_int(int(x), base, length_of_int)
if isinstance(x, (float, sympy.Float)):
# fexps = list(range(-1, -int(precision + 1), -1))
real_part = convert_float(x - int(x), base, precision + 1)
return int_part + real_part
elif isinstance(x, int):
return int_part
else:
raise TypeError(x)


def eval_Accuracy(z: BaseElement) -> Optional[float]:
"""
Determine the accuracy of an expression expr.
Expand Down Expand Up @@ -129,7 +211,7 @@ def cancel(expr):
return None

# result = sympy.powsimp(result, deep=True)
result = sympy.cancel(result)
result = tracing.run_sympy(sympy.cancel, result)

# cancel factors out rationals, so we factor them again
result = sympy_factor(result)
Expand All @@ -140,25 +222,11 @@ def cancel(expr):
return expr


def cancel(expr):
if expr.has_form("Plus", None):
return Expression(SymbolPlus, *[cancel(element) for element in expr.elements])
else:
try:
result = expr.to_sympy()
if result is None:
return None

# result = sympy.powsimp(result, deep=True)
result = tracing.run_sympy(sympy.cancel, result)

# cancel factors out rationals, so we factor them again
result = sympy_factor(result)

return from_sympy(result)
except sympy.PolynomialError:
# e.g. for non-commutative expressions
return expr
@lru_cache()
def log_n_b(py_n, py_b) -> int:
return (
int(mpmath.floor(mpmath.log(py_n, py_b))) + 1 if py_n != 0 and py_n != 1 else 1
)


def sympy_factor(expr_sympy):
Expand Down

0 comments on commit a35c843

Please sign in to comment.