Skip to content
Closed
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
34 changes: 34 additions & 0 deletions .github/workflows/ubuntu-minimal.yml
Original file line number Diff line number Diff line change
@@ -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
35 changes: 35 additions & 0 deletions .github/workflows/ubuntu-pyston.yml
Original file line number Diff line number Diff line change
@@ -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 full 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
5 changes: 4 additions & 1 deletion mathics/algorithm/optimizers.py
Original file line number Diff line number Diff line change
Expand Up @@ -350,16 +350,19 @@ 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 = {
"Automatic": find_root_newton,
"Newton": find_root_newton,
"Secant": find_root_secant,
}
native_findroot_messages = {}


def is_zero(
Expand Down
1 change: 1 addition & 0 deletions mathics/builtin/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
SympyObject,
Operator,
PatternObject,
check_requires_list,
)


Expand Down
58 changes: 40 additions & 18 deletions mathics/builtin/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
64 changes: 35 additions & 29 deletions mathics/builtin/numbers/calculus.py
Original file line number Diff line number Diff line change
Expand Up @@ -1287,7 +1287,6 @@ class _BaseFinder(Builtin):
"""

attributes = hold_all | protected
requires = ["scipy"]
methods = {}
messages = {
"snum": "Value `1` is not a number.",
Expand Down Expand Up @@ -1483,9 +1482,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:
Expand Down Expand Up @@ -1519,8 +1522,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:
Expand All @@ -1532,9 +1535,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:
Expand Down Expand Up @@ -1562,8 +1569,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:
Expand Down Expand Up @@ -2017,37 +2024,35 @@ class NIntegrate(Builtin):
<dt>'NIntegrate[$expr$, $interval$]'
<dd>returns a numeric approximation to the definite integral of $expr$ with limits $interval$ and with a precision of $prec$ digits.

<dt>'NIntegrate[$expr$, $interval1$, $interval2$, ...]'
<dd>returns a numeric approximation to the multiple integral of $expr$ with limits $interval1$, $interval2$ and with a precision of $prec$ digits.
<dt>'NIntegrate[$expr$, $interval1$, $interval2$, ...]'
<dd>returns a numeric approximation to the multiple integral of $expr$ with limits $interval1$, $interval2$ and with a precision of $prec$ digits.
</dl>

>> NIntegrate[Exp[-x],{x,0,Infinity},Tolerance->1*^-6]
>> NIntegrate[Exp[-x],{x,0,Infinity},Tolerance->1*^-6, Method->"Internal"]
= 1.
>> NIntegrate[Exp[x],{x,-Infinity, 0},Tolerance->1*^-6]
>> NIntegrate[Exp[x],{x,-Infinity, 0},Tolerance->1*^-6, Method->"Internal"]
= 1.
>> NIntegrate[Exp[-x^2/2.],{x,-Infinity, Infinity},Tolerance->1*^-6]
= 2.50663
>> NIntegrate[Exp[-x^2/2.],{x,-Infinity, Infinity},Tolerance->1*^-6, Method->"Internal"]
= 2.50664

>> Table[1./NIntegrate[x^k,{x,0,1},Tolerance->1*^-6], {k,0,6}]
: The specified method failed to return a number. Falling back into the internal evaluator.
= {1., 2., 3., 4., 5., 6., 7.}
"""

>> NIntegrate[1 / z, {z, -1 - I, 1 - I, 1 + I, -1 + I, -1 - I}, Tolerance->1.*^-4]
: Integration over a complex domain is not implemented yet
= NIntegrate[1 / z, {z, -1 - I, 1 - I, 1 + I, -1 + I, -1 - I}, Tolerance -> 0.0001]
## = 6.2832 I
# ## The Following tests fails if sympy is not installed.
# >> Table[1./NIntegrate[x^k,{x,0,1},Tolerance->1*^-6], {k,0,6}]
# : The specified method failed to return a number. Falling back into the internal evaluator.
# = {1., 2., 3., 4., 5., 6., 7.}

Integrate singularities with weak divergences:
>> Table[ NIntegrate[x^(1./k-1.), {x,0,1.}, Tolerance->1*^-6], {k,1,7.} ]
= {1., 2., 3., 4., 5., 6., 7.}
# >> NIntegrate[1 / z, {z, -1 - I, 1 - I, 1 + I, -1 + I, -1 - I}, Tolerance->1.*^-4]
# ## = 6.2832 I

Mutiple Integrals :
>> NIntegrate[x * y,{x, 0, 1}, {y, 0, 1}]
= 0.25
# Integrate singularities with weak divergences:
# >> Table[ NIntegrate[x^(1./k-1.), {x,0,1.}, Tolerance->1*^-6], {k,1,7.}]
# = {1., 2., 3., 4., 5., 6., 7.}

"""
# Mutiple Integrals :
# >> NIntegrate[x * y,{x, 0, 1}, {y, 0, 1}]
# = 0.25

requires = ["scipy"]
summary_text = "numerical integration in one or several variables"
messages = {
"bdmtd": "The Method option should be a built-in method name.",
Expand Down Expand Up @@ -2122,6 +2127,8 @@ def apply_with_func_domain(self, func, domain, evaluation, options):
method = method.value
elif isinstance(method, Symbol):
method = method.get_name()
# strip context
method = method[method.rindex("`") + 1 :]
else:
evaluation.message("NIntegrate", "bdmtd", method)
return
Expand Down Expand Up @@ -2155,7 +2162,6 @@ def apply_with_func_domain(self, func, domain, evaluation, options):

intvars = ListExpression(*coords)
integrand = Expression(SymbolCompile, intvars, func).evaluate(evaluation)

if len(integrand.elements) >= 3:
integrand = integrand.elements[2].cfunc
else:
Expand Down
3 changes: 2 additions & 1 deletion mathics/builtin/scipy_utils/integrators.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# -*- coding: utf-8 -*-
import sys
from mathics.builtin import check_requires_list

IS_PYPY = "__pypy__" in sys.builtin_module_names
if IS_PYPY:
if IS_PYPY or not check_requires_list(["scipy", "numpy"]):
raise ImportError

import numpy as np
Expand Down
5 changes: 4 additions & 1 deletion mathics/builtin/scipy_utils/optimizers.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# -*- coding: utf-8 -*-
import sys

from mathics.builtin import check_requires_list

from mathics.core.expression import Expression
from mathics.core.evaluation import Evaluation
from mathics.core.atoms import Number, Real
Expand All @@ -12,9 +14,10 @@
SymbolCompile = Symbol("Compile")

IS_PYPY = "__pypy__" in sys.builtin_module_names
if IS_PYPY:
if IS_PYPY or not check_requires_list(["scipy", "numpy"]):
raise ImportError


from scipy.optimize import (
minimize_scalar,
# minimize,
Expand Down
Loading