Skip to content

Commit

Permalink
Merge pull request #1 from smichr/li
Browse files Browse the repository at this point in the history
remove kern
  • Loading branch information
lidavidm committed Jan 30, 2013
2 parents bd775d3 + 9713132 commit 73cfa65
Show file tree
Hide file tree
Showing 4 changed files with 151 additions and 45 deletions.
133 changes: 130 additions & 3 deletions sympy/core/sympify.py
Expand Up @@ -24,9 +24,9 @@ def __str__(self):

def sympify(a, locals=None, convert_xor=True, strict=False, rational=False):
"""
Converts an arbitrary expression to a type that can be used inside sympy.
Converts an arbitrary expression to a type that can be used inside SymPy.
For example, it will convert python ints into instance of sympy.Rational,
For example, it will convert Python ints into instance of sympy.Rational,
floats into instances of sympy.Float, etc. It is also able to coerce symbolic
expressions which inherit from Basic. This can be useful in cooperation
with SAGE.
Expand All @@ -38,7 +38,7 @@ def sympify(a, locals=None, convert_xor=True, strict=False, rational=False):
- booleans, including ``None`` (will leave them unchanged)
- lists, sets or tuples containing any of the above
If the argument is already a type that sympy understands, it will do
If the argument is already a type that SymPy understands, it will do
nothing but return that value. This can be used at the beginning of a
function to ensure you are working with the correct type.
Expand Down Expand Up @@ -164,6 +164,28 @@ def sympify(a, locals=None, convert_xor=True, strict=False, rational=False):
[1]
[2]
Notes
=====
There are some times when autosimplification during sympification
may result in an expression that is very different (in appearance)
from what you enter. In this case (until such autosimplification is
no longer done) you can use the ``kernS`` function for these special
cases.
>>> from sympy.core.sympify import kernS
>>> from sympy.abc import x
>>> -1 - 2*(-(-x + 1/x)/(x*(x - 1/x)**2) - 1/(x*(x - 1/x)))
-1
>>> sympify('-1 - 2*(-(-x + 1/x)/(x*(x - 1/x)**2) - 1/(x*(x - 1/x)))')
-1
>>> kernS('-1 - 2*(-(-x + 1/x)/(x*(x - 1/x)**2) - 1/(x*(x - 1/x)))')
-1 + 2*(-x + 1/x)/(x*(x - 1/x)**2) + 2/(x*(x - 1/x))
In the last expression, the result is not exactly the same as the
entered string, but it is a lot closer.
"""
try:
cls = a.__class__
Expand Down Expand Up @@ -276,3 +298,108 @@ def _sympify(a):
see: sympify
"""
return sympify(a, strict=True)


def kernS(s):
"""Use a hack to try keep autosimplification from joining Integer or
minus sign into an Add of a Mul; this modification doesn't
prevent the 2-arg Mul from becoming an Add, however.
Examples
========
>>> from sympy.core.sympify import kernS
>>> from sympy.abc import x, y, z
The 2-arg Mul allows a leading Integer to be distributed and kernS does
not prevent that:
>>> 2*(x + y) == kernS('2*(x + y)') == 2*x + 2*y
True
But a Mul with more than 2 args need not have the leading Integer
distributed and the kernS version of S will keep that distribution
from occuring:
>>> 2*(x + y)*z # the first two arguments undergo the distribution
z*(2*x + 2*y)
>>> 2*z*(x + y) # in this form, the Add is not multiplied by an Integer
2*z*(x + y)
>>> kernS('2*(x + y)*z') # kernS will stop the distribution in this case
2*z*(x + y)
>>> kernS('E**-(x)')
exp(-x)
If use of the hack fails, the un-hacked string will be passed to sympify...
and you get what you get.
XXX This hack should not be necessary once issue 1497 has been resolved.
"""
import re
from sympy.core.symbol import Symbol

hit = False
if '(' in s:
if s.count('(') != s.count(")"):
raise SympifyError('unmatch laft parentheses')

kern = '_kern'
while kern in s:
kern += "_"
lparen = 'kern_2'
while lparen in s:
lparen += "_"
olds = s
s = re.sub(r'(\d *\*|-) *\(', r'\1(%s*%s' % (kern, lparen), s)

hit = kern in s
if hit:
# close parentheses after kerns; if this fails there is
# either an error in this parsing or in the original string
i = 0
close = []
while True:
i = s.find(kern, i)
if i == -1:
break
count = 1 # for the one before the kern
for j in range(i + 1, len(s)):
c = s[j]
if c == "(":
count += 1
elif c == ")":
count -= 1
else:
continue
if count == 0:
close.append(j)
i += len(kern) # continue from the last kern
break
for i in reversed(close):
s = ')'.join([s[:i], s[i:]])
s = s.replace(lparen, "(")

for i in range(2):
try:
expr = sympify(s)
break
except: # the kern might cause unknown errors, so use bare except
if hit:
s = olds # maybe it didn't like the kern; use un-kerned s
hit = False
continue
expr = sympify(s) # let original error raise

if not hit:
return expr

rep = {Symbol(kern): 1}
if hasattr(expr, 'xreplace'):
return expr.xreplace(rep)
elif isinstance(expr, (list, tuple, set)):
return type(expr)([_clear(e) for e in expr])
if hasattr(expr, 'subs'):
return expr.subs(rep)
# hope that kern is not there anymore
return expr
24 changes: 13 additions & 11 deletions sympy/core/tests/test_sympify.py
@@ -1,8 +1,8 @@
from __future__ import with_statement
from sympy import Symbol, exp, Integer, Float, sin, cos, log, Poly, Lambda, \
Function, I, S, sqrt, srepr, Rational, Tuple, Matrix
Function, I, S, sqrt, srepr, Rational, Tuple, Matrix, Interval
from sympy.abc import x, y
from sympy.core.sympify import sympify, _sympify, SympifyError
from sympy.core.sympify import sympify, _sympify, SympifyError, kernS
from sympy.core.decorators import _sympifyit
from sympy.utilities.pytest import XFAIL, raises
from sympy.utilities.decorator import conserve_mpmath_dps
Expand Down Expand Up @@ -432,20 +432,22 @@ def test_geometry():
assert L == Line((0, 1), (1, 0)) and type(L) == Line


def test_no_autosimplify_into_Mul():
s = '-1 - 2*(-(-x + 1/x)/(x*(x - 1/x)**2) - 1/(x*(x - 1/x)))'

def clean(s):
return ''.join(str(s).split())
def test_kernS():
s = '-1 - 2*(-(-x + 1/x)/(x*(x - 1/x)**2) - 1/(x*(x - 1/x)))'
# when 1497 is fixed, this no longer should pass: the expression
# should be unchanged
assert -1 - 2*(-(-x + 1/x)/(x*(x - 1/x)**2) - 1/(x*(x - 1/x))) == -1
# sympification should not allow the constant to enter a Mul
# or else the structure can change dramatically
ss = S(s)
assert ss != 1 and ss.simplify() == -1
ss = kernS(s)
assert ss != -1 and ss.simplify() == -1
s = '-1 - 2*(-(-x + 1/x)/(x*(x - 1/x)**2) - 1/(x*(x - 1/x)))'.replace(
'x', '_kern')
ss = S(s)
assert ss != 1 and ss.simplify() == -1
ss = kernS(s)
assert ss != -1 and ss.simplify() == -1
# issue 3588
assert kernS('Interval(-1,-2 - 4*(-3))') == Interval(-1, 10)
assert kernS('_kern') == Symbol('_kern')


def test_issue_3441_3453():
Expand Down
38 changes: 8 additions & 30 deletions sympy/parsing/sympy_parser.py
Expand Up @@ -12,7 +12,7 @@
from sympy.core.basic import Basic, C

_re_repeated = re.compile(r"^(\d*)\.(\d*)\[(\d+)\]$")
UNSPLITTABLE_TOKEN_NAMES = ['_kern']
UNSPLITTABLE_TOKEN_NAMES = []


def _token_splittable(token):
Expand Down Expand Up @@ -510,25 +510,14 @@ def rationalize(tokens, local_dict, global_dict):
#: datatypes and allows the use of standard factorial notation (e.g. ``x!``).
standard_transformations = (auto_symbol, auto_number, factorial_notation)


def stringify_expr(s, local_dict, global_dict, transformations):
"""
Converts the string ``s`` to Python code, in ``local_dict``
Generally, ``parse_expr`` should be used.
"""

# keep autosimplification from joining Integer or
# minus sign into a Mul; this modification doesn't
# prevent the 2-arg Mul from becoming an Add, however.
hit = False
kern = None
if '(' in s:
kern = '_kern'
while kern in s:
kern += "_"
s = re.sub(r'(\d *\*|-) *\(', r'\1%s*(' % kern, s)
hit = kern in s

tokens = []
input_code = StringIO(s.strip())
for toknum, tokval, _, _, _ in generate_tokens(input_code.readline):
Expand All @@ -537,9 +526,10 @@ def stringify_expr(s, local_dict, global_dict, transformations):
for transform in transformations:
tokens = transform(tokens, local_dict, global_dict)

return untokenize(tokens), hit, kern
return untokenize(tokens)


def eval_expr(code, hit, kern, local_dict, global_dict):
def eval_expr(code, local_dict, global_dict):
"""
Evaluate Python code generated by ``stringify_expr``.
Expand All @@ -548,19 +538,8 @@ def eval_expr(code, hit, kern, local_dict, global_dict):
expr = eval(
code, global_dict, local_dict) # take local objects in preference

if not hit:
return expr
rep = {C.Symbol(kern): 1}
return expr

def _clear(expr):
if hasattr(expr, 'xreplace'):
return expr.xreplace(rep)
elif isinstance(expr, (list, tuple, set)):
return type(expr)([_clear(e) for e in expr])
if hasattr(expr, 'subs'):
return expr.subs(rep)
return expr
return _clear(expr)

def parse_expr(s, local_dict=None, transformations=standard_transformations,
global_dict=None):
Expand Down Expand Up @@ -618,6 +597,5 @@ def parse_expr(s, local_dict=None, transformations=standard_transformations,
global_dict = {}
exec 'from sympy import *' in global_dict

code, hit, kern = stringify_expr(
s, local_dict, global_dict, transformations)
return eval_expr(code, hit, kern, local_dict, global_dict)
code = stringify_expr(s, local_dict, global_dict, transformations)
return eval_expr(code, local_dict, global_dict)
1 change: 0 additions & 1 deletion sympy/parsing/tests/test_sympy_parser.py
Expand Up @@ -19,7 +19,6 @@ def test_sympy_parser():
'x!': factorial(x),
'3.[3]': Rational(10, 3),
'10!': 3628800,
'(_kern)': Symbol('_kern'),
'-(2)': -Integer(2),
'[-1, -2, 3]': [Integer(-1), Integer(-2), Integer(3)],
'Symbol("x").free_symbols': x.free_symbols,
Expand Down

0 comments on commit 73cfa65

Please sign in to comment.