Skip to content

Commit 4f17008

Browse files
committed
Merge remote-tracking branch 'upstream/master'
2 parents 3e897a1 + c88706e commit 4f17008

File tree

5 files changed

+286
-63
lines changed

5 files changed

+286
-63
lines changed

mathics/builtin/drawing/plot.py

Lines changed: 40 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
from mathics.core.symbols import Symbol, SymbolList
3030
from mathics.core.systemsymbols import (
3131
SymbolAll,
32+
SymbolAutomatic,
3233
SymbolBlack,
3334
SymbolEdgeForm,
3435
SymbolFull,
@@ -260,43 +261,53 @@ def eval(self, functions, x, start, stop, evaluation: Evaluation, options: dict)
260261

261262
# Mesh Option
262263
mesh_option = self.get_option(options, "Mesh", evaluation)
263-
mesh = mesh_option.to_python()
264-
if mesh not in ["System`None", "System`Full", "System`All"]:
264+
if mesh_option not in (SymbolNone, SymbolFull, SymbolAll):
265265
evaluation.message("Mesh", "ilevels", mesh_option)
266266
mesh = "System`None"
267+
else:
268+
mesh = mesh_option.to_python()
267269

268270
# PlotPoints Option
269271
plotpoints_option = self.get_option(options, "PlotPoints", evaluation)
270-
plotpoints = plotpoints_option.to_python()
271-
if plotpoints == "System`None":
272+
if plotpoints_option is SymbolNone:
272273
plotpoints = 57
274+
else:
275+
plotpoints = plotpoints_option.to_python()
273276
if not (isinstance(plotpoints, int) and plotpoints >= 2):
274277
evaluation.message(self.get_name(), "ppts", plotpoints)
275278
return
276279

277280
# MaxRecursion Option
278281
max_recursion_limit = 15
279282
maxrecursion_option = self.get_option(options, "MaxRecursion", evaluation)
280-
maxrecursion = maxrecursion_option.to_python()
283+
284+
# Investigate whether the maxrecursion value is optimal. Bruce
285+
# Lucas observes that in some cases, using more points and
286+
# decreasing recursion is faster and gives better results.
287+
# Note that the tradeoff may be different for Plot versus
288+
# Plot3D. Recursive subdivision in Plot3D is probably a lot
289+
# harder.
290+
maxrecursion = 3
291+
281292
try:
282-
if maxrecursion == "System`Automatic":
283-
maxrecursion = 3
284-
elif maxrecursion == float("inf"):
285-
maxrecursion = max_recursion_limit
286-
raise ValueError
287-
elif isinstance(maxrecursion, int):
288-
if maxrecursion > max_recursion_limit:
293+
if maxrecursion_option is not SymbolAutomatic:
294+
maxrecursion = maxrecursion_option.to_python()
295+
if maxrecursion == float("inf"):
289296
maxrecursion = max_recursion_limit
290297
raise ValueError
291-
if maxrecursion < 0:
298+
elif isinstance(maxrecursion, int):
299+
if maxrecursion > max_recursion_limit:
300+
maxrecursion = max_recursion_limit
301+
raise ValueError
302+
if maxrecursion < 0:
303+
maxrecursion = 0
304+
raise ValueError
305+
else:
292306
maxrecursion = 0
293307
raise ValueError
294-
else:
295-
maxrecursion = 0
296-
raise ValueError
297308
except ValueError:
298309
evaluation.message(
299-
self.get_name(), "invmaxrec", maxrecursion, max_recursion_limit
310+
self.get_name(), "invmaxrec", maxrecursion_option, max_recursion_limit
300311
)
301312
assert isinstance(maxrecursion, int)
302313

@@ -312,22 +323,23 @@ def check_exclusion(excl):
312323
return True
313324

314325
exclusions_option = self.get_option(options, "Exclusions", evaluation)
315-
exclusions = eval_N(exclusions_option, evaluation).to_python()
316326
# TODO Turn expressions into points E.g. Sin[x] == 0 becomes 0, 2 Pi...
317327

318-
if exclusions in ["System`None", ["System`None"]]:
328+
if exclusions_option in (SymbolNone, (SymbolNone,)):
319329
exclusions = "System`None"
320-
elif not isinstance(exclusions, list):
321-
exclusions = [exclusions]
330+
else:
331+
exclusions = eval_N(exclusions_option, evaluation).to_python()
332+
if not isinstance(exclusions, list):
333+
exclusions = [exclusions]
322334

323-
if isinstance(exclusions, list) and all( # noqa
324-
check_exclusion(excl) for excl in exclusions
325-
):
326-
pass
335+
if isinstance(exclusions, list) and all( # noqa
336+
check_exclusion(excl) for excl in exclusions
337+
):
338+
pass
327339

328-
else:
329-
evaluation.message(self.get_name(), "invexcl", exclusions_option)
330-
exclusions = ["System`Automatic"]
340+
else:
341+
evaluation.message(self.get_name(), "invexcl", exclusions_option)
342+
exclusions = ["System`Automatic"]
331343

332344
# exclusions is now either 'None' or a list of reals and 'Automatic'
333345
assert exclusions == "System`None" or isinstance(exclusions, list)

mathics/core/atoms.py

Lines changed: 83 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import math
66
import re
77
from functools import cache
8-
from typing import Any, Dict, Generic, Optional, Tuple, TypeVar, Union
8+
from typing import Any, Dict, Generic, Optional, Tuple, TypeVar, Union, cast
99

1010
import mpmath
1111
import numpy
@@ -239,11 +239,14 @@ def __new__(cls, value) -> "Integer":
239239
return self
240240

241241
def __eq__(self, other) -> bool:
242-
return (
243-
self._value == other.value
244-
if isinstance(other, Integer)
245-
else super().__eq__(other)
246-
)
242+
if isinstance(other, Integer):
243+
return self._value == other._value
244+
if isinstance(other, Number):
245+
# If other is a number of a wider class, use
246+
# its implementation:
247+
return other.__eq__(self)
248+
249+
return super().__eq__(other)
247250

248251
def __ge__(self, other) -> bool:
249252
return (
@@ -346,7 +349,7 @@ def to_sympy(self, **_) -> sympy_numbers.Integer:
346349

347350
def sameQ(self, rhs) -> bool:
348351
"""Mathics SameQ"""
349-
return isinstance(rhs, Integer) and self._value == rhs.value
352+
return isinstance(rhs, Integer) and self._value == rhs._value
350353

351354
def do_copy(self) -> "Integer":
352355
return Integer(self._value)
@@ -404,17 +407,18 @@ def __new__(cls, value, p: Optional[int] = None) -> "Real":
404407
return PrecisionReal.__new__(PrecisionReal, value)
405408

406409
def __eq__(self, other) -> bool:
407-
if isinstance(other, Real):
408-
# MMA Docs: "Approximate numbers that differ in their last seven
409-
# binary digits are considered equal"
410-
_prec = min_prec(self, other)
411-
if _prec is not None:
412-
with mpmath.workprec(_prec):
413-
rel_eps = 0.5 ** float(_prec - 7)
414-
return mpmath.almosteq(
415-
self.to_mpmath(), other.to_mpmath(), abs_eps=0, rel_eps=rel_eps
416-
)
417-
return super().__eq__(other)
410+
if not isinstance(other, Number):
411+
return super().__eq__(other)
412+
413+
_prec: Optional[int] = min_prec(self, other)
414+
if _prec is None:
415+
return self._value == other._value
416+
417+
with mpmath.workprec(_prec):
418+
rel_eps = 0.5 ** float(_prec - 7)
419+
return mpmath.almosteq(
420+
self.to_mpmath(), other.to_mpmath(), abs_eps=0, rel_eps=rel_eps
421+
)
418422

419423
def __hash__(self):
420424
# ignore last 7 binary digits when hashing
@@ -492,6 +496,20 @@ def get_precision(self) -> int:
492496
def get_float_value(self, permit_complex=False) -> float:
493497
return self._value
494498

499+
@property
500+
def element_order(self) -> tuple:
501+
"""
502+
Return a tuple value that is used in ordering elements
503+
of an expression. The tuple is ultimately compared lexicographically.
504+
"""
505+
return (
506+
BASIC_ATOM_NUMBER_ELT_ORDER,
507+
self._value,
508+
0,
509+
1,
510+
0, # Machine precision comes first, and after Integers
511+
)
512+
495513
@property
496514
def is_approx_zero(self) -> bool:
497515
# In WMA, Chop[10.^(-10)] == 0,
@@ -514,7 +532,7 @@ def make_boxes(self, form):
514532

515533
@property
516534
def is_zero(self) -> bool:
517-
return self.value == 0.0
535+
return self._value == 0.0
518536

519537
def sameQ(self, rhs) -> bool:
520538
"""Mathics SameQ for MachineReal.
@@ -524,9 +542,9 @@ def sameQ(self, rhs) -> bool:
524542
rhs-value's precision. For any rhs type, sameQ is False.
525543
"""
526544
if isinstance(rhs, MachineReal):
527-
return self.value == rhs.value
545+
return self._value == rhs._value
528546
if isinstance(rhs, PrecisionReal):
529-
rhs_value = rhs.value
547+
rhs_value = rhs._value
530548
value = self.to_sympy()
531549
# If sympy fixes the issue, this comparison would be
532550
# enough
@@ -603,6 +621,21 @@ def get_precision(self) -> int:
603621
"""Returns the default specification for precision (in binary digits) in N and other numerical functions."""
604622
return self.value._prec + 1
605623

624+
@property
625+
def element_order(self) -> tuple:
626+
"""
627+
Return a tuple value that is used in ordering elements
628+
of an expression. The tuple is ultimately compared lexicographically.
629+
"""
630+
631+
value = self._value
632+
value, prec = float(value), value._prec
633+
# For large values, use the sympy.Float value...
634+
if math.isinf(value):
635+
value, prec = self._value, value._prec
636+
637+
return (BASIC_ATOM_NUMBER_ELT_ORDER, value, 0, 2, prec)
638+
606639
@property
607640
def is_zero(self) -> bool:
608641
# self.value == 0 does not work for sympy >=1.13
@@ -757,7 +790,7 @@ def sameQ(self, rhs) -> bool:
757790
"""Mathics3 SameQ"""
758791
# FIX: check
759792
if isinstance(rhs, ByteArray):
760-
return self.value == rhs.value
793+
return self._value == rhs._value
761794
return False
762795

763796
def get_string_value(self) -> Optional[str]:
@@ -902,12 +935,15 @@ def element_order(self) -> tuple:
902935
Return a tuple value that is used in ordering elements
903936
of an expression. The tuple is ultimately compared lexicographically.
904937
"""
905-
return (
906-
BASIC_ATOM_NUMBER_ELT_ORDER,
907-
self.real.element_order[1],
908-
self.imag.element_order[1],
909-
1,
910-
)
938+
order_real, order_imag = self.real.element_order, self.imag.element_order
939+
940+
# If the real of the imag parts are real numbers, sort according
941+
# the minimum precision.
942+
# Example:
943+
# Sort[{1+2I, 1.+2.I, 1.`4+2.`5I, 1.`2+2.`7 I}]
944+
#
945+
# = {1+2I, 1.+2.I, 1.`2+2.`7 I, 1.`4+2.`5I}
946+
return order_real + order_imag
911947

912948
@property
913949
def pattern_precedence(self) -> tuple:
@@ -965,9 +1001,13 @@ def user_hash(self, update) -> None:
9651001

9661002
def __eq__(self, other) -> bool:
9671003
if isinstance(other, Complex):
968-
return self.real == other.real and self.imag == other.imag
969-
else:
970-
return super().__eq__(other)
1004+
return self.real.__eq__(other.real) and self.imag.__eq__(other.imag)
1005+
if isinstance(other, Number):
1006+
if abs(self.imag._value) != 0:
1007+
return False
1008+
return self.real.__eq__(other)
1009+
1010+
return super().__eq__(other)
9711011

9721012
@property
9731013
def is_zero(self) -> bool:
@@ -1019,6 +1059,17 @@ def __new__(cls, numerator, denominator=1) -> "Rational":
10191059
self.hash = hash(key)
10201060
return self
10211061

1062+
def __eq__(self, other) -> bool:
1063+
if isinstance(other, Rational):
1064+
return self.value.as_numer_denom() == other.value.as_numer_denom()
1065+
if isinstance(other, Integer):
1066+
return (other._value, 1) == self.value.as_numer_denom()
1067+
if isinstance(other, Number):
1068+
# For general numbers, rely on Real or Complex implementations.
1069+
return other.__eq__(self)
1070+
# General expressions
1071+
return super().__eq__(other)
1072+
10221073
def __getnewargs__(self) -> tuple:
10231074
return (self.numerator().value, self.denominator().value)
10241075

@@ -1078,7 +1129,7 @@ def element_order(self) -> tuple:
10781129
return (
10791130
BASIC_ATOM_NUMBER_ELT_ORDER,
10801131
sympy.Float(self.value),
1081-
0,
1132+
1,
10821133
1,
10831134
)
10841135

test/builtin/test_file_operations.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"""
33
Unit tests for mathics.builtin.file_operations
44
"""
5-
5+
import os
66
import sys
77
import time
88
from test.helper import check_evaluation, evaluate
@@ -101,6 +101,10 @@
101101
),
102102
],
103103
)
104+
@pytest.mark.skipif(
105+
os.getenv("SANDBOX", False),
106+
reason="Test doesn't work in a sandboxed environment with access to local files",
107+
)
104108
def test_private_doctests_file_properties(str_expr, msgs, str_expected, fail_msg):
105109
"""file_opertions.file_properties"""
106110
check_evaluation(

test/core/convert/test_mpmath.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ def test_from_to_mpmath():
4444
(MachineReal(1.2), MachineReal(1.2)),
4545
(PrecisionReal(SympyFloat(1.3, 10)), PrecisionReal(SympyFloat(1.3, 10))),
4646
(PrecisionReal(SympyFloat(1.3, 30)), PrecisionReal(SympyFloat(1.3, 30))),
47-
(Complex(Integer1, IntegerM1), Complex(Integer1, IntegerM1)),
47+
# After conversion, val1 == val2 but not SameQ[val1,val2]
48+
# (Complex(Integer1, IntegerM1), Complex(Integer1, IntegerM1)),
4849
(Complex(Integer1, Real(-1.0)), Complex(Integer1, Real(-1.0))),
4950
(Complex(Real(1.0), Real(-1.0)), Complex(Real(1.0), Real(-1.0))),
5051
(

0 commit comments

Comments
 (0)