Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Load single builtin module #903

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
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
122 changes: 75 additions & 47 deletions mathics/core/load_builtin.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,15 @@
# is initialized via below import_builtins modules
mathics3_builtins_modules: List[ModuleType] = []

# Used in definition_contribute
_builtins = {}

# builtins_by_module gives a way of mapping a Python module name
# e.g. 'mathics.builtin.arithmetic' to the list of Builtin class instances
# that appear inside that module, e.g. for key 'mathics.builtin.arithmetic' we
# have:
# [<mathics.builtin.arithmetic.Arg object>, <mathics.builtin.arithmetic.Assuming object, ...]
# [<mathics.builtin.arithmetic.Arg object>, <mathics.builtin.arithmetic.Assuming object,
# ...]
#
builtins_by_module: Dict[str, list] = {}

Expand All @@ -42,12 +44,13 @@
display_operators_set: Set[str] = set()


def add_builtins_from_builtin_module(module: ModuleType, builtins_list: list):
def add_builtins_from_builtin_module(module: ModuleType):
"""
Process a modules which contains Builtin classes so that the
class is imported in the Python sense but also that we
have information added to module variable ``builtins_by_module``.

Process a module which contains Builtin classes so that the
class is imported in the Python sense, but also that we
have information added to internal tables, like operator
precedence or the mapping of Mathics3 builtin names to sympy equivalents. Also add
``module`` to variable ``builtins_by_module``.
"""
from mathics.core.builtin import Builtin

Expand All @@ -63,48 +66,21 @@ class is imported in the Python sense but also that we
# This set the default context for symbols in mathics.builtins
if not type(instance).context:
type(instance).context = "System`"
builtins_list.append((instance.get_name(), instance))
builtins_by_module[module.__name__].append(instance)
update_display_operators_set(instance)
update_builtin_properties(instance)


def add_builtins_from_builtin_modules(modules: List[ModuleType]):
builtins_list = []
def add_builtins_from_builtin_modules(modules: List[ModuleType]) -> Dict[str, list]:
"""
Process modules listed in ``modules` which contain Builtin classes so that
for each module the classes contained in them are is imported in the Python sense,
but also that we have information added to module variable ``builtins_by_module``.
"""
for module in modules:
add_builtins_from_builtin_module(module, builtins_list)
add_builtins(builtins_list)
add_builtins_from_builtin_module(module)
return builtins_by_module


# The fact that we are importing inside here, suggests add_builtins
# should get moved elsewhere.
def add_builtins(new_builtins):
from mathics.core.builtin import (
Operator,
PatternObject,
SympyObject,
mathics_to_python,
)

for _, builtin in new_builtins:
name = builtin.get_name()
if hasattr(builtin, "python_equivalent"):
# print("XXX0", builtin.python_equivalent)
mathics_to_python[name] = builtin.python_equivalent

if isinstance(builtin, SympyObject):
mathics_to_sympy[name] = builtin
for sympy_name in builtin.get_sympy_names():
# print("XXX1", sympy_name)
sympy_to_mathics[sympy_name] = builtin
if isinstance(builtin, Operator):
assert builtin.precedence is not None
builtins_precedence[Symbol(name)] = builtin.precedence
if isinstance(builtin, PatternObject):
pattern_objects[name] = builtin.__class__
_builtins.update(dict(new_builtins))


def builtins_dict(builtins_by_module):
return {
builtin.get_name(): builtin
Expand Down Expand Up @@ -140,18 +116,30 @@ 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():
def import_and_load_builtins(exclude_files: Optional[Set[str]] = None, clean_all=False):
"""
Imports Builtin modules in mathics.builtin and add rules, and definitions from that.
"""

# If clean_all, clean the _builtin and builtins_by_module to start
# with a fresh instance of this module.
# TODO: check if we need to clean the other dicts and lists.
if clean_all:
global mathics3_builtins_modules, _builtins
_builtins = {}
mathics3_builtins_modules = []

builtin_path = osp.join(
osp.dirname(
__file__,
),
"..",
"builtin",
)
exclude_files = {"codetables", "base"}

if exclude_files is None:
exclude_files = {"codetables"}

module_names = get_module_names(builtin_path, exclude_files)
import_builtins(module_names, mathics3_builtins_modules)

Expand All @@ -164,15 +152,17 @@ def import_and_load_builtins():
disable_file_module_names = set() if ENABLE_FILES_MODULE else {"files_io"}

subdirectory_list = next(os.walk(builtin_path))[1]
subdirectories = set(subdirectory_list) - set("__pycache__")
subdirectories = set(subdirectory_list) - (set("__pycache__") | exclude_files)
import_builtin_subdirectories(
subdirectories, disable_file_module_names, mathics3_builtins_modules
)

add_builtins_from_builtin_modules(mathics3_builtins_modules)


def import_builtin_module(import_name: str, modules: List[ModuleType]):
def import_builtin_module(
import_name: str, modules: List[ModuleType]
) -> Optional[ModuleType]:
"""
Imports ``the list of Mathics3 Built-in modules so that inside
Mathics3 Builtin Functions, like Plus[], List[] are defined.
Expand All @@ -189,6 +179,7 @@ def import_builtin_module(import_name: str, modules: List[ModuleType]):

if module:
modules.append(module)
return module


# TODO: When we drop Python 3.7,
Expand Down Expand Up @@ -218,7 +209,7 @@ def import_builtins(


def import_builtin_subdirectories(
subdirectories: Set[str], disable_file_module_names: set, modules
subdirectories: Set[str], disable_file_module_names: set, modules: List[ModuleType]
):
"""
Runs import_builtisn on the each subdirectory in ``subdirectories`` that inside
Expand Down Expand Up @@ -299,11 +290,48 @@ def name_is_builtin_symbol(module: ModuleType, name: str) -> Optional[type]:
return module_object


def update_display_operators_set(builtin_instance):
def update_builtin_properties(builtin_instance):
"""
Update, where appropriate, various internal builtin tables:
* mathics_to_python[name],
* mathics_to_sympy[name],
* builtins_precedence[name]
* pattern_object[name]

Information to store comes from ``builtin``.

If builtin_instance is an operator of some kind, add that
to the set of opererator strings ``display_operators_set``.

Finally add {name: builtin} to global _builtins
"""
# The fact that we are importing inside here, suggests
# this function might get moved elsewhere.
from mathics.core.builtin import (
Operator,
PatternObject,
SympyObject,
mathics_to_python,
)

name = builtin_instance.get_name()
if hasattr(builtin_instance, "python_equivalent"):
# print("XXX0", builtin_instance.python_equivalent)
mathics_to_python[name] = builtin_instance.python_equivalent

if isinstance(builtin_instance, SympyObject):
mathics_to_sympy[name] = builtin_instance
for sympy_name in builtin_instance.get_sympy_names():
# print("XXX1", sympy_name)
sympy_to_mathics[sympy_name] = builtin_instance
if isinstance(builtin_instance, Operator):
assert builtin_instance.precedence is not None
builtins_precedence[Symbol(name)] = builtin_instance.precedence
if isinstance(builtin_instance, PatternObject):
pattern_objects[name] = builtin_instance.__class__

operator = builtin_instance.get_operator_display()
if operator is not None:
display_operators_set.add(operator)

_builtins.update({name: builtin_instance})
42 changes: 42 additions & 0 deletions test/core/test_load_builtin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import importlib

from mathics.core.load_builtin import (
_builtins,
add_builtins_from_builtin_module,
import_and_load_builtins,
)
from mathics.session import MathicsSession


def test_add_builtins_from_builtin_module():
"""
Test that add_builtins_from_module() loads a single Builtin module
and updates definitions.
"""
# Set up a session with all but one module.
# Then evaluate a builtin in that module and see that we
# now have the function defined.

# First, load in many modules except quantum_mechanics.
_builtins = {}
import_and_load_builtins(exclude_files={"quantum_mechanics"}, clean_all=True)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pytest loads all the tests before running them. So, as other unit tests load test.helper, and it calls import_and_load_builtins, when we reach this test, all the modules are already loaded. So, if we want to do a test over a "fresh" instance of the module, we need this second parameter.


# Create a session, evaluate an expression using a missing Builtin function
# and see that it is not defined...
session = MathicsSession(character_encoding="ASCII")
assert str(session.evaluate("PauliMatrix[0]")) == "Global`PauliMatrix[0]"
assert (
str(session.evaluate("SixJSymbol[{1,2,3}, {1,2,3}]"))
== "Global`SixJSymbol[{1,2,3}, {1,2,3}]"
)
# Finally add in the module and see that when we use Builtin functions
# in that module work.
angular_module = importlib.import_module(
"mathics.builtin.quantum_mechanics.angular"
)
add_builtins_from_builtin_module(angular_module)

# Note that adding more builtins does not update the session, so we need a new one.
session = MathicsSession(character_encoding="ASCII")
assert str(session.evaluate("PauliMatrix[0]")) == "{{1,0},{0,1}}"
assert str(session.evaluate("SixJSymbol[{1, 2, 3}, {1, 2, 3}]")) == "1/105"
Loading