Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Two postponed TODOs in recent PRs... #1148

Merged
merged 1 commit into from
Oct 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens with this function? Is it not needed anymore?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function is duplicated. This is easier to see in the master branch right now:

https://github.com/Mathics3/mathics-core/blob/master/mathics/eval/numbers/numbers.py#L122-L149

image

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the clarification!

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