def block_diag(values): """Combine a sequence of 2D tensors to form a block diagonal tensor. Args: values (Sequence[tensor_like]): Sequence of 2D arrays/tensors to form the block diagonal tensor. Returns: tensor_like: the block diagonal tensor **Example** >>> t = [ ... np.array([[1, 2], [3, 4]]), ... torch.tensor([[1, 2, 3], [-1, -6, -3]]), ... torch.tensor(5) ... ] >>> qml.math.block_diag(t) tensor([[ 1, 2, 0, 0, 0, 0], [ 3, 4, 0, 0, 0, 0], [ 0, 0, 1, 2, 3, 0], [ 0, 0, -1, -6, -3, 0], [ 0, 0, 0, 0, 0, 5]]) """ interface = _multi_dispatch(values) values = np.coerce(values, like=interface) return np.block_diag(values, like=interface)
def stack(values, axis=0): """Stack a sequence of tensors along the specified axis. .. warning:: Tensors that are incompatible (such as Torch and TensorFlow tensors) cannot both be present. Args: values (Sequence[tensor_like]): Sequence of tensor-like objects to stack. Each object in the sequence must have the same size in the given axis. axis (int): The axis along which the input tensors are stacked. ``axis=0`` corresponds to vertical stacking. Returns: tensor_like: The stacked array. The stacked array will have one additional dimension compared to the unstacked tensors. **Example** >>> x = tf.constant([0.6, 0.1, 0.6]) >>> y = tf.Variable([0.1, 0.2, 0.3]) >>> z = np.array([5., 8., 101.]) >>> stack([x, y, z]) <tf.Tensor: shape=(3, 3), dtype=float32, numpy= array([[6.00e-01, 1.00e-01, 6.00e-01], [1.00e-01, 2.00e-01, 3.00e-01], [5.00e+00, 8.00e+00, 1.01e+02]], dtype=float32)> """ interface = _multi_dispatch(values) values = np.coerce(values, like=interface) return np.stack(values, axis=axis, like=interface)
def frobenius_inner_product(A, B, normalize=False): r"""Frobenius inner product between two matrices. .. math:: \langle A, B \rangle_F = \sum_{i,j=1}^n A_{ij} B_{ij} = \operatorname{tr} (A^T B) The Frobenius inner product is equivalent to the Hilbert-Schmidt inner product for matrices with real-valued entries. Args: A (tensor_like[float]): First matrix, assumed to be a square array. B (tensor_like[float]): Second matrix, assumed to be a square array. normalize (bool): If True, divide the inner product by the Frobenius norms of A and B. Returns: float: Frobenius inner product of A and B **Example** >>> A = np.random.random((3,3)) >>> B = np.random.random((3,3)) >>> qml.math.frobenius_inner_product(A, B) 3.091948202943376 """ interface = _multi_dispatch([A, B]) A, B = np.coerce([A, B], like=interface) inner_product = np.sum(A * B) if normalize: norm = np.sqrt(np.sum(A * A) * np.sum(B * B)) inner_product = inner_product / norm return inner_product
def tensordot(tensor1, tensor2, axes=None, like=None): """Returns the tensor product of two tensors. In general ``axes`` specifies either the set of axes for both tensors that are contracted (with the first/second entry of ``axes`` giving all axis indices for the first/second tensor) or --- if it is an integer --- the number of last/first axes of the first/second tensor to contract over. There are some non-obvious special cases: * If both tensors are 0-dimensional, ``axes`` must be 0. and a 0-dimensional scalar is returned containing the simple product. * If both tensors are 1-dimensional and ``axes=0``, the outer product is returned. * Products between a non-0-dimensional and a 0-dimensional tensor are not supported in all interfaces. Args: tensor1 (tensor_like): input tensor tensor2 (tensor_like): input tensor axes (int or list[list[int]]): Axes to contract over, see detail description. Returns: tensor_like: the tensor product of the two input tensors """ tensor1, tensor2 = np.coerce([tensor1, tensor2], like=like) return np.tensordot(tensor1, tensor2, axes=axes, like=like)
def dot(tensor1, tensor2): """Returns the matrix or dot product of two tensors. * If both tensors are 0-dimensional, elementwise multiplication is performed and a 0-dimensional scalar returned. * If both tensors are 1-dimensional, the dot product is returned. * If the first array is 2-dimensional and the second array 1-dimensional, the matrix-vector product is returned. * If both tensors are 2-dimensional, the matrix product is returned. * Finally, if the the first array is N-dimensional and the second array M-dimensional, a sum product over the last dimension of the first array, and the second-to-last dimension of the second array is returned. Args: tensor1 (tensor_like): input tensor tensor2 (tensor_like): input tensor Returns: tensor_like: the matrix or dot product of two tensors """ interface = _multi_dispatch([tensor1, tensor2]) x, y = np.coerce([tensor1, tensor2], like=interface) if interface == "torch": if x.ndim == 0 and y.ndim == 0: return x * y if x.ndim <= 2 and y.ndim <= 2: return x @ y return np.tensordot(x, y, dims=[[-1], [-2]], like=interface) if interface == "tensorflow": if x.ndim == 0 and y.ndim == 0: return x * y if y.ndim == 1: return np.tensordot(x, y, axes=[[-1], [0]], like=interface) if x.ndim == 2 and y.ndim == 2: return x @ y return np.tensordot(x, y, axes=[[-1], [-2]], like=interface) return np.dot(x, y, like=interface)
def diag(values, k=0): """Construct a diagonal tensor from a list of scalars. Args: values (tensor_like or Sequence[scalar]): sequence of numeric values that make up the diagonal k (int): The diagonal in question. ``k=0`` corresponds to the main diagonal. Use ``k>0`` for diagonals above the main diagonal, and ``k<0`` for diagonals below the main diagonal. Returns: tensor_like: the 2D diagonal tensor **Example** >>> x = [1., 2., tf.Variable(3.)] >>> diag(x) <tf.Tensor: shape=(3, 3), dtype=float32, numpy= array([[1., 0., 0.], [0., 2., 0.], [0., 0., 3.]], dtype=float32)> >>> y = tf.Variable([0.65, 0.2, 0.1]) >>> diag(y, k=-1) <tf.Tensor: shape=(4, 4), dtype=float32, numpy= array([[0. , 0. , 0. , 0. ], [0.65, 0. , 0. , 0. ], [0. , 0.2 , 0. , 0. ], [0. , 0. , 0.1 , 0. ]], dtype=float32)> >>> z = torch.tensor([0.1, 0.2]) >>> qml.math.diag(z, k=1) tensor([[0.0000, 0.1000, 0.0000], [0.0000, 0.0000, 0.2000], [0.0000, 0.0000, 0.0000]]) """ interface = _multi_dispatch(values) if isinstance(values, (list, tuple)): values = np.stack(np.coerce(values, like=interface), like=interface) return np.diag(values, k=k, like=interface)