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

Replace ugly hack for wrapping int with Integer in the IPython #167

Merged
merged 5 commits into from Dec 25, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
30 changes: 24 additions & 6 deletions docs/modules/matrices/matrices.rst
Expand Up @@ -468,15 +468,33 @@ Let's take a look at the vectors:
>>> for i in out1:
... print(i)
...
Matrix([[2], [3], [5]])
Matrix([[23/19], [63/19], [-47/19]])
Matrix([[1692/353], [-1551/706], [-423/706]])
Matrix([
[2],
[3],
[5]])
Matrix([
[ 23/19],
[ 63/19],
[-47/19]])
Matrix([
[ 1692/353],
[-1551/706],
[ -423/706]])
>>> for i in out2:
... print(i)
...
Matrix([[sqrt(38)/19], [3*sqrt(38)/38], [5*sqrt(38)/38]])
Matrix([[23*sqrt(6707)/6707], [63*sqrt(6707)/6707], [-47*sqrt(6707)/6707]])
Matrix([[12*sqrt(706)/353], [-11*sqrt(706)/706], [-3*sqrt(706)/706]])
Matrix([
[ sqrt(38)/19],
[3*sqrt(38)/38],
[5*sqrt(38)/38]])
Matrix([
[ 23*sqrt(6707)/6707],
[ 63*sqrt(6707)/6707],
[-47*sqrt(6707)/6707]])
Matrix([
[ 12*sqrt(706)/353],
[-11*sqrt(706)/706],
[ -3*sqrt(706)/706]])

We can spot-check their orthogonality with dot() and their normality with
norm():
Expand Down
118 changes: 26 additions & 92 deletions sympy/interactive/session.py
@@ -1,5 +1,7 @@
"""Tools for setting up interactive sessions. """

import ast

from sympy.external import import_module
from sympy.interactive.printing import init_printing

Expand Down Expand Up @@ -78,93 +80,24 @@ def _make_message(ipython=True, quiet=False, source=None):
return message


def int_to_Integer(s):
"""
Wrap integer literals with Integer.

This is based on the decistmt example from
http://docs.python.org/library/tokenize.html.

Only integer literals are converted. Float literals are left alone.

Examples
========

>>> from sympy.interactive.session import int_to_Integer
>>> from sympy import Integer
>>> s = '1.2 + 1/2 - 0x12 + a1'
>>> int_to_Integer(s)
'1.2 +Integer (1 )/Integer (2 )-Integer (0x12 )+a1 '
>>> s = 'print (1/2)'
>>> int_to_Integer(s)
'print (Integer (1 )/Integer (2 ))'
>>> exec(s)
0.5
>>> exec(int_to_Integer(s))
1/2
"""
from tokenize import generate_tokens, untokenize, NUMBER, NAME, OP
from io import StringIO

def _is_int(num):
"""
Returns true if string value num (with token NUMBER) represents an integer.
"""
# XXX: Is there something in the standard library that will do this?
if '.' in num or 'j' in num.lower() or 'e' in num.lower():
return False
return True

result = []
g = generate_tokens(StringIO(s).readline) # tokenize the string
for toknum, tokval, _, _, _ in g:
if toknum == NUMBER and _is_int(tokval): # replace NUMBER tokens
result.extend([
(NAME, 'Integer'),
(OP, '('),
(NUMBER, tokval),
(OP, ')')
])
else:
result.append((toknum, tokval))
return untokenize(result)


def enable_automatic_int_sympification(app):
"""
Allow IPython to automatically convert integer literals to Integer.
"""
hasshell = hasattr(app, 'shell')
class IntegerWrapper(ast.NodeTransformer):
"""Wraps all integers in a call to Integer()"""
def visit_Num(self, node):
if isinstance(node.n, int):
return ast.Call(func=ast.Name(id='Integer', ctx=ast.Load()),
args=[node], keywords=[],
starargs=None, kwargs=None)
return node

import ast
if hasshell:
old_run_cell = app.shell.run_cell
else:
old_run_cell = app.run_cell

def my_run_cell(cell, *args, **kwargs):
try:
# Check the cell for syntax errors. This way, the syntax error
# will show the original input, not the transformed input. The
# downside here is that IPython magic like %timeit will not work
# with transformed input (but on the other hand, IPython magic
# that doesn't expect transformed input will continue to work).
ast.parse(cell)
except SyntaxError:
pass
else:
cell = int_to_Integer(cell)
old_run_cell(cell, *args, **kwargs)

if hasshell:
app.shell.run_cell = my_run_cell
else:
app.run_cell = my_run_cell
def visit_Call(self, node):
if isinstance(node.func, ast.Name) and node.func.id != "Integer":
node = self.generic_visit(node)
return node


def enable_automatic_symbols(app):
"""Allow IPython to automatially create symbols. """
# XXX: This should perhaps use tokenize, like int_to_Integer() above.
# XXX: This should perhaps use ast, like IntegerWrapper above.
# This would avoid re-executing the code, which can lead to subtle
# issues. For example:
#
Expand Down Expand Up @@ -237,12 +170,12 @@ def _handler(self, etype, value, tb, tb_offset=None):
app.set_custom_exc((NameError,), _handler)


def init_ipython_session(argv=[], auto_symbols=False, auto_int_to_Integer=False):
def init_ipython_session(argv=[], auto_symbols=False,
auto_int_to_Integer=False):
"""Construct new IPython session. """
import IPython
from IPython.terminal import ipapp

app = ipapp.TerminalIPythonApp()
app = IPython.terminal.ipapp.TerminalIPythonApp()

# don't draw IPython banner during initialization:
app.display_banner = False
Expand All @@ -253,7 +186,7 @@ def init_ipython_session(argv=[], auto_symbols=False, auto_int_to_Integer=False)
if readline:
enable_automatic_symbols(app)
if auto_int_to_Integer:
enable_automatic_int_sympification(app)
app.ast_transformers.append(IntegerWrapper())

return app.shell

Expand Down Expand Up @@ -292,12 +225,13 @@ def __init__(self):


def init_session(ipython=None, pretty_print=True, order=None,
use_unicode=None, use_latex=None, quiet=False, auto_symbols=False,
auto_int_to_Integer=False, argv=[]):
use_unicode=None, use_latex=None, quiet=False,
auto_symbols=False, auto_int_to_Integer=False, argv=[]):
"""
Initialize an embedded IPython or Python session. The IPython session is
initiated with the --pylab option, without the numpy imports, so that
matplotlib plotting can be interactive.
Initialize an embedded IPython or Python session.

The IPython session is initiated with the --pylab option, without the
numpy imports, so that matplotlib plotting can be interactive.

Parameters
==========
Expand Down Expand Up @@ -402,7 +336,7 @@ def init_session(ipython=None, pretty_print=True, order=None,
else:
if ip is None:
ip = init_ipython_session(argv=argv, auto_symbols=auto_symbols,
auto_int_to_Integer=auto_int_to_Integer)
auto_int_to_Integer=auto_int_to_Integer)

# runsource is gone, use run_cell instead, which doesn't
# take a symbol arg. The second arg is `store_history`,
Expand Down
14 changes: 0 additions & 14 deletions sympy/interactive/tests/test_interactive.py

This file was deleted.

31 changes: 28 additions & 3 deletions sympy/interactive/tests/test_ipython.py
@@ -1,8 +1,10 @@
"""Tests of tools for setting up interactive IPython sessions. """

from sympy.interactive.session import (init_ipython_session,
enable_automatic_symbols, enable_automatic_int_sympification)
import ast

from sympy.interactive.session import (init_ipython_session,
enable_automatic_symbols,
IntegerWrapper)
from sympy.core import Symbol, Rational, Integer
from sympy.external import import_module

Expand All @@ -14,6 +16,29 @@
disabled = True


def test_IntegerWrapper():
tree = ast.parse('1/3')
dump = ("Module(body=[Expr(value=BinOp(left=Call(func=Name(id='Integer', "
"ctx=Load()), args=[Num(n=1)], keywords=[], starargs=None, "
"kwargs=None), op=Div(), right=Call(func=Name(id='Integer', "
"ctx=Load()), args=[Num(n=3)], keywords=[], starargs=None, "
"kwargs=None)))])")
tree = IntegerWrapper().visit(tree)
assert ast.dump(tree) == dump
tree2 = ast.parse('Integer(1)/Integer(3)')
tree_new = IntegerWrapper().visit(tree2)
assert ast.dump(tree_new) == dump
dump3 = ("Module(body=[Expr(value=Call(func=Name(id='f', ctx=Load()), "
"args=[Call(func=Name(id='Integer', ctx=Load()), args=[Num(n=1)], "
"keywords=[], starargs=None, kwargs=None)], keywords=[], "
"starargs=None, kwargs=None))])")
tree3 = ast.parse('f(1)')
tree_new = IntegerWrapper().visit(tree3)
assert ast.dump(tree_new) == dump3
tree_new2 = IntegerWrapper().visit(tree_new)
assert ast.dump(tree_new2) == dump3


def test_automatic_symbols():
# this implicitly requires readline
if not readline:
Expand Down Expand Up @@ -53,7 +78,7 @@ def test_int_to_Integer():
app.run_cell("a = 1")
assert isinstance(app.user_ns['a'], int)

enable_automatic_int_sympification(app)
app.ast_transformers.append(IntegerWrapper())
app.run_cell("a = 1/2")
assert isinstance(app.user_ns['a'], Rational)
app.run_cell("a = 1")
Expand Down
20 changes: 15 additions & 5 deletions sympy/matrices/matrices.py
Expand Up @@ -685,13 +685,23 @@ def _format_str(self, printer=None):
return "Matrix([%s])" % self.table(printer, rowsep=',\n')
return "Matrix([\n%s])" % self.table(printer, rowsep=',\n')

# Note, we always use the default ordering (lex) in __str__ and __repr__,
# regardless of the global setting. See issue 5487.
def __repr__(self):
from sympy.printing import sstr
return sstr(self, order=None)

def __str__(self):
if self.rows == 0 or self.cols == 0:
return 'Matrix(%s, %s, [])' % (self.rows, self.cols)
return "Matrix(%s)" % str(self.tolist())
from sympy.printing import sstr
return sstr(self, order=None)

def __repr__(self):
return sstr(self)
def _repr_pretty_(self, p, cycle):
from sympy.printing import pretty
p.text(pretty(self, order=None))

def _repr_latex_(self):
from sympy.printing import latex
return '$$' + latex(self, order=None) + '$$'

def cholesky(self):
"""Returns the Cholesky decomposition L of a matrix A
Expand Down
2 changes: 1 addition & 1 deletion sympy/matrices/tests/test_matrices.py
Expand Up @@ -2156,7 +2156,7 @@ def test_issue_3959():


def test_issue_5964():
assert str(Matrix([[1, 2], [3, 4]])) == 'Matrix([[1, 2], [3, 4]])'
assert str(Matrix([[1, 2], [3, 4]])) == 'Matrix([\n[1, 2],\n[3, 4]])'


def test_issue_7604():
Expand Down
1 change: 1 addition & 0 deletions sympy/printing/__init__.py
Expand Up @@ -14,4 +14,5 @@
from .tree import print_tree
from .str import StrPrinter, sstr, sstrrepr
del str # or this hide the str function
del repr # or this hide the repr function
from .tableform import TableForm
1 change: 0 additions & 1 deletion sympy/printing/pretty/tests/test_pretty.py
Expand Up @@ -262,7 +262,6 @@ def test_upretty_subs_missing_in_24():
assert upretty( Symbol('F_x') ) == 'Fₓ'


@pytest.mark.xfail
def test_missing_in_2X_issue_9047():
import warnings
with warnings.catch_warnings():
Expand Down
4 changes: 2 additions & 2 deletions sympy/printing/tests/test_str.py
Expand Up @@ -180,7 +180,7 @@ def test_list():

def test_Matrix_str():
M = Matrix([[x**+1, 1], [y, x + y]])
assert str(M) == "Matrix([[x, 1], [y, x + y]])"
assert str(M) == "Matrix([\n[x, 1],\n[y, x + y]])"
assert sstr(M) == "Matrix([\n[x, 1],\n[y, x + y]])"
M = Matrix([[1]])
assert str(M) == sstr(M) == "Matrix([[1]])"
Expand Down Expand Up @@ -518,7 +518,7 @@ def test_set():

def test_SparseMatrix():
M = SparseMatrix([[x**+1, 1], [y, x + y]])
assert str(M) == "Matrix([[x, 1], [y, x + y]])"
assert str(M) == "Matrix([\n[x, 1],\n[y, x + y]])"
assert sstr(M) == "Matrix([\n[x, 1],\n[y, x + y]])"


Expand Down