Repository: sympy/sympy

Issue:
rewrite(abs) should call rewrite(Abs)
```py
>>> sign(x).rewrite(abs)
sign(x)
>>> sign(x).rewrite(Abs)
Piecewise((0, Eq(x, 0)), (x/Abs(x), True))
```

This is because `rewrite(x)` just dispatches to the method called `_eval_rewrite_as_x`, and the method here is called `_eval_rewrite_as_Abs` since `Abs` is the name of the class. But most people use `abs`, the built-in function which dispatches to `Abs`. So it should be special-cased in rewrite to call `Abs`. 

Discussion:
sympy-bot: :white_check_mark:

Hi, I am the [SymPy bot](https://github.com/sympy/sympy-bot). I'm here to help you write a release notes entry. Please read the [guide on how to write release notes](https://github.com/sympy/sympy/wiki/Writing-Release-Notes).



Your release notes are in good order.

Here is what the release notes will look like:
* core
  * Fixed rewrite(abs) to call rewrite(Abs), ensuring consistent behavior. ([#27325](https://github.com/sympy/sympy/pull/27325) by [@Ytemiloluwa](https://github.com/Ytemiloluwa))

This will be added to https://github.com/sympy/sympy/wiki/Release-Notes-for-1.14.

<details><summary>Click here to see the pull request description that was parsed.</summary>

    <!-- Your title above should be a short description of what
    was changed. Do not include the issue number in the title. -->

    #### References to other Issues or PRs
    <!-- If this pull request fixes an issue, write "Fixes #NNNN" in that exact
    format, e.g. "Fixes #1234" (see
    https://tinyurl.com/auto-closing for more information). Also, please
    write a comment on that issue linking back to this pull request once it is
    open. -->

    Fixes #27323
    #### Brief description of what is fixed or changed
    This PR resolves issue #27323 by ensuring that rewrite(abs) correctly maps to rewrite(Abs) for consistency when using the built-in abs function. @asmeurer 

    #### Other comments
    Changes Made:

    - Updated the rewrite method to handle abs as a special case, redirecting it to Abs.
    - Added a test case in sympy/core/tests/test_basic.py to confirm that rewrite(abs) produces the same output as rewrite(Abs).

    #### Release Notes

    <!-- Write the release notes for this release below between the BEGIN and END
    statements. The basic format is a bulleted list with the name of the subpackage
    and the release note for this PR. For example:

    * solvers
      * Added a new solver for logarithmic equations.

    * functions
      * Fixed a bug with log of integers. Formerly, `log(-x)` incorrectly gave `-log(x)`.

    * physics.units
      * Corrected a semantical error in the conversion between volt and statvolt which
        reported the volt as being larger than the statvolt.

    or if no release note(s) should be included use:

    NO ENTRY

    See https://github.com/sympy/sympy/wiki/Writing-Release-Notes for more
    information on how to write release notes. The bot will check your release
    notes automatically to see if they are formatted correctly. -->

    <!-- BEGIN RELEASE NOTES -->
    * core
         * Fixed rewrite(abs) to call rewrite(Abs), ensuring consistent behavior.
    <!-- END RELEASE NOTES -->


</details><p>

Ytemiloluwa: > Approving but I'll still wait for reviews by other maintainers .... Just to make sure this is the best place to add the condition we're interested in !

Okay thank you. Hello @oscarbenjamin please can you take a look at this PR, it has been approved and it is ready to merge. 
oscarbenjamin: Are there any other functions analogous to `abs` where we would want to do this?
Ytemiloluwa: > Are there any other functions analogous to `abs` where we would want to do this?

 Thank you for your suggestion! The _eval_rewrite_as_Abs method already exists for both Max and Min in sympy/functions/elementary/miscellaneous.py. These functions rewrite themselves in terms of Abs. 
 
 To confirm this functionality, I’ve written tests to verify their behavior and they all passed:
 
Basic cases for Max and Min with two arguments.
Multi-argument cases (e.g., Max(x, y, z)).
Nested Max and Min.
Special cases, such as when arguments are identical (e.g., Max(x, x)).
Explicit numerical values.
 
 All the tests passed successfully, which confirms the correctness of the existing implementation.

Would you like me to include these tests in my PR or handle them in a separate one?
 
oscarbenjamin: I mean are there any other functions like `abs` where there is a builtin function and a SymPy function and we would want to be able to do the equivalent of `.rewrite(abs)` but for a different function.
Ytemiloluwa: > I mean are there any other functions like `abs` where there is a builtin function and a SymPy function and we would want to be able to do the equivalent of `.rewrite(abs)` but for a different function.

yes I think the most relevant analogous functions are trigonometric functions (sin/cos) and logarithmic function (log), There might be more 
oscarbenjamin: > yes I think the most relevant analogous functions are trigonometric functions (sin/cos) and logarithmic function (log),

None of these are builtins though. I don't think we would want to get into `math.sin`, `cmath.sin`, `np.sin` etc. Maybe `abs` is the only case.

The reason I am asking about this is because the code change here makes sense if there is exactly one function for which we would want to do this. If there were others then we would probably want some kind of mapping somewhere.
Ytemiloluwa: > > yes I think the most relevant analogous functions are trigonometric functions (sin/cos) and logarithmic function (log),
> 
> None of these are builtins though. I don't think we would want to get into `math.sin`, `cmath.sin`, `np.sin` etc. Maybe `abs` is the only case.
> 
> The reason I am asking about this is because the code change here makes sense if there is exactly one function for which we would want to do this. If there were others then we would probably want some kind of mapping somewhere.

Agreed, abs appears to be the only case where this specific functionality is relevant.
oscarbenjamin: Probably it is fine just to go with `abs` then.

An alternative approach could just be to add `_eval_rewrite_as_abs` to the two classes that support rewriting to `Abs`:
```
$ git grep '_eval_rewrite_as_Abs'
sympy/functions/elementary/complexes.py:    def _eval_rewrite_as_Abs(self, arg, **kwargs):
sympy/functions/elementary/miscellaneous.py:    def _eval_rewrite_as_Abs(self, *args, **kwargs):
```
Ytemiloluwa: > Probably it is fine just to go with `abs` then.
> 
> An alternative approach could just be to add `_eval_rewrite_as_abs` to the two classes that support rewriting to `Abs`:
> 
> ```
> $ git grep '_eval_rewrite_as_Abs'
> sympy/functions/elementary/complexes.py:    def _eval_rewrite_as_Abs(self, arg, **kwargs):
> sympy/functions/elementary/miscellaneous.py:    def _eval_rewrite_as_Abs(self, *args, **kwargs):
> ```

if it is fine to go with abs, will that be all for this PR, or there is a change you want me to incorporate?
anutosh491: Hmm, maybe let's stick to what was suggested by Aaron in the issue. He says 

> So it should be special-cased in rewrite to call Abs.

Which is what @Ytemiloluwa is doing. So lets get this in for now. And maybe address anything that remains if pointed out !


Ytemiloluwa: > Made a small change with respect to the comment (a link as a comment should be enough)
> 
> @Ytemiloluwa once your squash the commits into a meaningful one, we can get this in.

Okay let me do that then. @anutosh491 

Patch:
diff --git a/.github/workflows/emscripten.yml b/.github/workflows/emscripten.yml
index e952a94530..b00052789c 100644
--- a/.github/workflows/emscripten.yml
+++ b/.github/workflows/emscripten.yml
@@ -54,7 +54,7 @@ jobs:
         run: pip install pyodide-build
 
       - name: Set up Node.js
-        uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
+        uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
         with:
           node-version: ${{ env.NODE_VERSION }}
 
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 2c49acd07f..42e72829ab 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -142,7 +142,7 @@ jobs:
         # It is recommended to pin a commit hash here for security but it
         # should be kept up to date. Probably all actions and dependencies
         # used by the build script should be pinned...
-        uses: pypa/gh-action-pypi-publish@15c56dba361d8335944d31a2ecd17d700fc7bcbc  # v1.12.2
+        uses: pypa/gh-action-pypi-publish@fb13cb306901256ace3dab689990e13a5550ffaa  # v1.11.0
 
   # -------------------- Make a GitHub release --------------------- #
 
diff --git a/sympy/core/basic.py b/sympy/core/basic.py
index 62ccd3ed55..f9f04c7282 100644
--- a/sympy/core/basic.py
+++ b/sympy/core/basic.py
@@ -2075,6 +2075,11 @@ def rewrite(self, *args, deep=True, **hints):
         pattern = args[:-1]
         rule = args[-1]
 
+        # Special case: map `abs` to `Abs`
+        if rule is abs:
+            from sympy.functions.elementary.complexes import Abs
+            rule = Abs
+
         # support old design by _eval_rewrite_as_[...] method
         if isinstance(rule, str):
             method = "_eval_rewrite_as_%s" % rule


Test patch:
diff --git a/sympy/core/tests/test_basic.py b/sympy/core/tests/test_basic.py
index 44faf93162..d452af7e2a 100644
--- a/sympy/core/tests/test_basic.py
+++ b/sympy/core/tests/test_basic.py
@@ -17,6 +17,9 @@
 from sympy.integrals.integrals import Integral
 from sympy.functions.elementary.exponential import exp
 from sympy.testing.pytest import raises, warns_deprecated_sympy
+from sympy.functions.elementary.complexes import Abs, sign
+from sympy.functions.elementary.piecewise import Piecewise
+from sympy.core.relational import Eq
 
 b1 = Basic()
 b2 = Basic(b1)
@@ -331,3 +334,10 @@ class A(Symbol, Generic[T]):
 
     class B(A[T]):
         pass
+
+
+def test_rewrite_abs():
+    # https://github.com/sympy/sympy/issues/27323
+    x = Symbol('x')
+    assert sign(x).rewrite(abs) == sign(x).rewrite(Abs)
+    assert sign(x).rewrite(abs) == Piecewise((0, Eq(x, 0)), (x / Abs(x), True))