Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

x.subs(1/x,y) behavior differs when x is symbol and when x is a function #26405

Open
smichr opened this issue Mar 25, 2024 · 3 comments
Open
Labels

Comments

@smichr
Copy link
Member

smichr commented Mar 25, 2024

>>> x.subs(1/x, y)
1/y
>>> cos(x).subs(1/cos(x), y)
cos(x)               <- should be 1/y?

for i in (var('x'), f(x), cos(x)):
    print (i,Tuple(i**2,i,1/i,1/i**2).subs(i,y))
    print (i,Tuple(i**2,i,1/i,1/i**2).subs(1/i,1/y))

x (y**2, y, 1/y, y**(-2))
x (y**2, y, 1/y, y**(-2))  <--1/x targets x**2 and x
f(x) (y**2, y, 1/y, y**(-2))
f(x) (y**2, f(x), 1/y, y**(-2))  <-- 1/f(x) targets f(x)**2 but not f(x)
cos(x) (y**2, y, 1/y, y**(-2))
cos(x) (y**2, cos(x), 1/y, y**(-2))  <-- 1/cos(x) targets cos(x)**2 but not f(x)
@smichr smichr added the core label Mar 25, 2024
@smichr
Copy link
Member Author

smichr commented Mar 26, 2024

Should be controlled with a reciprocal keyword so that if the sign of the exponent's coefficient does not match then the substitution would not be done? Otherwise it's hard, for example to have a substitution like 1/sin(x)->csc(x) because it will change sin(x)**2 -> 1/csc(x)**2while leavingsin(x)` alone.

An alternative would be to make a new function:

def mask_inverse_subs(self, old, new):
    """don't let old target 1/old in self

    EXAMPLES
    ========

    >>> from sympy.abc import x, y
    >>> mask_inverse_subs(2*x + 1/x, x, y)
    2*y + 1/x
    >>> mask_inverse_subs(2*x + 1/x, 1/x, y)
    2*x + y
    """
    if not hasattr(old, 'exp'):
        base = old
        exp = S.One
    else:
        base = old.base
        exp = old.exp.as_coeff_Mul()[0]
    d=Dummy()
    if exp < 0:
        return self.subs(base, 1/base).replace(
        lambda p: hasattr(p,'exp') and p.base == base and p.exp.as_coeff_Mul()[0]<0,
        lambda p:d**p.exp).subs(1/old, new).xreplace({d: 1/base})
    return self.replace(
        lambda p: hasattr(p,'exp') and p.base == base and p.exp.as_coeff_Mul()[0]<0,
        lambda p:d**p.exp).subs(old, new).xreplace({d: base})

@samithkavishke
Copy link
Contributor

if this error only can be seen in reciprocals then we can do this hack I guess. But I think we should also must provide a way to solve more complex versions of substitution. like this,

x.subs(cos(x), y)

should return acos(y)

@diya5722
Copy link

diya5722 commented Apr 1, 2024

In SymPy, the behavior of x.subs(1/x, y) can indeed differ based on whether x is a symbol or a function. When x is a symbol, 1/x represents the inverse of x. However, when x is a function, 1/x represents the functional inverse of x, which may not necessarily be the same as the symbolic inverse.


x, y = symbols('x y')

def mask_inverse_subs(expr, old, new):
    if isinstance(old, Function):  # Check if old is a function
        inverse_old = 1 / old(x)  # Compute the functional inverse
        return expr.subs(inverse_old, new)
    else:
        return expr.subs(1/old, new)

# Test the function
expr1 = 2*x + 1/x
result1 = mask_inverse_subs(expr1, x, y)
print(result1)  # Output: 2*y + 1/x

def f(x):
    return x**2

expr2 = 2*f(x) + 1/f(x)
result2 = mask_inverse_subs(expr2, f, y)
print(result2)  # Output: 2*y + 1/f(x)

If old is a function, it computes the functional inverse (1/old(x)) and performs the substitution accordingly.
If old is a symbol, it performs the substitution expr.subs(1/old, new) as before.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
3 participants