diff --git a/.github/workflows/consistency-checks.yml b/.github/workflows/consistency-checks.yml
index 8bc300226..03dada2f0 100644
--- a/.github/workflows/consistency-checks.yml
+++ b/.github/workflows/consistency-checks.yml
@@ -15,7 +15,7 @@ jobs:
steps:
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
- uses: actions/setup-python@v2
+ uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
diff --git a/.github/workflows/isort-and-black-checks.yml b/.github/workflows/isort-and-black-checks.yml
index ab890f67d..37cde2a21 100644
--- a/.github/workflows/isort-and-black-checks.yml
+++ b/.github/workflows/isort-and-black-checks.yml
@@ -11,7 +11,7 @@ jobs:
steps:
- uses: actions/checkout@v3
- name: Set up Python 3.11
- uses: actions/setup-python@v2
+ uses: actions/setup-python@v4
with:
python-version: 3.11
- name: Install click, black and isort
diff --git a/.github/workflows/osx.yml b/.github/workflows/osx.yml
index 01fa611f1..09d594949 100644
--- a/.github/workflows/osx.yml
+++ b/.github/workflows/osx.yml
@@ -19,7 +19,7 @@ jobs:
steps:
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
- uses: actions/setup-python@v2
+ uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install OS dependencies
diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml
index 840c1a618..e32d9b54b 100644
--- a/.github/workflows/ubuntu.yml
+++ b/.github/workflows/ubuntu.yml
@@ -15,7 +15,7 @@ jobs:
steps:
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
- uses: actions/setup-python@v3
+ uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install OS dependencies
diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml
index be286d35b..d2a6bc21e 100755
--- a/.github/workflows/windows.yml
+++ b/.github/workflows/windows.yml
@@ -16,7 +16,7 @@ jobs:
steps:
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
- uses: actions/setup-python@v3
+ uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install OS dependencies
diff --git a/.gitignore b/.gitignore
index ccd7736f5..2d93eb961 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,9 +14,10 @@
.settings
.vscode
/.cache
+/.gdbinit
/.python-version
-/Mathics3.egg-info
/Mathics.egg-info
+/Mathics3.egg-info
ChangeLog
Documents/
Homepage/
@@ -33,9 +34,9 @@ mathics/doc/tex/logo-heptatom.pdf
mathics/doc/tex/logo-text-nodrop.pdf
mathics/doc/tex/mathics-*.asy
mathics/doc/tex/mathics-*.dvi
+mathics/doc/tex/mathics-*.dvi
mathics/doc/tex/mathics-*.eps
mathics/doc/tex/mathics-*.pdf
-mathics/doc/tex/mathics-*.dvi
mathics/doc/tex/mathics-*.tex
mathics/doc/tex/mathics.aux
mathics/doc/tex/mathics.dvi
diff --git a/CHANGES.rst b/CHANGES.rst
index 76238a823..6bfe2990d 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -9,7 +9,7 @@ New Builtins
* `Elements`
-
+* `RealAbs` and `RealSign`
Compatibility
-------------
@@ -24,7 +24,7 @@ Internals
* ``eval_abs`` and ``eval_sign`` extracted from ``Abs`` and ``Sign`` and added to ``mathics.eval.arithmetic``.
* Maximum number of digits allowed in a string set to 7000 and can be adjusted using environment variable
``MATHICS_MAX_STR_DIGITS`` on Python versions that don't adjust automatically (like pyston).
-
+* Real number comparisons implemented is based now in the internal implementation of `RealSign`.
Bugs
----
diff --git a/SYMBOLS_MANIFEST.txt b/SYMBOLS_MANIFEST.txt
index 1bf18d1ea..d3ecf4204 100644
--- a/SYMBOLS_MANIFEST.txt
+++ b/SYMBOLS_MANIFEST.txt
@@ -824,8 +824,10 @@ System`Read
System`ReadList
System`ReadProtected
System`Real
+System`RealAbs
System`RealDigits
System`RealNumberQ
+System`RealSign
System`Reals
System`Reap
System`Record
diff --git a/admin-tools/build_and_check_manifest.py b/admin-tools/build_and_check_manifest.py
index 9057a0f34..0feb64af9 100755
--- a/admin-tools/build_and_check_manifest.py
+++ b/admin-tools/build_and_check_manifest.py
@@ -2,7 +2,14 @@
import sys
-from mathics.builtin import Builtin, modules, name_is_builtin_symbol
+from mathics.builtin.base import Builtin
+from mathics.core.load_builtin import (
+ import_and_load_builtins,
+ modules,
+ name_is_builtin_symbol,
+)
+
+import_and_load_builtins()
def generate_available_builtins_names():
diff --git a/examples/symbolic_logic/gries_schneider/test_gs.py b/examples/symbolic_logic/gries_schneider/test_gs.py
index 7212ec8ee..19cd96d03 100644
--- a/examples/symbolic_logic/gries_schneider/test_gs.py
+++ b/examples/symbolic_logic/gries_schneider/test_gs.py
@@ -4,8 +4,10 @@
from mathics.core.definitions import Definitions
from mathics.core.evaluation import Evaluation
+from mathics.core.load_builtin import import_and_load_builtins
from mathics.core.parser import MathicsSingleLineFeeder, parse
+import_and_load_builtins()
definitions = Definitions(add_builtin=True)
for i in range(0, 4):
diff --git a/mathics/builtin/__init__.py b/mathics/builtin/__init__.py
index 946d024ba..efb232604 100755
--- a/mathics/builtin/__init__.py
+++ b/mathics/builtin/__init__.py
@@ -15,38 +15,3 @@
builtin class such as the Builtin's Attributes, its Information text,
among other things.
"""
-
-import os
-import os.path as osp
-
-from mathics.core.load_builtin import (
- add_builtins_from_builtin_modules,
- get_module_names,
- import_builtin_subdirectories,
- import_builtins,
- initialize_display_operators_set,
-)
-from mathics.settings import ENABLE_FILES_MODULE
-
-# Get import modules in this directory of Python modules that contain
-# Mathics3 Builtin class definitions.
-
-builtin_path = osp.dirname(__file__)
-exclude_files = {"codetables", "base"}
-module_names = get_module_names(builtin_path, exclude_files)
-modules = []
-import_builtins(module_names, modules)
-
-# Get import modules in subdirectories of this directory of Python
-# modules that contain Mathics3 Builtin class definitions.
-
-# The files_io module handles local file access, reading and writing..
-# In some sandboxed settings, such as running Mathics from as a remote
-# server, we disallow local file access.
-disable_file_module_names = set() if ENABLE_FILES_MODULE else {"files_io"}
-
-subdirectories = next(os.walk(builtin_path))[1]
-import_builtin_subdirectories(subdirectories, disable_file_module_names, modules)
-
-add_builtins_from_builtin_modules(modules)
-initialize_display_operators_set()
diff --git a/mathics/builtin/arithmetic.py b/mathics/builtin/arithmetic.py
index d54f8f7bd..51980d633 100644
--- a/mathics/builtin/arithmetic.py
+++ b/mathics/builtin/arithmetic.py
@@ -77,7 +77,13 @@
SymbolTable,
SymbolUndefined,
)
-from mathics.eval.arithmetic import eval_Abs, eval_mpmath_function, eval_Sign
+from mathics.eval.arithmetic import (
+ eval_Abs,
+ eval_mpmath_function,
+ eval_negate_number,
+ eval_RealSign,
+ eval_Sign,
+)
from mathics.eval.nevaluator import eval_N
from mathics.eval.numerify import numerify
@@ -1207,6 +1213,44 @@ class Real_(Builtin):
name = "Real"
+class RealAbs(Builtin):
+ """
+ :WMA link:https://reference.wolfram.com/language/ref/RealAbs.html
+
+
+ - 'RealAbs[$x$]'
+
- returns the absolute value of a real number $x$.
+
+ 'RealAbs' is also known as modulus. It is evaluated if $x$ can be compared \
+ with $0$.
+
+ >> RealAbs[-3.]
+ = 3.
+ 'RealAbs[$z$]' is left unevaluated for complex $z$:
+ >> RealAbs[2. + 3. I]
+ = RealAbs[2. + 3. I]
+ >> D[RealAbs[x ^ 2], x]
+ = 2 x ^ 3 / RealAbs[x ^ 2]
+ """
+
+ attributes = A_LISTABLE | A_NUMERIC_FUNCTION | A_PROTECTED
+ rules = {
+ "D[RealAbs[x_],x_]": "x/RealAbs[x]",
+ "Integrate[RealAbs[x_],x_]": "1/2 x RealAbs[x]",
+ "Integrate[RealAbs[u_],{u_,a_,b_}]": "1/2 b RealAbs[b]-1/2 a RealAbs[a]",
+ }
+ summary_text = "real absolute value"
+
+ def eval(self, x: BaseElement, evaluation: Evaluation):
+ """RealAbs[x_]"""
+ real_sign = eval_RealSign(x)
+ if real_sign is IntegerM1:
+ return eval_negate_number(x)
+ if real_sign is None:
+ return
+ return x
+
+
class RealNumberQ(Test):
"""
## Not found in WMA
@@ -1237,6 +1281,42 @@ def test(self, expr) -> bool:
return isinstance(expr, (Integer, Rational, Real))
+class RealSign(Builtin):
+ """
+ :WMA link:https://reference.wolfram.com/language/ref/RealAbs.html
+
+
+ - 'RealSign[$x$]'
+
- returns $-1$, $0$ or $1$ depending on whether $x$ is negative,
+ zero or positive.
+
+ 'RealSign' is also known as $sgn$ or $signum$ function.
+
+ >> RealSign[-3.]
+ = -1
+ 'RealSign[$z$]' is left unevaluated for complex $z$:
+ >> RealSign[2. + 3. I]
+ = RealSign[2. + 3. I]
+
+ >> D[RealSign[x^2],x]
+ = 2 x Piecewise[{{0, x ^ 2 != 0}}, Indeterminate]
+ >> Integrate[RealSign[u],{u,0,x}]
+ = RealAbs[x]
+ """
+
+ attributes = A_LISTABLE | A_NUMERIC_FUNCTION | A_PROTECTED
+ rules = {
+ "D[RealSign[x_],x_]": "Piecewise[{{0, x!=0}}, Indeterminate]",
+ "Integrate[RealSign[x_],x_]": "RealAbs[x]",
+ "Integrate[RealSign[u_],{u_, a_, b_}]": "RealAbs[b]-RealSign[a]",
+ }
+ summary_text = "real sign"
+
+ def eval(self, x: Number, evaluation: Evaluation) -> Optional[Integer]:
+ """RealSign[x_]"""
+ return eval_RealSign(x)
+
+
class Sign(SympyFunction):
"""
:WMA link:https://reference.wolfram.com/language/ref/Sign.html
diff --git a/mathics/builtin/optiondoc.py b/mathics/builtin/drawing/drawing_options.py
similarity index 99%
rename from mathics/builtin/optiondoc.py
rename to mathics/builtin/drawing/drawing_options.py
index a4d3592f1..5b88f680c 100644
--- a/mathics/builtin/optiondoc.py
+++ b/mathics/builtin/drawing/drawing_options.py
@@ -18,7 +18,7 @@
from mathics.builtin.base import Builtin
# This tells documentation how to sort this module
-sort_order = "mathics.builtin.drawing-options-and-option-values"
+sort_order = "mathics.builtin.graphing-and-drawing.drawing-options-and-option-values"
class Automatic(Builtin):
diff --git a/mathics/builtin/drawing/graphics3d.py b/mathics/builtin/drawing/graphics3d.py
index 5244b6dda..f50ac9bae 100644
--- a/mathics/builtin/drawing/graphics3d.py
+++ b/mathics/builtin/drawing/graphics3d.py
@@ -69,8 +69,8 @@ class Graphics3D(Graphics):
'Graphics3D[$primitives$, $options$]'
represents a three-dimensional graphic.
- See also the :Drawing Option and Option Values:
- /doc/reference-of-built-in-symbols/drawing-options-and-option-values
+ See :Drawing Option and Option Values:
+ /doc/reference-of-built-in-symbols/graphics-and-drawing/drawing-options-and-option-values
for a list of Plot options.
diff --git a/mathics/builtin/drawing/plot.py b/mathics/builtin/drawing/plot.py
index d641cfa06..87789651c 100644
--- a/mathics/builtin/drawing/plot.py
+++ b/mathics/builtin/drawing/plot.py
@@ -1100,6 +1100,7 @@ class BarChart(_Chart):
'BarChart[{$b1$, $b2$ ...}]'
makes a bar chart with lengths $b1$, $b2$, ....
+
Drawing options include -
Charting:
@@ -2563,24 +2564,11 @@ class Plot3D(_Plot3D):
- creates a three-dimensional plot of $f$ with $x$ ranging from $xmin$ to \
$xmax$ and $y$ ranging from $ymin$ to $ymax$.
+ See :Drawing Option and Option Values:
+ /doc/reference-of-built-in-symbols/graphics-and-drawing/drawing-options-and-option-values
+ for a list of Plot options.
- Plot3D has the same options as :Graphics3D:
-/doc/reference-of-built-in-symbols/graphics-and-drawing/three-dimensional-graphics/graphics3d,\
- in particular:
-
- -
- :Mesh:
- /doc/reference-of-built-in-symbols/drawing-options-and-option-values/mesh
-
-
- :PlotPoints:
- /doc/reference-of-built-in-symbols/drawing-options-and-option-values/plotpoints
-
-
- :MaxRecursion:
- /doc/reference-of-built-in-symbols/drawing-options-and-option-values/maxrecursion
-
-
-
>> Plot3D[x ^ 2 + 1 / y, {x, -1, 1}, {y, 1, 4}]
= -Graphics3D-
diff --git a/mathics/builtin/options.py b/mathics/builtin/options.py
index 5c52e5459..3b6acb581 100644
--- a/mathics/builtin/options.py
+++ b/mathics/builtin/options.py
@@ -59,7 +59,7 @@ class All(Predefined):
/doc/reference-of-built-in-symbols/graphics-and-drawing/plotting-data/plot
, \
setting the
:Mesh:
- /doc/reference-of-built-in-symbols/drawing-options-and-option-values/mesh \
+/doc/reference-of-built-in-symbols/graphics-and-drawing/drawing-options-and-option-values/mesh \
option to 'All' will show the specific plot points:
>> Plot[x^2, {x, -1, 1}, MaxRecursion->5, Mesh->All]
@@ -180,7 +180,7 @@ class None_(Predefined):
Plot3D shows the mesh grid between computed points by default. This the
:Mesh:
- /doc/reference-of-built-in-symbols/drawing-options-and-option-values/mesh option.
+/doc/reference-of-built-in-symbols/graphics-and-drawing/drawing-options-and-option-values/mesh \
However, you hide the mesh by setting the 'Mesh' option value to 'None':
diff --git a/mathics/builtin/tensors.py b/mathics/builtin/tensors.py
index 57e8198ab..c76f74486 100644
--- a/mathics/builtin/tensors.py
+++ b/mathics/builtin/tensors.py
@@ -216,7 +216,7 @@ class Inner(Builtin):
messages = {
"incom": (
"Length `1` of dimension `2` in `3` is incommensurate with "
- "length `4` of dimension 1 in `5."
+ "length `4` of dimension 1 in `5`."
),
}
diff --git a/mathics/builtin/testing_expressions/equality_inequality.py b/mathics/builtin/testing_expressions/equality_inequality.py
index f95289f17..d5e0ce6e5 100644
--- a/mathics/builtin/testing_expressions/equality_inequality.py
+++ b/mathics/builtin/testing_expressions/equality_inequality.py
@@ -70,7 +70,7 @@ def numerify_args(items, evaluation) -> list:
for item in items:
if not isinstance(item, Number):
# TODO: use $MaxExtraPrecision insterad of hard-coded 50
- item = eval_N(item, evaluation, SymbolMaxPrecision)
+ item = eval_N(item, evaluation, SymbolMaxExtraPrecision)
n_items.append(item)
items = n_items
else:
diff --git a/mathics/core/definitions.py b/mathics/core/definitions.py
index 93643fcc0..bd2d8d3bf 100644
--- a/mathics/core/definitions.py
+++ b/mathics/core/definitions.py
@@ -15,9 +15,10 @@
from mathics.core.convert.expression import to_mathics_list
from mathics.core.element import fully_qualified_symbol_name
from mathics.core.expression import Expression
-from mathics.core.load_builtin import definition_contribute
+from mathics.core.load_builtin import definition_contribute, mathics3_builtins_modules
from mathics.core.symbols import Atom, Symbol, strip_context
from mathics.core.systemsymbols import SymbolGet
+from mathics.settings import ROOT_DIR
type_compiled_pattern = type(re.compile("a.a"))
@@ -138,12 +139,12 @@ def __init__(
self.timing_trace_evaluation = False
if add_builtin:
- from mathics.builtin import modules
- from mathics.settings import ROOT_DIR
-
loaded = False
if builtin_filename is not None:
- builtin_dates = [get_file_time(module.__file__) for module in modules]
+ builtin_dates = [
+ get_file_time(module.__file__)
+ for module in mathics3_builtins_modules
+ ]
builtin_time = max(builtin_dates)
if get_file_time(builtin_filename) > builtin_time:
builtin_file = open(builtin_filename, "rb")
@@ -770,7 +771,6 @@ def __init__(
builtin=None,
is_numeric=False,
) -> None:
-
super(Definition, self).__init__()
self.name = name
diff --git a/mathics/core/load_builtin.py b/mathics/core/load_builtin.py
index 376412412..a2a3453e2 100755
--- a/mathics/core/load_builtin.py
+++ b/mathics/core/load_builtin.py
@@ -8,19 +8,29 @@
import importlib
import inspect
+import os
import os.path as osp
import pkgutil
from glob import glob
+from types import ModuleType
from typing import List, Optional
from mathics.core.pattern import pattern_objects
from mathics.core.symbols import Symbol
from mathics.eval.makeboxes import builtins_precedence
+from mathics.settings import ENABLE_FILES_MODULE
+
+# List of Python modules contain Mathics3 Builtins.
+# This list used outside to gather documentation,
+# and test module consistency. It is
+# is initialized via below import_builtins modules
+mathics3_builtins_modules: List[ModuleType] = []
_builtins = {}
builtins_by_module = {}
display_operators_set = set()
+
# The fact that are importing inside here, suggests add_builtins
# should get moved elsewhere.
def add_builtins(new_builtins):
@@ -50,7 +60,7 @@ def add_builtins(new_builtins):
_builtins.update(dict(new_builtins))
-def add_builtins_from_builtin_modules(modules):
+def add_builtins_from_builtin_modules(modules: List[ModuleType]):
# This can be put at the top after mathics.builtin.__init__
# cleanup is done.
from mathics.builtin.base import Builtin
@@ -110,10 +120,44 @@ def get_module_names(builtin_path: str, exclude_files: set) -> list:
return [f for f in py_files if f not in exclude_files]
+def import_and_load_builtins():
+ """
+ Imports Builtin modules in mathics.builtin and add rules, and definitions from that.
+ """
+ builtin_path = osp.join(
+ osp.dirname(
+ __file__,
+ ),
+ "..",
+ "builtin",
+ )
+ exclude_files = {"codetables", "base"}
+ module_names = get_module_names(builtin_path, exclude_files)
+ import_builtins(module_names, mathics3_builtins_modules)
+
+ # Get import modules in subdirectories of this directory of Python
+ # modules that contain Mathics3 Builtin class definitions.
+
+ # The files_io module handles local file access, reading and writing..
+ # In some sandboxed settings, such as running Mathics from as a remote
+ # server, we disallow local file access.
+ disable_file_module_names = set() if ENABLE_FILES_MODULE else {"files_io"}
+
+ subdirectories = next(os.walk(builtin_path))[1]
+ import_builtin_subdirectories(
+ subdirectories, disable_file_module_names, mathics3_builtins_modules
+ )
+
+ add_builtins_from_builtin_modules(mathics3_builtins_modules)
+ initialize_display_operators_set()
+
+
# TODO: When we drop Python 3.7,
# module_names can be a List[Literal]
def import_builtins(
- module_names: List[str], modules: list, submodule_name: Optional[str] = None
+ module_names: List[str],
+ modules: List[ModuleType],
+ submodule_name: Optional[str] = None,
):
"""
Imports the list of Mathics3 Built-in modules so that inside
@@ -174,7 +218,7 @@ def initialize_display_operators_set():
display_operators_set.add(operator)
-def name_is_builtin_symbol(module, name: str) -> Optional[type]:
+def name_is_builtin_symbol(module: ModuleType, name: str) -> Optional[type]:
"""
Checks if ``name`` should be added to definitions, and return
its associated Builtin class.
diff --git a/mathics/core/systemsymbols.py b/mathics/core/systemsymbols.py
index 31d945385..fa388a92d 100644
--- a/mathics/core/systemsymbols.py
+++ b/mathics/core/systemsymbols.py
@@ -200,7 +200,9 @@
SymbolRational = Symbol("System`Rational")
SymbolRe = Symbol("System`Re")
SymbolReal = Symbol("System`Real")
+SymbolRealAbs = Symbol("System`RealAbs")
SymbolRealDigits = Symbol("System`RealDigits")
+SymbolRealSign = Symbol("System`RealSign")
SymbolRepeated = Symbol("System`Repeated")
SymbolRepeatedNull = Symbol("System`RepeatedNull")
SymbolReturn = Symbol("System`Return")
@@ -223,7 +225,7 @@
SymbolSlot = Symbol("System`Slot")
SymbolSparseArray = Symbol("System`SparseArray")
SymbolSplit = Symbol("System`Split")
-SymbolSqrt = Symbol("System'Sqrt")
+SymbolSqrt = Symbol("System`Sqrt")
SymbolSqrtBox = Symbol("System`SqrtBox")
SymbolStandardDeviation = Symbol("System`StandardDeviation")
SymbolStandardForm = Symbol("System`StandardForm")
diff --git a/mathics/doc/common_doc.py b/mathics/doc/common_doc.py
index f6c1a169e..617c43e27 100644
--- a/mathics/doc/common_doc.py
+++ b/mathics/doc/common_doc.py
@@ -31,10 +31,13 @@
from types import ModuleType
from typing import Callable
-from mathics import builtin, settings
+from mathics import settings
from mathics.builtin.base import check_requires_list
from mathics.core.evaluation import Message, Print
-from mathics.core.load_builtin import builtins_by_module as global_builtins_by_module
+from mathics.core.load_builtin import (
+ builtins_by_module as global_builtins_by_module,
+ mathics3_builtins_modules,
+)
from mathics.core.util import IS_PYPY
from mathics.doc.utils import slugify
from mathics.eval.pymathics import pymathics_builtins_by_module, pymathics_modules
@@ -111,7 +114,8 @@
SUBSECTION_RE = re.compile('(?s)')
TESTCASE_RE = re.compile(
- r"""(?mx)^ # re.MULTILINE (multi-line match) and re.VERBOSE (readable regular expressions
+ r"""(?mx)^ # re.MULTILINE (multi-line match)
+ # and re.VERBOSE (readable regular expressions
((?:.|\n)*?)
^\s+([>#SX])>[ ](.*) # test-code indicator
((?:\n\s*(?:[:|=.][ ]|\.).*)*) # test-code results"""
@@ -122,12 +126,6 @@
test_result_map = {}
-def _replace_all(text, pairs):
- for i, j in pairs:
- text = text.replace(i, j)
- return text
-
-
def get_module_doc(module: ModuleType) -> tuple:
doc = module.__doc__
if doc is not None:
@@ -635,7 +633,7 @@ def gather_doctest_data(self):
for title, modules, builtins_by_module, start in [
(
"Reference of Built-in Symbols",
- builtin.modules,
+ mathics3_builtins_modules,
global_builtins_by_module,
True,
)
diff --git a/mathics/doc/latex/Makefile b/mathics/doc/latex/Makefile
index 9f4369ad3..0b0fdac95 100644
--- a/mathics/doc/latex/Makefile
+++ b/mathics/doc/latex/Makefile
@@ -10,7 +10,7 @@ BASH ?= /bin/bash
DOCTEST_LATEX_DATA_PCL ?= $(HOME)/.local/var/mathics/doctest_latex_data.pcl
# Variable indicating Mathics3 Modules you have available on your system, in latex2doc option format
-MATHICS3_MODULE_OPTION ?= # --load-module pymathics.graph,pymathics.natlang
+MATHICS3_MODULE_OPTION ?=--load-module pymathics.graph,pymathics.natlang
#: Default target: Make everything
all doc texdoc: mathics.pdf
diff --git a/mathics/doc/latex/sed-hack.sh b/mathics/doc/latex/sed-hack.sh
index 358e8648e..a8e213653 100755
--- a/mathics/doc/latex/sed-hack.sh
+++ b/mathics/doc/latex/sed-hack.sh
@@ -43,3 +43,8 @@ sed -i -e "s/′/'/g" documentation.tex
# assumes LaTeX gensymb package
sed -i -e "s/°/\\\\degree{}/g" documentation.tex
+
+# Work around a doc2latex.py bug which strips "s"
+# from Properties in a Section heading.
+# TODO: figure out how to fix that bug.
+sed -i -e "s/Propertie\\\\/Properties\\\\/g" documentation.tex
diff --git a/mathics/doc/latex_doc.py b/mathics/doc/latex_doc.py
index d17f6f7cc..8dc6a53e0 100644
--- a/mathics/doc/latex_doc.py
+++ b/mathics/doc/latex_doc.py
@@ -35,7 +35,6 @@
DocText,
Documentation,
XMLDoc,
- _replace_all,
gather_tests,
get_results_by_test,
post_sub,
@@ -104,7 +103,7 @@ def repl_python(match):
text, post_substitutions = pre_sub(PYTHON_RE, text, repl_python)
- text = _replace_all(
+ text = replace_all(
text,
[
("\\", "\\\\"),
@@ -120,7 +119,7 @@ def repl_python(match):
def repl(match):
text = match.group(1)
if text:
- text = _replace_all(text, [("\\'", "'"), ("^", "\\^")])
+ text = replace_all(text, [("\\'", "'"), ("^", "\\^")])
escape_char = get_latex_escape_char(text)
text = LATEX_RE.sub(
lambda m: "%s%s\\codevar{\\textit{%s}}%s\\lstinline%s"
@@ -163,7 +162,7 @@ def repl_list(match):
text = LIST_RE.sub(repl_list, text)
# FIXME: get this from MathicsScanner
- text = _replace_all(
+ text = replace_all(
text,
[
("$", r"\$"),
@@ -313,7 +312,7 @@ def repl_subsection(match):
def escape_latex_output(text) -> str:
"""Escape Mathics output"""
- text = _replace_all(
+ text = replace_all(
text,
[
("\\", "\\\\"),
@@ -448,6 +447,12 @@ def repl_nonasy(match):
return OUTSIDE_ASY_RE.sub(repl_nonasy, result)
+def replace_all(text, pairs):
+ for i, j in pairs:
+ text = text.replace(i, j)
+ return text
+
+
def strip_system_prefix(name):
if name.startswith("System`"):
stripped_name = name[len("System`") :]
diff --git a/mathics/docpipeline.py b/mathics/docpipeline.py
index a3be9bef1..b8d6f4cd4 100644
--- a/mathics/docpipeline.py
+++ b/mathics/docpipeline.py
@@ -24,7 +24,11 @@
from mathics import settings, version_string
from mathics.core.definitions import Definitions
from mathics.core.evaluation import Evaluation, Output
-from mathics.core.load_builtin import builtins_by_module, builtins_dict
+from mathics.core.load_builtin import (
+ builtins_by_module,
+ builtins_dict,
+ import_and_load_builtins,
+)
from mathics.core.parser import MathicsSingleLineFeeder
from mathics.doc.common_doc import MathicsMainDocumentation
from mathics.eval.pymathics import PyMathicsLoadException, eval_LoadModule
@@ -177,7 +181,6 @@ def test_tests(
max_tests=MAX_TESTS,
excludes=[],
):
-
# For consistency set the character encoding ASCII which is
# the lowest common denominator available on all systems.
mathics.settings.SYSTEM_CHARACTER_ENCODING = "ASCII"
@@ -487,6 +490,8 @@ def main():
global definitions
global logfile
global check_partial_elapsed_time
+
+ import_and_load_builtins()
definitions = Definitions(add_builtin=True)
parser = ArgumentParser(description="Mathics test suite.", add_help=False)
diff --git a/mathics/eval/arithmetic.py b/mathics/eval/arithmetic.py
index e06720ddc..22275757f 100644
--- a/mathics/eval/arithmetic.py
+++ b/mathics/eval/arithmetic.py
@@ -33,7 +33,6 @@
from mathics.core.element import BaseElement, ElementsProperties
from mathics.core.expression import Expression
from mathics.core.number import FP_MANTISA_BINARY_DIGITS, SpecialValueError, min_prec
-from mathics.core.rules import Rule
from mathics.core.symbols import Atom, Symbol, SymbolPlus, SymbolPower, SymbolTimes
from mathics.core.systemsymbols import (
SymbolAbs,
@@ -42,7 +41,9 @@
SymbolI,
SymbolIndeterminate,
SymbolLog,
+ SymbolRealSign,
SymbolSign,
+ SymbolSqrt,
)
RationalMOneHalf = Rational(-1, 2)
@@ -96,8 +97,23 @@ def eval_Abs(expr: BaseElement) -> Optional[BaseElement]:
if abs_base is None:
abs_base = Expression(SymbolAbs, base)
return Expression(SymbolPower, abs_base, exp)
+ if expr.has_form("Exp", 1):
+ exp = expr.elements[0]
+ if isinstance(exp, (Integer, Real, Rational)):
+ return expr
+ if isinstance(exp, Complex):
+ return Expression(SymbolExp, exp.real)
if expr.get_head() is SymbolTimes:
- return eval_multiply_numbers(*(eval_Abs(x) for x in expr.elements))
+ factors = []
+ rest = []
+ for x in expr.elements:
+ factor = eval_Abs(x)
+ if factor:
+ factors.append(factor)
+ else:
+ rest.append(x)
+ if factors:
+ return Expression(SymbolTimes, eval_multiply_numbers(*factors), *rest)
if test_nonnegative_arithmetic_expr(expr):
return expr
if test_negative_arithmetic_expr(expr):
@@ -109,21 +125,16 @@ def eval_Abs_number(n: Number) -> Number:
"""
Evals the absolute value of a number
"""
- if isinstance(n, Integer):
+ if isinstance(n, (Real, Integer)):
n_val = n.value
if n_val >= 0:
return n
- return Integer(-n_val)
+ return eval_negate_number(n)
if isinstance(n, Rational):
n_num, n_den = n.value.as_numer_denom()
if n_num >= 0:
return n
return Rational(-n_num, n_den)
- if isinstance(n, Real):
- n_val = n.value
- if n_val >= 0:
- return n
- return eval_multiply_numbers(IntegerM1, n)
if isinstance(n, Complex):
if n.real.is_zero:
return eval_Abs_number(n.imag)
@@ -135,68 +146,180 @@ def eval_Abs_number(n: Number) -> Number:
return result
-def eval_Sign(expr: BaseElement) -> Optional[BaseElement]:
+def eval_Exp(exp: BaseElement) -> BaseElement:
"""
- if expr is a number, return its sign.
+ Eval E^exp
"""
- if isinstance(expr, Atom):
- return eval_Sign_number(expr)
- if expr.has_form("Power", 2):
- base, exp = expr.elements
- if exp.is_zero:
+ # If both base and exponent are exact quantities,
+ # use sympy.
+
+ if not exp.is_inexact():
+ exp_sp = exp.to_sympy()
+ if exp_sp is None:
+ return None
+ return from_sympy(sympy.Exp(exp_sp))
+
+ prec = exp.get_precision()
+ if prec is not None:
+ if exp.is_machine_precision():
+ number = mpmath.exp(exp.to_mpmath())
+ result = from_mpmath(number)
+ return result
+ else:
+ with mpmath.workprec(prec):
+ number = mpmath.exp(exp.to_mpmath())
+ return from_mpmath(number, prec)
+
+
+def eval_RealSign(expr: BaseElement) -> Optional[Integer]:
+ """
+ If the argument is a real algebraic expression,
+ return the sign of the expression.
+ """
+ if expr.is_zero:
+ return Integer0
+ if isinstance(expr, (Integer, Rational, Real)):
+ return Integer1 if expr.value > 0 else IntegerM1
+ if expr in NUMERICAL_CONSTANTS:
+ return Integer1
+ if expr.has_form("Abs", 1):
+ arg = expr.elements[0]
+ arg_sign = eval_Sign(arg)
+ if arg_sign is None:
+ return None
+ if arg_sign.is_zero:
+ return Integer0
+ if isinstance(arg_sign, Number):
return Integer1
- if isinstance(exp, (Integer, Real, Rational)):
- sign = eval_Sign(base) or Expression(SymbolSign, base)
- return Expression(SymbolPower, sign, exp)
- if isinstance(exp, Complex):
- sign = eval_Sign(base) or Expression(SymbolSign, base)
- return Expression(SymbolPower, sign, exp.real)
- if test_arithmetic_expr(exp):
- sign = eval_Sign(base) or Expression(SymbolSign, base)
- return Expression(SymbolPower, sign, exp)
return None
+ if expr.has_form("Sqrt", 1):
+ return Integer1 if eval_Sign(expr.elements[0]) is Integer1 else None
if expr.has_form("Exp", 1):
- exp = expr.elements[0]
- if isinstance(exp, (Integer, Real, Rational)):
- return Integer1
- if isinstance(exp, Complex):
- return Expression(SymbolExp, exp.imag)
- if expr.get_head() is SymbolTimes:
- abs_value = eval_Abs(eval_multiply_numbers(*expr.elements))
- if abs_value is Integer1:
- return expr
- if abs_value is None:
+ return Integer1 if test_arithmetic_expr(expr.elements[0]) else None
+ if expr.has_form("Log", 1) or expr.has_form("DirectedInfinity", 1):
+ return eval_RealSign(eval_add_numbers(expr.elements[0], IntegerM1))
+ if expr.has_form("Times", None):
+ sign = 1
+ for factor in expr.elements:
+ factor_sign = eval_RealSign(factor)
+ if factor_sign in (None, Integer0):
+ return factor_sign
+ if factor_sign is IntegerM1:
+ sign = -sign
+ return Integer1 if sign == 1 else IntegerM1
+ if expr.has_form("Power", 2):
+ base, exp = expr.elements
+ base_sign = eval_RealSign(base)
+ if base_sign is None:
return None
- criteria = eval_add_numbers(abs_value, IntegerM1)
- if test_zero_arithmetic_expr(criteria, numeric=True):
- return expr
+ if base_sign is Integer0:
+ if eval_RealSign(exp) in (IntegerM1, Integer0, None):
+ return None
+ return Integer0
+ # The exponent must represent a real number to continue:
+ if not test_arithmetic_expr(exp):
+ return None
+ # At this point, the exponent is a real number, so if the base
+ # is 1, does not matter its value:
+ if base_sign is Integer1:
+ return Integer1
+ if base_sign is IntegerM1:
+ if not isinstance(base, Integer):
+ return None
+ if isinstance(exp, Integer):
+ return base_sign if (exp.value % 2 == 1) else Integer1
return None
- if expr.get_head() is SymbolPlus:
- abs_value = eval_Abs(eval_add_numbers(*expr.elements))
- if abs_value is Integer1:
- return expr
- if abs_value is None:
+ if expr.has_form("Plus", None):
+ signed = {Integer1: [], IntegerM1: []}
+ for term in expr.elements:
+ rsign = eval_RealSign(term)
+ if rsign is Integer0:
+ continue
+ elif rsign is None:
+ return None
+ signed[rsign].append(term)
+ if len(signed[IntegerM1]) == 0:
+ return Integer0 if len(signed[Integer1]) == 0 else Integer1
+ if len(signed[Integer1]) == 0:
+ return IntegerM1
+ # Try to explicitly add the numbers:
+ try_add = eval_add_numbers(*(term for term in expr.elements))
+ if try_add is not None and not try_add.sameQ(expr):
+ return eval_RealSign(try_add)
+ # Now, try to convert to inexact values:
+ try_add = eval_add_numbers(*(to_inexact_value(term) for term in expr.elements))
+ if try_add is not None and try_add is not expr:
+ return eval_RealSign(try_add)
+
+
+def eval_Sign(expr: BaseElement) -> Optional[BaseElement]:
+ """
+ if expr is a number, return its sign.
+ """
+
+ def eval_complex_sign(n: BaseElement) -> Optional[BaseElement]:
+ if isinstance(n, Complex):
+ abs_sq = eval_add_numbers(
+ *(eval_multiply_numbers(x, x) for x in (n.real, n.imag))
+ )
+ criteria = eval_add_numbers(abs_sq, IntegerM1)
+ if test_zero_arithmetic_expr(criteria):
+ return n
+ if n.is_inexact():
+ return eval_multiply_numbers(n, eval_Power_number(abs_sq, RealM0p5))
+ if test_zero_arithmetic_expr(criteria, numeric=True):
+ return n
+ return eval_multiply_numbers(n, eval_Power_number(abs_sq, RationalMOneHalf))
+ if isinstance(n, Atom):
return None
- criteria = eval_add_numbers(abs_value, IntegerM1)
- if test_zero_arithmetic_expr(criteria, numeric=True):
- return expr
+ if n.has_form("Abs", 1):
+ inner_sign = eval_Sign(n.elements[0])
+ if inner_sign is Integer0:
+ return Integer0
+ if isinstance(inner_sign, Number):
+ return Integer1
+
+ if n.has_form("Exp", 1):
+ exponent = n.elements[0]
+ if isinstance(exponent, Complex):
+ return Expression(SymbolExp, exponent.imag)
+ return None
+ if n.has_form("DirectedInfinity", 1):
+ return eval_Sign(n.elements[0])
+ if n.has_form("Power", 2):
+ base, exponent = expr.elements
+ base_rsign = eval_RealSign(base)
+ if exponent.is_zero:
+ return SymbolIndeterminate if base_rsign is Integer0 else Integer1
+ if test_arithmetic_expr(exponent):
+ base_sign = eval_Sign(base) or Expression(SymbolSign, base)
+ return eval_Power_number(base_sign, exponent)
+ if isinstance(exponent, Complex):
+ if base_rsign is Integer1:
+ exp_im = exponent.imag
+ return eval_Power_number(base, Complex(Integer0, exp_im))
+
+ if test_arithmetic_expr(base):
+ eval_Power_number(base_sign, exponent)
+ base_sign = eval_Sign(base)
+ return eval_Power_number(base_sign, exponent)
+ if n.head is SymbolTimes:
+ signs = []
+ for factor in expr.elements:
+ factor_sign = eval_Sign(factor)
+ if factor_sign in (None, Integer0):
+ return factor_sign
+ if factor_sign is not Integer1:
+ signs.append(factor_sign)
+ return Integer1 if len(signs) == 0 else eval_multiply_numbers(*signs)
+
+ try_inexact = to_inexact_value(n)
+ if try_inexact:
+ return eval_Sign(try_inexact)
return None
- if test_nonnegative_arithmetic_expr(expr):
- return Integer1
- if test_negative_arithmetic_expr(expr):
- return IntegerM1
- if test_zero_arithmetic_expr:
- return Integer0
- return None
- if isinstance(expr, Complex):
- re, im = expr.real, expr.imag
- sqabs = eval_add_numbers(eval_Times(re, re), eval_Times(im, im))
- norm = Expression(SymbolPower, sqabs, RationalMOneHalf)
- result = eval_Times(expr, norm)
- if result is None:
- return Expression(SymbolTimes, expr, norm)
- return result
- return None
+
+ sign = eval_RealSign(expr)
+ return sign or eval_complex_sign(expr)
def eval_Sign_number(n: Number) -> Number:
@@ -492,6 +615,150 @@ def eval_Power_inexact(base: Number, exp: Number) -> BaseElement:
return from_mpmath(number, prec)
+def eval_Power_number(base: Number, exp: Number) -> Optional[Number]:
+ """
+ Eval base^exp for `base` and `exp` two numbers. If the expression
+ remains the same, return None.
+ """
+ # If both base and exponent are exact quantities,
+ # use sympy.
+ # If base or exp are inexact quantities, use
+ # the inexact routine.
+ if base.is_inexact() or exp.is_inexact():
+ return eval_Power_inexact(base, exp)
+
+ # Trivial special cases
+ if exp is Integer1:
+ return base
+ if exp is Integer0:
+ return Integer1
+ if base is Integer1:
+ return Integer1
+
+ def eval_Power_sympy() -> Optional[Number]:
+ """
+ Tries to compute x^p using sympy rules.
+ If the answer is again x^p, return None.
+ """
+ # This function is called just if useful native rules
+ # are available.
+ result = from_sympy(sympy.Pow(base.to_sympy(), exp.to_sympy()))
+ if result.has_form("Power", 2):
+ # If the expression didn´t change, return None
+ if result.elements[0].sameQ(base):
+ return None
+ return result
+
+ # Rational exponent
+ if isinstance(exp, Rational):
+ exp_p, exp_q = exp.value.as_numer_denom()
+ if abs(exp_p) > exp_q:
+ exp_int, exp_num = divmod(exp_p, exp_q)
+ exp_rem = Rational(exp_num, exp_q)
+ factor_1 = eval_Power_number(base, Integer(exp_int))
+ factor_2 = eval_Power_number(base, exp_rem) or Expression(
+ SymbolPower, base, exp_rem
+ )
+ if factor_1 is Integer1:
+ return factor_2
+ return Expression(SymbolTimes, factor_1, factor_2)
+
+ # Integer base
+ if isinstance(base, Integer):
+ base_value = base.value
+ if base_value == -1:
+ if isinstance(exp, Rational):
+ if exp.sameQ(RationalOneHalf):
+ return SymbolI
+ return None
+ return eval_Power_sympy()
+ elif base_value < 0:
+ neg_base = eval_negate_number(base)
+ candidate = eval_Power_number(neg_base, exp)
+ if candidate is None:
+ return None
+ sign_factor = eval_Power_number(IntegerM1, exp)
+ if candidate is Integer1:
+ return sign_factor
+ return Expression(SymbolTimes, candidate, sign_factor)
+
+ # Rational base
+ if isinstance(base, Rational):
+ # If the exponent is an Integer or Rational negative value
+ # restate as a positive power
+ if (
+ isinstance(exp, Integer)
+ and exp.value < 0
+ or isinstance(exp, Rational)
+ and exp.value.p < 0
+ ):
+ base, exp = eval_inverse_number(base), eval_negate_number(exp)
+ return eval_Power_number(base, exp) or Expression(SymbolPower, base, exp)
+
+ p, q = (Integer(u) for u in base.value.as_numer_denom())
+ p_eval, q_eval = (eval_Power_number(u, exp) for u in (p, q))
+ # If neither p^exp or q^exp produced a new result,
+ # leave it alone
+ if q_eval is None and p_eval is None:
+ return None
+ # if q^exp == 1: return p_eval
+ # (should not happen)
+ if q_eval is Integer1:
+ return p_eval
+ if isinstance(q_eval, Integer):
+ if isinstance(p_eval, Integer):
+ return Rational(p_eval.value, q_eval.value)
+
+ if p_eval is None:
+ p_eval = Expression(SymbolPower, p, exp)
+
+ if q_eval is None:
+ q_eval = Expression(SymbolPower, q, exp)
+ return Expression(
+ SymbolTimes, p_eval, Expression(SymbolPower, q_eval, IntegerM1)
+ )
+ # Pure imaginary base case
+ elif isinstance(base, Complex) and base.real.is_zero:
+ base = base.imag
+ if base.value < 0:
+ base = eval_negate_number(base)
+ phase = Expression(
+ SymbolPower,
+ IntegerM1,
+ eval_multiply_numbers(IntegerM1, RationalOneHalf, exp),
+ )
+ else:
+ phase = Expression(
+ SymbolPower, IntegerM1, eval_multiply_numbers(RationalOneHalf, exp)
+ )
+ real_factor = eval_Power_number(base, exp)
+
+ if real_factor is None:
+ return None
+ return Expression(SymbolTimes, real_factor, phase)
+
+ # Generic case
+ return eval_Power_sympy()
+
+
+def eval_Power_inexact(base: Number, exp: Number) -> BaseElement:
+ """
+ Eval base^exp for `base` and `exp` inexact numbers
+ """
+ # If both base and exponent are exact quantities,
+ # use sympy.
+ prec = min_prec(base, exp)
+ if prec is not None:
+ is_machine_precision = base.is_machine_precision() or exp.is_machine_precision()
+ if is_machine_precision:
+ number = mpmath.power(base.to_mpmath(), exp.to_mpmath())
+ return from_mpmath(number)
+ else:
+ with mpmath.workprec(prec):
+ number = mpmath.power(base.to_mpmath(), exp.to_mpmath())
+ return from_mpmath(number, prec)
+
+
def eval_Times(*items: BaseElement) -> BaseElement:
elements = []
numbers = []
@@ -739,7 +1006,7 @@ def eval_inverse_number(n: Number) -> Number:
return eval_Power_number(n, IntegerM1)
-def eval_multiply_numbers(*numbers: List[Number]) -> Number:
+def eval_multiply_numbers(*numbers: Number) -> Number:
"""
Multiply the elements in ``numbers``.
"""
@@ -845,11 +1112,19 @@ def test_arithmetic_expr(expr: BaseElement, only_real: bool = True) -> bool:
return not only_real
if isinstance(expr, Symbol):
return False
+ if isinstance(expr, Atom):
+ return False
head, elements = expr.head, expr.elements
if head in (SymbolPlus, SymbolTimes):
return all(test_arithmetic_expr(term, only_real) for term in elements)
+ if expr.has_form("Power", 2):
+ base, exponent = elements
+ if only_real:
+ if isinstance(exponent, Integer):
+ return test_arithmetic_expr(base)
+ return all(test_arithmetic_expr(item, only_real) for item in elements)
if expr.has_form("Exp", 1):
return test_arithmetic_expr(elements[0], only_real)
if head is SymbolLog:
@@ -857,15 +1132,16 @@ def test_arithmetic_expr(expr: BaseElement, only_real: bool = True) -> bool:
return False
if len(elements) == 2:
base = elements[0]
- if not test_positive_arithmetic_expr(base):
+ if only_real and eval_RealSign(base) is not Integer1:
+ return False
+ elif not test_arithmetic_expr(base):
return False
return test_arithmetic_expr(elements[-1], only_real)
- if expr.has_form("Power", 2):
- base, exponent = elements
+ if expr.has_form("Sqrt", 1):
+ radicand = elements[0]
if only_real:
- if isinstance(exponent, Integer):
- return test_arithmetic_expr(base)
- return all(test_arithmetic_expr(item, only_real) for item in elements)
+ return eval_RealSign(radicand) in (Integer0, Integer1)
+ return test_arithmetic_expr(radicand, only_real)
return False
@@ -874,11 +1150,7 @@ def test_negative_arithmetic_expr(expr: BaseElement) -> bool:
Check if the expression is an arithmetic expression
representing a negative value.
"""
- if isinstance(expr, (Integer, Rational, Real)):
- return expr.value < 0
-
- expr = eval_multiply_numbers(IntegerM1, expr)
- return test_positive_arithmetic_expr(expr)
+ return eval_RealSign(expr) is IntegerM1
def test_nonnegative_arithmetic_expr(expr: BaseElement) -> bool:
@@ -886,11 +1158,7 @@ def test_nonnegative_arithmetic_expr(expr: BaseElement) -> bool:
Check if the expression is an arithmetic expression
representing a nonnegative number
"""
- if not test_arithmetic_expr(expr):
- return False
-
- if test_zero_arithmetic_expr(expr) or test_positive_arithmetic_expr(expr):
- return True
+ return eval_RealSign(expr) in (Integer0, Integer1)
def test_nonpositive_arithetic_expr(expr: BaseElement) -> bool:
@@ -898,12 +1166,7 @@ def test_nonpositive_arithetic_expr(expr: BaseElement) -> bool:
Check if the expression is an arithmetic expression
representing a nonnegative number
"""
- if not test_arithmetic_expr(expr):
- return False
-
- if test_zero_arithmetic_expr(expr) or test_negative_arithmetic_expr(expr):
- return True
- return False
+ return eval_RealSign(expr) in (Integer0, IntegerM1)
def test_positive_arithmetic_expr(expr: BaseElement) -> bool:
@@ -911,79 +1174,7 @@ def test_positive_arithmetic_expr(expr: BaseElement) -> bool:
Check if the expression is an arithmetic expression
representing a positive value.
"""
- if isinstance(expr, (Integer, Rational, Real)):
- return expr.value > 0
- if expr in NUMERICAL_CONSTANTS:
- return True
- if isinstance(expr, Atom):
- return False
-
- head, elements = expr.get_head(), expr.elements
-
- if head is SymbolPlus:
- positive_nonpositive_terms = {True: [], False: []}
- for term in elements:
- positive_nonpositive_terms[test_positive_arithmetic_expr(term)].append(term)
-
- if len(positive_nonpositive_terms[False]) == 0:
- return True
- if len(positive_nonpositive_terms[True]) == 0:
- return False
-
- pos, neg = (
- eval_add_numbers(*items) for items in positive_nonpositive_terms.values()
- )
- if neg.is_zero:
- return True
- if not test_arithmetic_expr(neg):
- return False
-
- total = eval_add_numbers(pos, neg)
- # Check positivity of the evaluated expression
- if isinstance(total, (Integer, Rational, Real)):
- return total.value > 0
- if isinstance(total, Complex):
- return False
- if total.sameQ(expr):
- return False
- return test_positive_arithmetic_expr(total)
-
- if head is SymbolTimes:
- nonpositive_factors = tuple(
- (item for item in elements if not test_positive_arithmetic_expr(item))
- )
- if len(nonpositive_factors) == 0:
- return True
- evaluated_expr = eval_multiply_numbers(*nonpositive_factors)
- if evaluated_expr.sameQ(expr):
- return False
- return test_positive_arithmetic_expr(evaluated_expr)
- if expr.has_form("Power", 2):
- base, exponent = elements
- if isinstance(exponent, Integer) and exponent.value % 2 == 0:
- return test_arithmetic_expr(base)
- return test_arithmetic_expr(exponent) and test_positive_arithmetic_expr(base)
- if expr.has_form("Exp", 1):
- return test_arithmetic_expr(expr.elements[0], only_real=True)
- if expr.has_form("Sqrt", 1):
- return test_positive_arithmetic_expr(expr.elements[0])
- if head is SymbolLog:
- if len(elements) > 2:
- return False
- if len(elements) == 2:
- if not test_positive_arithmetic_expr(elements[0]):
- return False
- arg = elements[-1]
- return test_positive_arithmetic_expr(eval_add_numbers(arg, IntegerM1))
- if expr.has_form("Abs", 1):
- arg = elements[0]
- return test_arithmetic_expr(
- arg, only_real=False
- ) and not test_zero_arithmetic_expr(arg)
- if head.has_form("DirectedInfinity", 1):
- return test_positive_arithmetic_expr(elements[0])
-
- return False
+ return eval_RealSign(expr) is Integer1
def test_zero_arithmetic_expr(expr: BaseElement, numeric: bool = False) -> bool:
@@ -991,47 +1182,28 @@ def test_zero_arithmetic_expr(expr: BaseElement, numeric: bool = False) -> bool:
return True if expr evaluates to a number compatible
with 0
"""
-
- def is_numeric_zero(z: Number):
- if isinstance(z, Complex):
- if abs(z.real.value) + abs(z.imag.value) < 2.0e-10:
+ if numeric:
+ if isinstance(expr, Complex):
+ if abs(expr.real.value) + abs(expr.imag.value) < 2.0e-10:
return True
- if isinstance(z, Number):
- if abs(z.value) < 1e-10:
+ if isinstance(expr, Number):
+ if abs(expr.value) < 1e-10:
return True
- return False
-
- if expr.is_zero:
- return True
- if numeric:
- if is_numeric_zero(expr):
- return True
expr = to_inexact_value(expr)
- if expr.has_form("Times", None):
- if any(
- test_zero_arithmetic_expr(element, numeric=numeric)
- for element in expr.elements
- ) and not any(
- element.has_form("DirectedInfinity", None) for element in expr.elements
- ):
- return True
- if expr.has_form("Power", 2):
- base, exp = expr.elements
- if test_zero_arithmetic_expr(base, numeric):
- return test_nonnegative_arithmetic_expr(exp)
- if base.has_form("DirectedInfinity", None):
- return test_positive_arithmetic_expr(exp)
- if expr.has_form("Plus", None):
- result = eval_add_numbers(*expr.elements)
- if numeric:
- if isinstance(result, complex):
- if abs(result.real.value) + abs(result.imag.value) < 2.0e-10:
- return True
- if isinstance(result, Number):
- if abs(result.value) < 1e-10:
- return True
- return result.is_zero
- return False
+
+ return eval_RealSign(expr) is Integer0
+
+
+EVAL_TO_INEXACT_DISPATCH = {
+ SymbolPlus: eval_add_numbers,
+ SymbolTimes: eval_multiply_numbers,
+ SymbolPower: eval_Power_number,
+ SymbolExp: eval_Exp,
+ SymbolSqrt: (lambda x: eval_Power_number(x, RationalOneHalf)),
+ SymbolAbs: eval_Abs,
+ SymbolSign: eval_Sign,
+ SymbolRealSign: eval_RealSign,
+}
def to_inexact_value(expr: BaseElement) -> BaseElement:
@@ -1042,9 +1214,18 @@ def to_inexact_value(expr: BaseElement) -> BaseElement:
"""
if expr.is_inexact():
return expr
+ if isinstance(expr, Number):
+ return expr.round()
+ if expr is SymbolI:
+ return Complex(Integer0, RealOne)
+ if isinstance(expr, Symbol):
+ return NUMERICAL_CONSTANTS.get(expr, None)
if isinstance(expr, Expression):
- for const, value in NUMERICAL_CONSTANTS.items():
- expr, _ = expr.do_apply_rule(Rule(const, value))
-
- return eval_multiply_numbers(RealOne, expr)
+ try:
+ head = expr.head
+ elements = tuple(to_inexact_value(element) for element in expr.elements)
+ return EVAL_TO_INEXACT_DISPATCH[head](*elements)
+ except Exception:
+ pass
+ return None
diff --git a/mathics/eval/testing_expressions.py b/mathics/eval/testing_expressions.py
index c15471f48..b0783ceb9 100644
--- a/mathics/eval/testing_expressions.py
+++ b/mathics/eval/testing_expressions.py
@@ -6,10 +6,12 @@
from mathics.core.expression import Expression
from mathics.core.systemsymbols import SymbolDirectedInfinity
-
+# TODO: Remove me. The following function is not used anywhere
+"""
def cmp(a, b) -> int:
"Returns 0 if a == b, -1 if a < b and 1 if a > b"
return (a > b) - (a < b)
+"""
def do_cmp(x1, x2) -> Optional[int]:
diff --git a/mathics/main.py b/mathics/main.py
index 3d220a8e3..51eb3efbd 100755
--- a/mathics/main.py
+++ b/mathics/main.py
@@ -24,6 +24,7 @@
from mathics.core.definitions import Definitions, Symbol, autoload_files
from mathics.core.evaluation import Evaluation, Output
from mathics.core.expression import Expression
+from mathics.core.load_builtin import import_and_load_builtins
from mathics.core.parser import MathicsFileLineFeeder, MathicsLineFeeder
from mathics.core.read import channel_to_stream
from mathics.core.rules import BuiltinRule
@@ -31,6 +32,12 @@
from mathics.core.symbols import SymbolNull, strip_context
from mathics.timing import show_lru_cache_statistics
+# from mathics.timing import TimeitContextManager
+# with TimeitContextManager("import_and_load_builtins()"):
+# import_and_load_builtins()
+
+import_and_load_builtins()
+
def get_srcdir():
filename = osp.normcase(osp.dirname(osp.abspath(__file__)))
diff --git a/mathics/version.py b/mathics/version.py
index 11bfc1b45..7b0a25141 100644
--- a/mathics/version.py
+++ b/mathics/version.py
@@ -5,4 +5,4 @@
# well as importing into Python. That's why there is no
# space around "=" below.
# fmt: off
-__version__="6.0.2dev0" # noqa
+__version__="7.0.0dev0" # noqa
diff --git a/test/builtin/arithmetic/test_lowlevel_properties.py b/test/builtin/arithmetic/test_lowlevel_properties.py
index 10cebfd06..14f3836ff 100644
--- a/test/builtin/arithmetic/test_lowlevel_properties.py
+++ b/test/builtin/arithmetic/test_lowlevel_properties.py
@@ -56,7 +56,7 @@ def test_positivity(str_expr, expected, msg):
("1", False, None),
("Pi", False, None),
("a", False, None),
- ("a-a", True, None),
+ ("a-a", False, "the low-level check does not try to evaluate the input"),
("3-3.", True, None),
("2-Sqrt[4]", True, None),
("-Pi", False, None),
@@ -73,9 +73,9 @@ def test_positivity(str_expr, expected, msg):
("Log[3]", False, None),
("Log[I]", False, None),
("Abs[a]", False, None),
- ("Abs[0]", False, None),
+ ("Abs[0]", True, None),
("Abs[1+3 I]", False, None),
- # ("Sin[Pi]", False, None),
+ ("Sin[Pi]", False, None),
],
)
def test_zero(str_expr, expected, msg):
diff --git a/test/consistency-and-style/test_duplicate_builtins.py b/test/consistency-and-style/test_duplicate_builtins.py
index 2e2a0dec8..5ece63ccb 100644
--- a/test/consistency-and-style/test_duplicate_builtins.py
+++ b/test/consistency-and-style/test_duplicate_builtins.py
@@ -8,9 +8,8 @@
import pytest
-from mathics.builtin import modules
from mathics.builtin.base import Builtin
-from mathics.core.load_builtin import name_is_builtin_symbol
+from mathics.core.load_builtin import mathics3_builtins_modules, name_is_builtin_symbol
@pytest.mark.skipif(
@@ -19,7 +18,7 @@
def test_check_duplicated():
msg = ""
builtins_by_name = {}
- for module in modules:
+ for module in mathics3_builtins_modules:
vars = dir(module)
for name in vars:
var = name_is_builtin_symbol(module, name)
@@ -33,15 +32,18 @@ def test_check_duplicated():
"""
assert (
builtins_by_name.get(name, None) is None
- ), f"{name} defined in {module} already defined in {builtins_by_name[name]}."
+ ), f"{name} defined in {module} already defined in "
+ f{builtins_by_name[name]}."
"""
# if builtins_by_name.get(name, None) is not None:
# print(
- # f"\n{name} defined in {module} already defined in {builtins_by_name[name]}."
+ # (f"\n{name} defined in {module} already defined in
+ # f{builtins_by_name[name]}.")
# )
# msg = (
# msg
- # + f"\n{name} defined in {module} already defined in {builtins_by_name[name]}."
+ # + (f"\n{name} defined in {module} already defined in "
+ # {builtins_by_name[name]}.")
# )
builtins_by_name[name] = module
assert msg == "", msg
diff --git a/test/helper.py b/test/helper.py
index 025dd3ca4..fa7b87d47 100644
--- a/test/helper.py
+++ b/test/helper.py
@@ -2,8 +2,11 @@
import time
from typing import Optional
+from mathics.core.load_builtin import import_and_load_builtins
from mathics.session import MathicsSession
+import_and_load_builtins()
+
# Set up a Mathics session with definitions.
# For consistency set the character encoding ASCII which is
# the lowest common denominator available on all systems.