Repository: OpenMined/PySyft

Issue:
`np.ones_like` doesn't work on data pointers
## Description
`np.ones_like(d_ptr)` throws `ValueError: Invalid Numpy Operation: ones_like for DSA`.

## How to Reproduce

```py
import syft as sy
import numpy as np

domain_client = sy.login(email="info@openmined.org", password="changethis", port=8081)

arr = np.random.randint(0, 10000, (10000))

data = sy.Tensor(arr).annotate_with_dp_metadata(-1, 11000, data_subjects="Bob")
data_ptr = data.send(domain_client, tags=["my_data"])

domain_client.create_user(name="DS", email="test@email.com", password="password", budget=99999)
ds_client = sy.login(email="test@email.com", password="password", port=8081)

d_ptr = ds_client.store[data_ptr.id_at_location]

np.ones_like(d_ptr)
```

```py
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In [7], line 1
----> 1 np.ones_like(d_ptr)

File <__array_function__ internals>:180, in ones_like(*args, **kwargs)

File ~/pysyft/packages/syft/src/syft/core/tensor/passthrough.py:698, in PassthroughTensor.__array_function__(self, func, types, args, kwargs)
    696 implementation = query_implementation(self.__class__, func)
    697 if implementation:
--> 698     return implementation(*args, **kwargs)
    699 return self.__class__(func(*args, **kwargs))

File ~/pysyft/packages/syft/src/syft/core/tensor/autodp/phi_tensor.py:1693, in ones_like(tensor, *args, **kwargs)
   1687 @implements(TensorWrappedPhiTensorPointer, np.ones_like)
   1688 def ones_like(
   1689     tensor: TensorWrappedPhiTensorPointer,
   1690     *args: Any,
   1691     **kwargs: Any,
   1692 ) -> TensorWrappedPhiTensorPointer:
-> 1693     return tensor.ones_like(*args, **kwargs)

File ~/pysyft/packages/syft/src/syft/core/tensor/autodp/phi_tensor.py:962, in TensorWrappedPhiTensorPointer.ones_like(self, *args, **kwargs)
    949 def ones_like(
    950     self,
    951     *args: Any,
    952     **kwargs: Any,
    953 ) -> TensorWrappedPhiTensorPointer:
    954     """Apply the "ones_like" operation between "self" and "other"
    955 
    956     Args:
   (...)
    960         Union[TensorWrappedPhiTensorPointer,MPCTensor] : Result of the operation.
    961     """
--> 962     return self._apply_self_tensor_op("ones_like", *args, **kwargs)

File ~/pysyft/packages/syft/src/syft/core/tensor/autodp/phi_tensor.py:318, in TensorWrappedPhiTensorPointer._apply_self_tensor_op(self, op_str, *args, **kwargs)
    316         data_subjects = self.data_subjects
    317 else:
--> 318     raise ValueError(f"Invalid Numpy Operation: {op_str} for DSA")
    320 result = TensorWrappedPhiTensorPointer(
    321     data_subjects=data_subjects,
    322     min_vals=min_vals,
    323     max_vals=max_vals,
    324     client=self.client,
    325 )
    327 # QUESTION can the id_at_location be None?

ValueError: Invalid Numpy Operation: ones_like for DSA
```

## Expected Behavior
A clear and concise description of what you expected to happen.

## Screenshots
If applicable, add screenshots to help explain your problem.

## System Information
 - OS: [e.g. iOS]
 - OS Version: [e.g. 22]
 - Language Version: [e.g. Python 3.7, Node 10.18.1]
 - Package Manager Version: [e.g. Conda 4.6.1, NPM 6.14.1]
 - Browser (if applicable): [e.g. Google Chrome]
 - Browser Version (if applicable): [e.g. 81.0.4044.138]

## Additional Context
Add any other context about the problem here.



Discussion:
Before fixing this we need to decide whether we're actually going to try to modify the numpy API or whether we should just make an imitation syft.numpy module where we define our own methods (like zeroes_like()) with no connection to actual numpy. If unsure we should do the latter for now because it's easier to build and easier to change later (and less likely to be buggy)
@kiendang is this a duplicate of this issue: https://github.com/OpenMined/PySyft/issues/6922
I don't think so. `ones_like` was implemented but doesn't work. Maybe @rasswanth-s can confirm?
Sorry I mean the `ufunc` support:
https://github.com/OpenMined/PySyft/issues/7042
Could be related. What I understand is `ufunc` support for `np.ones_like` was supposed to work but broken? #7042 is discussing whether we support `ufunc` at all? So yes, these 2 issues are closely related.

Resolving patch:
diff --git a/packages/syft/src/syft/core/tensor/autodp/gamma_tensor.py b/packages/syft/src/syft/core/tensor/autodp/gamma_tensor.py
--- a/packages/syft/src/syft/core/tensor/autodp/gamma_tensor.py
+++ b/packages/syft/src/syft/core/tensor/autodp/gamma_tensor.py
@@ -377,6 +377,8 @@ def _apply_self_tensor_op(self, op_str: str, *args: Any, **kwargs: Any) -> Any:
                 data_subjects = getattr(self.data_subjects, op_str)(*args, **kwargs)
             if op_str in INPLACE_OPS:
                 data_subjects = self.data_subjects
+        elif op_str in ("ones_like", "zeros_like"):
+            data_subjects = self.data_subjects
         else:
             raise ValueError(f"Invalid Numpy Operation: {op_str} for DSA")
 
@@ -1382,12 +1384,27 @@ def __getitem__(
         """
         return self._apply_self_tensor_op("__getitem__", key)
 
+    def zeros_like(
+        self,
+        *args: Any,
+        **kwargs: Any,
+    ) -> Union[TensorWrappedGammaTensorPointer, MPCTensor]:
+        """Apply the "zeros_like" operation on "self"
+
+        Args:
+            y (Union[TensorWrappedGammaTensorPointer,MPCTensor,int,float,np.ndarray]) : second operand.
+
+        Returns:
+            Union[TensorWrappedGammaTensorPointer,MPCTensor] : Result of the operation.
+        """
+        return self._apply_self_tensor_op("zeros_like", *args, **kwargs)
+
     def ones_like(
         self,
         *args: Any,
         **kwargs: Any,
     ) -> Union[TensorWrappedGammaTensorPointer, MPCTensor]:
-        """Apply the "ones like" operation on self"
+        """Apply the "ones_like" operation on "self"
 
         Args:
             y (Union[TensorWrappedGammaTensorPointer,MPCTensor,int,float,np.ndarray]) : second operand.
@@ -1765,6 +1782,15 @@ def to_local_object_without_private_data_child(self) -> GammaTensor:
         )
 
 
+@implements(TensorWrappedGammaTensorPointer, np.zeros_like)
+def zeros_like(
+    tensor: TensorWrappedGammaTensorPointer,
+    *args: Any,
+    **kwargs: Any,
+) -> TensorWrappedGammaTensorPointer:
+    return tensor.zeros_like(*args, **kwargs)
+
+
 @implements(TensorWrappedGammaTensorPointer, np.ones_like)
 def ones_like(
     tensor: TensorWrappedGammaTensorPointer,
diff --git a/packages/syft/src/syft/core/tensor/autodp/phi_tensor.py b/packages/syft/src/syft/core/tensor/autodp/phi_tensor.py
--- a/packages/syft/src/syft/core/tensor/autodp/phi_tensor.py
+++ b/packages/syft/src/syft/core/tensor/autodp/phi_tensor.py
@@ -322,6 +322,8 @@ def _apply_self_tensor_op(self, op_str: str, *args: Any, **kwargs: Any) -> Any:
                 data_subjects = getattr(self.data_subjects, op_str)(*args, **kwargs)
             if op_str in INPLACE_OPS:
                 data_subjects = self.data_subjects
+        elif op_str in ("ones_like", "zeros_like"):
+            data_subjects = self.data_subjects
         else:
             raise ValueError(f"Invalid Numpy Operation: {op_str} for DSA")
 
@@ -954,12 +956,27 @@ def __getitem__(
         """
         return self._apply_self_tensor_op("__getitem__", key)
 
+    def zeros_like(
+        self,
+        *args: Any,
+        **kwargs: Any,
+    ) -> TensorWrappedPhiTensorPointer:
+        """Apply the "zeros_like" operation on "self"
+
+        Args:
+            y (Union[TensorWrappedPhiTensorPointer,MPCTensor,int,float,np.ndarray]) : second operand.
+
+        Returns:
+            Union[TensorWrappedPhiTensorPointer,MPCTensor] : Result of the operation.
+        """
+        return self._apply_self_tensor_op("zeros_like", *args, **kwargs)
+
     def ones_like(
         self,
         *args: Any,
         **kwargs: Any,
     ) -> TensorWrappedPhiTensorPointer:
-        """Apply the "ones_like" operation between "self" and "other"
+        """Apply the "ones_like" operation on "self"
 
         Args:
             y (Union[TensorWrappedPhiTensorPointer,MPCTensor,int,float,np.ndarray]) : second operand.
@@ -1692,6 +1709,15 @@ def reshape(self, *args: Any, **kwargs: Any) -> TensorWrappedPhiTensorPointer:
         return self._apply_self_tensor_op("reshape", *args, **kwargs)
 
 
+@implements(TensorWrappedPhiTensorPointer, np.zeros_like)
+def zeros_like(
+    tensor: TensorWrappedPhiTensorPointer,
+    *args: Any,
+    **kwargs: Any,
+) -> TensorWrappedPhiTensorPointer:
+    return tensor.zeros_like(*args, **kwargs)
+
+
 @implements(TensorWrappedPhiTensorPointer, np.ones_like)
 def ones_like(
     tensor: TensorWrappedPhiTensorPointer,
diff --git a/packages/syft/src/syft/core/tensor/lazy_repeat_array.py b/packages/syft/src/syft/core/tensor/lazy_repeat_array.py
--- a/packages/syft/src/syft/core/tensor/lazy_repeat_array.py
+++ b/packages/syft/src/syft/core/tensor/lazy_repeat_array.py
@@ -536,6 +536,9 @@ def compute_min_max(
         dummy_res = np.empty(x_min_vals.shape).max(*args, **kwargs)
         min_vals = lazyrepeatarray(data=x_min_vals.data, shape=dummy_res.shape)
         max_vals = lazyrepeatarray(data=x_max_vals.data, shape=dummy_res.shape)
+    elif op_str == "zeros_like":
+        min_vals = x_min_vals.zeros_like(*args, **kwargs)
+        max_vals = x_max_vals.zeros_like(*args, **kwargs)
     elif op_str == "ones_like":
         min_vals = x_min_vals.ones_like(*args, **kwargs)
         max_vals = x_max_vals.ones_like(*args, **kwargs)


Test patch:
diff --git a/packages/syft/tests/syft/core/tensor/adp/gamma_tensor_test.py b/packages/syft/tests/syft/core/tensor/adp/gamma_tensor_test.py
--- a/packages/syft/tests/syft/core/tensor/adp/gamma_tensor_test.py
+++ b/packages/syft/tests/syft/core/tensor/adp/gamma_tensor_test.py
@@ -157,6 +157,50 @@ def deduct_epsilon_for_user(*args: Any, **kwargs: Any) -> bool:
     print(ledger_store.kv_store)
 
 
+def test_zeros_like(
+    reference_data: np.ndarray,
+    upper_bound: np.ndarray,
+    lower_bound: np.ndarray,
+    ishan: DataSubjectArray,
+) -> None:
+    data_subjects = np.broadcast_to(
+        np.array(DataSubjectArray(["eagle", "potato"])), reference_data.shape
+    )
+    reference_tensor = GammaTensor(
+        child=reference_data,
+        data_subjects=data_subjects,
+        max_vals=upper_bound,
+        min_vals=lower_bound,
+    )
+    output = reference_tensor.zeros_like()
+    assert np.all(output.child == 0)
+    assert output.min_vals.shape == reference_tensor.min_vals.shape
+    assert output.max_vals.shape == reference_tensor.max_vals.shape
+    assert (output.data_subjects == reference_tensor.data_subjects).all()
+
+
+def test_ones_like(
+    reference_data: np.ndarray,
+    upper_bound: np.ndarray,
+    lower_bound: np.ndarray,
+    ishan: DataSubjectArray,
+) -> None:
+    data_subjects = np.broadcast_to(
+        np.array(DataSubjectArray(["eagle", "potato"])), reference_data.shape
+    )
+    reference_tensor = GammaTensor(
+        child=reference_data,
+        data_subjects=data_subjects,
+        max_vals=upper_bound,
+        min_vals=lower_bound,
+    )
+    output = reference_tensor.ones_like()
+    assert np.all(output.child == 1)
+    assert output.min_vals.shape == reference_tensor.min_vals.shape
+    assert output.max_vals.shape == reference_tensor.max_vals.shape
+    assert (output.data_subjects == reference_tensor.data_subjects).all()
+
+
 def test_sum(
     reference_data: np.ndarray,
     upper_bound: np.ndarray,
diff --git a/packages/syft/tests/syft/core/tensor/adp/phi_tensor_test.py b/packages/syft/tests/syft/core/tensor/adp/phi_tensor_test.py
--- a/packages/syft/tests/syft/core/tensor/adp/phi_tensor_test.py
+++ b/packages/syft/tests/syft/core/tensor/adp/phi_tensor_test.py
@@ -131,6 +131,46 @@ def test_eq(
     ).all(), "Equality between identical PTs fails"
 
 
+def test_zeros_like(
+    reference_data: np.ndarray,
+    upper_bound: np.ndarray,
+    lower_bound: np.ndarray,
+    ishan: DataSubjectArray,
+) -> None:
+    ishan = np.broadcast_to(ishan, reference_data.shape)
+    reference_tensor = PT(
+        child=reference_data,
+        data_subjects=ishan,
+        max_vals=upper_bound,
+        min_vals=lower_bound,
+    )
+    output = reference_tensor.zeros_like()
+    assert np.all(output.child == 0)
+    assert output.min_vals.shape == reference_tensor.min_vals.shape
+    assert output.max_vals.shape == reference_tensor.max_vals.shape
+    assert (output.data_subjects == reference_tensor.data_subjects).all()
+
+
+def test_ones_like(
+    reference_data: np.ndarray,
+    upper_bound: np.ndarray,
+    lower_bound: np.ndarray,
+    ishan: DataSubjectArray,
+) -> None:
+    ishan = np.broadcast_to(ishan, reference_data.shape)
+    reference_tensor = PT(
+        child=reference_data,
+        data_subjects=ishan,
+        max_vals=upper_bound,
+        min_vals=lower_bound,
+    )
+    output = reference_tensor.ones_like()
+    assert np.all(output.child == 1)
+    assert output.min_vals.shape == reference_tensor.min_vals.shape
+    assert output.max_vals.shape == reference_tensor.max_vals.shape
+    assert (output.data_subjects == reference_tensor.data_subjects).all()
+
+
 def test_add_wrong_types(
     reference_data: np.ndarray,
     upper_bound: np.ndarray,