diff --git a/.github/workflows/ubuntu-minimal.yml b/.github/workflows/ubuntu-minimal.yml new file mode 100644 index 000000000..9709ecd5e --- /dev/null +++ b/.github/workflows/ubuntu-minimal.yml @@ -0,0 +1,34 @@ +name: Mathics (ubuntu-minimal) + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + env: + NO_CYTHON: 1 + runs-on: ubuntu-20.04 + strategy: + matrix: + python-version: [3.9] + steps: + - uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + sudo apt-get update -qq && sudo apt-get install -qq liblapack-dev llvm-dev + python -m pip install --upgrade pip + # Can remove after next Mathics-Scanner release + # python -m pip install -e git+https://github.com/Mathics3/mathics-scanner#egg=Mathics-Scanner[full] + - name: Install Mathics with full dependencies + run: | + make develop + - name: Test Mathics + run: | + make -j3 check diff --git a/.github/workflows/ubuntu-pyston.yml b/.github/workflows/ubuntu-pyston.yml new file mode 100644 index 000000000..7ae71a233 --- /dev/null +++ b/.github/workflows/ubuntu-pyston.yml @@ -0,0 +1,35 @@ +name: Mathics (ubuntu full with Pyston) + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + runs-on: ubuntu-22.04 + strategy: + matrix: + pyston-version: [2.3.4] + steps: + - uses: actions/checkout@v3 + - name: Set up Pyston ${{ matrix.pyston-version }} + run: | + wget https://github.com/pyston/pyston/releases/download/pyston_2.3.4/pyston_2.3.4_20.04_amd64.deb + sudo apt-get install tk8.6-blt2.5 libffi7 + sudo dpkg -i pyston_2.3.4_20.04_amd64.deb + - name: Install dependencies + run: | + sudo apt-get update -qq && sudo apt-get install -qq liblapack-dev llvm-11 llvm-11-dev libxml2-dev libxslt-dev + # sudo /usr/lib/llvm-11/bin/llvm-config --version + sudo pyston -m pip install --upgrade pip + sudo LLVM_CONFIG=/usr/lib/llvm-11/bin/llvm-config pyston -m pip install llvmlite + - name: Install Mathics with minimal dependencies + run: | + make develop-full + - name: Test Mathics + run: | + # py.test seems to use always the default python interpreter of the system. Any idea about how to + # change this behaviour? + make -j3 PYTHON=pyston doctest diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index 7a8b7796c..10d5d7d5c 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -10,14 +10,14 @@ jobs: build: env: NO_CYTHON: 1 - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest strategy: matrix: - python-version: [3.6, 3.7, 3.8, 3.9] + python-version: [3.6, 3.7, 3.8, 3.9, pypy3.8] 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/mathics/algorithm/optimizers.py b/mathics/algorithm/optimizers.py index af5e4745a..20f0c7cc0 100644 --- a/mathics/algorithm/optimizers.py +++ b/mathics/algorithm/optimizers.py @@ -350,9 +350,11 @@ def sub(evaluation): return x0, True +native_optimizer_messages = {} + native_local_optimizer_methods = { "Automatic": find_minimum_newton1d, - "newton": find_minimum_newton1d, + "Newton": find_minimum_newton1d, } native_findroot_methods = { @@ -360,6 +362,7 @@ def sub(evaluation): "Newton": find_root_newton, "Secant": find_root_secant, } +native_findroot_messages = {} def is_zero( diff --git a/mathics/builtin/__init__.py b/mathics/builtin/__init__.py index 6e2b0ddf9..95ac55eae 100755 --- a/mathics/builtin/__init__.py +++ b/mathics/builtin/__init__.py @@ -34,6 +34,7 @@ SympyObject, Operator, PatternObject, + check_requires_list, ) diff --git a/mathics/builtin/base.py b/mathics/builtin/base.py index 664b7d413..854c08964 100644 --- a/mathics/builtin/base.py +++ b/mathics/builtin/base.py @@ -42,6 +42,31 @@ from mathics.core.attributes import protected, read_protected +# This global dict stores which libraries was required to +# be available, and the corresponding result. +requires_lib_cache = {} + + +def check_requires_list(requires: list) -> bool: + """ + Check if module names in ``requires`` can be imported and return True if they can or False if not. + + This state value is also recorded in dictionary `requires_lib_cache` keyed by module name and is used to determine whether to skip trying to get information from the module.""" + for package in requires: + lib_is_installed = requires_lib_cache.get(package, None) + if lib_is_installed is None: + lib_is_installed = True + try: + importlib.import_module(package) + except ImportError: + lib_is_installed = False + requires_lib_cache[package] = lib_is_installed + + if not lib_is_installed: + return False + return True + + def get_option(options, name, evaluation, pop=False, evaluate=True): # we do not care whether an option X is given as System`X, # Global`X, or with any prefix from $ContextPath for that @@ -407,7 +432,6 @@ def get_functions(self, prefix="apply", is_pymodule=False): unavailable_function = self._get_unavailable_function() for name in dir(self): if name.startswith(prefix): - function = getattr(self, name) pattern = function.__doc__ if pattern is None: # Fixes PyPy bug @@ -439,25 +463,23 @@ def get_functions(self, prefix="apply", is_pymodule=False): def get_option(options, name, evaluation, pop=False): return get_option(options, name, evaluation, pop) - def _get_unavailable_function(self): - requires = getattr(self, "requires", []) - - for package in requires: - try: - importlib.import_module(package) - except ImportError: - - def apply(**kwargs): # will override apply method - kwargs["evaluation"].message( - "General", - "pyimport", # see inout.py - strip_context(self.get_name()), - package, - ) + def _get_unavailable_function(self) -> "Optional[function]": + """ + If some of the required libraries for a symbol are not available, + returns a default function that override the ``apply_`` methods + of the class. Otherwise, returns ``None``. + """ - return apply + def apply_unavailable(**kwargs): # will override apply method + kwargs["evaluation"].message( + "General", + "pyimport", # see inout.py + strip_context(self.get_name()), + package, + ) - return None + requires = getattr(self, "requires", []) + return None if check_requires_list(requires) else apply_unavailable def get_option_string(self, *params): s = self.get_option(*params) diff --git a/mathics/builtin/numbers/calculus.py b/mathics/builtin/numbers/calculus.py index 5a07f56ad..28d728b53 100644 --- a/mathics/builtin/numbers/calculus.py +++ b/mathics/builtin/numbers/calculus.py @@ -1286,7 +1286,6 @@ class _BaseFinder(Builtin): """ attributes = hold_all | protected - requires = ["scipy"] methods = {} messages = { "snum": "Value `1` is not a number.", @@ -1482,9 +1481,13 @@ class FindRoot(_BaseFinder): ) try: - from mathics.algorithm.optimizers import native_findroot_methods + from mathics.algorithm.optimizers import ( + native_findroot_methods, + native_findroot_messages, + ) methods.update(native_findroot_methods) + messages.update(native_findroot_messages) except Exception: pass try: @@ -1518,8 +1521,8 @@ class FindMinimum(_BaseFinder): = {2., {x -> 3.}} >> FindMinimum[Sin[x], {x, 1}] = {-1., {x -> -1.5708}} - >> phi[x_?NumberQ]:=NIntegrate[u,{u,0,x}]; - >> FindMinimum[phi[x]-x,{x,1.2}] + >> phi[x_?NumberQ]:=NIntegrate[u,{u,0,x}, Method->"Internal"]; + >> Quiet[FindMinimum[phi[x]-x,{x, 1.2}, Method->"Newton"]] = {-0.5, {x -> 1.00001}} >> Clear[phi]; For a not so well behaving function, the result can be less accurate: @@ -1531,9 +1534,13 @@ class FindMinimum(_BaseFinder): methods = {} summary_text = "local minimum optimization" try: - from mathics.algorithm.optimizers import native_local_optimizer_methods + from mathics.algorithm.optimizers import ( + native_local_optimizer_methods, + native_optimizer_messages, + ) methods.update(native_local_optimizer_methods) + messages.update(native_optimizer_messages) except Exception: pass try: @@ -1561,8 +1568,8 @@ class FindMaximum(_BaseFinder): = {2., {x -> 3.}} >> FindMaximum[Sin[x], {x, 1}] = {1., {x -> 1.5708}} - >> phi[x_?NumberQ]:=NIntegrate[u,{u,0,x}]; - >> FindMaximum[-phi[x]+x,{x,1.2}] + >> phi[x_?NumberQ]:=NIntegrate[u, {u, 0., x}, Method->"Internal"]; + >> Quiet[FindMaximum[-phi[x] + x, {x, 1.2}, Method->"Newton"]] = {0.5, {x -> 1.00001}} >> Clear[phi]; For a not so well behaving function, the result can be less accurate: @@ -2021,33 +2028,31 @@ class NIntegrate(Builtin):