Repository: ivy-llc/ivy

Issue:
[Bug]: Inconsistent Behavior of paddle.sqrt in Ivy Backend with Non-Floating Dtypes Support
### Bug Explanation

As the [paddle.sqrt](https://www.paddlepaddle.org.cn/documentation/docs/en/api/paddle/sqrt_en.html#sqrt) only supports floating dtypes (`float16`, `float32`, `float64`) natively, the current backend implementation of it in `ivy` is modified to add support for integer, unsigned, bool, and complex dtypes. However, this current implementation behavior does not match with any other framework that supports these dtypes and the value outputted seems to be wrong too due to casting it back to their original dtype .

Thus, I want to know which framework's behavior we should try to emulate with it, and upon reaching this resolution, I can raise a pull-request for it.


### Steps to Reproduce Bug

```
import numpy as np
import jax.numpy as jnp
import torch
import ivy


dtypes = [
    np.int8,
    np.int16,
    np.int32,
    np.int64,
    np.uint8,
    np.float16,
    np.complex64,
    np.complex128,
]

for dtype in dtypes:
    x = np.array([2], dtype=dtype)
    y = np.sqrt(x)

    print(f"Output:{y}, Input dtype: {x.dtype}, Output dtype: {y.dtype}")

dtypes = [
    torch.int8,
    torch.int16,
    torch.int32,
    torch.int64,
    torch.uint8,
    torch.complex64,
    torch.complex128,
    torch.bool,
]

for dtype in dtypes:
    x = torch.tensor([2], dtype=dtype)
    y = torch.sqrt(x)

    print(f"Output:{y},Input dtype: {x.dtype}, Output dtype: {y.dtype}")


dtypes = [
    jnp.int8,
    jnp.int16,
    jnp.int32,
    jnp.int64,
    jnp.uint8,
    jnp.float16,
    jnp.complex64,
    jnp.complex128,
    jnp.bool_,
]

for dtype in dtypes:
    x = jnp.array([2], dtype=dtype)
    y = jnp.sqrt(x)

    print(f"Output:{y},Input dtype: {x.dtype}, Output dtype: {y.dtype}")


dtypes = [
    ivy.int8,
    ivy.int16,
    ivy.int32,
    ivy.int64,
    ivy.uint8,
    ivy.complex64,
    ivy.complex128,
    ivy.bool,
]

for dtype in dtypes:
    ivy.set_backend("paddle")
    x = ivy.array([2], dtype=dtype)
    y = ivy.sqrt(x)
    print(f"Output:{y},Input dtype: {x.dtype}, Output dtype: {y.dtype}")
    
    
    """
# --- Numpy  --- #
Output:[1.414], Input dtype: int8, Output dtype: float16
Output:[1.4142135], Input dtype: int16, Output dtype: float32
Output:[1.41421356], Input dtype: int32, Output dtype: float64
Output:[1.41421356], Input dtype: int64, Output dtype: float64
Output:[1.414], Input dtype: uint8, Output dtype: float16
Output:[1.414], Input dtype: float16, Output dtype: float16
Output:[1.4142135+0.j], Input dtype: complex64, Output dtype: complex64
Output:[1.41421356+0.j], Input dtype: complex128, Output dtype: complex128

# --- Torch  --- #
Output:tensor([1.4142]),Input dtype: torch.int8, Output dtype: torch.float32
Output:tensor([1.4142]),Input dtype: torch.int16, Output dtype: torch.float32
Output:tensor([1.4142]),Input dtype: torch.int32, Output dtype: torch.float32
Output:tensor([1.4142]),Input dtype: torch.int64, Output dtype: torch.float32
Output:tensor([1.4142]),Input dtype: torch.uint8, Output dtype: torch.float32
Output:tensor([1.4142+0.j]),Input dtype: torch.complex64, Output dtype: torch.complex64
Output:tensor([1.4142+0.j], dtype=torch.complex128),Input dtype: torch.complex128, Output dtype: torch.complex128
Output:tensor([1.]),Input dtype: torch.bool, Output dtype: torch.float32

# --- Jax  --- #
Output:[1.4142135],Input dtype: int8, Output dtype: float32
Output:[1.4142135],Input dtype: int16, Output dtype: float32
Output:[1.4142135],Input dtype: int32, Output dtype: float32
Output:[1.41421356],Input dtype: int64, Output dtype: float64
Output:[1.4142135],Input dtype: uint8, Output dtype: float32
Output:[1.414],Input dtype: float16, Output dtype: float16
Output:[1.4142135+0.j],Input dtype: complex64, Output dtype: complex64
Output:[1.41421356+0.j],Input dtype: complex128, Output dtype: complex128
Output:[1.],Input dtype: bool, Output dtype: float32

# --- Paddle --- #
Output:ivy.array([1]),Input dtype: int8, Output dtype: int8
Output:ivy.array([1]),Input dtype: int16, Output dtype: int16
Output:ivy.array([1]),Input dtype: int32, Output dtype: int32
Output:ivy.array([1]),Input dtype: int64, Output dtype: int64
Output:ivy.array([1]),Input dtype: uint8, Output dtype: uint8
Output:ivy.array([1.4142135+0.j]),Input dtype: complex64, Output dtype: complex64
Output:ivy.array([1.41421356+0.j]),Input dtype: complex128, Output dtype: complex128
Output:ivy.array([ True]),Input dtype: bool, Output dtype: bool
    
    """

```

### Environment

Linux + vscode + docker

### Ivy Version

1.1.9

### Backend

- [ ] NumPy
- [ ] TensorFlow
- [ ] PyTorch
- [ ] JAX
- [x] Paddle

### Device

CPU


Discussion:
Based on the standard for sqrt function [here](https://data-apis.org/array-api/latest/API_specification/generated/array_api.sqrt.html?highlight=square%20root), I think we should follow the type promotion linked in the guide [here](https://data-apis.org/array-api/latest/API_specification/type_promotion.html#type-promotion), and the returned type must be a floating-point data type.

So in the types you gave, the return types should be

```
# --- Paddle --- #
Output:ivy.array([1]),Input dtype: int8, Output dtype: float32
Output:ivy.array([1]),Input dtype: int16, Output dtype: float32
Output:ivy.array([1]),Input dtype: int32, Output dtype: float32
Output:ivy.array([1]),Input dtype: int64, Output dtype: float64
Output:ivy.array([1]),Input dtype: uint8, Output dtype: float32
Output:ivy.array([1.4142135+0.j]),Input dtype: complex64, Output dtype: complex64
Output:ivy.array([1.41421356+0.j]),Input dtype: complex128, Output dtype: complex128
Output:ivy.array([1.]),Input dtype: bool, Output dtype: float32
```
^ obviously the outputs would then be fixed to not be all 1's. 

I think we should support float16 -> float16 as well, unless you see a reason we shouldn't for paddle.

The last case of bool is a special case. The doc says "Type promotion of non-numerical data types to numerical data types is unspecified." This makes me really curious of cases such as Input dtype: torch.bool, Output dtype: torch.float32. Since all the other frameworks do that, I recommend we do that as well.

Resolving patch:
diff --git a/ivy/functional/backends/paddle/elementwise.py b/ivy/functional/backends/paddle/elementwise.py
--- a/ivy/functional/backends/paddle/elementwise.py
+++ b/ivy/functional/backends/paddle/elementwise.py
@@ -1,5 +1,5 @@
 # global
-from typing import Union, Optional
+from typing import Union, Optional, Tuple, Type
 
 import paddle
 import math
@@ -255,26 +255,55 @@ def sign(
     return paddle.sgn(x)
 
 
-def sqrt(x: paddle.Tensor, /, *, out: Optional[paddle.Tensor] = None) -> paddle.Tensor:
-    if x.dtype in [
+# TODO: Remove `float16` from the list once paddle add it's supporting kernel to `CPU`.
+def _determine_sqrt_dtype_cast(
+    dtype: Type[paddle.Tensor],
+) -> Tuple[Optional[str], Optional[str]]:
+    """
+    Determine the appropriate casting dtype for sqrt operations.
+
+    Returns:
+        (intermediate_dtype, output_dtype)
+    """
+
+    cast_and_return_float32_dtype = {
         paddle.int8,
         paddle.int16,
         paddle.int32,
-        paddle.int64,
         paddle.uint8,
-        paddle.float16,
-        paddle.complex64,
-        paddle.complex128,
         paddle.bool,
-    ]:
-        if paddle.is_complex(x):
-            angle = paddle.angle(x)
-            result = paddle.complex(
-                paddle.cos(angle / 2), paddle.sin(angle / 2)
-            ) * paddle.sqrt(paddle.abs(x))
-            return result
-        return paddle.sqrt(x.astype("float32")).astype(x.dtype)
-    return paddle.sqrt(x)
+    }
+
+    if dtype in cast_and_return_float32_dtype:
+        return "float32", "float32"
+    elif dtype == paddle.int64:
+        return "float64", "float64"
+    elif dtype == paddle.float16:
+        return "float32", "float16"
+    elif dtype == paddle.bfloat16:
+        return "float32", "bfloat16"
+    else:
+        return None, None
+
+
+def sqrt(x: paddle.Tensor, /, *, out: Optional[paddle.Tensor] = None) -> paddle.Tensor:
+    """Calculate the square root with type handling."""
+
+    if paddle.is_complex(x):
+        angle = paddle.angle(x)
+        return paddle.complex(
+            paddle.cos(angle / 2), paddle.sin(angle / 2)
+        ) * paddle.sqrt(paddle.abs(x))
+
+    if x.dtype in {paddle.float32, paddle.float64}:
+        return paddle.sqrt(x)
+
+    intermediate_dtype, output_dtype = _determine_sqrt_dtype_cast(x.dtype)
+    if intermediate_dtype:
+        result = paddle.sqrt(x.astype(intermediate_dtype))
+        return result.astype(output_dtype)
+
+    raise ValueError(f"Unsupported data type for sqrt: {x.dtype}")
 
 
 @with_unsupported_device_and_dtypes(


Test patch:
diff --git a/ivy_tests/test_ivy/test_functional/test_core/test_elementwise.py b/ivy_tests/test_ivy/test_functional/test_core/test_elementwise.py
--- a/ivy_tests/test_ivy/test_functional/test_core/test_elementwise.py
+++ b/ivy_tests/test_ivy/test_functional/test_core/test_elementwise.py
@@ -1423,7 +1423,7 @@ def test_square(*, dtype_and_x, test_flags, backend_fw, fn_name, on_device):
     fn_tree="functional.ivy.sqrt",
     dtype_and_x=helpers.dtype_and_values(
         available_dtypes=helpers.get_dtypes("float"), allow_inf=False
-    ),
+    ).filter(lambda x: x[0][0] not in ["bfloat16"]),
 )
 def test_sqrt(*, dtype_and_x, test_flags, backend_fw, fn_name, on_device):
     input_dtype, x = dtype_and_x