sympy / sympy

A computer algebra system written in pure Python
https://sympy.org/
Other
12.92k stars 4.42k forks source link

Inconsistencies in `subs` #19078

Open czgdp1807 opened 4 years ago

czgdp1807 commented 4 years ago

The following examples show inconsistencies of subs,

>>> (x*y).subs({y: z, z: t, t: 2}) # Expected 2*x
t⋅x
>>> exp(x/2 + y).subs({exp(y + 1): 2, x: 2}) # Expected 2
 y + 1
ℯ     
>>> l = [(sin(x), 2), (x, 1)]
>>> (sin(x)).subs(l)
2
>>> (sin(x)).subs(dict(l))
2
>>> sin(x).subs(reversed(l)) # Expected 2 but sin(1) looks fine too.
sin(1)
>>> l = [(x, 3), (y, x**2)]
>>> (x + y).subs(reversed(l))
12
>>> (x + y).subs(l) # Expected 12
 2    
x  + 3
>>> l = [(y, z + 2), (1 + z, 5), (z, 2)]
>>> (y - 1 + 3*x).subs(l) # Expected 5*x + 5
3⋅x + 5

Examples taken from https://github.com/sympy/sympy/pull/11372

namannimmo10 commented 4 years ago

I'm not sure if this is a deliberate behaviour or an inconsistency:

>>> (1/(x**4)).subs(x**2, 1)
1
>>> (1/(x**3)).subs(x**2, 1)
x**(-3)   # shouldn't this be 1/x ??
smichr commented 4 years ago

see #6257

>>> (x*y).subs(repsort(*Dict({y: z, z: t, t: 2}).items()))
2*x
>>> exp(x/2 + y).subs(repsort(*Dict({exp(y + 1): 2, x: 2}).items()))
2
>>> l = [(x, 3), (y, x**2)]
>>> repsort(*S(l))
[(y, x**2), (x, 3)]
>>> (x + y).subs(_)
12

For sin(x).subs({x:1, sin(x):2}) subs shouldn't have to guess at which is more important or whether you intend the subs to be self consistent. But you can make it so:

>>> repsort(*Dict((sin(x), 2), (x, 1)).items())
[(x, 1), (sin(x), 2)]
>>> reps = _
>>> for i in range(len(reps)):
...  for j in range(i + 1,len(reps)):
...    reps[j]=reps[j][0].subs(*reps[i]), reps[j][1]
...
>>> reps
[(x, 1), (sin(1), 2)]

(Maybe repsort should flag this case, too.) For this case, again, subs shouldn't have to guess at what you mean:

>>> l = [(y, z + 2), (1 + z, 5), (z, 2)]
>>> repsort(*S(l))
[(y, z + 2), (z, 2), (z + 1, 5)]
>>> (y - 1 + 3*x).subs(_)
3*x + 3
>>> reps = repsort(*S(l))
>>> for i in range(len(reps)):
...  for j in range(i + 1,len(reps)):
...    reps[j]=reps[j][0].subs(*reps[i]), reps[j][1]
...
>>> reps
[(y, z + 2), (z, 2), (3, 5)]
>>> (y - 1 + 3*x).subs(_)
5*x + 5

The consistent reps gives what you wanted...but are you sure you wanted to replace all 3s with 5s?

smichr commented 4 years ago

I'm not sure if this is a deliberate behaviour or an inconsistency:

subs aims to fully replace the power with the given value and if it can't, it doesn't replace any part of it...but for integer exponents it probably could so (x**3).subs(x**2,2) -> 2*x and the user would have to decide if they want x = +/-sqrt(2).

namannimmo10 commented 4 years ago

I just realized that I have had this discussion before also, here.

but for integer exponents it probably could so (x3).subs(x2,2) -> 2*x

Yeah, it should work for int exponents!