Permalink
Browse files

move inline function replacement to subs; use 'inline' keyword

  • Loading branch information...
smichr committed May 27, 2012
1 parent 941ba24 commit 818c14606f2c73946308853fe50f0cb00f06dc6f
Showing with 104 additions and 86 deletions.
  1. +89 −76 sympy/core/basic.py
  2. +7 −4 sympy/core/function.py
  3. +8 −6 sympy/core/tests/test_basic.py
View
@@ -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})
@@ -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:
@@ -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
@@ -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")
@@ -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.
@@ -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:
@@ -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
@@ -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.
View
@@ -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
@@ -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.