Short version
The example uses an unconstrained Wild
(a
), which should normally be given exclude
or properties
arguments to make more sensible matches, while their use of exact
is largely a red herring
>>> a = Wild('a', properties=[lambda a: a.is_Symbol]) # constraint
>>> (x**(1 + y)).replace(x**(1 + a), lambda a: x**-a)
x**(-y)
Extended Version
From earlier blocks in the .replace
method examples you're referring to, a
is a Wild
(2.1 pattern -> expr) and x
and y
are basic Symbols (Initial setup)
>>> from sympy.abc import x, y # initial setup
>>> a, b = map(Wild, 'ab') # 2.1
>>> srepr((a, b, x, y))
"(Wild('a'), Wild('b'), Symbol('x'), Symbol('y'))"
The docs say about the exact
argument
Setting this to False
accepts a match of 0
; while setting it True
accepts all matches that have a 0
in them.
More clearly, exact=True
prevents 0
from matching Wild
>>> sympify(0).replace(Wild('w'), 5, exact=False)
5
>>> sympify(0).replace(Wild('w'), 5, exact=True)
0
>>> f(0).replace(f(a), lambda a: f(a + 1), exact=False)
f(1)
>>> f(0).replace(f(a), lambda a: f(a + 1), exact=True)
f(0)
However, replacement in the given example (and in general) becomes confusing because SymPy can make bizarre-looking matches like replacing simply 1
as x**(1+a)
, effectively breaking apart the expression
>>> sympify(1).replace(x**(1 + a), lambda a: x**-a) # a: -1 -> x**1
x
>>> x.replace(x**(1 + a), lambda a: x**-a, exact=False) # a: 0 -> x**-0
1
>>> x.replace(x**(1 + a), lambda a: x**-a, exact=True) # doesn't match
x
You can add the map=True
arg to see the result mapping (tuple of (result, match_dict)
is returned), highlighting the unexpected 1:x
mapping
>>> (x**(1 + y)).replace(x**(1 + a), lambda a: x**-a, map=True, exact=False)[1]
{x: 1, 1: x}
>>> (x**(1 + y)).replace(x**(1 + a), lambda a: x**-a, map=True, exact=True)[1]
{1: x, x**(x + y): x**(-x - y + 1)}
What's really happening here is that the docs and example imo don't make it suffiently clear that they expect someone to be familar with Wild
when using it, and go right into showing off some of the bizarre matches possible .. while the docs for Wild
right away suggest using its exclude
and properties
to constrain matches
https://docs.sympy.org/latest/modules/core.html#sympy.core.symbol.Wild
Also note that for a pattern like 2.2. pattern -> func, only the wild value is passed to the lambda, which the examples further mask by having the same form as the original equation
obj.replace(pattern(wild), lambda wild: expr(wild))
For example, we could provide a property that a
can't be -1
>>> sympify(1).replace(x**(1 + a), lambda a: x**-a) # a: -1 -> x**1
x
>>> sympify(1).replace(x**(a - 5), lambda a: a) # a: 5
5
>>> a = Wild('a', properties=[lambda a: a!=-1]) # don't match -1
>>> sympify(1).replace(x**(1 + a), lambda a: x**-a) # doesn't match
1
Fundamentally .replace()
or .match()
with an unrestricted Wild
is setting oneself up for failure and the inclusion of exact
only serves to handle a special case amongst many unexpected matches
To complete the example, we can then try to interpret what might have been wanted by someone using the example
Perhaps finding exactly y
as the match? (same as my first example)
>>> a = Wild('a', properties=[lambda a: a.is_Symbol]) # constraint
>>> (x**(1 + y)).replace(x**(1 + a), lambda a: x**-a)
x**(-y)
Or inverting the power
>>> (x**(1 + y))**-1 # trivial
x**(-y - 1)
>>> (x**(1 + y)).replace(Pow, lambda a, b: a**-b) # match, unpack Pow
x**(-y - 1)