Skip to content

Commit

Permalink
move inline function replacement to subs; use 'inline' keyword
Browse files Browse the repository at this point in the history
  • Loading branch information
smichr committed May 27, 2012
1 parent 941ba24 commit 818c146
Show file tree
Hide file tree
Showing 3 changed files with 104 additions and 86 deletions.
165 changes: 89 additions & 76 deletions sympy/core/basic.py
Expand Up @@ -728,11 +728,15 @@ def subs(self, *args, **kwargs):
If the keyword ``simultaneous`` is True, the subexpressions will not be
evaluated until all the substitutions have been made.
Use the keyword ``inline`` to make a function definition explicit, as in
defining f(x, y) to be the sum of its arguments so that f(a, b) + f(c, d)
--> a + b + c + d. In this case, only a single old/new pair may be given.
Examples
========
>>> from sympy import pi, exp
>>> from sympy.abc import x, y
>>> from sympy.abc import a, b, x, y
>>> (1 + x*y).subs(x, pi)
pi*y + 1
>>> (1 + x*y).subs({x:pi, y:2})
Expand All @@ -753,6 +757,20 @@ def subs(self, *args, **kwargs):
>>> (x**2 + x**4).xreplace({x**2: y})
x**4 + y
It is possible to replace generic functions with either new
functions or explicit expressions computed from the arguments:
>>> from sympy import Function
>>> f = Function('f')
>>> f(x).subs(f, exp)
exp(x)
>>> (f(x, y) + f(x + 1, y)).subs(f(a, b), a**b, inline=True)
x**y + (x + 1)**y
Without the ``inline`` keyword, the substitution is literal:
>>> (f(x, y) + f(x + 1, y)).subs(f(a, b), a**b)
f(x, y) + f(x + 1, y)
To delay evaluation until all substitutions have been made,
set the keyword ``simultaneous`` to True:
Expand Down Expand Up @@ -793,8 +811,6 @@ def subs(self, *args, **kwargs):
replace: replacement capable of doing wildcard-like matching,
parsing of match, and conditional replacements
rewrite: rewrite a function in terms of another function
inline: make a generic function explicit, e.g. f(x) ->
x**2 regardless of x
xreplace: exact node replacement in expr tree; also capable of
using matching rules
Expand All @@ -816,6 +832,8 @@ def subs(self, *args, **kwargs):
When a single argument is passed to subs
it should be an iterable of (old, new) tuples."""))
elif len(args) == 2:
if kwargs.get('inline', False):
return self._inline(*args)
sequence = [args]
else:
raise ValueError("subs accepts either 1 or 2 arguments")
Expand Down Expand Up @@ -870,6 +888,74 @@ def subs(self, *args, **kwargs):
break
return rv

def _inline(self, func, definition):
"""Rewrite a generic function like f(x, y) as a given
expression, e.g. make f(x, y) -> x**y.
Examples
========
>>> from sympy import Function
>>> from sympy.abc import x, y
Rewriting a generic function is more powerful than doing a
replacement since the arguments of the generic function can be
handled in the re-write:
>>> f = Function('f')
>>> (f(x, y) + f(x + 1, y))._inline(f(x, y), x**y)
x**y + (x + 1)**y
If subs were used to do this, each instance of f() would have to
be targetted separately, once for each different argument.
See Also
========
subs: substitution of subexpressions as defined by the objects
themselves.
replace: replacement capable of doing wildcard-like matching,
parsing of match, and conditional replacements
rewrite: rewrite a function in terms of another function
xreplace: exact node replacement in expr tree; also capable of
using matching rules
"""
args = func, definition
if not isinstance(args[0], C.Function):
raise ValueError(
'inline expected first arg to be a function but got %s' % func)

def _ok(a):
"""check that `a` is:
* a Symbol or
* independent of args[1]'s free symbols or
* is linear in exactly one of the free symbols of args[1], f.
In case of the latter, replace `a` in fargs with a dummy, d,
and replace it in the args[1] with the linear solution to
`solve_linear(a - d, f)`
"""
from sympy import solve_linear, Dummy
if a.is_Symbol:
return True
afree = a.free_symbols & free
if not afree:
return True
if len(afree) == 1:
s = afree.pop()
d = Dummy()
islinear = solve_linear(a - d, symbols=[s])
if islinear and islinear[0] == s:
fargs[fargs.index(a)] = d
args[1] = args[1].subs(*islinear)
return True

args = list(args)
fargs = list(args[0].args)
free = args[1].free_symbols
if all(_ok(a) for a in fargs):
foo = lambda *x: args[1].subs(zip(fargs, x))
return self.replace(args[0].func, foo)
raise NotImplementedError('Could not rewrite function args as symbols.')

@cacheit
def _subs(self, old, new, **hints):
"""Substitutes an expression old -> new.
Expand Down Expand Up @@ -1033,9 +1119,6 @@ def xreplace(self, rule):
replace: replacement capable of doing wildcard-like matching,
parsing of match, and conditional replacements
rewrite: rewrite a function in terms of another function
inline: make a generic function explicit, e.g. f(x) ->
x**2 regardless of x
"""
if self in rule:
Expand Down Expand Up @@ -1207,8 +1290,6 @@ def replace(self, query, value, map=False):
subs: substitution of subexpressions as defined by the objects
themselves.
rewrite: rewrite a function in terms of another function
inline: make a generic function explicit, e.g. f(x) ->
x**2 regardless of x
xreplace: exact node replacement in expr tree; also capable of
using matching rules
Expand Down Expand Up @@ -1515,74 +1596,6 @@ def _ok(a):
else:
return self

def inline(self, func, definition):
"""Rewrite a generic function like f(x, y) as a given
expression, e.g. make f(x, y) -> x**y.
Examples
========
>>> from sympy import Function
>>> from sympy.abc import x, y
Rewriting a generic function is more powerful than doing a
replacement since the arguments of the generic function can be
handled in the re-write:
>>> f = Function('f')
>>> (f(x, y) + f(x + 1, y)).inline(f(x, y), x**y)
x**y + (x + 1)**y
If subs were used to do this, each instance of f() would have to
be targetted separately, once for each different argument.
See Also
========
subs: substitution of subexpressions as defined by the objects
themselves.
replace: replacement capable of doing wildcard-like matching,
parsing of match, and conditional replacements
rewrite: rewrite a function in terms of another function
xreplace: exact node replacement in expr tree; also capable of
using matching rules
"""
args = func, definition
if not isinstance(args[0], C.Function):
raise ValueError(
'inline expected first arg to be a function but got %s' % func)

def _ok(a):
"""check that `a` is:
* a Symbol or
* independent of args[1]'s free symbols or
* is linear in exactly one of the free symbols of args[1], f.
In case of the latter, replace `a` in fargs with a dummy, d,
and replace it in the args[1] with the linear solution to
`solve_linear(a - d, f)`
"""
from sympy import solve_linear, Dummy
if a.is_Symbol:
return True
afree = a.free_symbols & free
if not afree:
return True
if len(afree) == 1:
s = afree.pop()
d = Dummy()
islinear = solve_linear(a - d, symbols=[s])
if islinear and islinear[0] == s:
fargs[fargs.index(a)] = d
args[1] = args[1].subs(*islinear)
return True

args = list(args)
fargs = list(args[0].args)
free = args[1].free_symbols
if all(_ok(a) for a in fargs):
foo = lambda *x: args[1].subs(zip(fargs, x))
return self.replace(args[0].func, foo)
raise NotImplementedError('Could not rewrite function args as symbols.')

class Atom(Basic):
"""
A parent class for atomic things. An atom is an expression with no subexpressions.
Expand Down
11 changes: 7 additions & 4 deletions sympy/core/function.py
Expand Up @@ -160,10 +160,13 @@ def func(self):
return self.__class__

def _eval_subs(self, old, new):
if (old.is_Function and new.is_Function and
old == self.func and
(self.nargs == new.nargs or not new.nargs or
isinstance(new.nargs, tuple) and self.nargs in new.nargs)):
if (
old.is_Function and
old == self.func and
new.is_Function and
(self.nargs == new.nargs or
not new.nargs or
isinstance(new.nargs, tuple) and self.nargs in new.nargs)):
return new(*self.args)

@deprecated
Expand Down
14 changes: 8 additions & 6 deletions sympy/core/tests/test_basic.py
Expand Up @@ -105,13 +105,15 @@ class MySingleton_sub(MySingleton):
assert MySingleton_sub() is not MySingleton()
assert MySingleton_sub() is MySingleton_sub()

def test_inline():
def test_inline_subs():
from sympy.abc import x, y, a, b, c
f = Function('f')
assert (f(x, y) + f(x + 1, y)).inline(f(a, b), a**b) == x**y + (x + 1)**y
assert (f(x, y) + f(x + 1, y)).inline(f(a - c, b), a**b) == \
assert (f(x, y) + f(x + 1, y)).subs(f(a, b), a**b, inline=True) == \
x**y + (x + 1)**y
assert (f(x, y) + f(x + 1, y)).subs(f(a - c, b), a**b, inline=True) == \
(c + x)**y + (c + x + 1)**y
assert (f(x, y) + f(x + 1, y)).inline(f(1/a - c, b), a**b) == \
assert (f(x, y) + f(x + 1, y)).subs(f(1/a - c, b), a**b, inline=True) == \
(1/(c + x))**y + (1/(c + x + 1))**y
assert (f(x, y) + f(x + 1, y)).inline(f(c**2, b), a**b) == 2*a**y
raises(NotImplementedError, 'f(x).inline(f(a**2), a)')
assert (f(x, y) + f(x + 1, y)).subs(f(c**2, b), a**b, inline=True) == \
2*a**y
raises(NotImplementedError, 'f(x).subs(f(a**2), a, inline=True)')

0 comments on commit 818c146

Please sign in to comment.