Repository: PennyLaneAI/pennylane

Issue:
Improve documentation of numpy interface
### Feature details

There have recently been several changes to the numpy/autograd interface that significantly impact how trainability of parameters is treated and which input and output types QNodes accept/produce with this interface.

At the same time I find the [documentation of this important interface](https://pennylane.readthedocs.io/en/stable/introduction/interfaces/numpy.html) is quite hard to find. The only way I found was to go via "Gradients and training", scroll down to "Training and interfaces" and then click on the "numpy" link hidden in the text. 

Also searching for "tensor" via the search box only produces hits under `pennylane.operation.Tensor` but does tell the user nothing about the `pennylane.numpy.tensor` objects returned from QNodes. Similarly, searching for `requires_grad` only shows the `pennylane.math.requires_grad` function but not hit concerning the argument of `pennylane.numpy.array()` with the same name, that is arguably more "user facing". 

It would be great if the peculiarities of the `pennylane.numpy` and `math` modules (the first does not even show up as a module in the menu under API) were explained in more detail and if this documentation were more accessible. Thanks for considering this!

### Implementation

_No response_

### How important would you say this feature is?

2: Somewhat important. Needed this quarter.

### Additional information

_No response_


Discussion:
@cvjjm this is a good idea indeed

Resolving patch:
diff --git a/pennylane/numpy/__init__.py b/pennylane/numpy/__init__.py
--- a/pennylane/numpy/__init__.py
+++ b/pennylane/numpy/__init__.py
@@ -12,8 +12,73 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 """
-This package provides a wrapped version of autograd.numpy, such that
-it works with the PennyLane :class:`~.tensor` class.
+Overview
+--------
+
+The PennyLane NumPy subpackage provides a differentiable wrapper around NumPy, that enables
+backpropagation through standard NumPy code.
+
+This version of NumPy **must** be used when using PennyLane with the :doc:`Autograd interface
+</introduction/interfaces/numpy>`:
+
+>>> from pennylane import numpy as np
+
+.. note::
+
+    If using other interfaces, such as :doc:`TensorFlow </introduction/interfaces/tf>` :doc:`PyTorch
+    </introduction/interfaces/torch>`, or :doc:`JAX </introduction/interfaces/jax>`, then the
+    PennyLane-provided NumPy should not be used; instead, simply use the standard NumPy import.
+
+This package is a wrapper around ``autograd.numpy``; for details on all available functions,
+please refer to the `Autograd
+docs <https://github.com/HIPS/autograd/blob/master/docs/tutorial.md>`__.
+
+PennyLane additionally extends Autograd with the following classes,
+errors, and functions:
+
+.. autosummary::
+    :toctree: api
+    :nosignatures:
+    :template: autosummary/class_no_inherited.rst
+
+    ~wrap_arrays
+    ~extract_tensors
+    ~tensor_wrapper
+    ~tensor
+    ~NonDifferentiableError
+
+Caveats
+-------
+
+This package is a wrapper around ``autograd.numpy``, and therefore comes with several caveats
+inherited from Autograd:
+
+**Do not use:**
+
+- Assignment to arrays, such as ``A[0, 0] = x``.
+
+..
+
+- Implicit casting of lists to arrays, for example ``A = np.sum([x, y])``.
+  Make sure to explicitly cast to a NumPy array first, i.e.,
+  ``A = np.sum(np.array([x, y]))`` instead.
+
+..
+
+- ``A.dot(B)`` notation. Use ``np.dot(A, B)`` or ``A @ B`` instead.
+
+..
+
+- In-place operations such as ``a += b``. Use ``a = a + b`` instead.
+
+..
+
+- Some ``isinstance`` checks, like ``isinstance(x, np.ndarray)`` or ``isinstance(x, tuple)``,
+  without first doing ``from autograd.builtins import isinstance, tuple``.
+
+For more details, please consult the `Autograd
+docs <https://github.com/HIPS/autograd/blob/master/docs/tutorial.md>`__.
+
 """
 # pylint: disable=wrong-import-position,wildcard-import,undefined-variable
 from autograd import numpy as _np
@@ -36,5 +101,3 @@
 from .tensor import tensor, NonDifferentiableError, asarray as _asarray
 
 asarray = tensor_wrapper(_asarray)
-
-__doc__ = "NumPy with automatic differentiation support, provided by Autograd and PennyLane."
diff --git a/pennylane/numpy/wrapper.py b/pennylane/numpy/wrapper.py
--- a/pennylane/numpy/wrapper.py
+++ b/pennylane/numpy/wrapper.py
@@ -26,6 +26,21 @@
 def extract_tensors(x):
     """Iterate through an iterable, and extract any PennyLane
     tensors that appear.
+
+    Args:
+        x (.tensor or Sequence): an input tensor or sequence
+
+    Yields:
+        tensor: the next tensor in the sequence. If the input was a single
+        tensor, than the tensor is yielded and the iterator completes.
+
+    **Example**
+
+    >>> from pennylane import numpy as np
+    >>> import numpy as onp
+    >>> iterator = np.extract_tensors([0.1, np.array(0.1), "string", onp.array(0.5)])
+    >>> list(iterator)
+    [tensor(0.1, requires_grad=True)]
     """
     if isinstance(x, tensor):
         # If the item is a tensor, return it
@@ -48,8 +63,35 @@ def tensor_wrapper(obj):
     output converted to a :class:`~.tensor`; this avoids superfluous conversion
     of scalars and other native-Python types.
 
+    .. note::
+
+        This wrapper does *not* enable autodifferentiation of the wrapped function,
+        it merely adds support for :class:`~pennylane.numpy.tensor` output.
+
     Args:
         obj: a callable object or class
+
+    **Example**
+
+    By default, the ``ones`` function provided by Autograd
+    constructs standard ``ndarray`` objects, and does not
+    permit a ``requires_grad`` argument:
+
+    >>> from autograd.numpy import ones
+    >>> ones([2, 2])
+    array([[1., 1.],
+        [1., 1.]])
+    >>> ones([2, 2], requires_grad=True)
+    TypeError: ones() got an unexpected keyword argument 'requires_grad'
+
+    ``tensor_wrapper`` both enables construction of :class:`~pennylane.numpy.tensor`
+    objects, while also converting the output.
+
+    >>> from pennylane import numpy as np
+    >>> ones = np.tensor_wrapper(ones)
+    >>> ones([2, 2], requires_grad=True)
+    tensor([[1., 1.],
+        [1., 1.]], requires_grad=True)
     """
 
     @functools.wraps(obj)
@@ -86,13 +128,26 @@ def _wrapped(*args, **kwargs):
 
 def wrap_arrays(old, new):
     """Loop through an object's symbol table,
+    wrapping each function with :func:`~pennylane.numpy.tensor_wrapper`.
 
-    wrapping each function with :func:`~.tensor_wrapper`.
+    This is useful if you would like to wrap **every** function
+    provided by an imported module.
 
     Args:
         old (dict): The symbol table to be wrapped. Note that
-            callable classes are ignored; only function are wrapped.
+            callable classes are ignored; only functions are wrapped.
         new (dict): The symbol table that contains the wrapped values.
+
+    .. seealso:: :func:`~pennylane.numpy.tensor_wrapper`
+
+    **Example**
+
+    This function is used to wrap the imported ``autograd.numpy``
+    module, to enable all functions to support ``requires_grad``
+    arguments, and to output :class:`~pennylane.numpy.tensor` objects:
+
+    >>> from autograd import numpy as _np
+    >>> wrap_arrays(_np.__dict__, globals())
     """
     for name, obj in old.items():
         if callable(obj) and not isinstance(obj, type):


Test patch: