Skip to content

Commit

Permalink
Miscellaneous fixes (#1153)
Browse files Browse the repository at this point in the history
This just fixes a few crashes I was seeing running Mathics through OEIS
programs, with corresponding doctests (and a few more type error fixes)
  • Loading branch information
davidar authored Nov 3, 2024
1 parent 732d33f commit e4bb914
Show file tree
Hide file tree
Showing 8 changed files with 54 additions and 20 deletions.
13 changes: 8 additions & 5 deletions mathics/builtin/atomic/numbers.py
Original file line number Diff line number Diff line change
Expand Up @@ -486,11 +486,14 @@ def eval_with_base(self, n, b, evaluation, nr_elements=None, pos=None):
digits = []
if not py_b == 10:
digits = convert_float_base(py_n, py_b, display_len - exp)
# truncate all the leading 0's
i = 0
while digits and digits[i] == 0:
i += 1
digits = digits[i:]
if all(d == 0 for d in digits):
digits = [0]
else:
# truncate all the leading 0's
i = 0
while digits and digits[i] == 0:
i += 1
digits = digits[i:]

if not isinstance(n, Integer):
if len(digits) > display_len:
Expand Down
9 changes: 8 additions & 1 deletion mathics/builtin/list/constructing.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,14 +184,21 @@ class Normal(Builtin):
<dd> Brings special expressions to a normal expression from different special \
forms.
</dl>
>> Normal[Pi]
= Pi
>> Series[Exp[x], {x, 0, 5}]
= 1 + x + 1 / 2 x ^ 2 + 1 / 6 x ^ 3 + 1 / 24 x ^ 4 + 1 / 120 x ^ 5 + O[x] ^ 6
>> Normal[%]
= 1 + x + x ^ 2 / 2 + x ^ 3 / 6 + x ^ 4 / 24 + x ^ 5 / 120
"""

summary_text = "convert objects to normal expressions"

def eval_general(self, expr: Expression, evaluation: Evaluation):
"Normal[expr_]"
if isinstance(expr, Atom):
return
return expr
if expr.has_form("RootSum", 2):
return from_sympy(expr.to_sympy().doit(roots=True))
return Expression(
Expand Down
23 changes: 15 additions & 8 deletions mathics/builtin/list/rearrange.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@
import functools
from collections import defaultdict
from itertools import chain
from typing import Callable
from typing import Callable, Optional

from mathics.core.atoms import Integer, Integer0
from mathics.core.atoms import Integer, Integer0, Number
from mathics.core.attributes import A_FLAT, A_ONE_IDENTITY, A_PROTECTED
from mathics.core.builtin import Builtin, MessageException
from mathics.core.element import BaseElement
from mathics.core.evaluation import Evaluation
from mathics.core.expression import Expression, structure
from mathics.core.expression_predefined import MATHICS3_INFINITY
Expand Down Expand Up @@ -655,6 +656,7 @@ class Flatten(Builtin):
"Level `1` specified in `2` exceeds the levels, `3`, "
"which can be flattened together in `4`."
),
"normal": "Nonatomic expression expected at position `1` in `2`.",
}

rules = {
Expand Down Expand Up @@ -759,21 +761,25 @@ def insert_element(elements):

return Expression(h, *insert_element(elements))

def eval(self, expr, n, h, evaluation):
def eval(self, expr: BaseElement, n: Number, h, evaluation):
"Flatten[expr_, n_, h_]"

n_int: Optional[int]
if n.sameQ(MATHICS3_INFINITY):
n = -1 # a negative number indicates an unbounded level
n_int = -1 # a negative number indicates an unbounded level
else:
n_int = n.get_int_value()
# Here we test for negative since in Mathics Flatten[] as opposed to flatten_with_respect_to_head()
# negative numbers (and None) are not allowed.
if n_int is None or n_int < 0:
evaluation.message("Flatten", "flpi", n)
return
n = n_int

return expr.flatten_with_respect_to_head(h, level=n)
if not isinstance(expr, Expression):
evaluation.message("Flatten", "normal", 1, expr)
return

return expr.flatten_with_respect_to_head(h, level=n_int)


class GatherBy(_GatherOperation):
Expand Down Expand Up @@ -814,14 +820,15 @@ class GatherBy(_GatherOperation):
summary_text = "gather based on values of a function applied to elements"
_bin = _GatherBin

def eval(self, values, func, evaluation: Evaluation):
def eval(self, values: BaseElement, func, evaluation: Evaluation):
"%(name)s[values_, func_]"

if not self._check_list(values, func, evaluation):
return

keys = Expression(SymbolMap, func, values).evaluate(evaluation)
if len(keys.elements) != len(values.elements):
assert keys is not None
if len(keys.get_elements()) != len(values.get_elements()):
return

return self._gather(keys, values, _FastEquivalence())
Expand Down
6 changes: 5 additions & 1 deletion mathics/builtin/numbers/algebra.py
Original file line number Diff line number Diff line change
Expand Up @@ -899,6 +899,8 @@ class CoefficientList(Builtin):
= {{{0, 0, 0, 27}, {0, 0, -54, 0}, {0, 36, 0, 0}, {-8, 0, 0, 0}}, {{0, 0, 27, 0}, {0, -36, 0, 0}, {12, 0, 0, 0}, {0, 0, 0, 0}}, {{0, 9, 0, 0}, {-6, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}}, {{1, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}}}
>> CoefficientList[Series[Log[1-x], {x, 0, 9}], x]
= {0, -1, -1 / 2, -1 / 3, -1 / 4, -1 / 5, -1 / 6, -1 / 7, -1 / 8, -1 / 9}
>> CoefficientList[Series[2x, {x, 0, 9}], x]
= {0, 2}
"""

messages = {
Expand Down Expand Up @@ -944,7 +946,9 @@ def eval(self, expr: Expression, form: Expression, evaluation: Evaluation):
return ListExpression(
*[
coeffs.elements[i - nmin.value] if i >= nmin.value else Integer0
for i in range(0, nmax.value)
for i in range(
0, min(nmax.value, nmin.value + len(coeffs.elements))
)
]
)

Expand Down
6 changes: 4 additions & 2 deletions mathics/builtin/numbers/calculus.py
Original file line number Diff line number Diff line change
Expand Up @@ -1818,6 +1818,8 @@ class SeriesCoefficient(Builtin):
= 31 / 5760
>> SeriesCoefficient[Exp[-x], {x, 0, 5}]
= -1 / 120
>> SeriesCoefficient[2x, {x, 0, 2}]
= 0
>> SeriesCoefficient[SeriesData[x, c, Table[i^2, {i, 10}], 7, 17, 3], 14/3]
= 64
Expand All @@ -1842,10 +1844,10 @@ def eval(self, series: Expression, n: Rational, evaluation: Evaluation):
den: Integer
coeffs, nmin, nmax, den = series.elements[2:]
index = n.value * den.value - nmin.value
if index < 0:
return Integer0
if index >= nmax.value - nmin.value:
return SymbolIndeterminate
if index < 0 or index >= len(coeffs.elements):
return Integer0
return coeffs[index]


Expand Down
12 changes: 11 additions & 1 deletion mathics/builtin/numbers/numbertheory.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,8 @@ class EulerPhi(SympyFunction):
'EulerPhi' of a negative integer is same as its positive counterpart:
>> EulerPhi[-11] == EulerPhi[11]
= True
>> EulerPhi[0]
= 0
Large arguments are computed quickly:
>> EulerPhi[40!]
Expand All @@ -238,8 +240,16 @@ class EulerPhi(SympyFunction):

def eval(self, n: Integer, evaluation: Evaluation):
"EulerPhi[n_Integer]"
if n.is_zero:
return Integer0
return super().eval(abs(n), evaluation)

def to_sympy(self, expr, **kwargs):
try:
return super().to_sympy(expr, **kwargs)
except ValueError: # n must be a positive integer
return None


class FactorInteger(Builtin):
"""
Expand Down Expand Up @@ -441,7 +451,7 @@ class IntegerPartitions(Builtin):
"IntegerPartitions[n_Integer, All]": "IntegerPartitions[n, n]",
"IntegerPartitions[n_Integer, k_Integer]": "IntegerPartitions[n, {1, k}]",
"IntegerPartitions[n_Integer, {k_Integer}]": "IntegerPartitions[n, {k, k}]",
"IntegerPartitions[n_Integer, kspec_, s_List] /; SubsetQ[Range[n], s]": "Select[IntegerPartitions[n, kspec], SubsetQ[s, #] &]",
"IntegerPartitions[n_Integer, kspec_, s_List] /; SubsetQ[Range[n], s] && s == Union[s]": "Select[IntegerPartitions[n, kspec], SubsetQ[s, #] &]",
"IntegerPartitions[n_Integer, kspec_, All]": "IntegerPartitions[n, kspec]",
"IntegerPartitions[n_Integer, kspec_, sspec_, m_]": "Take[IntegerPartitions[n, kspec, sspec], m]",
"IntegerPartitions[n_Integer, {k_Integer}, s_List]": "ReverseSort@Select[Union[ReverseSort /@ Tuples[s, k]], Total[#] == n &]",
Expand Down
2 changes: 1 addition & 1 deletion mathics/core/element.py
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,7 @@ def get_head_name(self) -> str:
def get_float_value(self, permit_complex=False):
return None

def get_int_value(self):
def get_int_value(self) -> Optional[int]:
return None

def get_lookup_name(self) -> str:
Expand Down
3 changes: 2 additions & 1 deletion mathics/core/expression.py
Original file line number Diff line number Diff line change
Expand Up @@ -662,7 +662,7 @@ def sequence(element):
return self._flatten_sequence(sequence, evaluation)

def flatten_with_respect_to_head(
self, head, pattern_only=False, callback=None, level=100
self, head: Symbol, pattern_only=False, callback=None, level=100
) -> "Expression":
"""
Flatten elements in self which have `head` in them.
Expand Down Expand Up @@ -1304,6 +1304,7 @@ def flatten_callback(new_elements, old):
element.unevaluated = old.unevaluated

if A_FLAT & attributes:
assert isinstance(new._head, Symbol)
new = new.flatten_with_respect_to_head(new._head, callback=flatten_callback)
if new.elements_properties is None:
new._build_elements_properties()
Expand Down

0 comments on commit e4bb914

Please sign in to comment.